mixedreality/com.microsoft.mixedreality..../Core/Providers/BaseSpatialMeshObserver.cs

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);
}
}
}
}