2025-05-19 04:03:28 +02:00

231 lines
8.3 KiB
C#

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using UnityEngine;
using Meta.XR.MRUtilityKit;
using Unity.Collections;
using TMPro;
public class DestructibleMeshExperience : MonoBehaviour
{
public DestructibleGlobalMeshSpawner destructibleGlobalMeshSpawner;
public GameObject ExceptionUI;
public TextMeshProUGUI ExceptionMessage;
private DestructibleMeshComponent _destructibleMeshComponent;
private List<GameObject> _globalMeshSegments = new();
private OVRCameraRig _cameraRig;
private void Awake()
{
_cameraRig = FindAnyObjectByType<OVRCameraRig>();
}
private void OnEnable()
{
destructibleGlobalMeshSpawner.OnSegmentationCompleted += AddBarycentricCoordinates;
destructibleGlobalMeshSpawner.OnDestructibleMeshCreated.AddListener(OnDestructibleMeshCreated);
}
private void OnDisable()
{
destructibleGlobalMeshSpawner.OnSegmentationCompleted -= AddBarycentricCoordinates;
destructibleGlobalMeshSpawner.OnDestructibleMeshCreated.RemoveListener(OnDestructibleMeshCreated);
}
private void Start()
{
GlobalMeshSanityCheck();
}
private void Update()
{
if (OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger) ||
OVRInput.GetDown(OVRInput.Button.SecondaryIndexTrigger))
{
TryDestroyMeshSegment();
}
if (OVRInput.GetDown(OVRInput.Button.One) ||
OVRInput.GetDown(OVRInput.Button.Three))
{
GlobalMeshSanityCheck();
destructibleGlobalMeshSpawner.AddDestructibleGlobalMesh(MRUK.Instance.GetCurrentRoom());
}
if (OVRInput.GetDown(OVRInput.Button.Two) ||
OVRInput.GetDown(OVRInput.Button.Four))
{
destructibleGlobalMeshSpawner.RemoveDestructibleGlobalMesh(MRUK.Instance.GetCurrentRoom());
}
if ((OVRInput.GetDown(OVRInput.Button.PrimaryHandTrigger) ||
OVRInput.GetDown(OVRInput.Button.SecondaryHandTrigger)) &&
_destructibleMeshComponent)
{
_destructibleMeshComponent.DebugDestructibleMeshComponent();
}
}
private void OnDestructibleMeshCreated(DestructibleMeshComponent destructibleMeshComponent)
{
_destructibleMeshComponent = destructibleMeshComponent;
destructibleMeshComponent.GetDestructibleMeshSegments(_globalMeshSegments);
foreach (var globalMeshSegment in _globalMeshSegments)
{
globalMeshSegment.AddComponent<MeshCollider>();
}
}
private void GlobalMeshSanityCheck()
{
if (OVRPlugin.GetSystemHeadsetType() == OVRPlugin.SystemHeadset.Meta_Quest_3 ||
OVRPlugin.GetSystemHeadsetType() == OVRPlugin.SystemHeadset.Meta_Quest_3S ||
OVRPlugin.GetSystemHeadsetType() == OVRPlugin.SystemHeadset.Meta_Link_Quest_3S ||
OVRPlugin.GetSystemHeadsetType() == OVRPlugin.SystemHeadset.Meta_Link_Quest_3)
{
if (MRUK.Instance != null && MRUK.Instance.GetCurrentRoom() != null &&
MRUK.Instance.GetCurrentRoom().GlobalMeshAnchor == null)
{
ExceptionUI.SetActive(true);
ExceptionMessage.text =
"No Global Mesh was found for the current room. Make sure to have correctly set up the space through a room capture.";
}
else
{
ExceptionUI.SetActive(false);
}
}
else
{
ExceptionUI.SetActive(true);
ExceptionMessage.text =
"Destructible meshes are only supported on Quest 3 and Quest 3S devices.";
}
}
private void TryDestroyMeshSegment()
{
var ray = GetControllerRay();
if (!Physics.Raycast(ray, out var hit))
{
return;
}
var hitObject = hit.collider.gameObject;
if (_globalMeshSegments.Contains(hitObject) && hitObject != _destructibleMeshComponent.ReservedSegment)
{
// The DestroySegment function is preferred when destroying mesh segments
// as it takes care of destroying the assets instantiated.
_destructibleMeshComponent.DestroySegment(hitObject);
}
}
private static DestructibleMeshComponent.MeshSegmentationResult AddBarycentricCoordinates(
DestructibleMeshComponent.MeshSegmentationResult meshSegmentationResult)
{
var newSegments = new List<DestructibleMeshComponent.MeshSegment>();
foreach (var segment in meshSegmentationResult.segments)
{
var newSegment = AddBarycentricCoordinatesToMeshSegment(segment);
newSegments.Add(newSegment);
}
var newReservedSegment = AddBarycentricCoordinatesToMeshSegment(meshSegmentationResult.reservedSegment);
return new DestructibleMeshComponent.MeshSegmentationResult()
{
segments = newSegments,
reservedSegment = newReservedSegment
};
}
private static DestructibleMeshComponent.MeshSegment AddBarycentricCoordinatesToMeshSegment(
DestructibleMeshComponent.MeshSegment segment)
{
using var vs = new NativeArray<Vector3>(segment.positions, Allocator.Temp);
using var ts = new NativeArray<int>(segment.indices, Allocator.Temp);
var vertices = new Vector3[ts.Length];
var idx = new int[ts.Length];
var barCoord = new Color[ts.Length];
for (var i = 0; i < ts.Length; i += 3)
{
vertices[i + 0] = vs[ts[i + 0]];
vertices[i + 1] = vs[ts[i + 1]];
vertices[i + 2] = vs[ts[i + 2]];
barCoord[i + 0] = new Color(1, 0, 0, 0); // Barycentric coordinates for vertex 1
barCoord[i + 1] = new Color(0, 1, 0, 0); // Barycentric coordinates for vertex 2
barCoord[i + 2] = new Color(0, 0, 1, 0); // Barycentric coordinates for vertex 3
}
for (var i = 0; i < ts.Length; i++)
{
idx[i] = i;
}
var newSegment = new DestructibleMeshComponent.MeshSegment()
{
indices = idx,
positions = vertices,
colors = barCoord
};
return newSegment;
}
private Ray GetControllerRay()
{
Vector3 rayOrigin;
Vector3 rayDirection;
switch (OVRInput.activeControllerType)
{
case OVRInput.Controller.Touch:
case OVRInput.Controller.RTouch:
rayOrigin = _cameraRig.rightHandOnControllerAnchor.position;
rayDirection = _cameraRig.rightHandOnControllerAnchor.forward;
break;
case OVRInput.Controller.LTouch:
rayOrigin = _cameraRig.leftHandOnControllerAnchor.position;
rayDirection = _cameraRig.leftHandOnControllerAnchor.forward;
break;
// hands
default:
{
var rightHand = _cameraRig.rightHandAnchor.GetComponentInChildren<OVRHand>();
// can be null if running in Editor with Meta Linq app and the headset is put off
if (rightHand != null)
{
rayOrigin = rightHand.PointerPose.position;
rayDirection = rightHand.PointerPose.forward;
}
else
{
rayOrigin = _cameraRig.centerEyeAnchor.position;
rayDirection = _cameraRig.centerEyeAnchor.forward;
}
break;
}
}
return new Ray(rayOrigin, rayDirection);
}
}