// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; using UnityEngine; using HoloToolkit.Unity.SpatialMapping; namespace HoloToolkit.Unity { /// <summary> /// The SpatialUnderstanding class controls the state and flow of the /// scanning process used in the understanding module. /// </summary> [RequireComponent(typeof(SpatialUnderstandingSourceMesh))] [RequireComponent(typeof(SpatialUnderstandingCustomMesh))] public class SpatialUnderstanding : Singleton<SpatialUnderstanding> { // Consts public const float ScanSearchDistance = 8.0f; // Enums public enum ScanStates { None, ReadyToScan, Scanning, Finishing, Done } // Config [Tooltip("If set to false, scanning will only begin after RequestBeginScanning is called")] public bool AutoBeginScanning = true; [Tooltip("Update period used during the scanning process (typically faster than after scanning is completed)")] public float UpdatePeriod_DuringScanning = 1.0f; [Tooltip("Update period used after the scanning process is completed")] public float UpdatePeriod_AfterScanning = 4.0f; // Properties /// <summary> /// Switch used by the entire SpatialUnderstanding module to activate processing. /// </summary> public bool AllowSpatialUnderstanding { get { return true; } } /// <summary> /// Reference to the SpatialUnderstandingDLL class (wraps the understanding DLL functions). /// </summary> public SpatialUnderstandingDll UnderstandingDLL { get; private set; } /// <summary> /// Reference to the SpatialUnderstandingSourceMesh behavior (input mesh data for the understanding module). /// </summary> public SpatialUnderstandingSourceMesh UnderstandingSourceMesh { get; private set; } /// <summary> /// Reference to the UnderstandingCustomMesh behavior (output mesh data from the understanding module). /// </summary> public SpatialUnderstandingCustomMesh UnderstandingCustomMesh { get; private set; } /// <summary> /// Indicates the current state of the scan process /// </summary> public ScanStates ScanState { get { return scanState; } private set { scanState = value; if (ScanStateChanged != null) { ScanStateChanged(); } // Update scan period, based on state SpatialMappingManager.Instance.GetComponent<SpatialMappingObserver>().TimeBetweenUpdates = (scanState == ScanStates.Done) ? UpdatePeriod_AfterScanning : UpdatePeriod_DuringScanning; } } /// <summary> /// Indicates the scanning statistics are still being processed. /// Request finish should not be called when this is true. /// </summary> public bool ScanStatsReportStillWorking { get { if (AllowSpatialUnderstanding) { SpatialUnderstandingDll.Imports.PlayspaceStats stats = UnderstandingDLL.GetStaticPlayspaceStats(); return (stats.IsWorkingOnStats != 0); } return false; } } public delegate void OnScanDoneDelegate(); // Events /// <summary> /// Event indicating that the scan is done /// </summary> public event OnScanDoneDelegate OnScanDone; /// <summary> /// Event indicating that the scan state has changed /// </summary> public event Action ScanStateChanged; // Privates private ScanStates scanState; private float timeSinceLastUpdate = 0.0f; // Functions protected override void Awake() { base.Awake(); // Cache references to required component UnderstandingDLL = new SpatialUnderstandingDll(); UnderstandingSourceMesh = GetComponent<SpatialUnderstandingSourceMesh>(); UnderstandingCustomMesh = GetComponent<SpatialUnderstandingCustomMesh>(); } private void Start() { // Initialize the DLL if (AllowSpatialUnderstanding) { SpatialUnderstandingDll.Imports.SpatialUnderstanding_Init(); } } private void Update() { if (!AllowSpatialUnderstanding) { return; } // Only update every few frames, and only if we aren't pulling in a mesh // already. timeSinceLastUpdate += Time.deltaTime; if ((!UnderstandingCustomMesh.IsImportActive) && (Time.frameCount % 3 == 0)) { // Real-Time scan Update_Scan(timeSinceLastUpdate); timeSinceLastUpdate = 0; } } protected override void OnDestroy() { // Term the DLL if (AllowSpatialUnderstanding) { SpatialUnderstandingDll.Imports.SpatialUnderstanding_Term(); } base.OnDestroy(); } /// <summary> /// Call to request that scanning should begin. If AutoBeginScanning /// is false, this function should be used to initiate the scanning process. /// </summary> public void RequestBeginScanning() { if (ScanState == ScanStates.None) { ScanState = ScanStates.ReadyToScan; } } /// <summary> /// Call to request that the scanning progress be finishing. The application must do /// this to finalize the playspace. The scan state will not progress pass /// Scanning until this is called. The spatial understanding queries will not function /// until the playspace is finalized. /// </summary> public void RequestFinishScan() { if (AllowSpatialUnderstanding) { SpatialUnderstandingDll.Imports.GeneratePlayspace_RequestFinish(); ScanState = ScanStates.Finishing; } } /// <summary> /// Update the scan progress. This function will initialize the scan, update it, /// and issue a final mesh import, when the scan is complete. /// </summary> /// <param name="deltaTime">The amount of time that has passed since the last update (typically Time.deltaTime)</param> private void Update_Scan(float deltaTime) { // If we auto-start scanning, do it now if (AutoBeginScanning && (ScanState == ScanStates.None)) { RequestBeginScanning(); } // Update the scan bool scanDone = false; if (((ScanState == ScanStates.ReadyToScan) || (ScanState == ScanStates.Scanning) || (ScanState == ScanStates.Finishing)) && (AllowSpatialUnderstanding)) { // Camera Transform cameraTransform = CameraCache.Main.transform; Vector3 camPos = cameraTransform.position; Vector3 camFwd = cameraTransform.forward; Vector3 camUp = cameraTransform.up; // If not yet initialized, do that now if (ScanState == ScanStates.ReadyToScan) { SpatialUnderstandingDll.Imports.GeneratePlayspace_InitScan( camPos.x, camPos.y, camPos.z, camFwd.x, camFwd.y, camFwd.z, camUp.x, camUp.y, camUp.z, ScanSearchDistance, ScanSearchDistance); ScanState = ScanStates.Scanning; } // Update int meshCount; IntPtr meshList; if (UnderstandingSourceMesh.GetInputMeshList(out meshCount, out meshList)) { var stopWatch = System.Diagnostics.Stopwatch.StartNew(); scanDone = SpatialUnderstandingDll.Imports.GeneratePlayspace_UpdateScan( meshCount, meshList, camPos.x, camPos.y, camPos.z, camFwd.x, camFwd.y, camFwd.z, camUp.x, camUp.y, camUp.z, deltaTime) == 1; stopWatch.Stop(); if (stopWatch.Elapsed.TotalMilliseconds > (1000.0 / 30.0)) { Debug.LogWarningFormat("SpatialUnderstandingDll.Imports.GeneratePlayspace_UpdateScan took {0,9:N2} ms", stopWatch.Elapsed.TotalMilliseconds); } } } // If it's done, finish up if ((ScanState == ScanStates.Finishing) && (scanDone) && (!UnderstandingCustomMesh.IsImportActive) && (UnderstandingCustomMesh != null)) { // Final mesh import StartCoroutine(UnderstandingCustomMesh.Import_UnderstandingMesh()); // Mark it ScanState = ScanStates.Done; if (OnScanDone != null) OnScanDone.Invoke(); } } } }