/// <summary> /// Handles the SurfaceObserver's OnSurfaceChanged event. /// </summary> /// <param name="surfaceId">The identifier assigned to the surface which has changed.</param> /// <param name="changeType">The type of change that occurred on the surface.</param> /// <param name="bounds">The bounds of the surface.</param> /// <param name="updateTime">The date and time at which the change occurred.</param> private async void SurfaceObserver_OnSurfaceChanged(SurfaceId surfaceId, SurfaceChange changeType, Bounds bounds, DateTime updateTime) { // If we're adding or updating a mesh if (changeType != SurfaceChange.Removed) { var spatialMeshObject = await RequestSpatialMeshObject(surfaceId.handle); spatialMeshObject.GameObject.name = $"SpatialMesh_{surfaceId.handle.ToString()}"; var worldAnchor = spatialMeshObject.GameObject.EnsureComponent <WorldAnchor>(); var surfaceData = new SurfaceData(surfaceId, spatialMeshObject.Filter, worldAnchor, spatialMeshObject.Collider, MeshTrianglesPerCubicMeter, true); if (!observer.RequestMeshAsync(surfaceData, OnDataReady)) { Debug.LogError($"Mesh request failed for spatial observer with Id {surfaceId.handle.ToString()}"); RaiseMeshRemoved(spatialMeshObject); } void OnDataReady(SurfaceData cookedData, bool outputWritten, float elapsedCookTimeSeconds) { if (!outputWritten) { Debug.LogWarning($"No output for {cookedData.id.handle}"); return; } if (!SpatialMeshObjects.TryGetValue(cookedData.id.handle, out SpatialMeshObject meshObject)) { // Likely it was removed before data could be cooked. return; } // Apply the appropriate material to the mesh. SpatialMeshDisplayOptions displayOption = MeshDisplayOption; if (displayOption != SpatialMeshDisplayOptions.None) { meshObject.Renderer.enabled = true; meshObject.Renderer.sharedMaterial = (displayOption == SpatialMeshDisplayOptions.Visible) ? MeshVisibleMaterial : MeshOcclusionMaterial; } else { meshObject.Renderer.enabled = false; } // Recalculate the mesh normals if requested. if (MeshRecalculateNormals) { meshObject.Filter.sharedMesh.RecalculateNormals(); } meshObject.GameObject.SetActive(true); switch (changeType) { case SurfaceChange.Added: RaiseMeshAdded(meshObject); break; case SurfaceChange.Updated: RaiseMeshUpdated(meshObject); break; } } } else if (SpatialMeshObjects.TryGetValue(surfaceId.handle, out SpatialMeshObject meshObject)) { RaiseMeshRemoved(meshObject); } }
/// <summary> /// Called once per frame. /// </summary> private void Update() { if ((ObserverState == ObserverStates.Running) && (outstandingMeshRequest == null)) { if (surfaceWorkQueue.Count > 0) { // We're using a simple first-in-first-out rule for requesting meshes, but a more sophisticated algorithm could prioritize // the queue based on distance to the user or some other metric. SurfaceId surfaceID = surfaceWorkQueue.Dequeue(); string surfaceName = ("Surface-" + surfaceID.handle); SurfaceObject newSurface; WorldAnchor worldAnchor; if (spareSurfaceObject == null) { newSurface = CreateSurfaceObject( mesh: null, objectName: surfaceName, parentObject: transform, meshID: surfaceID.handle, drawVisualMeshesOverride: false ); worldAnchor = newSurface.Object.AddComponent <WorldAnchor>(); } else { newSurface = spareSurfaceObject.Value; spareSurfaceObject = null; Debug.Assert(!newSurface.Object.activeSelf); newSurface.Object.SetActive(true); Debug.Assert(newSurface.Filter.sharedMesh == null); Debug.Assert(newSurface.Collider.sharedMesh == null); newSurface.Object.name = surfaceName; Debug.Assert(newSurface.Object.transform.parent == transform); newSurface.ID = surfaceID.handle; newSurface.Renderer.enabled = false; worldAnchor = newSurface.Object.GetComponent <WorldAnchor>(); Debug.Assert(worldAnchor != null); } var surfaceData = new SurfaceData( surfaceID, newSurface.Filter, worldAnchor, newSurface.Collider, TrianglesPerCubicMeter, _bakeCollider: true ); if (observer.RequestMeshAsync(surfaceData, SurfaceObserver_OnDataReady)) { outstandingMeshRequest = newSurface; } else { Debug.LogErrorFormat("Mesh request for failed. Is {0} a valid Surface ID?", surfaceID.handle); Debug.Assert(outstandingMeshRequest == null); ReclaimSurface(newSurface); } } else if ((Time.unscaledTime - updateTime) >= TimeBetweenUpdates) { observer.Update(SurfaceObserver_OnSurfaceChanged); updateTime = Time.unscaledTime; } } }
/// <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) { using (RequestMeshPerfMarker.Auto()) { string meshName = ("SpatialMesh - " + surfaceId.handle); SpatialAwarenessMeshObject newMesh; WorldAnchor worldAnchor; if (spareMeshObject == null) { newMesh = SpatialAwarenessMeshObject.Create( null, MeshPhysicsLayer, meshName, surfaceId.handle, ObservedObjectParent); // 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); } } }
/// <summary> /// Handles the SurfaceObserver's OnSurfaceChanged event. /// </summary> /// <param name="id">The identifier assigned to the surface which has changed.</param> /// <param name="changeType">The type of change that occurred on the surface.</param> /// <param name="bounds">The bounds of the surface.</param> /// <param name="updateTime">The date and time at which the change occurred.</param> private void SurfaceObserver_OnSurfaceChanged(SurfaceId id, SurfaceChange changeType, Bounds bounds, System.DateTime updateTime) { // Verify that the client of the Surface Observer is expecting updates. if (ObserverState != ObserverStates.Running) { return; } GameObject surface; switch (changeType) { // Adding and updating are nearly identical. The only difference is if a new gameobject to contain // the surface needs to be created. case SurfaceChange.Added: case SurfaceChange.Updated: // Check to see if the surface is known to the observer. // If so, we want to add it for cleanup after we get new meshes // We do this because Unity doesn't properly cleanup baked collision data if (surfaces.TryGetValue(id.handle, out surface)) { pendingCleanup.Add(id.handle, surface); surfaces.Remove(id.handle); } // Get an available surface object ready to be used surface = GetSurfaceObject(id.handle, transform); // Add the surface to our dictionary of known surfaces so // we can interact with it later. surfaces.Add(id.handle, surface); // Add the request to create the mesh for this surface to our work queue. QueueSurfaceDataRequest(id, surface); break; case SurfaceChange.Removed: // Always process surface removal events. // This code can be made more thread safe if (surfaces.TryGetValue(id.handle, out surface)) { surfaces.Remove(id.handle); CleanupSurface(surface); RemoveSurfaceObject(surface, false); } break; } //List<MeshFilter> filters = SpatialMappingManager.Instance.GetMeshFilters(); //CombineInstance[] combine = new CombineInstance[filters.Count]; //int i = 0; //while (i < filters.Count) //{ // combine[i].mesh = filters[i].mesh; // combine[i].transform = filters[i].transform.localToWorldMatrix; // i++; //} //Mesh mesh = new Mesh(); //mesh.CombineMeshes(combine); //byte[] serialized = MeshSerializer.WriteMesh(mesh, true); //GameObject obj = PhotonView.Find("MyPrefabName"); //PhotonView photonView = PhotonView.Get(obj); //if (photonView) //{ // photonView.RPC("GetStreamData", RpcTarget.All, serialized); //} // Event if (SurfaceChanged != null) { SurfaceChanged(id, changeType, bounds, updateTime); } }
// This handler receives surface changed events and is propagated by the // Update method on SurfaceObserver. void SurfaceChangedHandler(SurfaceId id, SurfaceChange changeType, Bounds bounds, DateTime updateTime) { SurfaceEntry entry; switch (changeType) { case SurfaceChange.Added: case SurfaceChange.Updated: if (m_Surfaces.TryGetValue(id.handle, out entry)) { // If this surface has already been baked, mark it as needing bake // in addition to the update time so the "next surface to bake" // logic will order it correctly. if (entry.m_BakedState == BakedState.Baked) { entry.m_BakedState = BakedState.UpdatePostBake; entry.m_UpdateTime = updateTime; //send mesh to the ground staton. //old way /* * NetworkMeshSource.getSingleton().sendMesh(entry.m_Surface.GetComponent<MeshFilter>().mesh, * entry.m_Surface.transform.position, * entry.m_Surface.transform.rotation); */ } } else { // This is a brand new surface so create an entry for it. entry = new SurfaceEntry(); entry.m_BakedState = BakedState.NeverBaked; entry.m_UpdateTime = updateTime; entry.m_Id = id.handle; entry.m_Surface = new GameObject(System.String.Format("Surface-{0}", id.handle)); entry.m_Surface.AddComponent <MeshFilter>(); entry.m_Surface.AddComponent <MeshCollider>(); MeshRenderer mr = entry.m_Surface.AddComponent <MeshRenderer>(); mr.shadowCastingMode = ShadowCastingMode.Off; mr.receiveShadows = false; entry.m_Surface.AddComponent <WorldAnchor>(); entry.m_Surface.GetComponent <MeshRenderer>().sharedMaterial = m_drawMat; m_Surfaces[id.handle] = entry; if (!SurfacesList.Contains(entry)) { SurfacesList.Add(entry); } } break; case SurfaceChange.Removed: if (m_Surfaces.TryGetValue(id.handle, out entry)) { m_Surfaces.Remove(id.handle); Mesh mesh = entry.m_Surface.GetComponent <MeshFilter>().mesh; if (mesh) { Destroy(mesh); } Destroy(entry.m_Surface); } break; } }
/// <summary> /// This handler receives events when surfaces change, and propagates those events /// using the SurfaceObserver’s Update method /// </summary> /// <param name="id">Handle identifying the surface</param> /// <param name="changeType">Reason for update</param> /// <param name="bounds">New bounds of th esurface</param> /// <param name="updateTime">Time stamp of modification.</param> private void SurfaceChangedHandler(SurfaceId id, SurfaceChange changeType, Bounds bounds, DateTime updateTime) { SurfaceEntry entry; switch (changeType) { case SurfaceChange.Added: case SurfaceChange.Updated: if (surfaces.TryGetValue(id.handle, out entry)) { // If the system has already baked this Surface, lower its priority. if (entry.currentState == BakedState.Baked) { entry.currentState = BakedState.UpdatePostBake; entry.updateTime = updateTime; } } else { // This is a brand new Surface so create an entry for it. entry = new SurfaceEntry(); entry.currentState = BakedState.NeverBaked; entry.updateTime = updateTime; entry.handle = id.handle; entry.surfaceObject = new GameObject(System.String.Format("Surface-{0}", id.handle)); entry.surfaceObject.layer = spatialMappingLayer; if (HangerObject != null) { entry.surfaceObject.transform.SetParent(HangerObject, false); } entry.surfaceObject.AddComponent <MeshFilter>(); if (Collide) { entry.surfaceObject.AddComponent <MeshCollider>(); } MeshRenderer mr = entry.surfaceObject.AddComponent <MeshRenderer>(); mr.shadowCastingMode = ShadowCastingMode.Off; mr.receiveShadows = false; mr.sharedMaterial = DrawMaterial; mr.enabled = this.Display; entry.worldAnchorChild = new GameObject(entry.surfaceObject.name + "WorldAnchor"); entry.worldAnchorChild.transform.SetParent(entry.surfaceObject.transform, false); entry.worldAnchorChild.AddComponent <WorldAnchor>(); // Add an adapter component to keep the surface object where the WorldAnchor means it to be. var adapter = entry.surfaceObject.AddComponent <WorldAnchorAdapter>(); adapter.TargetObject = entry.surfaceObject.transform; adapter.WorldAnchorObject = entry.worldAnchorChild; surfaces[id.handle] = entry; } break; case SurfaceChange.Removed: if (surfaces.TryGetValue(id.handle, out entry)) { surfaces.Remove(id.handle); Destroy(entry.surfaceObject); // Note entry.worldAnchorChild is child of surfaceObject, so will get destroyed // along with components. } break; } }