// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using UnityEngine; namespace HoloToolkit.Unity.SpatialMapping { /// <summary> /// The SpatialMappingManager class allows applications to use a SurfaceObserver or a stored /// Spatial Mapping mesh (loaded from a file). /// When an application loads a mesh file, the SurfaceObserver is stopped. /// Calling StartObserver() clears the stored mesh and enables real-time SpatialMapping updates. /// </summary> [RequireComponent(typeof(SpatialMappingObserver))] public partial class SpatialMappingManager : Singleton<SpatialMappingManager> { [Tooltip("The physics layer for spatial mapping objects to be set to.")] public int PhysicsLayer = 31; [Tooltip("The material to use for rendering spatial mapping data.")] [SerializeField] private Material surfaceMaterial; [Tooltip("Determines if the surface observer should be automatically started.")] [SerializeField] private bool autoStartObserver = true; [Tooltip("Determines if spatial mapping data will be rendered.")] [SerializeField] private bool drawVisualMeshes = false; [Tooltip("Determines if spatial mapping data will cast shadows.")] [SerializeField] private bool castShadows = false; /// <summary> /// Used for gathering real-time Spatial Mapping data on the HoloLens. /// </summary> private SpatialMappingObserver surfaceObserver; /// <summary> /// Time when StartObserver() was called. /// </summary> public float StartTime { get; private set; } /// <summary> /// SurfaceMappingObserver GET /// </summary> public SpatialMappingObserver SurfaceObserver { get { return surfaceObserver; } } /// <summary> /// The current source of spatial mapping data. /// </summary> public SpatialMappingSource Source { get { return source; } private set { if (source != value) { UpdateRendering(false); var oldSource = source; source = value; UpdateRendering(DrawVisualMeshes); var handlers = SourceChanged; if (handlers != null) { handlers(this, PropertyChangedEventArgsEx.Create(() => Source, oldSource, source)); } } } } private SpatialMappingSource source; /// <summary> /// Occurs when <see cref="Source" /> changes. /// </summary> public event EventHandler<PropertyChangedEventArgsEx<SpatialMappingSource>> SourceChanged; // Called when the GameObject is first created. protected override void Awake() { base.Awake(); surfaceObserver = gameObject.GetComponent<SpatialMappingObserver>(); Source = surfaceObserver; } // Use for initialization. private void Start() { if (autoStartObserver) { StartObserver(); } } /// <summary> /// Returns the layer as a bit mask. /// </summary> public int LayerMask { get { return (1 << PhysicsLayer); } } /// <summary> /// The material to use when rendering surfaces. /// </summary> public Material SurfaceMaterial { get { return surfaceMaterial; } set { if (value != surfaceMaterial) { surfaceMaterial = value; SetSurfaceMaterial(surfaceMaterial); } } } /// <summary> /// Specifies whether or not the SpatialMapping meshes are to be rendered. /// </summary> public bool DrawVisualMeshes { get { return drawVisualMeshes; } set { if (value != drawVisualMeshes) { drawVisualMeshes = value; UpdateRendering(drawVisualMeshes); } } } /// <summary> /// Specifies whether or not the SpatialMapping meshes can cast shadows. /// </summary> public bool CastShadows { get { return castShadows; } set { if (value != castShadows) { castShadows = value; SetShadowCasting(castShadows); } } } /// <summary> /// Sets the source of surface information. /// </summary> /// <param name="mappingSource">The source to switch to. Null means return to the live stream if possible.</param> public void SetSpatialMappingSource(SpatialMappingSource mappingSource) { Source = (mappingSource ?? surfaceObserver); } /// <summary> /// Sets the material used by all Spatial Mapping meshes. /// </summary> /// <param name="setSurfaceMaterial">New material to apply.</param> public void SetSurfaceMaterial(Material setSurfaceMaterial) { SurfaceMaterial = setSurfaceMaterial; if (DrawVisualMeshes) { foreach (MeshRenderer sourceRenderer in Source.GetMeshRenderers()) { if (sourceRenderer != null) { sourceRenderer.sharedMaterial = setSurfaceMaterial; } } } } /// <summary> /// Checks to see if the SurfaceObserver is currently running. /// </summary> /// <returns>True, if the observer state is running.</returns> public bool IsObserverRunning() { return surfaceObserver.ObserverState == ObserverStates.Running; } /// <summary> /// Instructs the SurfaceObserver to start updating the SpatialMapping mesh. /// </summary> public void StartObserver() { #if UNITY_WSA // Allow observing if a device is present (Holographic Remoting) #if UNITY_2017_2_OR_NEWER if (!UnityEngine.XR.XRDevice.isPresent) { return; } #else if (!UnityEngine.VR.VRDevice.isPresent) { return; } #endif #endif if (!IsObserverRunning()) { surfaceObserver.StartObserving(); StartTime = Time.unscaledTime; } } /// <summary> /// Instructs the SurfaceObserver to stop updating the SpatialMapping mesh. /// </summary> public void StopObserver() { #if UNITY_WSA // Allow observing if a device is present (Holographic Remoting) #if UNITY_2017_2_OR_NEWER if (!UnityEngine.XR.XRDevice.isPresent) { return; } #else if (!UnityEngine.VR.VRDevice.isPresent) { return; } #endif #endif if (IsObserverRunning()) { surfaceObserver.StopObserving(); } } /// <summary> /// Instructs the SurfaceObserver to stop and cleanup all meshes. /// </summary> public void CleanupObserver() { surfaceObserver.CleanupObserver(); } /// <summary> /// Gets all meshes that are associated with the SpatialMapping mesh. /// </summary> /// <returns> /// Collection of Mesh objects representing the SpatialMapping mesh. /// </returns> public List<Mesh> GetMeshes() { List<Mesh> meshes = new List<Mesh>(); List<MeshFilter> meshFilters = GetMeshFilters(); // Get all valid mesh filters for observed surfaces. for (int i = 0; i < meshFilters.Count; i++) { // GetMeshFilters ensures that both filter and filter.sharedMesh are not null. meshes.Add(meshFilters[i].sharedMesh); } return meshes; } /// <summary> /// Gets all the surface objects associated with the Spatial Mapping mesh. /// </summary> /// <returns>Collection of SurfaceObjects.</returns> public ReadOnlyCollection<SpatialMappingSource.SurfaceObject> GetSurfaceObjects() { return Source.SurfaceObjects; } /// <summary> /// Gets all Mesh Filter objects associated with the Spatial Mapping mesh. /// </summary> /// <returns>Collection of Mesh Filter objects.</returns> public List<MeshFilter> GetMeshFilters() { return Source.GetMeshFilters(); } /// <summary> /// Sets the Cast Shadows property for each Spatial Mapping mesh renderer. /// </summary> private void SetShadowCasting(bool canCastShadows) { CastShadows = canCastShadows; foreach (MeshRenderer sourceRenderer in Source.GetMeshRenderers()) { if (sourceRenderer != null) { if (canCastShadows) { sourceRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On; } else { sourceRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } } } } /// <summary> /// Updates the rendering state on the currently enabled surfaces. /// Updates the material and shadow casting mode for each renderer. /// </summary> /// <param name="enable">True, if meshes should be rendered.</param> private void UpdateRendering(bool enable) { if (Source != null) { List<MeshRenderer> renderers = Source.GetMeshRenderers(); for (int index = 0; index < renderers.Count; index++) { if (renderers[index] != null) { renderers[index].enabled = enable; if (enable) { renderers[index].sharedMaterial = SurfaceMaterial; } } } } } } }