/// <summary> /// Creates a <see cref="SpatialMeshObject"/>. /// </summary> /// <param name="mesh"></param> todo: add comments /// <param name="name"></param> /// <param name="meshId"></param> /// <returns> /// SpatialMeshObject containing the fields that describe the mesh. /// </returns> protected SpatialMeshObject CreateSpatialMeshObject( Mesh mesh, string name, int meshId) { SpatialMeshObject newMesh = new SpatialMeshObject(); newMesh.Id = meshId; newMesh.GameObject = new GameObject(name, requiredMeshComponents); newMesh.GameObject.layer = MixedRealityToolkit.SpatialAwarenessSystem.MeshPhysicsLayer; newMesh.Filter = newMesh.GameObject.GetComponent <MeshFilter>(); newMesh.Filter.sharedMesh = mesh; newMesh.Renderer = newMesh.GameObject.GetComponent <MeshRenderer>(); // Reset the surface mesh collider to fit the updated mesh. // Unity tribal knowledge indicates that to change the mesh assigned to a // mesh collider, the mesh must first be set to null. Presumably there // is a side effect in the setter when setting the shared mesh to null. newMesh.Collider = newMesh.GameObject.GetComponent <MeshCollider>(); newMesh.Collider.sharedMesh = null; newMesh.Collider.sharedMesh = newMesh.Filter.sharedMesh; return(newMesh); }
/// <summary> /// Clean up the resources associated with the surface. /// </summary> /// <param name="meshObject">The <see cref="SpatialMeshObject"/> whose resources will be cleaned up.</param> /// <param name="destroyGameObject"></param> /// <param name="destroyMeshes"></param> protected void CleanupMeshObject( SpatialMeshObject meshObject, bool destroyGameObject = true, bool destroyMeshes = true) { if (destroyGameObject && (meshObject.GameObject != null)) { Object.Destroy(meshObject.GameObject); meshObject.GameObject = null; } Mesh filterMesh = meshObject.Filter.sharedMesh; Mesh colliderMesh = meshObject.Collider.sharedMesh; if (destroyMeshes) { if (filterMesh != null) { Object.Destroy(filterMesh); meshObject.Filter.sharedMesh = null; } if ((colliderMesh != null) && (colliderMesh != filterMesh)) { Object.Destroy(colliderMesh); meshObject.Collider.sharedMesh = null; } } }
/// <inheritdoc /> public void RaiseMeshUpdated(IMixedRealitySpatialMeshObserver observer, SpatialMeshObject spatialMeshObject) { // Parent the mesh object spatialMeshObject.GameObject.transform.parent = SpatialMeshesParent.transform; meshEventData.Initialize(observer, spatialMeshObject.Id, spatialMeshObject); HandleEvent(meshEventData, OnMeshUpdated); }
/// <summary> /// Reclaims the <see cref="SpatialMeshObject"/> to allow for later reuse. /// </summary> /// <param name="availableMeshObject"></param> protected void ReclaimMeshObject(SpatialMeshObject availableMeshObject) { if (!spareMeshObject.HasValue) { // Cleanup the mesh object. // Do not destroy the game object, destroy the meshes. CleanupMeshObject(availableMeshObject, false); availableMeshObject.GameObject.name = "Unused Spatial Mesh"; availableMeshObject.GameObject.SetActive(false); spareMeshObject = availableMeshObject; } else { // Cleanup the mesh object. // Destroy the game object, destroy the meshes. CleanupMeshObject(availableMeshObject); } }
/// <inheritdoc /> public void RaiseMeshRemoved(IMixedRealitySpatialMeshObserver observer, SpatialMeshObject spatialMeshObject) { meshEventData.Initialize(observer, spatialMeshObject.Id, spatialMeshObject); HandleEvent(meshEventData, OnMeshRemoved); }
private async Task GenerateMeshAsync(SpatialSurfaceInfo meshInfo, SpatialMeshObject spatialMeshObject) { spatialMeshObject.LastUpdated = meshInfo.UpdateTime; // TODO Check if the spatialSurfaceMeshOptions are correct for what we need. var spatialSurfaceMesh = await meshInfo.TryComputeLatestMeshAsync(TrianglesPerCubicMeter, spatialSurfaceMeshOptions); await Awaiters.UnityMainThread; var mesh = spatialMeshObject.Mesh == null ? new Mesh() : spatialMeshObject.Mesh; mesh.name = $"Mesh_{meshInfo.Id}"; await Awaiters.BackgroundThread; if (MeshRecalculateNormals) { var normalCount = (int)spatialSurfaceMesh.VertexNormals.ElementCount; var normals = new NativeArray <VertexData>(normalCount, Allocator.None); var vertexBuffer = DataReader.FromBuffer(spatialSurfaceMesh.VertexPositions.Data); var normalBuffer = DataReader.FromBuffer(spatialSurfaceMesh.VertexNormals.Data); for (int i = 0; i < normalCount; i++) { normals[i] = new VertexData { // TODO Check if spatialSurfaceMesh.VertexPositionScale needs to be accounted for. Position = new Vector3(vertexBuffer.ReadSingle(), vertexBuffer.ReadSingle(), -vertexBuffer.ReadSingle()), Normal = new Vector3(normalBuffer.ReadSingle(), normalBuffer.ReadSingle(), -normalBuffer.ReadSingle()) }; } mesh.SetVertexBufferParams(normalCount, NormalsLayout); mesh.SetVertexBufferData(normals, 0, 0, normalCount); vertexBuffer.Dispose(); normalBuffer.Dispose(); normals.Dispose(); } else { var vertexCount = (int)spatialSurfaceMesh.VertexPositions.ElementCount; var vertices = new NativeArray <Vector3>(vertexCount, Allocator.None); var vertexBuffer = DataReader.FromBuffer(spatialSurfaceMesh.VertexPositions.Data); for (int i = 0; i < vertexCount; i++) { // TODO Check if spatialSurfaceMesh.VertexPositionScale needs to be accounted for. vertices[i] = new Vector3(vertexBuffer.ReadSingle(), vertexBuffer.ReadSingle(), -vertexBuffer.ReadSingle()); } mesh.SetVertexBufferParams(vertexCount, VertexLayout); mesh.SetVertexBufferData(vertices, 0, 0, vertexCount); vertexBuffer.Dispose(); vertices.Dispose(); } var indicesCount = (int)spatialSurfaceMesh.TriangleIndices.ElementCount; var indices = new NativeArray <short>(indicesCount, Allocator.None); var indicesBuffer = DataReader.FromBuffer(spatialSurfaceMesh.TriangleIndices.Data); for (int i = 0; i < indicesCount; i++) { indices[i] = indicesBuffer.ReadInt16(); } mesh.SetIndexBufferParams(indicesCount, IndexFormat.UInt16); mesh.SetIndexBufferData(indices, 0, 0, indicesCount); indicesBuffer.Dispose(); indices.Dispose(); mesh.SetSubMesh(0, new SubMeshDescriptor(0, indicesCount)); mesh.Optimize(); mesh.RecalculateBounds(); if (MeshRecalculateNormals) { mesh.RecalculateNormals(); } spatialMeshObject.Mesh = mesh; await Awaiters.UnityMainThread; }
/// <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.HasValue) { Debug.LogWarning($"OnDataReady called for mesh id {cookedData.id.handle} while no request was outstanding."); return; } if (!outputWritten) { Debug.LogWarning($"OnDataReady reported no data written for mesh id {cookedData.id.handle}"); ReclaimMeshObject(outstandingMeshObject.Value); outstandingMeshObject = null; return; } // Since there is only one outstanding mesh object, update the id to match // the one received after baking. SpatialMeshObject meshObject = outstandingMeshObject.Value; meshObject.Id = cookedData.id.handle; outstandingMeshObject = null; // Apply the appropriate material to the mesh. SpatialMeshDisplayOptions displayOption = SpatialAwarenessSystem.MeshDisplayOption; if (displayOption != SpatialMeshDisplayOptions.None) { meshObject.Renderer.enabled = true; meshObject.Renderer.sharedMaterial = (displayOption == SpatialMeshDisplayOptions.Visible) ? SpatialAwarenessSystem.MeshVisibleMaterial : SpatialAwarenessSystem.MeshOcclusionMaterial; } else { meshObject.Renderer.enabled = false; } // Recalculate the mesh normals if requested. if (SpatialAwarenessSystem.MeshRecalculateNormals) { meshObject.Filter.sharedMesh.RecalculateNormals(); } // Add / update the mesh to our collection bool sendUpdatedEvent = false; if (meshObjects.ContainsKey(cookedData.id.handle)) { // Reclaim the old mesh object for future use. ReclaimMeshObject(meshObjects[cookedData.id.handle]); meshObjects.Remove(cookedData.id.handle); sendUpdatedEvent = true; } meshObjects.Add(cookedData.id.handle, meshObject); if (sendUpdatedEvent) { SpatialAwarenessSystem.RaiseMeshUpdated(cookedData.id.handle, meshObject.GameObject); } else { SpatialAwarenessSystem.RaiseMeshAdded(cookedData.id.handle, meshObject.GameObject); } }
private async Task <MeshGenerationResult> GenerateMeshAsync(MlMeshing2.MLMeshingBlockInfo meshInfo, SpatialMeshObject spatialMeshObject) { int levelOfDetail = (int)MeshLevelOfDetail; if (levelOfDetail < 0) { Debug.LogWarning($"{MeshLevelOfDetail} is unsupported! Falling back to low level of detail."); levelOfDetail = 0; } var blockRequest = new MlMeshing2.MLMeshingBlockRequest { id = meshInfo.id, level = (MlMeshing2.MLMeshingLOD)levelOfDetail }; var meshRequest = new MlMeshing2.MLMeshingMeshRequest { request_count = 1, data = blockRequest }; if (!MlMeshing2.MLMeshingRequestMesh(meshingClientHandle, in meshRequest, out var outRequestHandle).IsOk) { Debug.LogError("Failed to request a new mesh!"); return(new MeshGenerationResult(meshInfo.id, MlMeshing2.MLMeshingResult.Failed)); } var meshRequestResult = new MlApi.MLResult(MlApi.MLResult.Code.Pending); var outMeshResult = new MlMeshing2.MLMeshingMesh { result = MlMeshing2.MLMeshingResult.Pending }; await Awaiters.BackgroundThread; while (meshRequestResult.Value == MlApi.MLResult.Code.Pending) { meshRequestResult = MlMeshing2.MLMeshingGetMeshResult(meshingClientHandle, outRequestHandle, out outMeshResult); await Task.Delay(25); // TODO make this delay configurable? } await Awaiters.UnityMainThread; if (!meshRequestResult.IsOk || outMeshResult.result == MlMeshing2.MLMeshingResult.Failed || !MlMeshing2.MLMeshingFreeResource(meshingClientHandle, outRequestHandle).IsOk) { return(new MeshGenerationResult(meshInfo.id, MlMeshing2.MLMeshingResult.Failed)); } if (outMeshResult.data_count != meshRequest.request_count) { Debug.LogError($"Mesh Block count mismatch! Expected {meshRequest.request_count} but got {outMeshResult.data_count} blocks."); return(new MeshGenerationResult(meshInfo.id, MlMeshing2.MLMeshingResult.Failed)); } if (meshInfo.id != outMeshResult.data.id) { Debug.LogError($"Mesh info id mismatch!\n->{meshInfo.id}\n<-{outMeshResult.data.id}"); return(new MeshGenerationResult(meshInfo.id, MlMeshing2.MLMeshingResult.Failed)); } var mesh = spatialMeshObject.Mesh == null ? new Mesh() : spatialMeshObject.Mesh; mesh.name = $"Mesh_{meshInfo.id}"; if (outMeshResult.data.vertex_count == 0 || outMeshResult.data.vertex == null || outMeshResult.data.index_count == 0 || outMeshResult.data.index == null) { return(new MeshGenerationResult(meshInfo.id, outMeshResult.result)); } await Awaiters.BackgroundThread; if (MeshRecalculateNormals) { var normals = new NativeArray <VertexData>((int)outMeshResult.data.vertex_count, Allocator.None); for (int i = 0; i < normals.Length; i++) { normals[i] = new VertexData { Position = outMeshResult.data.vertex[i], Normal = outMeshResult.data.normal[i] }; } mesh.SetVertexBufferParams((int)outMeshResult.data.vertex_count, NormalsLayout); mesh.SetVertexBufferData(normals, 0, 0, (int)outMeshResult.data.vertex_count); normals.Dispose(); } else { var vertices = new NativeArray <Vector3>((int)outMeshResult.data.vertex_count, Allocator.None); for (int i = 0; i < vertices.Length; i++) { vertices[i] = outMeshResult.data.vertex[i]; } mesh.SetVertexBufferParams((int)outMeshResult.data.vertex_count, VertexLayout); mesh.SetVertexBufferData(vertices, 0, 0, (int)outMeshResult.data.vertex_count); vertices.Dispose(); } var indices = new NativeArray <short>(outMeshResult.data.index_count, Allocator.None); for (int i = 0; i < outMeshResult.data.index_count; i++) { indices[i] = (short)outMeshResult.data.index[i]; } mesh.SetIndexBufferParams(outMeshResult.data.index_count, IndexFormat.UInt16); mesh.SetIndexBufferData(indices, 0, 0, outMeshResult.data.index_count); indices.Dispose(); mesh.SetSubMesh(0, new SubMeshDescriptor(0, outMeshResult.data.index_count)); mesh.Optimize(); mesh.RecalculateBounds(); if (MeshRecalculateNormals) { mesh.RecalculateNormals(); } spatialMeshObject.Mesh = mesh; await Awaiters.UnityMainThread; return(new MeshGenerationResult(meshInfo.id, outMeshResult.result)); }