733 lines
29 KiB
C#
733 lines
29 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using Microsoft.MixedReality.Toolkit.SpatialAwareness;
|
|
using Microsoft.MixedReality.Toolkit.Utilities;
|
|
using Microsoft.MixedReality.Toolkit.Windows.Utilities;
|
|
using System.Collections.Generic;
|
|
using Unity.Profiling;
|
|
using UnityEngine;
|
|
|
|
#if UNITY_WSA
|
|
using UnityEngine.XR;
|
|
using UnityEngine.XR.WSA;
|
|
#endif // UNITY_WSA
|
|
|
|
#if WINDOWS_UWP
|
|
using WindowsSpatialSurfaces = global::Windows.Perception.Spatial.Surfaces;
|
|
#endif // WINDOWS_UWP
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.WindowsMixedReality.SpatialAwareness
|
|
{
|
|
[MixedRealityDataProvider(
|
|
typeof(IMixedRealitySpatialAwarenessSystem),
|
|
SupportedPlatforms.WindowsUniversal,
|
|
"Windows Mixed Reality Spatial Mesh Observer",
|
|
"Profiles/DefaultMixedRealitySpatialAwarenessMeshObserverProfile.asset",
|
|
"MixedRealityToolkit.SDK",
|
|
true,
|
|
SupportedUnityXRPipelines.LegacyXR)]
|
|
[HelpURL("https://docs.microsoft.com/windows/mixed-reality/mrtk-unity/features/spatial-awareness/spatial-awareness-getting-started")]
|
|
public class WindowsMixedRealitySpatialMeshObserver :
|
|
BaseSpatialMeshObserver,
|
|
IMixedRealityCapabilityCheck
|
|
{
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="registrar">The <see cref="IMixedRealityServiceRegistrar"/> instance that loaded the service.</param>
|
|
/// <param name="spatialAwarenessSystem">The service instance that receives data from this provider.</param>
|
|
/// <param name="name">Friendly name of the service.</param>
|
|
/// <param name="priority">Service priority. Used to determine order of instantiation.</param>
|
|
/// <param name="profile">The service's configuration profile.</param>
|
|
[System.Obsolete("This constructor is obsolete (registrar parameter is no longer required) and will be removed in a future version of the Microsoft Mixed Reality Toolkit.")]
|
|
public WindowsMixedRealitySpatialMeshObserver(
|
|
IMixedRealityServiceRegistrar registrar,
|
|
IMixedRealitySpatialAwarenessSystem spatialAwarenessSystem,
|
|
string name = null,
|
|
uint priority = DefaultPriority,
|
|
BaseMixedRealityProfile profile = null) : this(spatialAwarenessSystem, name, priority, profile)
|
|
{
|
|
Registrar = registrar;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="spatialAwarenessSystem">The service instance that receives data from this provider.</param>
|
|
/// <param name="name">Friendly name of the service.</param>
|
|
/// <param name="priority">Service priority. Used to determine order of instantiation.</param>
|
|
/// <param name="profile">The service's configuration profile.</param>
|
|
public WindowsMixedRealitySpatialMeshObserver(
|
|
IMixedRealitySpatialAwarenessSystem spatialAwarenessSystem,
|
|
string name = null,
|
|
uint priority = DefaultPriority,
|
|
BaseMixedRealityProfile profile = null) : base(spatialAwarenessSystem, name, priority, profile)
|
|
{ }
|
|
|
|
#region BaseSpatialObserver Implementation
|
|
|
|
/// <summary>
|
|
/// Creates the surface observer and handles the desired startup behavior.
|
|
/// </summary>
|
|
protected override void CreateObserver()
|
|
{
|
|
if (Service == null) { return; }
|
|
|
|
#if UNITY_WSA
|
|
if (observer == null)
|
|
{
|
|
observer = new SurfaceObserver();
|
|
ConfigureObserverVolume();
|
|
|
|
if (StartupBehavior == AutoStartBehavior.AutoStart)
|
|
{
|
|
Resume();
|
|
}
|
|
}
|
|
#endif // UNITY_WSA
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implements proper cleanup of the SurfaceObserver.
|
|
/// </summary>
|
|
protected override void CleanupObserver()
|
|
{
|
|
if (IsRunning)
|
|
{
|
|
Suspend();
|
|
}
|
|
|
|
#if UNITY_WSA
|
|
if (observer != null)
|
|
{
|
|
observer.Dispose();
|
|
observer = null;
|
|
}
|
|
#endif // UNITY_WSA
|
|
}
|
|
|
|
#endregion BaseSpatialObserver Implementation
|
|
|
|
#region BaseSpatialMeshObserver Implementation
|
|
|
|
/// <inheritdoc />
|
|
protected override int LookupTriangleDensity(SpatialAwarenessMeshLevelOfDetail levelOfDetail)
|
|
{
|
|
int triangleDensity;
|
|
switch (levelOfDetail)
|
|
{
|
|
case SpatialAwarenessMeshLevelOfDetail.Coarse:
|
|
triangleDensity = 0;
|
|
break;
|
|
|
|
case SpatialAwarenessMeshLevelOfDetail.Medium:
|
|
triangleDensity = 400;
|
|
break;
|
|
|
|
case SpatialAwarenessMeshLevelOfDetail.Fine:
|
|
case SpatialAwarenessMeshLevelOfDetail.Unlimited:
|
|
triangleDensity = 2000;
|
|
break;
|
|
|
|
default:
|
|
Debug.LogWarning($"There is no triangle density lookup for {levelOfDetail}, defaulting to Coarse");
|
|
triangleDensity = 0;
|
|
break;
|
|
}
|
|
|
|
return triangleDensity;
|
|
}
|
|
|
|
#endregion BaseSpatialMeshObserver Implementation
|
|
|
|
#region IMixedRealityCapabilityCheck Implementation
|
|
|
|
/// <inheritdoc />
|
|
public bool CheckCapability(MixedRealityCapability capability)
|
|
{
|
|
if (WindowsApiChecker.IsMethodAvailable(
|
|
"Windows.Perception.Spatial.Surfaces",
|
|
"SpatialSurfaceObserver",
|
|
"IsSupported"))
|
|
{
|
|
#if WINDOWS_UWP
|
|
return (capability == MixedRealityCapability.SpatialAwarenessMesh) && WindowsSpatialSurfaces.SpatialSurfaceObserver.IsSupported();
|
|
#endif // WINDOWS_UWP
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endregion IMixedRealityCapabilityCheck Implementation
|
|
|
|
#region IMixedRealityDataProvider Implementation
|
|
|
|
#if UNITY_WSA
|
|
/// <summary>
|
|
/// Creates and configures the spatial observer, as well as
|
|
/// setting the required SpatialPerception capability.
|
|
/// </summary>
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
#if UNITY_EDITOR && UNITY_WSA
|
|
Utilities.Editor.UWPCapabilityUtility.RequireCapability(
|
|
UnityEditor.PlayerSettings.WSACapability.SpatialPerception,
|
|
this.GetType());
|
|
#endif
|
|
// If we aren't using a HoloLens or there isn't an XR device present, return.
|
|
if (observer == null || HolographicSettings.IsDisplayOpaque || !XRDevice.isPresent) { return; }
|
|
|
|
if (RuntimeSpatialMeshPrefab != null)
|
|
{
|
|
AddRuntimeSpatialMeshPrefabToHierarchy();
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker UpdatePerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver.Update");
|
|
|
|
/// <inheritdoc />
|
|
public override void Update()
|
|
{
|
|
using (UpdatePerfMarker.Auto())
|
|
{
|
|
base.Update();
|
|
UpdateObserver();
|
|
}
|
|
}
|
|
#endif // UNITY_WSA
|
|
|
|
#endregion IMixedRealityDataProvider Implementation
|
|
|
|
#region IMixedRealitySpatialAwarenessObserver Implementation
|
|
|
|
#if UNITY_WSA
|
|
/// <summary>
|
|
/// The surface observer providing the spatial data.
|
|
/// </summary>
|
|
private SurfaceObserver observer = null;
|
|
|
|
/// <summary>
|
|
/// A queue of <see cref="SurfaceId"/> that need their meshes created (or updated).
|
|
/// </summary>
|
|
private readonly Queue<SurfaceId> meshWorkQueue = new Queue<SurfaceId>();
|
|
|
|
/// <summary>
|
|
/// To prevent too many meshes from being generated at the same time, we will
|
|
/// only request one mesh to be created at a time. This variable will track
|
|
/// if a mesh creation request is in flight.
|
|
/// </summary>
|
|
private SpatialAwarenessMeshObject outstandingMeshObject = null;
|
|
|
|
/// <summary>
|
|
/// When surfaces are replaced or removed, rather than destroying them, we'll keep
|
|
/// one as a spare for use in outstanding mesh requests. That way, we'll have fewer
|
|
/// game object create/destroy cycles, which should help performance.
|
|
/// </summary>
|
|
protected SpatialAwarenessMeshObject spareMeshObject = null;
|
|
|
|
/// <summary>
|
|
/// The time at which the surface observer was last asked for updated data.
|
|
/// </summary>
|
|
private float lastUpdated = 0;
|
|
#endif // UNITY_WSA
|
|
|
|
#if UNITY_WSA
|
|
private static readonly ProfilerMarker ResumePerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver.Resume");
|
|
#endif // UNITY_WSA
|
|
|
|
/// <inheritdoc/>
|
|
public override void Resume()
|
|
{
|
|
#if UNITY_WSA
|
|
if (IsRunning)
|
|
{
|
|
Debug.LogWarning("The Windows Mixed Reality spatial observer is currently running.");
|
|
return;
|
|
}
|
|
|
|
using (ResumePerfMarker.Auto())
|
|
{
|
|
// We want the first update immediately.
|
|
lastUpdated = 0;
|
|
|
|
// UpdateObserver keys off of this value to start observing.
|
|
IsRunning = true;
|
|
}
|
|
#endif // UNITY_WSA
|
|
}
|
|
|
|
#if UNITY_WSA
|
|
private static readonly ProfilerMarker SuspendPerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver.Suspend");
|
|
#endif // UNITY_WSA
|
|
|
|
/// <inheritdoc/>
|
|
public override void Suspend()
|
|
{
|
|
#if UNITY_WSA
|
|
if (!IsRunning)
|
|
{
|
|
Debug.LogWarning("The Windows Mixed Reality spatial observer is currently stopped.");
|
|
return;
|
|
}
|
|
|
|
using (SuspendPerfMarker.Auto())
|
|
{
|
|
// UpdateObserver keys off of this value to stop observing.
|
|
IsRunning = false;
|
|
|
|
// Halt any outstanding work.
|
|
if (outstandingMeshObject != null)
|
|
{
|
|
ReclaimMeshObject(outstandingMeshObject);
|
|
outstandingMeshObject = null;
|
|
}
|
|
|
|
// Clear any pending work.
|
|
meshWorkQueue.Clear();
|
|
}
|
|
#endif // UNITY_WSA
|
|
}
|
|
|
|
#if UNITY_WSA
|
|
private static readonly ProfilerMarker ClearObservationsPerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver.ClearObservations");
|
|
#endif // UNITY_WSA
|
|
|
|
#if UNITY_WSA
|
|
/// <inheritdoc />
|
|
public override void ClearObservations()
|
|
{
|
|
using (ClearObservationsPerfMarker.Auto())
|
|
{
|
|
bool wasRunning = false;
|
|
|
|
if (IsRunning)
|
|
{
|
|
wasRunning = true;
|
|
Debug.Log("Cannot clear observations while the observer is running. Suspending this observer.");
|
|
Suspend();
|
|
}
|
|
|
|
IReadOnlyList<int> observations = new List<int>(Meshes.Keys);
|
|
foreach (int meshId in observations)
|
|
{
|
|
RemoveMeshObject(meshId);
|
|
}
|
|
|
|
// Cleanup the outstanding mesh object.
|
|
if (outstandingMeshObject != null)
|
|
{
|
|
// Destroy the game object, destroy the meshes.
|
|
SpatialAwarenessMeshObject.Cleanup(outstandingMeshObject);
|
|
outstandingMeshObject = null;
|
|
}
|
|
|
|
// Cleanup the spare mesh object
|
|
if (spareMeshObject != null)
|
|
{
|
|
// Destroy the game object, destroy the meshes.
|
|
SpatialAwarenessMeshObject.Cleanup(spareMeshObject);
|
|
spareMeshObject = null;
|
|
}
|
|
|
|
if (wasRunning)
|
|
{
|
|
Resume();
|
|
}
|
|
}
|
|
}
|
|
#endif // UNITY_WSA
|
|
|
|
#endregion IMixedRealitySpatialAwarenessObserver Implementation
|
|
|
|
#region Helpers
|
|
|
|
#if UNITY_WSA
|
|
private static readonly ProfilerMarker UpdateObserverPerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver.UpdateObserver");
|
|
|
|
/// <summary>
|
|
/// Requests updates from the surface observer.
|
|
/// </summary>
|
|
private void UpdateObserver()
|
|
{
|
|
if (Service == null || HolographicSettings.IsDisplayOpaque || !XRDevice.isPresent) { return; }
|
|
|
|
using (UpdateObserverPerfMarker.Auto())
|
|
{
|
|
// Only update the observer if it is running.
|
|
if (IsRunning && (outstandingMeshObject == null))
|
|
{
|
|
// If we have a mesh to work on...
|
|
if (meshWorkQueue.Count > 0)
|
|
{
|
|
// We're using a simple first-in-first-out rule for requesting meshes, but a more sophisticated algorithm could prioritize
|
|
// the queue based on distance to the user or some other metric.
|
|
RequestMesh(meshWorkQueue.Dequeue());
|
|
}
|
|
// If enough time has passed since the previous observer update...
|
|
else if (Time.time - lastUpdated >= UpdateInterval)
|
|
{
|
|
// Update the observer orientation if user aligned
|
|
if (ObserverVolumeType == VolumeType.UserAlignedCube)
|
|
{
|
|
ObserverRotation = CameraCache.Main.transform.rotation;
|
|
}
|
|
|
|
// Update the observer location if it is not stationary
|
|
if (!IsStationaryObserver)
|
|
{
|
|
ObserverOrigin = CameraCache.Main.transform.position;
|
|
}
|
|
|
|
// The application can update the observer volume at any time, make sure we are using the latest.
|
|
ConfigureObserverVolume();
|
|
|
|
observer.Update(SurfaceObserver_OnSurfaceChanged);
|
|
|
|
lastUpdated = Time.time;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal component to monitor the WorldAnchor's transform, apply the MixedRealityPlayspace transform,
|
|
/// and apply it to its parent.
|
|
/// </summary>
|
|
private class PlayspaceAdapter : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// Compute concatenation of lhs * rhs such that lhs * (rhs * v) = Concat(lhs, rhs) * v
|
|
/// </summary>
|
|
/// <param name="lhs">Second transform to apply</param>
|
|
/// <param name="rhs">First transform to apply</param>
|
|
private static Pose Concatenate(Pose lhs, Pose rhs)
|
|
{
|
|
return rhs.GetTransformedBy(lhs);
|
|
}
|
|
|
|
private static readonly ProfilerMarker UpdatePerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver+PlayspaceAdapter.Update");
|
|
|
|
/// <summary>
|
|
/// Compute and set the parent's transform.
|
|
/// </summary>
|
|
private void Update()
|
|
{
|
|
using (UpdatePerfMarker.Auto())
|
|
{
|
|
Pose worldFromPlayspace = new Pose(MixedRealityPlayspace.Position, MixedRealityPlayspace.Rotation);
|
|
Pose anchorPose = new Pose(transform.position, transform.rotation);
|
|
/// Propagate any global scale on the playspace into the position.
|
|
Vector3 playspaceScale = MixedRealityPlayspace.Transform.lossyScale;
|
|
anchorPose.position *= playspaceScale.x;
|
|
Pose parentPose = Concatenate(worldFromPlayspace, anchorPose);
|
|
transform.parent.position = parentPose.position;
|
|
transform.parent.rotation = parentPose.rotation;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker RequestMeshPerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver.RequestMesh");
|
|
|
|
/// <summary>
|
|
/// Issue a request to the Surface Observer to begin baking the mesh.
|
|
/// </summary>
|
|
/// <param name="surfaceId">ID of the mesh to bake.</param>
|
|
private void RequestMesh(SurfaceId surfaceId)
|
|
{
|
|
using (RequestMeshPerfMarker.Auto())
|
|
{
|
|
string meshName = ("SpatialMesh - " + surfaceId.handle);
|
|
|
|
SpatialAwarenessMeshObject newMesh;
|
|
WorldAnchor worldAnchor;
|
|
|
|
if (spareMeshObject == null)
|
|
{
|
|
newMesh = SpatialAwarenessMeshObject.Create(
|
|
null,
|
|
MeshPhysicsLayer,
|
|
meshName,
|
|
surfaceId.handle,
|
|
ObservedObjectParent);
|
|
|
|
// The WorldAnchor component places its object where the anchor is in the same space as the camera.
|
|
// But since the camera is repositioned by the MixedRealityPlayspace's transform, the meshes' transforms
|
|
// should also the WorldAnchor position repositioned by the MixedRealityPlayspace's transform.
|
|
// So rather than put the WorldAnchor on the mesh's GameObject, the WorldAnchor is placed out of the way in the scene,
|
|
// and its transform is concatenated with the Playspace transform to compute the transform on the mesh's object.
|
|
// That adapting the WorldAnchor's transform into playspace is done by the internal PlayspaceAdapter component.
|
|
// The GameObject the WorldAnchor is placed on is unimportant, but it is convenient for cleanup to make it a child
|
|
// of the GameObject whose transform will track it.
|
|
GameObject anchorHolder = new GameObject(meshName + "_anchor");
|
|
anchorHolder.AddComponent<PlayspaceAdapter>(); // replace with required component?
|
|
worldAnchor = anchorHolder.AddComponent<WorldAnchor>(); // replace with required component and GetComponent()?
|
|
anchorHolder.transform.SetParent(newMesh.GameObject.transform, false);
|
|
}
|
|
else
|
|
{
|
|
newMesh = spareMeshObject;
|
|
spareMeshObject = null;
|
|
|
|
newMesh.GameObject.name = meshName;
|
|
newMesh.Id = surfaceId.handle;
|
|
newMesh.GameObject.SetActive(true);
|
|
|
|
// There should be exactly one child on the newMesh.GameObject, and that is the GameObject added above
|
|
// to hold the WorldAnchor component and adapter.
|
|
Debug.Assert(newMesh.GameObject.transform.childCount == 1, "Expecting a single child holding the WorldAnchor");
|
|
worldAnchor = newMesh.GameObject.transform.GetChild(0).gameObject.GetComponent<WorldAnchor>();
|
|
}
|
|
|
|
Debug.Assert(worldAnchor != null);
|
|
|
|
SurfaceData surfaceData = new SurfaceData(
|
|
surfaceId,
|
|
newMesh.Filter,
|
|
worldAnchor,
|
|
newMesh.Collider,
|
|
TrianglesPerCubicMeter,
|
|
true);
|
|
|
|
if (observer.RequestMeshAsync(surfaceData, SurfaceObserver_OnDataReady))
|
|
{
|
|
outstandingMeshObject = newMesh;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"Mesh request failed for Id == surfaceId.handle");
|
|
outstandingMeshObject = null;
|
|
ReclaimMeshObject(newMesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker RemoveMeshObjectPerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver.RemoveMeshObject");
|
|
|
|
/// <summary>
|
|
/// Removes the <see cref="SpatialAwarenessMeshObject"/> associated with the specified id.
|
|
/// </summary>
|
|
/// <param name="id">The id of the mesh to be removed.</param>
|
|
protected void RemoveMeshObject(int id)
|
|
{
|
|
using (RemoveMeshObjectPerfMarker.Auto())
|
|
{
|
|
SpatialAwarenessMeshObject mesh;
|
|
if (meshes.TryGetValue(id, out mesh))
|
|
{
|
|
// Remove the mesh object from the collection.
|
|
meshes.Remove(id);
|
|
|
|
// Reclaim the mesh object for future use.
|
|
ReclaimMeshObject(mesh);
|
|
|
|
// Send the mesh removed event
|
|
meshEventData.Initialize(this, id, null);
|
|
Service?.HandleEvent(meshEventData, OnMeshRemoved);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker ReclaimMeshObjectPerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver.ReclaimMeshObject");
|
|
|
|
/// <summary>
|
|
/// Reclaims the <see cref="SpatialAwarenessMeshObject"/> to allow for later reuse.
|
|
/// </summary>
|
|
protected void ReclaimMeshObject(SpatialAwarenessMeshObject availableMeshObject)
|
|
{
|
|
using (ReclaimMeshObjectPerfMarker.Auto())
|
|
{
|
|
if (spareMeshObject == null)
|
|
{
|
|
// Cleanup the mesh object.
|
|
// Do not destroy the game object, destroy the meshes.
|
|
SpatialAwarenessMeshObject.Cleanup(availableMeshObject, false);
|
|
|
|
if (availableMeshObject.GameObject != null)
|
|
{
|
|
availableMeshObject.GameObject.name = "Unused Spatial Mesh";
|
|
availableMeshObject.GameObject.SetActive(false);
|
|
}
|
|
|
|
spareMeshObject = availableMeshObject;
|
|
}
|
|
else
|
|
{
|
|
// Cleanup the mesh object.
|
|
// Destroy the game object, destroy the meshes.
|
|
SpatialAwarenessMeshObject.Cleanup(availableMeshObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker ConfigureObserverVolumePerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver.ConfigureObserverVolume");
|
|
|
|
/// <summary>
|
|
/// Applies the configured observation extents.
|
|
/// </summary>
|
|
private void ConfigureObserverVolume()
|
|
{
|
|
using (ConfigureObserverVolumePerfMarker.Auto())
|
|
{
|
|
if (MixedRealityPlayspace.Transform == null)
|
|
{
|
|
Debug.LogError("Unexpected failure acquiring MixedRealityPlayspace.");
|
|
return;
|
|
}
|
|
|
|
// If we aren't using a HoloLens or there isn't an XR device present, return.
|
|
if (observer == null || HolographicSettings.IsDisplayOpaque || !XRDevice.isPresent) { return; }
|
|
|
|
// The observer's origin is in world space, we need it in the camera's parent's space
|
|
// to set the volume. The MixedRealityPlayspace provides that space that the camera/head moves around in.
|
|
Vector3 observerOriginPlayspace = MixedRealityPlayspace.InverseTransformPoint(ObserverOrigin);
|
|
Quaternion observerRotationPlayspace = Quaternion.Inverse(MixedRealityPlayspace.Rotation) * ObserverRotation;
|
|
|
|
// Update the observer
|
|
switch (ObserverVolumeType)
|
|
{
|
|
case VolumeType.AxisAlignedCube:
|
|
observer.SetVolumeAsAxisAlignedBox(observerOriginPlayspace, ObservationExtents);
|
|
break;
|
|
|
|
case VolumeType.Sphere:
|
|
// We use the x value of the extents as the sphere radius
|
|
observer.SetVolumeAsSphere(observerOriginPlayspace, ObservationExtents.x);
|
|
break;
|
|
|
|
case VolumeType.UserAlignedCube:
|
|
observer.SetVolumeAsOrientedBox(observerOriginPlayspace, ObservationExtents, observerRotationPlayspace);
|
|
break;
|
|
|
|
default:
|
|
Debug.LogError($"Unsupported ObserverVolumeType value {ObserverVolumeType}");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker OnSurfaceChangedPerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver.SurfaceObserver_OnSurfaceChanged");
|
|
|
|
/// <summary>
|
|
/// Handles the SurfaceObserver's OnSurfaceChanged event.
|
|
/// </summary>
|
|
/// <param name="id">The identifier assigned to the surface which has changed.</param>
|
|
/// <param name="changeType">The type of change that occurred on the surface.</param>
|
|
/// <param name="bounds">The bounds of the surface.</param>
|
|
/// <param name="updateTime">The date and time at which the change occurred.</param>
|
|
private void SurfaceObserver_OnSurfaceChanged(SurfaceId id, SurfaceChange changeType, Bounds bounds, System.DateTime updateTime)
|
|
{
|
|
if (!IsRunning) { return; }
|
|
|
|
using (OnSurfaceChangedPerfMarker.Auto())
|
|
{
|
|
switch (changeType)
|
|
{
|
|
case SurfaceChange.Added:
|
|
case SurfaceChange.Updated:
|
|
meshWorkQueue.Enqueue(id);
|
|
break;
|
|
|
|
case SurfaceChange.Removed:
|
|
RemoveMeshObject(id.handle);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker OnDataReadyPerfMarker = new ProfilerMarker("[MRTK] WindowsMixedRealitySpatialMeshObserver.SurfaceObserver_OnDataReady");
|
|
|
|
/// <summary>
|
|
/// Handles the SurfaceObserver's OnDataReady event.
|
|
/// </summary>
|
|
/// <param name="cookedData">Struct containing output data.</param>
|
|
/// <param name="outputWritten">Set to true if output has been written.</param>
|
|
/// <param name="elapsedCookTimeSeconds">Seconds between mesh cook request and propagation of this event.</param>
|
|
private void SurfaceObserver_OnDataReady(SurfaceData cookedData, bool outputWritten, float elapsedCookTimeSeconds)
|
|
{
|
|
if (!IsRunning) { return; }
|
|
|
|
using (OnDataReadyPerfMarker.Auto())
|
|
{
|
|
if (outstandingMeshObject == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!outputWritten)
|
|
{
|
|
ReclaimMeshObject(outstandingMeshObject);
|
|
outstandingMeshObject = null;
|
|
return;
|
|
}
|
|
|
|
// Since there is only one outstanding mesh object, update the id to match
|
|
// the one received after baking.
|
|
SpatialAwarenessMeshObject meshObject = outstandingMeshObject;
|
|
meshObject.Id = cookedData.id.handle;
|
|
outstandingMeshObject = null;
|
|
|
|
// Check to see if this is a new or updated mesh.
|
|
bool isMeshUpdate = meshes.ContainsKey(meshObject.Id);
|
|
|
|
// We presume that if the display option is not occlusion, that we should
|
|
// default to the visible material.
|
|
// Note: We check explicitly for a display option of none later in this method.
|
|
Material material = (DisplayOption == SpatialAwarenessMeshDisplayOptions.Occlusion) ?
|
|
OcclusionMaterial : VisibleMaterial;
|
|
|
|
// If this is a mesh update, we want to preserve the mesh's previous material.
|
|
material = isMeshUpdate ? meshes[meshObject.Id].Renderer.sharedMaterial : material;
|
|
|
|
// Apply the appropriate material.
|
|
meshObject.Renderer.sharedMaterial = material;
|
|
|
|
// Recalculate the mesh normals if requested.
|
|
if (RecalculateNormals)
|
|
{
|
|
meshObject.Filter.sharedMesh.RecalculateNormals();
|
|
}
|
|
|
|
// Check to see if the display option is set to none. If so, we disable
|
|
// the renderer.
|
|
meshObject.Renderer.enabled = (DisplayOption != SpatialAwarenessMeshDisplayOptions.None);
|
|
|
|
// Set the physics material
|
|
if (meshObject.Renderer.enabled)
|
|
{
|
|
meshObject.Collider.material = PhysicsMaterial;
|
|
}
|
|
|
|
// Add / update the mesh to our collection
|
|
if (isMeshUpdate)
|
|
{
|
|
// Reclaim the old mesh object for future use.
|
|
ReclaimMeshObject(meshes[meshObject.Id]);
|
|
meshes.Remove(meshObject.Id);
|
|
}
|
|
meshes.Add(meshObject.Id, meshObject);
|
|
|
|
// Preserve local transform relative to parent.
|
|
meshObject.GameObject.transform.SetParent(ObservedObjectParent != null ?
|
|
ObservedObjectParent.transform : null, false);
|
|
|
|
meshEventData.Initialize(this, meshObject.Id, meshObject);
|
|
if (isMeshUpdate)
|
|
{
|
|
Service?.HandleEvent(meshEventData, OnMeshUpdated);
|
|
}
|
|
else
|
|
{
|
|
Service?.HandleEvent(meshEventData, OnMeshAdded);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // UNITY_WSA
|
|
|
|
#endregion Helpers
|
|
}
|
|
}
|