/// <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(UnityEngine.XR.WSA.SurfaceData cookedData, bool outputWritten, float elapsedCookTimeSeconds) { //We have new visuals, so we can disable and cleanup the older surface GameObject surfaceToCleanup; if (pendingCleanup.TryGetValue(cookedData.id.handle, out surfaceToCleanup)) { CleanupSurface(surfaceToCleanup); pendingCleanup.Remove(cookedData.id.handle); } GameObject surface; if (surfaces.TryGetValue(cookedData.id.handle, out surface)) { // Set the draw material for the renderer. MeshRenderer meshRenderer = surface.GetComponent <MeshRenderer>(); meshRenderer.sharedMaterial = SpatialMappingManager.Instance.SurfaceMaterial; meshRenderer.enabled = SpatialMappingManager.Instance.DrawVisualMeshes; if (SpatialMappingManager.Instance.CastShadows == false) { meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } } surfaceWorkOutstanding = false; UnityEngine.XR.WSA.SurfaceObserver.SurfaceDataReadyDelegate dataReady = DataReady; if (dataReady != null) { dataReady(cookedData, outputWritten, elapsedCookTimeSeconds); } }
private bool IsMatchingSurface(SurfaceObject surfaceObject, UnityEngine.XR.WSA.SurfaceData surfaceData) { return((surfaceObject.ID == surfaceData.id.handle) && (surfaceObject.Filter == surfaceData.outputMesh) && (surfaceObject.Collider == surfaceData.outputCollider) ); }
/// <summary> /// Attempt to attach an <see cref="InteractableFacade"/> found in the given <see cref="SurfaceData"/> to this <see cref="InteractorFacade"/>. /// </summary> /// <param name="data">The collision data containing a valid Interactable.</param> public virtual void Grab(UnityEngine.XR.WSA.SurfaceData data) { if (data == null || data.CollisionData.transform == null) { return; } Grab(data.CollisionData.transform.gameObject.TryGetComponent <InteractableFacade>(true, true), null, null); }
/// <summary> /// Gets a <see cref="DestinationLocation"/> if one exists in the given <see cref="SurfaceData"/> colliding transform or parent. /// </summary> /// <param name="data">The data to check.</param> /// <returns>The found <see cref="DestinationLocation"/>.</returns> protected virtual DestinationLocation GetLocation(UnityEngine.XR.WSA.SurfaceData data) { if (data == null || data.CollisionData.transform == null) { return(null); } return(data.CollisionData.transform.gameObject.TryGetComponent <DestinationLocation>(false, true)); }
public virtual void SetSource(UnityEngine.XR.WSA.SurfaceData source) { if (source == null || source.Transform == null) { return; } Source = source.Transform.GetComponentInParent <PointerFacade>(); }
/// <summary> /// Called by the surface observer when a mesh has had its data changed. /// </summary> /// <param name="bakedData">The data describing the surface.</param> /// <param name="outputWritten">If the data was successfully updated.</param> /// <param name="elapsedBakeTimeSeconds">How long it took to update.</param> private void MappingObserver_DataReady(UnityEngine.XR.WSA.SurfaceData bakedData, bool outputWritten, float elapsedBakeTimeSeconds) { if (!outputWritten) { return; } AddOrUpdateMeshInList(bakedData); }
/// <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(UnityEngine.XR.WSA.SurfaceData cookedData, bool outputWritten, float elapsedCookTimeSeconds) { if (outstandingMeshRequest == null) { Debug.LogErrorFormat("Got OnDataReady for surface {0} while no request was outstanding.", cookedData.id.handle ); return; } if (!IsMatchingSurface(outstandingMeshRequest.Value, cookedData)) { Debug.LogErrorFormat("Got mismatched OnDataReady for surface {0} while request for surface {1} was outstanding.", cookedData.id.handle, outstandingMeshRequest.Value.ID ); ReclaimSurface(outstandingMeshRequest.Value); outstandingMeshRequest = null; return; } if (ObserverState != ObserverStates.Running) { Debug.LogFormat("Got OnDataReady for surface {0}, but observer was no longer running.", cookedData.id.handle ); ReclaimSurface(outstandingMeshRequest.Value); outstandingMeshRequest = null; return; } if (!outputWritten) { ReclaimSurface(outstandingMeshRequest.Value); outstandingMeshRequest = null; return; } Debug.Assert(outstandingMeshRequest.Value.Object.activeSelf); outstandingMeshRequest.Value.Renderer.enabled = SpatialMappingManager.Instance.DrawVisualMeshes; SurfaceObject?replacedSurface = UpdateOrAddSurfaceObject(outstandingMeshRequest.Value, destroyGameObjectIfReplaced: false); outstandingMeshRequest = null; if (replacedSurface != null) { ReclaimSurface(replacedSurface.Value); } }
public void Exit(UnityEngine.XR.WSA.SurfaceData data) { DestinationLocation location = GetLocation(data); if (location == null) { return; } location.Exit(data); }
/// <summary> /// Calls GetMeshAsync to update the SurfaceData and re-activate the surface object when ready. /// </summary> /// <param name="id">Identifier of the SurfaceData object to update.</param> /// <param name="surface">The SurfaceData object to update.</param> private void QueueSurfaceDataRequest(UnityEngine.XR.WSA.SurfaceId id, GameObject surface) { UnityEngine.XR.WSA.SurfaceData surfaceData = new UnityEngine.XR.WSA.SurfaceData(id, surface.GetComponent <MeshFilter>(), surface.GetComponent <UnityEngine.XR.WSA.WorldAnchor>(), surface.GetComponent <MeshCollider>(), TrianglesPerCubicMeter, true); surfaceWorkQueue.Enqueue(surfaceData); }
/// <summary> /// Creates the payload to emit on the hovering events of <see cref="Entered"/> and <see cref="Exited"/>. /// </summary> /// <param name="data">The data that is mutating the hover state.</param> /// <returns>The data to emit.</returns> protected virtual UnityEngine.XR.WSA.SurfaceData CreateHoverPayload(UnityEngine.XR.WSA.SurfaceData data) { if (Origin == null || !Origin.activeInHierarchy) { return(data); } hoverHit = data.CollisionData; hoverHit.point = Origin.transform.position; data.CollisionData = hoverHit; return(data); }
public virtual void Enter(UnityEngine.XR.WSA.SurfaceData data) { if (data.Transform == null || !SourceValidity.Accepts(data.Transform)) { return; } IsHovered = true; HoveringElements.Add(data); if (HoveringElements.Count == 1) { HoverActivated?.Invoke(); } Entered?.Invoke(CreateHoverPayload(data)); }
public virtual void Exit(UnityEngine.XR.WSA.SurfaceData data) { if (data.Transform == null || !SourceValidity.Accepts(data.Transform) || !HoveringElements.Contains(data)) { return; } IsHovered = false; HoveringElements.Remove(data); Exited?.Invoke(CreateHoverPayload(data)); if (HoveringElements.Count == 0) { HoverDeactivated?.Invoke(); } }
/// <summary> /// Creates the payload to emit on the <see cref="Activated"/> event. /// </summary> /// <param name="data">The default data to potentially mutate.</param> /// <returns>The data to emit.</returns> protected virtual TransformData CreateSelectedPayload(UnityEngine.XR.WSA.SurfaceData data) { if (Destination == null || !Destination.activeInHierarchy) { return(data); } selectedPayload.Clear(); selectedPayload.Transform = Destination.transform; if (!ApplyDestinationRotation) { selectedPayload.RotationOverride = data.Rotation; } return(selectedPayload); }
public void Select(UnityEngine.XR.WSA.SurfaceData data) { if (SelectedLocation != null && data != null) { SelectedLocation.Deselect(); } DestinationLocation location = GetLocation(data); if (location == null) { return; } location.Select(data); SelectedLocation = location; }
public virtual void Select(UnityEngine.XR.WSA.SurfaceData data) { if (data.Transform == null || !SourceValidity.Accepts(data.Transform) || !HoveringElements.Contains(data)) { return; } IsActivated = true; Activated?.Invoke(CreateSelectedPayload(data)); if (!EmitExitOnSelect) { return; } foreach (UnityEngine.XR.WSA.SurfaceData element in HoveringElements.ToArray()) { Exit(element); } HoveringElements.Clear(); }
/// <summary> /// Updates an element of the behavior's mesh list from an element of the the spatial mapping's surface object list. /// Element will be either added or updated to match up to the surfaceObject list. /// </summary> /// <param name="surfaceId">The unique ID for the mesh (matches the id provided by spatial mapping)</param> /// <param name="surfaceObjectIndex">Index in the surfaceObjects list</param> /// <param name="surfaceObjects">The list of surfaceObjects</param> /// <param name="meshDataIndex">Index into the locally stored mesh data list</param> private void AddOrUpdateMeshInList( UnityEngine.XR.WSA.SurfaceData bakedData) { UnityEngine.XR.WSA.SurfaceId surfaceId = bakedData.id; MeshFilter meshFilter = bakedData.outputMesh; int meshDataIndex = FindMeshIndexInInputMeshList(surfaceId.handle); SpatialUnderstandingDll.MeshData meshData = new SpatialUnderstandingDll.MeshData(); int meshUpdateID = (meshDataIndex >= 0) ? (inputMeshList[meshDataIndex].LastUpdateID + 1) : 1; if ((meshFilter != null) && (meshFilter.mesh != null) && (meshFilter.mesh.triangles.Length > 0)) { // Fix surface mesh normals so we can get correct plane orientation. meshFilter.mesh.RecalculateNormals(); // Convert meshData.CopyFrom(meshFilter, surfaceId.handle, meshUpdateID); } else { // No filter yet, add as an empty mesh (will be updated later in the update loop) meshData.CopyFrom(null, surfaceId.handle, meshUpdateID); } // And add it (unless an index of an update item is specified) if (meshDataIndex < 0) { inputMeshList.Add(meshData); } else { inputMeshList[meshDataIndex] = meshData; } }
/// <summary> /// Called once per frame. /// </summary> private void Update() { // Only do processing if the observer is running. if (ObserverState == ObserverStates.Running) { // If we don't have mesh creation in flight, but we could schedule mesh creation, do so. if (surfaceWorkOutstanding == false && surfaceWorkQueue.Count > 0) { // Pop the SurfaceData off the queue. A more sophisticated algorithm could prioritize // the queue based on distance to the user or some other metric. UnityEngine.XR.WSA.SurfaceData surfaceData = surfaceWorkQueue.Dequeue(); // If RequestMeshAsync succeeds, then we have successfully scheduled mesh creation. surfaceWorkOutstanding = observer.RequestMeshAsync(surfaceData, SurfaceObserver_OnDataReady); } // If we don't have any other work to do, and enough time has passed since the previous // update request, request updates for the spatial mapping data. else if (surfaceWorkOutstanding == false && (Time.time - updateTime) >= TimeBetweenUpdates) { observer.Update(SurfaceObserver_OnSurfaceChanged); updateTime = Time.time; } } }
/// <summary> /// Extracts the <see cref="Source"/> <see cref="GameObject"/> from the given <see cref="SurfaceData"/> payload data. /// </summary> /// <param name="data">The <see cref="SurfaceData"/> payload data to extract from.</param> public virtual void DoExtract(UnityEngine.XR.WSA.SurfaceData data) { Extract(data); }
/// <summary> /// Emits the Exited event. /// </summary> public virtual void EmitExited(UnityEngine.XR.WSA.SurfaceData data) { Facade.Exited?.Invoke(data); }
/// <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. UnityEngine.XR.WSA.SurfaceId surfaceID = surfaceWorkQueue.Dequeue(); string surfaceName = ("Surface-" + surfaceID.handle); SurfaceObject newSurface; UnityEngine.XR.WSA.WorldAnchor worldAnchor; if (spareSurfaceObject == null) { newSurface = CreateSurfaceObject( mesh: null, objectName: surfaceName, parentObject: transform, meshID: surfaceID.handle, drawVisualMeshesOverride: false ); worldAnchor = newSurface.Object.AddComponent <UnityEngine.XR.WSA.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 <UnityEngine.XR.WSA.WorldAnchor>(); Debug.Assert(worldAnchor != null); } var surfaceData = new UnityEngine.XR.WSA.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> /// Extracts the <see cref="Source"/> <see cref="GameObject"/> from the given <see cref="SurfaceData"/> payload data. /// </summary> /// <param name="data">The <see cref="SurfaceData"/> payload data to extract from.</param> /// <returns>The <see cref="Source"/> <see cref="GameObject"/> within the <see cref="SurfaceData"/>.</returns> public virtual GameObject Extract(UnityEngine.XR.WSA.SurfaceData data) { SetSource(data); return(Extract()); }