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