// 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
{
///
/// Class providing a base implementation of the interface.
///
public abstract class BaseSpatialMeshObserver : BaseSpatialObserver, IMixedRealitySpatialAwarenessMeshObserver, ISpatialAwarenessPhysicsProperties
{
///
/// Constructor.
///
/// The to which the observer is providing data.
/// The friendly name of the data provider.
/// The registration priority of the data provider.
/// The configuration profile for the data provider.
protected BaseSpatialMeshObserver(
IMixedRealitySpatialAwarenessSystem spatialAwarenessSystem,
string name = null,
uint priority = DefaultPriority,
BaseMixedRealityProfile profile = null) : base(spatialAwarenessSystem, name, priority, profile)
{
}
#region BaseSpatialMeshObserver Implementation
protected MixedRealitySpatialAwarenessEventData 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");
///
/// Applies the mesh display option to existing meshes when modified at runtime.
///
/// The value to be used to determine the appropriate material.
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");
///
/// Applies the physical material to existing meshes when modified at runtime.
///
protected virtual void ApplyUpdatedMeshPhysics()
{
using (ApplyUpdatedMeshPhysicsPerfMarker.Auto())
{
foreach (SpatialAwarenessMeshObject meshObject in Meshes.Values)
{
if (meshObject?.Collider == null) { continue; }
meshObject.Collider.material = PhysicsMaterial;
}
}
}
///
/// Maps to .
///
/// The desired level of density for the spatial mesh.
///
/// The number of triangles per cubic meter that will result in the desired level of density.
///
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");
///
/// Updates the mesh physics layer for current mesh observations.
///
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");
///
/// Event sent whenever a mesh is added.
///
protected static readonly ExecuteEvents.EventFunction> OnMeshAdded =
delegate (IMixedRealitySpatialAwarenessObservationHandler handler, BaseEventData eventData)
{
using (OnMeshAddedPerfMarker.Auto())
{
MixedRealitySpatialAwarenessEventData spatialEventData = ExecuteEvents.ValidateEventData>(eventData);
handler.OnObservationAdded(spatialEventData);
}
};
private static readonly ProfilerMarker OnMeshUpdatedPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.OnMeshUpdated - Raising OnObservationUpdated");
///
/// Event sent whenever a mesh is updated.
///
protected static readonly ExecuteEvents.EventFunction> OnMeshUpdated =
delegate (IMixedRealitySpatialAwarenessObservationHandler handler, BaseEventData eventData)
{
using (OnMeshUpdatedPerfMarker.Auto())
{
MixedRealitySpatialAwarenessEventData spatialEventData = ExecuteEvents.ValidateEventData>(eventData);
handler.OnObservationUpdated(spatialEventData);
}
};
private static readonly ProfilerMarker OnMeshRemovedPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.OnMeshRemoved - Raising OnObservationRemoved");
///
/// Event sent whenever a mesh is discarded.
///
protected static readonly ExecuteEvents.EventFunction> OnMeshRemoved =
delegate (IMixedRealitySpatialAwarenessObservationHandler handler, BaseEventData eventData)
{
using (OnMeshRemovedPerfMarker.Auto())
{
MixedRealitySpatialAwarenessEventData spatialEventData = ExecuteEvents.ValidateEventData>(eventData);
handler.OnObservationRemoved(spatialEventData);
}
};
#endregion BaseSpatialMeshObserver Implementation
#region IMixedRealityDataProvider Implementation
///
/// Initializes event data and creates the observer.
///
public override void Initialize()
{
meshEventData = new MixedRealitySpatialAwarenessEventData(EventSystem.current);
ReadProfile();
base.Initialize();
}
#endregion IMixedRealityDataProvider Implementation
#region IMixedRealitySpatialMeshObserver Implementation
private SpatialAwarenessMeshDisplayOptions displayOption = SpatialAwarenessMeshDisplayOptions.Visible;
///
public SpatialAwarenessMeshDisplayOptions DisplayOption
{
get { return displayOption; }
set
{
displayOption = value;
if (Application.isPlaying)
{
ApplyUpdatedMeshDisplayOption(displayOption);
}
}
}
private SpatialAwarenessMeshLevelOfDetail levelOfDetail = SpatialAwarenessMeshLevelOfDetail.Coarse;
///
public SpatialAwarenessMeshLevelOfDetail LevelOfDetail
{
get { return levelOfDetail; }
set
{
if (Application.isPlaying && value != SpatialAwarenessMeshLevelOfDetail.Custom)
{
TrianglesPerCubicMeter = LookupTriangleDensity(value);
}
levelOfDetail = value;
}
}
///
/// The backing field for Meshes, to allow the mesh observer implementation to track its meshes.
///
protected readonly Dictionary meshes = new Dictionary();
///
public IReadOnlyDictionary Meshes => new Dictionary(meshes);
private int meshPhysicsLayer = 31;
///
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();
}
}
///
public int MeshPhysicsLayerMask => (1 << MeshPhysicsLayer);
///
public bool RecalculateNormals { get; set; } = true;
///
public int TrianglesPerCubicMeter { get; set; } = 0;
private Material occlusionMaterial = null;
///
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;
///
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;
///
public GameObject RuntimeSpatialMeshPrefab
{
get { return runtimeSpatialMeshPrefab; }
set
{
if (value != runtimeSpatialMeshPrefab)
{
runtimeSpatialMeshPrefab = value;
}
}
}
#endregion IMixedRealitySpatialMeshObserver Implementation
///
/// 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
/// ...
///
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);
}
}
}
}