390 lines
16 KiB
C#
390 lines
16 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections.Generic;
|
|
using Unity.Profiling;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.SpatialAwareness
|
|
{
|
|
/// <summary>
|
|
/// Class providing a base implementation of the <see cref="IMixedRealitySpatialAwarenessMeshObserver"/> interface.
|
|
/// </summary>
|
|
public abstract class BaseSpatialMeshObserver : BaseSpatialObserver, IMixedRealitySpatialAwarenessMeshObserver, ISpatialAwarenessPhysicsProperties
|
|
{
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="spatialAwarenessSystem">The <see cref="SpatialAwareness.IMixedRealitySpatialAwarenessSystem"/> to which the observer is providing data.</param>
|
|
/// <param name="name">The friendly name of the data provider.</param>
|
|
/// <param name="priority">The registration priority of the data provider.</param>
|
|
/// <param name="profile">The configuration profile for the data provider.</param>
|
|
protected BaseSpatialMeshObserver(
|
|
IMixedRealitySpatialAwarenessSystem spatialAwarenessSystem,
|
|
string name = null,
|
|
uint priority = DefaultPriority,
|
|
BaseMixedRealityProfile profile = null) : base(spatialAwarenessSystem, name, priority, profile)
|
|
{
|
|
}
|
|
|
|
#region BaseSpatialMeshObserver Implementation
|
|
|
|
protected MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> meshEventData = null;
|
|
|
|
protected virtual void ReadProfile()
|
|
{
|
|
if (ConfigurationProfile == null)
|
|
{
|
|
Debug.LogError($"{Name} requires a configuration profile to run properly.");
|
|
return;
|
|
}
|
|
|
|
MixedRealitySpatialAwarenessMeshObserverProfile profile = ConfigurationProfile as MixedRealitySpatialAwarenessMeshObserverProfile;
|
|
if (profile == null)
|
|
{
|
|
Debug.LogError($"{Name}'s configuration profile must be a MixedRealitySpatialAwarenessMeshObserverProfile.");
|
|
return;
|
|
}
|
|
|
|
// IMixedRealitySpatialAwarenessObserver settings
|
|
StartupBehavior = profile.StartupBehavior;
|
|
IsStationaryObserver = profile.IsStationaryObserver;
|
|
ObservationExtents = profile.ObservationExtents;
|
|
ObserverVolumeType = profile.ObserverVolumeType;
|
|
UpdateInterval = profile.UpdateInterval;
|
|
|
|
// IMixedRealitySpatialAwarenessMeshObserver settings
|
|
DisplayOption = profile.DisplayOption;
|
|
TrianglesPerCubicMeter = profile.TrianglesPerCubicMeter; // Set this before LevelOfDetail so it doesn't overwrite in the non-Custom case
|
|
LevelOfDetail = profile.LevelOfDetail;
|
|
MeshPhysicsLayer = profile.MeshPhysicsLayer;
|
|
OcclusionMaterial = profile.OcclusionMaterial;
|
|
PhysicsMaterial = profile.PhysicsMaterial;
|
|
RecalculateNormals = profile.RecalculateNormals;
|
|
VisibleMaterial = profile.VisibleMaterial;
|
|
|
|
RuntimeSpatialMeshPrefab = profile.RuntimeSpatialMeshPrefab;
|
|
}
|
|
|
|
private static readonly ProfilerMarker ApplyUpdatedMeshDisplayOptionPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.ApplyUpdatedMeshDisplayOption");
|
|
|
|
/// <summary>
|
|
/// Applies the mesh display option to existing meshes when modified at runtime.
|
|
/// </summary>
|
|
/// <param name="option">The <see cref="SpatialAwarenessMeshDisplayOptions"/> value to be used to determine the appropriate material.</param>
|
|
protected virtual void ApplyUpdatedMeshDisplayOption(SpatialAwarenessMeshDisplayOptions option)
|
|
{
|
|
using (ApplyUpdatedMeshDisplayOptionPerfMarker.Auto())
|
|
{
|
|
bool enable = (option != SpatialAwarenessMeshDisplayOptions.None);
|
|
|
|
foreach (SpatialAwarenessMeshObject meshObject in Meshes.Values)
|
|
{
|
|
if (meshObject?.Renderer == null) { continue; }
|
|
|
|
if (enable)
|
|
{
|
|
meshObject.Renderer.sharedMaterial = (option == SpatialAwarenessMeshDisplayOptions.Visible) ?
|
|
VisibleMaterial :
|
|
OcclusionMaterial;
|
|
}
|
|
|
|
meshObject.Renderer.enabled = enable;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker ApplyUpdatedMeshPhysicsPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.ApplyUpdatedMeshPhysics");
|
|
|
|
/// <summary>
|
|
/// Applies the physical material to existing meshes when modified at runtime.
|
|
/// </summary>
|
|
protected virtual void ApplyUpdatedMeshPhysics()
|
|
{
|
|
using (ApplyUpdatedMeshPhysicsPerfMarker.Auto())
|
|
{
|
|
foreach (SpatialAwarenessMeshObject meshObject in Meshes.Values)
|
|
{
|
|
if (meshObject?.Collider == null) { continue; }
|
|
meshObject.Collider.material = PhysicsMaterial;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Maps <see cref="SpatialAwarenessMeshLevelOfDetail"/> to <see cref="TrianglesPerCubicMeter"/>.
|
|
/// </summary>
|
|
/// <param name="levelOfDetail">The desired level of density for the spatial mesh.</param>
|
|
/// <returns>
|
|
/// The number of triangles per cubic meter that will result in the desired level of density.
|
|
/// </returns>
|
|
protected virtual int LookupTriangleDensity(SpatialAwarenessMeshLevelOfDetail levelOfDetail)
|
|
{
|
|
// By default, returns the existing value. This will be custom defined for each platform, if necessary.
|
|
return TrianglesPerCubicMeter;
|
|
}
|
|
|
|
private static readonly ProfilerMarker ApplyUpdatedPhysicsLayerPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.ApplyUpdatedPhysicsLayer");
|
|
|
|
/// <summary>
|
|
/// Updates the mesh physics layer for current mesh observations.
|
|
/// </summary>
|
|
protected virtual void ApplyUpdatedPhysicsLayer()
|
|
{
|
|
using (ApplyUpdatedPhysicsLayerPerfMarker.Auto())
|
|
{
|
|
foreach (SpatialAwarenessMeshObject meshObject in Meshes.Values)
|
|
{
|
|
if (meshObject?.GameObject == null) { continue; }
|
|
|
|
meshObject.GameObject.layer = MeshPhysicsLayer;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker OnMeshAddedPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.OnMeshAdded - Raising OnObservationAdded");
|
|
|
|
/// <summary>
|
|
/// Event sent whenever a mesh is added.
|
|
/// </summary>
|
|
protected static readonly ExecuteEvents.EventFunction<IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject>> OnMeshAdded =
|
|
delegate (IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject> handler, BaseEventData eventData)
|
|
{
|
|
using (OnMeshAddedPerfMarker.Auto())
|
|
{
|
|
MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> spatialEventData = ExecuteEvents.ValidateEventData<MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject>>(eventData);
|
|
handler.OnObservationAdded(spatialEventData);
|
|
}
|
|
};
|
|
|
|
private static readonly ProfilerMarker OnMeshUpdatedPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.OnMeshUpdated - Raising OnObservationUpdated");
|
|
|
|
/// <summary>
|
|
/// Event sent whenever a mesh is updated.
|
|
/// </summary>
|
|
protected static readonly ExecuteEvents.EventFunction<IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject>> OnMeshUpdated =
|
|
delegate (IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject> handler, BaseEventData eventData)
|
|
{
|
|
using (OnMeshUpdatedPerfMarker.Auto())
|
|
{
|
|
MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> spatialEventData = ExecuteEvents.ValidateEventData<MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject>>(eventData);
|
|
handler.OnObservationUpdated(spatialEventData);
|
|
}
|
|
};
|
|
|
|
private static readonly ProfilerMarker OnMeshRemovedPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.OnMeshRemoved - Raising OnObservationRemoved");
|
|
|
|
/// <summary>
|
|
/// Event sent whenever a mesh is discarded.
|
|
/// </summary>
|
|
protected static readonly ExecuteEvents.EventFunction<IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject>> OnMeshRemoved =
|
|
delegate (IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject> handler, BaseEventData eventData)
|
|
{
|
|
using (OnMeshRemovedPerfMarker.Auto())
|
|
{
|
|
MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> spatialEventData = ExecuteEvents.ValidateEventData<MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject>>(eventData);
|
|
handler.OnObservationRemoved(spatialEventData);
|
|
}
|
|
};
|
|
|
|
#endregion BaseSpatialMeshObserver Implementation
|
|
|
|
#region IMixedRealityDataProvider Implementation
|
|
|
|
/// <summary>
|
|
/// Initializes event data and creates the observer.
|
|
/// </summary>
|
|
public override void Initialize()
|
|
{
|
|
meshEventData = new MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject>(EventSystem.current);
|
|
|
|
ReadProfile();
|
|
|
|
base.Initialize();
|
|
}
|
|
|
|
#endregion IMixedRealityDataProvider Implementation
|
|
|
|
#region IMixedRealitySpatialMeshObserver Implementation
|
|
|
|
private SpatialAwarenessMeshDisplayOptions displayOption = SpatialAwarenessMeshDisplayOptions.Visible;
|
|
|
|
/// <inheritdoc />
|
|
public SpatialAwarenessMeshDisplayOptions DisplayOption
|
|
{
|
|
get { return displayOption; }
|
|
set
|
|
{
|
|
displayOption = value;
|
|
if (Application.isPlaying)
|
|
{
|
|
ApplyUpdatedMeshDisplayOption(displayOption);
|
|
}
|
|
}
|
|
}
|
|
|
|
private SpatialAwarenessMeshLevelOfDetail levelOfDetail = SpatialAwarenessMeshLevelOfDetail.Coarse;
|
|
|
|
/// <inheritdoc />
|
|
public SpatialAwarenessMeshLevelOfDetail LevelOfDetail
|
|
{
|
|
get { return levelOfDetail; }
|
|
set
|
|
{
|
|
if (Application.isPlaying && value != SpatialAwarenessMeshLevelOfDetail.Custom)
|
|
{
|
|
TrianglesPerCubicMeter = LookupTriangleDensity(value);
|
|
}
|
|
|
|
levelOfDetail = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The backing field for Meshes, to allow the mesh observer implementation to track its meshes.
|
|
/// </summary>
|
|
protected readonly Dictionary<int, SpatialAwarenessMeshObject> meshes = new Dictionary<int, SpatialAwarenessMeshObject>();
|
|
|
|
/// <inheritdoc />
|
|
public IReadOnlyDictionary<int, SpatialAwarenessMeshObject> Meshes => new Dictionary<int, SpatialAwarenessMeshObject>(meshes);
|
|
|
|
private int meshPhysicsLayer = 31;
|
|
|
|
/// <inheritdoc />
|
|
public int MeshPhysicsLayer
|
|
{
|
|
get { return meshPhysicsLayer; }
|
|
set
|
|
{
|
|
if ((value < 0) || (value > 31))
|
|
{
|
|
Debug.LogError("Specified MeshPhysicsLayer is out of bounds. Please set a value between 0 and 31, inclusive.");
|
|
return;
|
|
}
|
|
|
|
meshPhysicsLayer = value;
|
|
ApplyUpdatedPhysicsLayer();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public int MeshPhysicsLayerMask => (1 << MeshPhysicsLayer);
|
|
|
|
/// <inheritdoc />
|
|
public bool RecalculateNormals { get; set; } = true;
|
|
|
|
/// <inheritdoc />
|
|
public int TrianglesPerCubicMeter { get; set; } = 0;
|
|
|
|
private Material occlusionMaterial = null;
|
|
|
|
/// <inheritdoc />
|
|
public Material OcclusionMaterial
|
|
{
|
|
get { return occlusionMaterial; }
|
|
set
|
|
{
|
|
if (value != occlusionMaterial)
|
|
{
|
|
occlusionMaterial = value;
|
|
|
|
if (Application.isPlaying && DisplayOption == SpatialAwarenessMeshDisplayOptions.Occlusion)
|
|
{
|
|
ApplyUpdatedMeshDisplayOption(SpatialAwarenessMeshDisplayOptions.Occlusion);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private PhysicMaterial physicsMaterial;
|
|
|
|
public PhysicMaterial PhysicsMaterial
|
|
{
|
|
get { return physicsMaterial; }
|
|
set
|
|
{
|
|
if (value != physicsMaterial)
|
|
{
|
|
physicsMaterial = value;
|
|
ApplyUpdatedMeshPhysics();
|
|
}
|
|
}
|
|
}
|
|
|
|
private Material visibleMaterial = null;
|
|
|
|
/// <inheritdoc />
|
|
public Material VisibleMaterial
|
|
{
|
|
get { return visibleMaterial; }
|
|
set
|
|
{
|
|
if (value != visibleMaterial)
|
|
{
|
|
visibleMaterial = value;
|
|
|
|
if (Application.isPlaying && DisplayOption == SpatialAwarenessMeshDisplayOptions.Visible)
|
|
{
|
|
ApplyUpdatedMeshDisplayOption(SpatialAwarenessMeshDisplayOptions.Visible);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private GameObject runtimeSpatialMeshPrefab = null;
|
|
|
|
/// <inheritdoc />
|
|
public GameObject RuntimeSpatialMeshPrefab
|
|
{
|
|
get { return runtimeSpatialMeshPrefab; }
|
|
set
|
|
{
|
|
if (value != runtimeSpatialMeshPrefab)
|
|
{
|
|
runtimeSpatialMeshPrefab = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion IMixedRealitySpatialMeshObserver Implementation
|
|
|
|
/// <summary>
|
|
/// Instantiates and appends a prefab to the Runtime (on device and not in editor)
|
|
/// Spatial Awareness hierarchy.
|
|
///
|
|
/// The default structure of the Spatial Awareness System:
|
|
///
|
|
/// Spatial Awareness System
|
|
/// Windows Mixed Reality Spatial Mesh Observer
|
|
/// Spatial Mesh - ID
|
|
/// Spatial Mesh - ID
|
|
/// ...
|
|
///
|
|
/// If the Runtime Spatial Mesh Prefab field is not null, this method adds the prefab
|
|
/// between the Spatial Awareness System and the Windows Mixed Reality Spatial Mesh Observer which results in this structure:
|
|
///
|
|
/// Spatial Awareness System
|
|
/// Runtime Spatial Mesh Prefab
|
|
/// Windows Mixed Reality Spatial Mesh Observer
|
|
/// Spatial Mesh - ID
|
|
/// Spatial Mesh - ID
|
|
/// ...
|
|
/// </summary>
|
|
protected void AddRuntimeSpatialMeshPrefabToHierarchy()
|
|
{
|
|
if (RuntimeSpatialMeshPrefab != null)
|
|
{
|
|
GameObject spatialMeshPrefab = Object.Instantiate(RuntimeSpatialMeshPrefab, Service.SpatialAwarenessObjectParent.transform);
|
|
|
|
if (spatialMeshPrefab.transform.position != Vector3.zero)
|
|
{
|
|
spatialMeshPrefab.transform.position = Vector3.zero;
|
|
}
|
|
|
|
ObservedObjectParent.transform.SetParent(spatialMeshPrefab.transform, false);
|
|
}
|
|
}
|
|
}
|
|
}
|