/// <summary> /// Issue a request to the Surface Observer to begin baking the mesh. /// </summary> /// <param name="meshId">ID of the mesh to bake.</param> private void RequestMesh(MeshId meshId) { using (RequestMeshPerfMarker.Auto()) { string meshName = ("SpatialMesh - " + meshId); SpatialAwarenessMeshObject newMesh; if (spareMeshObject == null) { newMesh = SpatialAwarenessMeshObject.Create( null, MeshPhysicsLayer, meshName, meshId.GetHashCode()); } else { newMesh = spareMeshObject; spareMeshObject = null; newMesh.GameObject.name = meshName; newMesh.Id = meshId.GetHashCode(); newMesh.GameObject.SetActive(true); } XRSubsystemHelpers.MeshSubsystem.GenerateMeshAsync(meshId, newMesh.Filter.mesh, newMesh.Collider, MeshVertexAttributes.Normals, (MeshGenerationResult meshGenerationResult) => MeshGenerationAction(meshGenerationResult)); outstandingMeshObject = newMesh; } }
/// <inheritdoc/> public override void Suspend() { #if UNITY_WSA if (!IsRunning) { Debug.LogWarning("The Windows Mixed Reality spatial observer is currently stopped."); return; } using (SuspendPerfMarker.Auto()) { // UpdateObserver keys off of this value to stop observing. IsRunning = false; // Halt any outstanding work. if (outstandingMeshObject != null) { ReclaimMeshObject(outstandingMeshObject); outstandingMeshObject = null; } // Clear any pending work. meshWorkQueue.Clear(); } #endif // UNITY_WSA }
/// <summary> /// Reclaims the <see cref="SpatialAwarenessMeshObject"/> to allow for later reuse. /// </summary> protected void ReclaimMeshObject(SpatialAwarenessMeshObject availableMeshObject) { using (ReclaimMeshObjectPerfMarker.Auto()) { if (spareMeshObject == null) { // Cleanup the mesh object. // Do not destroy the game object, destroy the meshes. SpatialAwarenessMeshObject.Cleanup(availableMeshObject, false); if (availableMeshObject.GameObject != null) { availableMeshObject.GameObject.name = "Unused Spatial Mesh"; availableMeshObject.GameObject.SetActive(false); } spareMeshObject = availableMeshObject; } else { // Cleanup the mesh object. // Destroy the game object, destroy the meshes. SpatialAwarenessMeshObject.Cleanup(availableMeshObject); } } }
/// <summary> /// Sends the observations using the mesh data contained within the configured 3D model. /// </summary> private void SendMeshObjects() { if (!sendObservations) { return; } if (spatialMeshObject != null) { MeshFilter[] meshFilters = spatialMeshObject.GetComponentsInChildren <MeshFilter>(); for (int i = 0; i < meshFilters.Length; i++) { SpatialAwarenessMeshObject meshObject = SpatialAwarenessMeshObject.Create( meshFilters[i].sharedMesh, MeshPhysicsLayer, $"Spatial Object Mesh {currentMeshId}", currentMeshId, ObservedObjectParent); meshObject.GameObject.transform.localPosition = meshFilters[i].transform.position; meshObject.GameObject.transform.localRotation = meshFilters[i].transform.rotation; ApplyMeshMaterial(meshObject); meshes.Add(currentMeshId, meshObject); meshEventData.Initialize(this, currentMeshId, meshObject); SpatialAwarenessSystem?.HandleEvent(meshEventData, OnMeshAdded); currentMeshId++; } } sendObservations = false; }
/// <summary> /// Issue a request to the Surface Observer to begin baking the mesh. /// </summary> /// <param name="meshId">ID of the mesh to bake.</param> private void RequestMesh(MeshId meshId) { using (RequestMeshPerfMarker.Auto()) { string meshName = ("SpatialMesh - " + meshId); SpatialAwarenessMeshObject newMesh; if (spareMeshObject == null) { newMesh = SpatialAwarenessMeshObject.Create( null, MeshPhysicsLayer, meshName, meshId.GetHashCode()); GameObject anchorHolder = new GameObject(meshName + "_anchor"); anchorHolder.AddComponent <PlayspaceAdapter>(); // Right now, we don't add an anchor to the mesh. This may be resolved with the AnchorSubsystem. anchorHolder.transform.SetParent(newMesh.GameObject.transform, false); } else { newMesh = spareMeshObject; spareMeshObject = null; newMesh.GameObject.name = meshName; newMesh.Id = meshId.GetHashCode(); newMesh.GameObject.SetActive(true); } XRSubsystemHelpers.MeshSubsystem.GenerateMeshAsync(meshId, newMesh.Filter.mesh, newMesh.Collider, MeshVertexAttributes.Normals, (MeshGenerationResult meshGenerationResult) => MeshGenerationAction(meshGenerationResult)); outstandingMeshObject = newMesh; } }
/// <summary> /// Issue a request to the Surface Observer to begin baking the mesh. /// </summary> /// <param name="surfaceId">ID of the mesh to bake.</param> private void RequestMesh(SurfaceId surfaceId) { string meshName = ("SpatialMesh - " + surfaceId.handle); SpatialAwarenessMeshObject newMesh; WorldAnchor worldAnchor; if (spareMeshObject == null) { newMesh = SpatialAwarenessMeshObject.Create(null, MeshPhysicsLayer, meshName, surfaceId.handle); // The WorldAnchor component places its object where the anchor is in the same space as the camera. // But since the camera is repositioned by the MixedRealityPlayspace's transform, the meshes' transforms // should also the WorldAnchor position repositioned by the MixedRealityPlayspace's transform. // So rather than put the WorldAnchor on the mesh's GameObject, the WorldAnchor is placed out of the way in the scene, // and its transform is concatenated with the Playspace transform to compute the transform on the mesh's object. // That adapting the WorldAnchor's transform into playspace is done by the internal PlayspaceAdapter component. // The GameObject the WorldAnchor is placed on is unimportant, but it is convenient for cleanup to make it a child // of the GameObject whose transform will track it. GameObject anchorHolder = new GameObject(meshName + "_anchor"); anchorHolder.AddComponent <PlayspaceAdapter>(); // replace with required component? worldAnchor = anchorHolder.AddComponent <WorldAnchor>(); // replace with required component and GetComponent()? anchorHolder.transform.SetParent(newMesh.GameObject.transform, false); } else { newMesh = spareMeshObject; spareMeshObject = null; newMesh.GameObject.name = meshName; newMesh.Id = surfaceId.handle; newMesh.GameObject.SetActive(true); // There should be exactly one child on the newMesh.GameObject, and that is the GameObject added above // to hold the WorldAnchor component and adapter. Debug.Assert(newMesh.GameObject.transform.childCount == 1, "Expecting a single child holding the WorldAnchor"); worldAnchor = newMesh.GameObject.transform.GetChild(0).gameObject.GetComponent <WorldAnchor>(); } Debug.Assert(worldAnchor != null); SurfaceData surfaceData = new SurfaceData( surfaceId, newMesh.Filter, worldAnchor, newMesh.Collider, TrianglesPerCubicMeter, true); if (observer.RequestMeshAsync(surfaceData, SurfaceObserver_OnDataReady)) { outstandingMeshObject = newMesh; } else { Debug.LogError($"Mesh request failed for Id == surfaceId.handle"); outstandingMeshObject = null; ReclaimMeshObject(newMesh); } }
public MeshData(SpatialAwarenessMeshObject aMesh) { if (aMesh != null) { data = MeshSerializer.SerializeMesh(aMesh.Filter.sharedMesh); transform = new TransformS(aMesh.Renderer.transform); } else { data = new byte[0]; transform = new TransformS(null); } }
/// <summary> /// Removes an observation. /// </summary> private void RemoveMeshObject(int meshId) { if (meshes.TryGetValue(meshId, out SpatialAwarenessMeshObject meshObject)) { // Remove the mesh object from the collection. meshes.Remove(meshId); if (meshObject != null) { SpatialAwarenessMeshObject.Cleanup(meshObject); } // Send the mesh removed event meshEventData.Initialize(this, meshId, null); SpatialAwarenessSystem?.HandleEvent(meshEventData, OnMeshRemoved); } }
/// <summary> /// Issue a request to the Surface Observer to begin baking the mesh. /// </summary> /// <param name="surfaceId">ID of the mesh to bake.</param> private void RequestMesh(SurfaceId surfaceId) { string meshName = ("SpatialMesh - " + surfaceId.handle); SpatialAwarenessMeshObject newMesh; WorldAnchor worldAnchor; if (spareMeshObject == null) { newMesh = SpatialAwarenessMeshObject.Create(null, MeshPhysicsLayer, meshName, surfaceId.handle); worldAnchor = newMesh.GameObject.AddComponent <WorldAnchor>(); } else { newMesh = spareMeshObject; spareMeshObject = null; newMesh.GameObject.name = meshName; newMesh.Id = surfaceId.handle; newMesh.GameObject.SetActive(true); worldAnchor = newMesh.GameObject.GetComponent <WorldAnchor>(); } Debug.Assert(worldAnchor != null); SurfaceData surfaceData = new SurfaceData( surfaceId, newMesh.Filter, worldAnchor, newMesh.Collider, TrianglesPerCubicMeter, true); if (observer.RequestMeshAsync(surfaceData, SurfaceObserver_OnDataReady)) { outstandingMeshObject = newMesh; } else { Debug.LogError($"Mesh request failed for Id == surfaceId.handle"); outstandingMeshObject = null; ReclaimMeshObject(newMesh); } }
/// <summary> /// Applies the appropriate material, based on the current of the <see cref="SpatialAwarenessMeshDisplayOptions"/> property. /// </summary> /// <param name="meshObject">The <see cref="SpatialAwarenessMeshObject"/> for which the material is to be applied.</param> private void ApplyMeshMaterial(SpatialAwarenessMeshObject meshObject) { if (meshObject?.Renderer == null) { return; } bool enable = (DisplayOption != SpatialAwarenessMeshDisplayOptions.None); if (enable) { meshObject.Renderer.sharedMaterial = (DisplayOption == SpatialAwarenessMeshDisplayOptions.Visible) ? VisibleMaterial : OcclusionMaterial; meshObject.Collider.material = PhysicsMaterial; } meshObject.Renderer.enabled = enable; }
/// <inheritdoc /> public override void ClearObservations() { using (ClearObservationsPerfMarker.Auto()) { bool wasRunning = false; if (IsRunning) { wasRunning = true; Debug.Log("Cannot clear observations while the observer is running. Suspending this observer."); Suspend(); } IReadOnlyList <int> observations = new List <int>(Meshes.Keys); foreach (int meshId in observations) { RemoveMeshObject(meshId); } // Cleanup the outstanding mesh object. if (outstandingMeshObject != null) { // Destroy the game object, destroy the meshes. SpatialAwarenessMeshObject.Cleanup(outstandingMeshObject); outstandingMeshObject = null; } // Cleanup the spare mesh object if (spareMeshObject != null) { // Destroy the game object, destroy the meshes. SpatialAwarenessMeshObject.Cleanup(spareMeshObject); spareMeshObject = null; } if (wasRunning) { Resume(); } } }
/// <summary> /// Cleans up the objects created during observation. /// </summary> private void CleanupObservedObjects() { if (Application.isPlaying) { // Cleanup the scene objects are managing if (observedObjectParent != null) { observedObjectParent.transform.DetachChildren(); } foreach (SpatialAwarenessMeshObject meshObject in meshes.Values) { if (meshObject != null) { // Destroy the game object, destroy the meshes. SpatialAwarenessMeshObject.Cleanup(meshObject); } } meshes.Clear(); // Cleanup the outstanding mesh object. if (outstandingMeshObject != null) { // Destroy the game object, destroy the meshes. SpatialAwarenessMeshObject.Cleanup(outstandingMeshObject); outstandingMeshObject = null; } // Cleanup the spare mesh object if (spareMeshObject != null) { // Destroy the game object, destroy the meshes. SpatialAwarenessMeshObject.Cleanup(spareMeshObject); spareMeshObject = null; } } }
private void MeshGenerationAction(MeshGenerationResult meshGenerationResult) { if (!IsRunning) { return; } using (MeshGenerationActionPerfMarker.Auto()) { if (outstandingMeshObject == null) { Debug.LogWarning($"MeshGenerationAction called for mesh id {meshGenerationResult.MeshId} while no request was outstanding."); return; } switch (meshGenerationResult.Status) { case MeshGenerationStatus.InvalidMeshId: case MeshGenerationStatus.Canceled: case MeshGenerationStatus.UnknownError: outstandingMeshObject = null; break; case MeshGenerationStatus.Success: // Since there is only one outstanding mesh object, update the id to match // the one received after baking. SpatialAwarenessMeshObject meshObject = outstandingMeshObject; meshObject.Id = meshGenerationResult.MeshId.GetHashCode(); outstandingMeshObject = null; // Apply the appropriate material to the mesh. SpatialAwarenessMeshDisplayOptions displayOption = DisplayOption; if (displayOption != SpatialAwarenessMeshDisplayOptions.None) { meshObject.Renderer.enabled = true; meshObject.Renderer.sharedMaterial = (displayOption == SpatialAwarenessMeshDisplayOptions.Visible) ? VisibleMaterial : OcclusionMaterial; meshObject.Collider.material = PhysicsMaterial; } else { meshObject.Renderer.enabled = false; } // Recalculate the mesh normals if requested. if (RecalculateNormals) { meshObject.Filter.sharedMesh.RecalculateNormals(); } // Add / update the mesh to our collection bool sendUpdatedEvent = false; if (meshes.ContainsKey(meshObject.Id)) { // Reclaim the old mesh object for future use. ReclaimMeshObject(meshes[meshObject.Id]); meshes.Remove(meshObject.Id); sendUpdatedEvent = true; } meshes.Add(meshObject.Id, meshObject); meshObject.GameObject.transform.parent = (ObservedObjectParent.transform != null) ? ObservedObjectParent.transform : null; meshEventData.Initialize(this, meshObject.Id, meshObject); if (sendUpdatedEvent) { SpatialAwarenessSystem?.HandleEvent(meshEventData, OnMeshUpdated); } else { SpatialAwarenessSystem?.HandleEvent(meshEventData, OnMeshAdded); } break; } } }
/// <summary> /// Handles the SurfaceObserver's OnDataReady event. /// </summary> /// <param name="cookedData">Struct containing output data.</param> /// <param name="outputWritten">Set to true if output has been written.</param> /// <param name="elapsedCookTimeSeconds">Seconds between mesh cook request and propagation of this event.</param> private void SurfaceObserver_OnDataReady(SurfaceData cookedData, bool outputWritten, float elapsedCookTimeSeconds) { if (!IsRunning) { return; } using (OnDataReadyPerfMarker.Auto()) { if (outstandingMeshObject == null) { return; } if (!outputWritten) { ReclaimMeshObject(outstandingMeshObject); outstandingMeshObject = null; return; } // Since there is only one outstanding mesh object, update the id to match // the one received after baking. SpatialAwarenessMeshObject meshObject = outstandingMeshObject; meshObject.Id = cookedData.id.handle; outstandingMeshObject = null; // Check to see if this is a new or updated mesh. bool isMeshUpdate = meshes.ContainsKey(meshObject.Id); // We presume that if the display option is not occlusion, that we should // default to the visible material. // Note: We check explicitly for a display option of none later in this method. Material material = (DisplayOption == SpatialAwarenessMeshDisplayOptions.Occlusion) ? OcclusionMaterial : VisibleMaterial; // If this is a mesh update, we want to preserve the mesh's previous material. material = isMeshUpdate ? meshes[meshObject.Id].Renderer.sharedMaterial : material; // Apply the appropriate material. meshObject.Renderer.sharedMaterial = material; // Recalculate the mesh normals if requested. if (RecalculateNormals) { meshObject.Filter.sharedMesh.RecalculateNormals(); } // Check to see if the display option is set to none. If so, we disable // the renderer. meshObject.Renderer.enabled = (DisplayOption != SpatialAwarenessMeshDisplayOptions.None); // Set the physics material if (meshObject.Renderer.enabled) { meshObject.Collider.material = PhysicsMaterial; } // Add / update the mesh to our collection if (isMeshUpdate) { // Reclaim the old mesh object for future use. ReclaimMeshObject(meshes[meshObject.Id]); meshes.Remove(meshObject.Id); } meshes.Add(meshObject.Id, meshObject); // Preserve local transform relative to parent. meshObject.GameObject.transform.SetParent(ObservedObjectParent != null ? ObservedObjectParent.transform: null, false); meshEventData.Initialize(this, meshObject.Id, meshObject); if (isMeshUpdate) { SpatialAwarenessSystem?.HandleEvent(meshEventData, OnMeshUpdated); } else { SpatialAwarenessSystem?.HandleEvent(meshEventData, OnMeshAdded); } } }
/// <summary> /// Handles the SurfaceObserver's OnDataReady event. /// </summary> /// <param name="cookedData">Struct containing output data.</param> /// <param name="outputWritten">Set to true if output has been written.</param> /// <param name="elapsedCookTimeSeconds">Seconds between mesh cook request and propagation of this event.</param> private void SurfaceObserver_OnDataReady(SurfaceData cookedData, bool outputWritten, float elapsedCookTimeSeconds) { if (!IsRunning) { return; } if (outstandingMeshObject == null) { return; } if (!outputWritten) { ReclaimMeshObject(outstandingMeshObject); outstandingMeshObject = null; return; } // Since there is only one outstanding mesh object, update the id to match // the one received after baking. SpatialAwarenessMeshObject meshObject = outstandingMeshObject; meshObject.Id = cookedData.id.handle; outstandingMeshObject = null; // Apply the appropriate material to the mesh. SpatialAwarenessMeshDisplayOptions displayOption = DisplayOption; if (displayOption != SpatialAwarenessMeshDisplayOptions.None) { meshObject.Renderer.enabled = true; meshObject.Renderer.sharedMaterial = (displayOption == SpatialAwarenessMeshDisplayOptions.Visible) ? VisibleMaterial : OcclusionMaterial; } else { meshObject.Renderer.enabled = false; } // Recalculate the mesh normals if requested. if (RecalculateNormals) { meshObject.Filter.sharedMesh.RecalculateNormals(); } // Add / update the mesh to our collection bool sendUpdatedEvent = false; if (meshes.ContainsKey(cookedData.id.handle)) { // Reclaim the old mesh object for future use. ReclaimMeshObject(meshes[cookedData.id.handle]); meshes.Remove(cookedData.id.handle); sendUpdatedEvent = true; } meshes.Add(cookedData.id.handle, meshObject); meshObject.GameObject.transform.parent = (ObservedObjectParent.transform != null) ? ObservedObjectParent.transform : null; meshEventData.Initialize(this, cookedData.id.handle, meshObject); if (sendUpdatedEvent) { SpatialAwarenessSystem?.HandleEvent(meshEventData, OnMeshUpdated); } else { SpatialAwarenessSystem?.HandleEvent(meshEventData, OnMeshAdded); } }
private void MeshGenerationAction(MeshGenerationResult meshGenerationResult) { if (!IsRunning) { return; } using (MeshGenerationActionPerfMarker.Auto()) { if (outstandingMeshObject == null) { Debug.LogWarning($"MeshGenerationAction called for mesh id {meshGenerationResult.MeshId} while no request was outstanding."); return; } switch (meshGenerationResult.Status) { case MeshGenerationStatus.InvalidMeshId: case MeshGenerationStatus.Canceled: case MeshGenerationStatus.UnknownError: outstandingMeshObject = null; break; case MeshGenerationStatus.Success: // Since there is only one outstanding mesh object, update the id to match // the one received after baking. SpatialAwarenessMeshObject meshObject = outstandingMeshObject; meshObject.Id = meshGenerationResult.MeshId.GetHashCode(); outstandingMeshObject = null; // Check to see if this is a new or updated mesh. bool isMeshUpdate = meshes.ContainsKey(meshObject.Id); // We presume that if the display option is not occlusion, that we should // default to the visible material. // Note: We check explicitly for a display option of none later in this method. Material material = (DisplayOption == SpatialAwarenessMeshDisplayOptions.Occlusion) ? OcclusionMaterial : VisibleMaterial; // If this is a mesh update, we want to preserve the mesh's previous material. material = isMeshUpdate ? meshes[meshObject.Id].Renderer.sharedMaterial : material; // Apply the appropriate material. meshObject.Renderer.sharedMaterial = material; // Recalculate the mesh normals if requested. if (RecalculateNormals) { meshObject.Filter.sharedMesh.RecalculateNormals(); } // Check to see if the display option is set to none. If so, we disable // the renderer. meshObject.Renderer.enabled = (DisplayOption != SpatialAwarenessMeshDisplayOptions.None); // Set the physics material if (meshObject.Renderer.enabled) { meshObject.Collider.material = PhysicsMaterial; } // Add / update the mesh to our collection if (isMeshUpdate) { // Reclaim the old mesh object for future use. ReclaimMeshObject(meshes[meshObject.Id]); meshes.Remove(meshObject.Id); } meshes.Add(meshObject.Id, meshObject); meshObject.GameObject.transform.parent = (ObservedObjectParent.transform != null) ? ObservedObjectParent.transform : null; meshEventData.Initialize(this, meshObject.Id, meshObject); if (isMeshUpdate) { SpatialAwarenessSystem?.HandleEvent(meshEventData, OnMeshUpdated); } else { SpatialAwarenessSystem?.HandleEvent(meshEventData, OnMeshAdded); } break; } } }
/// <inheritdoc /> public void RaiseMeshUpdated(IMixedRealitySpatialAwarenessObserver observer, int meshId, SpatialAwarenessMeshObject meshObject) { meshEventData.Initialize(observer, meshId, meshObject); HandleEvent(meshEventData, OnMeshUpdated); }