diff --git a/HUX/Editor/InteractionManagerInspector.cs b/HUX/Editor/InteractionManagerInspector.cs index e578eeb..c623f4c 100644 --- a/HUX/Editor/InteractionManagerInspector.cs +++ b/HUX/Editor/InteractionManagerInspector.cs @@ -17,15 +17,15 @@ public override void OnInspectorGUI() { InteractionManager interactionManager = (InteractionManager)target; - interactionManager.RecognizableGesures = (UnityEngine.VR.WSA.Input.GestureSettings)HUXEditorUtils.EnumCheckboxField( + interactionManager.RecognizableGesures = (UnityEngine.XR.WSA.Input.GestureSettings)HUXEditorUtils.EnumCheckboxField( "Recognizable gestures", interactionManager.RecognizableGesures, "Default", - UnityEngine.VR.WSA.Input.GestureSettings.Tap | - UnityEngine.VR.WSA.Input.GestureSettings.DoubleTap | - UnityEngine.VR.WSA.Input.GestureSettings.Hold | - UnityEngine.VR.WSA.Input.GestureSettings.NavigationX | - UnityEngine.VR.WSA.Input.GestureSettings.NavigationY); + UnityEngine.XR.WSA.Input.GestureSettings.Tap | + UnityEngine.XR.WSA.Input.GestureSettings.DoubleTap | + UnityEngine.XR.WSA.Input.GestureSettings.Hold | + UnityEngine.XR.WSA.Input.GestureSettings.NavigationX | + UnityEngine.XR.WSA.Input.GestureSettings.NavigationY); HUXEditorUtils.SaveChanges(target, serializedObject); } diff --git a/HUX/Prefabs/Dialogs/BoundingBoxShell.prefab.meta b/HUX/Prefabs/Dialogs/BoundingBoxShell.prefab.meta index 5c859f5..6260adc 100644 --- a/HUX/Prefabs/Dialogs/BoundingBoxShell.prefab.meta +++ b/HUX/Prefabs/Dialogs/BoundingBoxShell.prefab.meta @@ -1,8 +1,10 @@ fileFormatVersion: 2 -guid: cd435fa946a12fb42b7fe3f3e3bd24dc +guid: 76ede5da4cb0a42e2b7019525ebbbeae timeCreated: 1450231244 licenseType: Pro NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 userData: assetBundleName: assetBundleVariant: diff --git a/HUX/Scripts/Core/Veil.cs b/HUX/Scripts/Core/Veil.cs index 9061385..9d24e96 100644 --- a/HUX/Scripts/Core/Veil.cs +++ b/HUX/Scripts/Core/Veil.cs @@ -241,7 +241,7 @@ private void SetDeviceFOV() switch (CurrentDevice) { case DeviceTypeEnum.Auto: - switch (VRSettings.loadedDeviceName) + switch (UnityEngine.XR.XRSettings.loadedDeviceName) { case "Oculus": m_deviceFOV = OCULUS_FOV; @@ -307,9 +307,9 @@ public IEnumerator CoViewFromVeilFOV() #if UNITY_EDITOR || !UNITY_WSA if (SetVeilFOVOnStartInEditor) { - if (VRDevice.isPresent) + if (UnityEngine.XR.XRDevice.isPresent) { - while (!VRSettings.enabled) + while (!UnityEngine.XR.XRSettings.enabled) { yield return null; } diff --git a/HUX/Scripts/Dialogs/Debug/DebugLog.cs b/HUX/Scripts/Dialogs/Debug/DebugLog.cs index e9d3b8f..7c471cb 100644 --- a/HUX/Scripts/Dialogs/Debug/DebugLog.cs +++ b/HUX/Scripts/Dialogs/Debug/DebugLog.cs @@ -385,7 +385,7 @@ private void Awake() Application.logMessageReceived += HandleLog; s_DebugOutputs.Add(this); - if (m_DebugLogObj && (UnityEngine.VR.VRSettings.loadedDeviceName == "Oculus" || UnityEngine.VR.VRSettings.loadedDeviceName == "OpenVR")) + if (m_DebugLogObj && (UnityEngine.XR.XRSettings.loadedDeviceName == "Oculus" || UnityEngine.XR.XRSettings.loadedDeviceName == "OpenVR")) { // Scale up RectTransform rectTransform = m_DebugLogObj.GetComponent(); diff --git a/HUX/Scripts/Dialogs/Debug/DebugMenu.cs b/HUX/Scripts/Dialogs/Debug/DebugMenu.cs index e33b624..fc5f5e0 100644 --- a/HUX/Scripts/Dialogs/Debug/DebugMenu.cs +++ b/HUX/Scripts/Dialogs/Debug/DebugMenu.cs @@ -239,7 +239,7 @@ private void Start() gameObject.AddComponent(); } - if (m_MenuContainer && (UnityEngine.VR.VRSettings.loadedDeviceName == "Oculus" || UnityEngine.VR.VRSettings.loadedDeviceName == "OpenVR")) + if (m_MenuContainer && (UnityEngine.XR.XRSettings.loadedDeviceName == "Oculus" || UnityEngine.XR.XRSettings.loadedDeviceName == "OpenVR")) { // Scale up -- We have to do this every time the page is dirty. transform.localScale = Vector3.one * m_ScaleForHMD; diff --git a/HUX/Scripts/Input/InputSourceHands.cs b/HUX/Scripts/Input/InputSourceHands.cs index d6a408a..9490c7a 100644 --- a/HUX/Scripts/Input/InputSourceHands.cs +++ b/HUX/Scripts/Input/InputSourceHands.cs @@ -1,586 +1,591 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. -// -using UnityEngine; -using System.Collections.Generic; -using System; - -#if UNITY_WSA -using UnityEngine.VR.WSA.Input; -#endif - -public class InputSourceHands : InputSourceBase, ITargetingInputSource -{ - public event Action OnSelectChanged = delegate { }; - public event Action OnMenuChanged = delegate { }; - - public event Action OnFingerPressed = delegate { }; - public event Action OnFingerReleased = delegate { }; - public event Action OnHandEntered = delegate { }; - public event Action OnHandLeft = delegate { }; - public event Action OnHandMoved = delegate { }; - - public enum HandednessEnum - { - Right, - Left, - Neither, - } - - - public int NumHandsVisible { get; private set; } - public int NumFingersPressed { get; private set; } - public int PrevFingersPressed { get; private set; } - - public const int FirstHandIndex = 0; - public const int SecondHandIndex = 1; - - bool prevDragging; - - public float BloomGesture_FingerHoldTime = 2f; - public float FingerHoldTime; - - public ButtonControlState menuGesture = new ButtonControlState(); - - // Activation - public float HandMinVisibleTime = 0.5f; - public float HandMinReadyTime = 0.25f; - public float HandReadyDot = 0.7f; - float m_HandVisibleTime; - float handReadyTime; - - // Special logic for a hand vector while dragging - Vector3 dragLocalHandStart; - Vector3 dragStartHeadPosition; - Quaternion dragStartHeadRotation = Quaternion.identity; - public Quaternion adjustedHandTargetRot = Quaternion.identity; - - // Depth gesture - float dragStartDistance; - float dragDistanceDeltaVel; - float dragDistanceLast; - Vector3 prevHandPosition; - - // Drag control - public Vector2ControlState dragControl = new Vector2ControlState(false); - - private bool m_Select; - private bool m_Menu; - private Vector3 m_LastHandPosition = Vector3.zero; - - private Transform HeadTransform { - get { - return Camera.main.transform; - } - } - - // -------------------------------------------------------------------------------- - - #region Targeting source interface - - /// - /// The item name to use for this debug item. - /// - bool ITargetingInputSource.ShouldActivate() { - return IsDoingHoldGesture(); - } - - Vector3 ITargetingInputSource.GetTargetOrigin() { - return HeadTransform.position; - } - - Quaternion ITargetingInputSource.GetTargetRotation() { - if (IsManipulating()) { - return adjustedHandTargetRot; - } - return HeadTransform.rotation; - } - - bool ITargetingInputSource.IsSelectPressed() { - return NumFingersPressed > 0; - } - - bool ITargetingInputSource.IsMenuPressed() { - return menuGesture.PressedOnce; - } - - void ITargetingInputSource.OnActivate(bool activated) { - m_HandVisibleTime = 0; - handReadyTime = 0; - FingerHoldTime = 0; - } - - bool ITargetingInputSource.IsReady() { - return m_HandVisibleTime >= HandMinVisibleTime; - } - - bool ITargetingInputSource.IsTargetingActive() { - return IsDoingHoldGesture(); - } - - #endregion - - // -------------------------------------------------------------------------------- - - public bool IsDoingHoldGesture() { - bool fingerPressed = NumFingersPressed > 0; - - // Consider the hand holding when visible and finger pressed. - // Ignore the hand ready timeout, usually used to prevent erroneous - return (IsHandVisible() && fingerPressed); - } - - public bool IsHandVisible() { - return m_HandVisibleTime >= HandMinVisibleTime; - } - - public bool IsHandVisible(HandednessEnum handedness, float minConfidence = 0.15f) { - if (!IsHandVisible()) - return false; - - for (int i = 0; i < trackedHands.Count; i++) { - if (trackedHands[i].Handedness == handedness && trackedHands[i].HandednessConfidence > minConfidence) { - return true; - } - } - return false; - } - - public bool IsAnyHandVisible() { - return NumHandsVisible > 0; - } - - public bool IsPrimaryHand(CurrentHandState state) { - return trackedHands.Count > 0 && trackedHands[0] == state; - } - - // Position of hand at index - public Vector3 GetWorldPosition(int idx) { - if (trackedHands.Count > idx) { - m_LastHandPosition = trackedHands[idx].Position; - } - return m_LastHandPosition; - } - - public CurrentHandState GetHandState(int idx) { - CurrentHandState handState = null; - if (trackedHands.Count > idx) { - handState = trackedHands[idx]; - } - return handState; - } - - public CurrentHandState GetOppositeHandState(CurrentHandState hand) { - CurrentHandState oppositeHand = null; - for (int i = 0; i < trackedHands.Count; i++) { - if (trackedHands[i] != hand) { - oppositeHand = trackedHands[i]; - break; - } - } - return oppositeHand; - } - - public CurrentHandState GetHandState(HandednessEnum handedness, float minConfidence = 0.15f) { - CurrentHandState handState = null; - for (int i = 0; i < trackedHands.Count; i++) { - if (trackedHands[i].Handedness == handedness) { - if (trackedHands[i].HandednessConfidence >= minConfidence) { - handState = trackedHands[i]; - } - break; - } - } - return handState; - } - - public override void _Update() { - PrevFingersPressed = NumFingersPressed; - NumHandsVisible = trackedHands.Count; - NumFingersPressed = 0; - foreach (CurrentHandState hand in trackedHands) { - if (hand.Pressed) { - ++NumFingersPressed; - } - } - - // Update hand left/right - UpdateHandedness(); - - // Update hand activation - UpdateHandActivationState(); - - // Update hand dragging, like a special hand based targeting vector while dragging and depth - UpdateHandDrag(); - - bool prev = m_Select; - m_Select = (this as ITargetingInputSource).IsSelectPressed(); - if (prev != m_Select) { - OnSelectChanged(this, m_Select); - } - - prev = m_Menu; - m_Menu = (this as ITargetingInputSource).IsMenuPressed(); - if (prev != m_Menu) { - OnMenuChanged(this, m_Menu); - } - - base._Update(); - } - - void UpdateHandedness() { - - for (int i = 0; i < trackedHands.Count; i++) { - CurrentHandState handState = trackedHands[i]; - CurrentHandState oppositeState = GetOppositeHandState(handState); - if (oppositeState == null) { - handState.Handedness = GetHandedness(HeadTransform.forward, handState.Position, HeadTransform.up, ref handState.HandednessConfidence, handState.Handedness); - } else { - handState.Handedness = GetHandedness( - HeadTransform.forward, - handState.Position, - oppositeState.Position, - HeadTransform.up, - ref handState.HandednessConfidence, - handState.Handedness, - oppositeState.Handedness); - - // Do a sanity check - opposite state handedness cannot equal this handdedness - /*if (oppositeState.Handedness != HandednessEnum.Neither - && handState.Handedness != HandednessEnum.Neither - && oppositeState.Handedness != handState.Handedness) { - // Switch the opposite state - oppositeState.Handedness = handState.Handedness == HandednessEnum.Left ? HandednessEnum.Right : HandednessEnum.Left; - }*/ - } - } - } - - void UpdateHandActivationState() { - // Update finger hold time and menu gesture detection - if (IsDoingHoldGesture()) { - FingerHoldTime += Time.deltaTime; - //bCompletedMenuGesture = (FingerHoldTime - Time.deltaTime < BloomGesture_FingerHoldTime && FingerHoldTime >= BloomGesture_FingerHoldTime); - } else { - FingerHoldTime = 0; - } - - menuGesture.ApplyState(FingerHoldTime >= BloomGesture_FingerHoldTime && InputShellMap.Instance.CanCompleteHoldGesture()); - - float startVisibleTime = m_HandVisibleTime; - float startReadyTime = handReadyTime; - - // Update hand state timers - if (NumHandsVisible > 0) { - // Track the visibility time - m_HandVisibleTime += Time.deltaTime; - - // Hand 'ready' time... raise in front of look direction - Vector3 handPos = GetWorldPosition(0); - if (Vector3.Dot((handPos - HeadTransform.position).normalized, HeadTransform.forward) >= HandReadyDot) { - handReadyTime += Time.deltaTime; - } - } - - // If the timers didn't change, they just get reset - if (m_HandVisibleTime == startVisibleTime) { - m_HandVisibleTime = 0; - } - if (handReadyTime == startReadyTime) { - handReadyTime = 0; - } - } - - void UpdateHandDrag() { - bool isDragging = NumFingersPressed > 0 && IsManipulating(); - Vector3 handPos = GetWorldPosition(0); - - if (isDragging) { - if (!prevDragging) { - dragStartHeadPosition = HeadTransform.position; - - if (handPos.y < dragStartHeadPosition.y) { - dragStartHeadPosition.y = handPos.y; - } - - dragStartHeadRotation = HeadTransform.rotation; - - dragLocalHandStart = Quaternion.Inverse(dragStartHeadRotation) * (handPos - dragStartHeadPosition); - - dragControl.ApplyPos(false, Vector2.zero); - - prevHandPosition = handPos; - prevDragging = isDragging; - UpdateHandDrag(); - } else { - // Use the head position pivot, but at the starting height - Vector3 pivotPos = HeadTransform.position; - pivotPos.y = dragStartHeadPosition.y; - - // Find where the hand has moved relative to the starting pose (of head and hand) - Vector3 localOffsetFromStart = Quaternion.Inverse(dragStartHeadRotation) * (handPos - pivotPos); - - // Get the difference in rotation - Quaternion handRotation = Quaternion.FromToRotation(dragLocalHandStart, localOffsetFromStart); - - // Apply to original head direction - adjustedHandTargetRot = dragStartHeadRotation * handRotation; - - // Update drag distance - Vector3 posDelta = handPos - prevHandPosition; - float pdDot = Vector3.Dot(posDelta.normalized, HeadTransform.forward); - float distDelta = pdDot * posDelta.magnitude; - - // Deadzone? - //distDelta = Mathf.Sign(distDelta) * Mathf.Max(0, Mathf.Abs(distDelta) - dragDistanceDeadzone); - - // Update the drag control - dragControl.ApplyDelta(true, new Vector2(0, distDelta)); - } - } else { - dragControl.ApplyPos(false, Vector2.zero); - } - - prevHandPosition = handPos; - prevDragging = isDragging; - } - - // -------------------------------------------------------------------------------- - - #region System-level hand input source - - [Serializable] - public class CurrentHandState - { - public uint HandId; - - public Vector3 Position; - public Vector3 Velocity; - - public bool Pressed; - public bool LastPressed; - - public HandednessEnum Handedness = HandednessEnum.Neither; - public float HandednessConfidence = 0f; - - public double SourceLossRisk; - public Vector3 SourceLossMitigationDirection; - } - - private static List trackedHands = new List(); - - // Subscribe to the hand events - private void Awake() { -#if UNITY_WSA - InteractionManager.SourceDetected += WSASourceEntered; - InteractionManager.SourceLost += WSASourceLost; - InteractionManager.SourceUpdated += WSASourceUpdate; - - InteractionManager.SourcePressed += WSAFingerPressed; - InteractionManager.SourceReleased += WSAFingerReleased; -#endif - } - - // Cleanup hand event subscriptions - private void OnDestroy() { -#if UNITY_WSA - InteractionManager.SourceDetected -= WSASourceEntered; - InteractionManager.SourceLost -= WSASourceLost; - InteractionManager.SourceUpdated -= WSASourceUpdate; - - InteractionManager.SourcePressed -= WSAFingerPressed; - InteractionManager.SourceReleased -= WSAFingerReleased; -#endif - } - -#if UNITY_WSA - public void WSASourceEntered(InteractionSourceState state) { - // Track Hands - if (state.source.kind == InteractionSourceKind.Hand) { - CurrentHandState inputState = new CurrentHandState(); - - state.properties.location.TryGetPosition(out inputState.Position); - - UpdateFromWSASource(inputState, state); - SourceEntered(inputState); - } - } - - public void WSASourceUpdate(InteractionSourceState state) { - if (state.source.kind == InteractionSourceKind.Hand) { - Vector3 newPosition; - if (state.properties.location.TryGetPosition(out newPosition)) { - CurrentHandState inputState = trackedHands.Find(CurrentInputState => CurrentInputState.HandId == state.source.id); // handID - if (inputState != null) { - UpdateFromWSASource(inputState, state); - SourceUpdate(inputState, newPosition); - } - } - } - } - - public void WSASourceLost(InteractionSourceState state) { - if (state.source.kind == InteractionSourceKind.Hand) { - CurrentHandState inputState = trackedHands.Find(CurrentInputState => CurrentInputState.HandId == state.source.id); // handID - - if (inputState != null) { - UpdateFromWSASource(inputState, state); - SourceLost(inputState); - } - } - } - - private void WSAFingerReleased(InteractionSourceState state) { - CurrentHandState inputState = trackedHands.Find(CurrentInputState => CurrentInputState.HandId == state.source.id); - if (inputState != null) { - UpdateFromWSASource(inputState, state); - OnFingerReleased(inputState); - } - } - - private void WSAFingerPressed(InteractionSourceState state) { - CurrentHandState inputState = trackedHands.Find(CurrentInputState => CurrentInputState.HandId == state.source.id); - if (inputState != null) { - UpdateFromWSASource(inputState, state); - OnFingerPressed(inputState); - } - } -#endif - - public void SourceEntered(CurrentHandState inputState) { - trackedHands.Add(inputState); - OnHandEntered(inputState); - } - - public void SourceUpdate(CurrentHandState inputState, Vector3 newPosition) { - inputState.Position = newPosition; - OnHandMoved(inputState); - } - - public void SourceLost(CurrentHandState inputState) { - lock (trackedHands) { - trackedHands.Remove(inputState); - } - - OnHandLeft(inputState); - - if (trackedHands.Count == 0) { - - } else { - trackedHands.Sort( - delegate (CurrentHandState h1, CurrentHandState h2) { - return h1.HandId == h2.HandId ? 0 : h1.HandId < h2.HandId ? -1 : 1; - } - ); - } - } - -#if UNITY_WSA - private void UpdateFromWSASource(CurrentHandState currentInputState, InteractionSourceState state) { - currentInputState.HandId = state.source.id; - currentInputState.LastPressed = currentInputState.Pressed; - currentInputState.Pressed = state.pressed; - state.properties.location.TryGetVelocity(out currentInputState.Velocity); - - currentInputState.SourceLossRisk = state.properties.sourceLossRisk; - currentInputState.SourceLossMitigationDirection = state.properties.sourceLossMitigationDirection; - - } -#endif - - /// - /// Gets the handedness of a position with respect to a camera position - /// - /// - /// - /// - /// - /// - /// - public static HandednessEnum GetHandedness(Vector3 forward, Vector3 handPos, Vector3 up, ref float confidence, HandednessEnum currentHandedness = HandednessEnum.Neither) { - - // Get handedness based on position relative to camera - Vector3 cross = Vector3.Cross(forward, handPos); - float direction = Vector3.Dot(cross, up); - float newConfidence = Mathf.Abs(direction); - HandednessEnum newHandedness = HandednessEnum.Neither; - if (direction >= 0f) { - // Favor right-handedness slightly - newHandedness = HandednessEnum.Right; - } else - { - newHandedness = HandednessEnum.Left; - } - - // If we've already got a handedness, and the new handedness is different - // reset the confidence - if (currentHandedness != HandednessEnum.Neither && currentHandedness != newHandedness) { - confidence = newConfidence; - } else { - // Otherwise, allow the confidence to remain at the max observed so far - confidence = Mathf.Max(confidence, newConfidence); - } - - return newHandedness; - } - - /// - /// Gets the handedness of a position with respect to a camera position and an opposing hand - /// - /// - /// - /// - /// - /// - /// - public static HandednessEnum GetHandedness( - Vector3 forward, - Vector3 handPos, - Vector3 oppositeHandPos, - Vector3 up, - ref float confidence, - HandednessEnum currentHandedness, - HandednessEnum oppositeHandedness) { - - // Get handedness based on position relative to camera - Vector3 cross = Vector3.Cross(forward, handPos); - float direction = Vector3.Dot(cross, up); - float newConfidence = Mathf.Abs(direction); - HandednessEnum newHandedness = HandednessEnum.Neither; - - if (direction >= 0f) { - // Favor right-handedness slightly - newHandedness = HandednessEnum.Right; - } else - { - newHandedness = HandednessEnum.Left; - } - - // If we've got an opposite handedness, and we're the same, - // Use the opposite hand position instead of the camera position - if (oppositeHandedness != HandednessEnum.Neither && newHandedness == oppositeHandedness) { - Vector3 oppCross = Vector3.Cross(forward, oppositeHandPos); - float oppDirection = Vector3.Dot(oppCross, up); - newConfidence = Mathf.Abs(oppDirection); - if (oppDirection < direction) { - newHandedness = HandednessEnum.Right; - } else { - newHandedness = HandednessEnum.Left; - } - } - - // If we've already got a handedness, and the new handedness is different - // reset the confidence - if (currentHandedness != HandednessEnum.Neither && currentHandedness != newHandedness) { - confidence = newConfidence; - } else { - // Otherwise, allow the confidence to remain at the max observed so far - confidence = Mathf.Max(confidence, newConfidence); - } - - return newHandedness; - } - - #endregion -} +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// +using UnityEngine; +using System.Collections.Generic; +using System; + +#if UNITY_WSA + +#endif + +public class InputSourceHands : InputSourceBase, ITargetingInputSource +{ + public event Action OnSelectChanged = delegate { }; + public event Action OnMenuChanged = delegate { }; + + public event Action OnFingerPressed = delegate { }; + public event Action OnFingerReleased = delegate { }; + public event Action OnHandEntered = delegate { }; + public event Action OnHandLeft = delegate { }; + public event Action OnHandMoved = delegate { }; + + public enum HandednessEnum + { + Right, + Left, + Neither, + } + + + public int NumHandsVisible { get; private set; } + public int NumFingersPressed { get; private set; } + public int PrevFingersPressed { get; private set; } + + public const int FirstHandIndex = 0; + public const int SecondHandIndex = 1; + + bool prevDragging; + + public float BloomGesture_FingerHoldTime = 2f; + public float FingerHoldTime; + + public ButtonControlState menuGesture = new ButtonControlState(); + + // Activation + public float HandMinVisibleTime = 0.5f; + public float HandMinReadyTime = 0.25f; + public float HandReadyDot = 0.7f; + float m_HandVisibleTime; + float handReadyTime; + + // Special logic for a hand vector while dragging + Vector3 dragLocalHandStart; + Vector3 dragStartHeadPosition; + Quaternion dragStartHeadRotation = Quaternion.identity; + public Quaternion adjustedHandTargetRot = Quaternion.identity; + + // Depth gesture + float dragStartDistance; + float dragDistanceDeltaVel; + float dragDistanceLast; + Vector3 prevHandPosition; + + // Drag control + public Vector2ControlState dragControl = new Vector2ControlState(false); + + private bool m_Select; + private bool m_Menu; + private Vector3 m_LastHandPosition = Vector3.zero; + + private Transform HeadTransform { + get { + return Camera.main.transform; + } + } + + // -------------------------------------------------------------------------------- + + #region Targeting source interface + + /// + /// The item name to use for this debug item. + /// + bool ITargetingInputSource.ShouldActivate() { + return IsDoingHoldGesture(); + } + + Vector3 ITargetingInputSource.GetTargetOrigin() { + return HeadTransform.position; + } + + Quaternion ITargetingInputSource.GetTargetRotation() { + if (IsManipulating()) { + return adjustedHandTargetRot; + } + return HeadTransform.rotation; + } + + bool ITargetingInputSource.IsSelectPressed() { + return NumFingersPressed > 0; + } + + bool ITargetingInputSource.IsMenuPressed() { + return menuGesture.PressedOnce; + } + + void ITargetingInputSource.OnActivate(bool activated) { + m_HandVisibleTime = 0; + handReadyTime = 0; + FingerHoldTime = 0; + } + + bool ITargetingInputSource.IsReady() { + return m_HandVisibleTime >= HandMinVisibleTime; + } + + bool ITargetingInputSource.IsTargetingActive() { + return IsDoingHoldGesture(); + } + + #endregion + + // -------------------------------------------------------------------------------- + + public bool IsDoingHoldGesture() { + bool fingerPressed = NumFingersPressed > 0; + + // Consider the hand holding when visible and finger pressed. + // Ignore the hand ready timeout, usually used to prevent erroneous + return (IsHandVisible() && fingerPressed); + } + + public bool IsHandVisible() { + return m_HandVisibleTime >= HandMinVisibleTime; + } + + public bool IsHandVisible(HandednessEnum handedness, float minConfidence = 0.15f) { + if (!IsHandVisible()) + return false; + + for (int i = 0; i < trackedHands.Count; i++) { + if (trackedHands[i].Handedness == handedness && trackedHands[i].HandednessConfidence > minConfidence) { + return true; + } + } + return false; + } + + public bool IsAnyHandVisible() { + return NumHandsVisible > 0; + } + + public bool IsPrimaryHand(CurrentHandState state) { + return trackedHands.Count > 0 && trackedHands[0] == state; + } + + // Position of hand at index + public Vector3 GetWorldPosition(int idx) { + if (trackedHands.Count > idx) { + m_LastHandPosition = trackedHands[idx].Position; + } + return m_LastHandPosition; + } + + public CurrentHandState GetHandState(int idx) { + CurrentHandState handState = null; + if (trackedHands.Count > idx) { + handState = trackedHands[idx]; + } + return handState; + } + + public CurrentHandState GetOppositeHandState(CurrentHandState hand) { + CurrentHandState oppositeHand = null; + for (int i = 0; i < trackedHands.Count; i++) { + if (trackedHands[i] != hand) { + oppositeHand = trackedHands[i]; + break; + } + } + return oppositeHand; + } + + public CurrentHandState GetHandState(HandednessEnum handedness, float minConfidence = 0.15f) { + CurrentHandState handState = null; + for (int i = 0; i < trackedHands.Count; i++) { + if (trackedHands[i].Handedness == handedness) { + if (trackedHands[i].HandednessConfidence >= minConfidence) { + handState = trackedHands[i]; + } + break; + } + } + return handState; + } + + public override void _Update() { + PrevFingersPressed = NumFingersPressed; + NumHandsVisible = trackedHands.Count; + NumFingersPressed = 0; + foreach (CurrentHandState hand in trackedHands) { + if (hand.Pressed) { + ++NumFingersPressed; + } + } + + // Update hand left/right + UpdateHandedness(); + + // Update hand activation + UpdateHandActivationState(); + + // Update hand dragging, like a special hand based targeting vector while dragging and depth + UpdateHandDrag(); + + bool prev = m_Select; + m_Select = (this as ITargetingInputSource).IsSelectPressed(); + if (prev != m_Select) { + OnSelectChanged(this, m_Select); + } + + prev = m_Menu; + m_Menu = (this as ITargetingInputSource).IsMenuPressed(); + if (prev != m_Menu) { + OnMenuChanged(this, m_Menu); + } + + base._Update(); + } + + void UpdateHandedness() { + + for (int i = 0; i < trackedHands.Count; i++) { + CurrentHandState handState = trackedHands[i]; + CurrentHandState oppositeState = GetOppositeHandState(handState); + if (oppositeState == null) { + handState.Handedness = GetHandedness(HeadTransform.forward, handState.Position, HeadTransform.up, ref handState.HandednessConfidence, handState.Handedness); + } else { + handState.Handedness = GetHandedness( + HeadTransform.forward, + handState.Position, + oppositeState.Position, + HeadTransform.up, + ref handState.HandednessConfidence, + handState.Handedness, + oppositeState.Handedness); + + // Do a sanity check - opposite state handedness cannot equal this handdedness + /*if (oppositeState.Handedness != HandednessEnum.Neither + && handState.Handedness != HandednessEnum.Neither + && oppositeState.Handedness != handState.Handedness) { + // Switch the opposite state + oppositeState.Handedness = handState.Handedness == HandednessEnum.Left ? HandednessEnum.Right : HandednessEnum.Left; + }*/ + } + } + } + + void UpdateHandActivationState() { + // Update finger hold time and menu gesture detection + if (IsDoingHoldGesture()) { + FingerHoldTime += Time.deltaTime; + //bCompletedMenuGesture = (FingerHoldTime - Time.deltaTime < BloomGesture_FingerHoldTime && FingerHoldTime >= BloomGesture_FingerHoldTime); + } else { + FingerHoldTime = 0; + } + + menuGesture.ApplyState(FingerHoldTime >= BloomGesture_FingerHoldTime && InputShellMap.Instance.CanCompleteHoldGesture()); + + float startVisibleTime = m_HandVisibleTime; + float startReadyTime = handReadyTime; + + // Update hand state timers + if (NumHandsVisible > 0) { + // Track the visibility time + m_HandVisibleTime += Time.deltaTime; + + // Hand 'ready' time... raise in front of look direction + Vector3 handPos = GetWorldPosition(0); + if (Vector3.Dot((handPos - HeadTransform.position).normalized, HeadTransform.forward) >= HandReadyDot) { + handReadyTime += Time.deltaTime; + } + } + + // If the timers didn't change, they just get reset + if (m_HandVisibleTime == startVisibleTime) { + m_HandVisibleTime = 0; + } + if (handReadyTime == startReadyTime) { + handReadyTime = 0; + } + } + + void UpdateHandDrag() { + bool isDragging = NumFingersPressed > 0 && IsManipulating(); + Vector3 handPos = GetWorldPosition(0); + + if (isDragging) { + if (!prevDragging) { + dragStartHeadPosition = HeadTransform.position; + + if (handPos.y < dragStartHeadPosition.y) { + dragStartHeadPosition.y = handPos.y; + } + + dragStartHeadRotation = HeadTransform.rotation; + + dragLocalHandStart = Quaternion.Inverse(dragStartHeadRotation) * (handPos - dragStartHeadPosition); + + dragControl.ApplyPos(false, Vector2.zero); + + prevHandPosition = handPos; + prevDragging = isDragging; + UpdateHandDrag(); + } else { + // Use the head position pivot, but at the starting height + Vector3 pivotPos = HeadTransform.position; + pivotPos.y = dragStartHeadPosition.y; + + // Find where the hand has moved relative to the starting pose (of head and hand) + Vector3 localOffsetFromStart = Quaternion.Inverse(dragStartHeadRotation) * (handPos - pivotPos); + + // Get the difference in rotation + Quaternion handRotation = Quaternion.FromToRotation(dragLocalHandStart, localOffsetFromStart); + + // Apply to original head direction + adjustedHandTargetRot = dragStartHeadRotation * handRotation; + + // Update drag distance + Vector3 posDelta = handPos - prevHandPosition; + float pdDot = Vector3.Dot(posDelta.normalized, HeadTransform.forward); + float distDelta = pdDot * posDelta.magnitude; + + // Deadzone? + //distDelta = Mathf.Sign(distDelta) * Mathf.Max(0, Mathf.Abs(distDelta) - dragDistanceDeadzone); + + // Update the drag control + dragControl.ApplyDelta(true, new Vector2(0, distDelta)); + } + } else { + dragControl.ApplyPos(false, Vector2.zero); + } + + prevHandPosition = handPos; + prevDragging = isDragging; + } + + // -------------------------------------------------------------------------------- + + #region System-level hand input source + + [Serializable] + public class CurrentHandState + { + public uint HandId; + + public Vector3 Position; + public Vector3 Velocity; + + public bool Pressed; + public bool LastPressed; + + public HandednessEnum Handedness = HandednessEnum.Neither; + public float HandednessConfidence = 0f; + + public double SourceLossRisk; + public Vector3 SourceLossMitigationDirection; + } + + private static List trackedHands = new List(); + + // Subscribe to the hand events + private void Awake() { +#if UNITY_WSA + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourceDetected += WSASourceEntered; + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourceLost += WSASourceLost; + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourceUpdated += WSASourceUpdate; + + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourcePressed += WSAFingerPressed; + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourceReleased += WSAFingerReleased; +#endif + } + + // Cleanup hand event subscriptions + private void OnDestroy() { +#if UNITY_WSA + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourceDetected -= WSASourceEntered; + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourceLost -= WSASourceLost; + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourceUpdated -= WSASourceUpdate; + + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourcePressed -= WSAFingerPressed; + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourceReleased -= WSAFingerReleased; +#endif + } + +#if UNITY_WSA + public void WSASourceEntered(UnityEngine.XR.WSA.Input.InteractionSourceDetectedEventArgs args) { + var state = args.state; + // Track Hands + if (state.source.kind == UnityEngine.XR.WSA.Input.InteractionSourceKind.Hand) { + CurrentHandState inputState = new CurrentHandState(); + + state.sourcePose.TryGetPosition(out inputState.Position); + + UpdateFromWSASource(inputState, state); + SourceEntered(inputState); + } + } + + public void WSASourceUpdate(UnityEngine.XR.WSA.Input.InteractionSourceUpdatedEventArgs args) { + var state = args.state; + if (state.source.kind == UnityEngine.XR.WSA.Input.InteractionSourceKind.Hand) { + Vector3 newPosition; + if (state.sourcePose.TryGetPosition(out newPosition)) { + CurrentHandState inputState = trackedHands.Find(CurrentInputState => CurrentInputState.HandId == state.source.id); // handID + if (inputState != null) { + UpdateFromWSASource(inputState, state); + SourceUpdate(inputState, newPosition); + } + } + } + } + + public void WSASourceLost(UnityEngine.XR.WSA.Input.InteractionSourceLostEventArgs args) { + var state = args.state; + if (state.source.kind == UnityEngine.XR.WSA.Input.InteractionSourceKind.Hand) { + CurrentHandState inputState = trackedHands.Find(CurrentInputState => CurrentInputState.HandId == state.source.id); // handID + + if (inputState != null) { + UpdateFromWSASource(inputState, state); + SourceLost(inputState); + } + } + } + + private void WSAFingerReleased(UnityEngine.XR.WSA.Input.InteractionSourceReleasedEventArgs args) { + var state = args.state; + CurrentHandState inputState = trackedHands.Find(CurrentInputState => CurrentInputState.HandId == state.source.id); + if (inputState != null) { + UpdateFromWSASource(inputState, state); + OnFingerReleased(inputState); + } + } + + private void WSAFingerPressed(UnityEngine.XR.WSA.Input.InteractionSourcePressedEventArgs args) { + var state = args.state; + CurrentHandState inputState = trackedHands.Find(CurrentInputState => CurrentInputState.HandId == state.source.id); + if (inputState != null) { + UpdateFromWSASource(inputState, state); + OnFingerPressed(inputState); + } + } +#endif + + public void SourceEntered(CurrentHandState inputState) { + trackedHands.Add(inputState); + OnHandEntered(inputState); + } + + public void SourceUpdate(CurrentHandState inputState, Vector3 newPosition) { + inputState.Position = newPosition; + OnHandMoved(inputState); + } + + public void SourceLost(CurrentHandState inputState) { + lock (trackedHands) { + trackedHands.Remove(inputState); + } + + OnHandLeft(inputState); + + if (trackedHands.Count == 0) { + + } else { + trackedHands.Sort( + delegate (CurrentHandState h1, CurrentHandState h2) { + return h1.HandId == h2.HandId ? 0 : h1.HandId < h2.HandId ? -1 : 1; + } + ); + } + } + +#if UNITY_WSA + private void UpdateFromWSASource(CurrentHandState currentInputState, UnityEngine.XR.WSA.Input.InteractionSourceState state) { + currentInputState.HandId = state.source.id; + currentInputState.LastPressed = currentInputState.Pressed; + currentInputState.Pressed = state.selectPressed; + state.sourcePose.TryGetVelocity(out currentInputState.Velocity); + + currentInputState.SourceLossRisk = state.properties.sourceLossRisk; + currentInputState.SourceLossMitigationDirection = state.properties.sourceLossMitigationDirection; + + } +#endif + + /// + /// Gets the handedness of a position with respect to a camera position + /// + /// + /// + /// + /// + /// + /// + public static HandednessEnum GetHandedness(Vector3 forward, Vector3 handPos, Vector3 up, ref float confidence, HandednessEnum currentHandedness = HandednessEnum.Neither) { + + // Get handedness based on position relative to camera + Vector3 cross = Vector3.Cross(forward, handPos); + float direction = Vector3.Dot(cross, up); + float newConfidence = Mathf.Abs(direction); + HandednessEnum newHandedness = HandednessEnum.Neither; + if (direction >= 0f) { + // Favor right-handedness slightly + newHandedness = HandednessEnum.Right; + } else + { + newHandedness = HandednessEnum.Left; + } + + // If we've already got a handedness, and the new handedness is different + // reset the confidence + if (currentHandedness != HandednessEnum.Neither && currentHandedness != newHandedness) { + confidence = newConfidence; + } else { + // Otherwise, allow the confidence to remain at the max observed so far + confidence = Mathf.Max(confidence, newConfidence); + } + + return newHandedness; + } + + /// + /// Gets the handedness of a position with respect to a camera position and an opposing hand + /// + /// + /// + /// + /// + /// + /// + public static HandednessEnum GetHandedness( + Vector3 forward, + Vector3 handPos, + Vector3 oppositeHandPos, + Vector3 up, + ref float confidence, + HandednessEnum currentHandedness, + HandednessEnum oppositeHandedness) { + + // Get handedness based on position relative to camera + Vector3 cross = Vector3.Cross(forward, handPos); + float direction = Vector3.Dot(cross, up); + float newConfidence = Mathf.Abs(direction); + HandednessEnum newHandedness = HandednessEnum.Neither; + + if (direction >= 0f) { + // Favor right-handedness slightly + newHandedness = HandednessEnum.Right; + } else + { + newHandedness = HandednessEnum.Left; + } + + // If we've got an opposite handedness, and we're the same, + // Use the opposite hand position instead of the camera position + if (oppositeHandedness != HandednessEnum.Neither && newHandedness == oppositeHandedness) { + Vector3 oppCross = Vector3.Cross(forward, oppositeHandPos); + float oppDirection = Vector3.Dot(oppCross, up); + newConfidence = Mathf.Abs(oppDirection); + if (oppDirection < direction) { + newHandedness = HandednessEnum.Right; + } else { + newHandedness = HandednessEnum.Left; + } + } + + // If we've already got a handedness, and the new handedness is different + // reset the confidence + if (currentHandedness != HandednessEnum.Neither && currentHandedness != newHandedness) { + confidence = newConfidence; + } else { + // Otherwise, allow the confidence to remain at the max observed so far + confidence = Mathf.Max(confidence, newConfidence); + } + + return newHandedness; + } + + #endregion +} diff --git a/HUX/Scripts/Interaction/InteractionManager.cs b/HUX/Scripts/Interaction/InteractionManager.cs index ad8a019..4a81207 100644 --- a/HUX/Scripts/Interaction/InteractionManager.cs +++ b/HUX/Scripts/Interaction/InteractionManager.cs @@ -7,7 +7,7 @@ using HUX.Utility; using UnityEngine.EventSystems; using HUX.Focus; -using UnityEngine.VR.WSA.Input; + namespace HUX.Interaction { @@ -36,9 +36,9 @@ public InteractionEventArgs(AFocuser focuser, Vector3 pos, bool isRelative, Ray /// /// Currently active gesture recognizer. /// - public GestureRecognizer ActiveRecognizer { get; private set; } + public UnityEngine.XR.WSA.Input.GestureRecognizer ActiveRecognizer { get; private set; } - public UnityEngine.VR.WSA.Input.InteractionManager ActiveInteractionManager { get; private set; } + public UnityEngine.XR.WSA.Input.InteractionManager ActiveInteractionManager { get; private set; } /// /// is the user currently navigating. @@ -59,7 +59,7 @@ public InteractionEventArgs(AFocuser focuser, Vector3 pos, bool isRelative, Ray /// /// Which gestures the interaction manager's active recognizer will capture /// - public GestureSettings RecognizableGesures = GestureSettings.Tap | GestureSettings.DoubleTap | GestureSettings.Hold | GestureSettings.NavigationX | GestureSettings.NavigationY; + public UnityEngine.XR.WSA.Input.GestureSettings RecognizableGesures = UnityEngine.XR.WSA.Input.GestureSettings.Tap | UnityEngine.XR.WSA.Input.GestureSettings.DoubleTap | UnityEngine.XR.WSA.Input.GestureSettings.Hold | UnityEngine.XR.WSA.Input.GestureSettings.NavigationX | UnityEngine.XR.WSA.Input.GestureSettings.NavigationY; /// /// Events that trigger manipulation or navigation is done on the specified object. @@ -105,7 +105,7 @@ public InteractionEventArgs(AFocuser focuser, Vector3 pos, bool isRelative, Ray // This should be Start instead of Awake right? protected void Start() { - ActiveRecognizer = new GestureRecognizer(); + ActiveRecognizer = new UnityEngine.XR.WSA.Input.GestureRecognizer(); ActiveRecognizer.SetRecognizableGestures(RecognizableGesures); @@ -128,21 +128,21 @@ protected void Start() ActiveRecognizer.StartCapturingGestures(); SetupEvents(true); - UnityEngine.VR.WSA.Input.InteractionManager.SourcePressed += InteractionManager_SourcePressedCallback; - UnityEngine.VR.WSA.Input.InteractionManager.SourceReleased += InteractionManager_SourceReleasedCallback; + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourcePressed += InteractionManager_SourcePressedCallback; + UnityEngine.XR.WSA.Input.InteractionManager.InteractionSourceReleased += InteractionManager_SourceReleasedCallback; bLockFocus = true; } - private void InteractionManager_SourcePressedCallback(InteractionSourceState state) + private void InteractionManager_SourcePressedCallback(UnityEngine.XR.WSA.Input.InteractionSourcePressedEventArgs args) { - AFocuser focuser = GetFocuserForSource(state.source.kind); + AFocuser focuser = GetFocuserForSource(args.state.source.kind); OnPressedEvent(focuser); } - private void InteractionManager_SourceReleasedCallback(InteractionSourceState state) + private void InteractionManager_SourceReleasedCallback(UnityEngine.XR.WSA.Input.InteractionSourceReleasedEventArgs args) { - AFocuser focuser = GetFocuserForSource(state.source.kind); + AFocuser focuser = GetFocuserForSource(args.state.source.kind); OnReleasedEvent(focuser); } @@ -153,7 +153,7 @@ void Update() // Only do manipulation event simulation in editor, and should we do it on PC? if (Application.platform == RuntimePlatform.WindowsEditor /*|| Application.platform == RuntimePlatform.WindowsPlayer*/) { - AFocuser focuser = GetFocuserForSource(InteractionSourceKind.Hand); + AFocuser focuser = GetFocuserForSource(UnityEngine.XR.WSA.Input.InteractionSourceKind.Hand); // Use the head's position + forward, since the focuser ray might point to the locked focus Vector3 newPos = Veil.Instance.HeadTransform.position + Veil.Instance.HeadTransform.forward; @@ -326,18 +326,18 @@ void OnDestroy() } - private AFocuser GetFocuserForSource(InteractionSourceKind source) + private AFocuser GetFocuserForSource(UnityEngine.XR.WSA.Input.InteractionSourceKind source) { AFocuser focuser = FocusManager.Instance != null ? FocusManager.Instance.GazeFocuser : null; switch (source) { - case InteractionSourceKind.Hand: + case UnityEngine.XR.WSA.Input.InteractionSourceKind.Hand: { focuser = InputSourceFocuser.GetFocuserForInputSource(InputSources.Instance.hands); break; } - case InteractionSourceKind.Controller: + case UnityEngine.XR.WSA.Input.InteractionSourceKind.Controller: { focuser = InputSourceFocuser.GetFocuserForInputSource(InputSources.Instance.sixDOFRay); break; @@ -847,7 +847,7 @@ private void SendEvent(Action sendEvent, GameO #endregion #region Gesture Recognizer Callbacks - private void NavigationStartedCallback(InteractionSourceKind source, Vector3 relativePosition, Ray ray) + private void NavigationStartedCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, Vector3 relativePosition, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) @@ -856,7 +856,7 @@ private void NavigationStartedCallback(InteractionSourceKind source, Vector3 rel } } - private void NavigationUpdatedCallback(InteractionSourceKind source, Vector3 relativePosition, Ray ray) + private void NavigationUpdatedCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, Vector3 relativePosition, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) @@ -865,7 +865,7 @@ private void NavigationUpdatedCallback(InteractionSourceKind source, Vector3 rel } } - private void NavigationCompletedCallback(InteractionSourceKind source, Vector3 relativePosition, Ray ray) + private void NavigationCompletedCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, Vector3 relativePosition, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) @@ -874,7 +874,7 @@ private void NavigationCompletedCallback(InteractionSourceKind source, Vector3 r } } - private void NavigationCanceledCallback(InteractionSourceKind source, Vector3 relativePosition, Ray ray) + private void NavigationCanceledCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, Vector3 relativePosition, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) @@ -883,7 +883,7 @@ private void NavigationCanceledCallback(InteractionSourceKind source, Vector3 re } } - private void TappedCallback(InteractionSourceKind source, int tapCount, Ray ray) + private void TappedCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, int tapCount, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) @@ -899,7 +899,7 @@ private void TappedCallback(InteractionSourceKind source, int tapCount, Ray ray) } } - private void HoldStartedCallback(InteractionSourceKind source, Ray ray) + private void HoldStartedCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) @@ -908,7 +908,7 @@ private void HoldStartedCallback(InteractionSourceKind source, Ray ray) } } - private void HoldCompletedCallback(InteractionSourceKind source, Ray ray) + private void HoldCompletedCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) @@ -917,7 +917,7 @@ private void HoldCompletedCallback(InteractionSourceKind source, Ray ray) } } - private void HoldCanceledCallback(InteractionSourceKind source, Ray ray) + private void HoldCanceledCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) @@ -926,7 +926,7 @@ private void HoldCanceledCallback(InteractionSourceKind source, Ray ray) } } - private void ManipulationStartedCallback(InteractionSourceKind source, Vector3 position, Ray ray) + private void ManipulationStartedCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, Vector3 position, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) @@ -935,7 +935,7 @@ private void ManipulationStartedCallback(InteractionSourceKind source, Vector3 p } } - private void ManipulationUpdatedCallback(InteractionSourceKind source, Vector3 position, Ray ray) + private void ManipulationUpdatedCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, Vector3 position, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) @@ -944,7 +944,7 @@ private void ManipulationUpdatedCallback(InteractionSourceKind source, Vector3 p } } - private void ManipulationCompletedCallback(InteractionSourceKind source, Vector3 position, Ray ray) + private void ManipulationCompletedCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, Vector3 position, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) @@ -953,7 +953,7 @@ private void ManipulationCompletedCallback(InteractionSourceKind source, Vector3 } } - private void ManipulationCanceledCallback(InteractionSourceKind source, Vector3 position, Ray ray) + private void ManipulationCanceledCallback(UnityEngine.XR.WSA.Input.InteractionSourceKind source, Vector3 position, Ray ray) { AFocuser focuser = GetFocuserForSource(source); if (focuser != null) diff --git a/HUX/Scripts/Spatial/Planes/SurfacePlaneManager.cs b/HUX/Scripts/Spatial/Planes/SurfacePlaneManager.cs index 00427b6..777c6e2 100644 --- a/HUX/Scripts/Spatial/Planes/SurfacePlaneManager.cs +++ b/HUX/Scripts/Spatial/Planes/SurfacePlaneManager.cs @@ -1,858 +1,858 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. -// -using System; -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using HUX.Utility; -using HoloToolkit.Unity.SpatialMapping; - -#if UNITY_WSA -using UnityEngine.VR.WSA; -#endif - -#if UNITY_WSA && !UNITY_EDITOR -using System.Threading; -using System.Threading.Tasks; -#endif - -#if UNITY_EDITOR -using UnityEditor; -#endif - -namespace HUX.Spatial -{ - /// - /// SurfaceMeshesToPlanes will find and create planes based on the meshes returned by the SpatialMappingManager's Observer. - /// - public class SurfacePlaneManager : HoloToolkit.Unity.Singleton - { - -#region Editor Variables - [Header("Plane Generation")] - /// - /// Minimum area required for a plane to be created. - /// - [SerializeField, Tooltip("Minimum area required for a plane to be created.")] - private float m_MinArea = 0.025f; - - /// - /// Threshold for acceptable normals (the closer to 1, the stricter the standard). Used when determining plane type. - /// - [SerializeField, Range(0.0f, 1.0f), Tooltip("Threshold for acceptable normals (the closer to 1, the stricter the standard). Used when determining plane type.")] - private float m_UpNormalThreshold = 0.9f; - - /// - /// The thickness to create the planes at. - /// - [SerializeField, Range(0.0f, 1.0f), Tooltip("Thickness to make each plane.")] - private float m_PlaneThickness = 0.01f; - - /// - /// Buffer to use when determining if a horizontal plane near the floor should be considered part of the floor. - /// - [SerializeField, Range(0.0f, 1.0f), Tooltip("Buffer to use when determining if a horizontal plane near the floor should be considered part of the floor.")] - private float m_FloorBuffer = 0.1f; - - /// - /// Buffer to use when determining if a horizontal plane near the ceiling should be considered part of the ceiling. - /// - [SerializeField, Range(0.0f, 1.0f), Tooltip("Buffer to use when determining if a horizontal plane near the ceiling should be considered part of the ceiling.")] - private float m_CeilingBuffer = 0.1f; - - /// - /// The maximum percentage different a plane can have before it will no longer be considered as possibly the same plane. - /// - [SerializeField, Range(0.0f, 1.0f), Tooltip("The maximum percentage different a surface can have before it will no longer be considered as possibly the same plane.")] - private float m_MaxAreaDiffPercent = 0.2f; - - /// - /// The maximum distance a plane can be from another surface before it will no longer be considered as possibly the same plane. - /// - [SerializeField, Tooltip("The maximum distance a plane can be from another surface before it will no longer be considered as possibly the same plane.")] - private float m_MaxDistChange = 0.15f; - - /// - /// Determines which plane types should be discarded. - /// Use this when the spatial mapping mesh is a better fit for the surface (ex: round tables). - /// - [SerializeField, HideInInspector] - private PlaneTypes m_DestroyPlanesMask = PlaneTypes.Unknown; - - [Header("Plane Rendering")] - /// - /// Toggle for turn the drawing of the planes on and off. - /// - [SerializeField] - private bool m_ShowPlanes = true; - - /// - /// Determines which plane types should be rendered. - /// - [SerializeField, HideInInspector] - private PlaneTypes m_DrawPlanesMask = (PlaneTypes.Wall | PlaneTypes.Floor | PlaneTypes.Ceiling | PlaneTypes.Table); - - /// - /// The Material to use for Wall Planes. - /// - [SerializeField] - private Material m_WallMaterial; - - /// - /// The Material to use for Floor planes. - /// - [SerializeField] - private Material m_FloorMaterial; - - /// - /// The Material to use for Ceiling planes. - /// - [SerializeField] - private Material m_CeilingMaterial; - - /// - /// The Material to use for Table planes. - /// - [SerializeField] - private Material m_TableMaterial; - - #endregion - - //----------------------------------------------------------------------------------------------- - - #region Event Handlers - /// - /// Delegate which is called when the MakePlanesCompleted event is triggered. - /// - /// - /// - public delegate void EventHandler(object source, EventArgs args); - - /// - /// EventHandler which is triggered when the MakePlanesRoutine is finished. - /// - public event EventHandler MakePlanesComplete; -#endregion - - //----------------------------------------------------------------------------------------------- - -#region Private Variables - /// - /// All of the currently active planes. - /// - private List m_ActivePlanes = new List(); - - /// - /// Searchable tree of the current wall planes. - /// - private BoundedPlaneKDTree m_WallPlanes = new BoundedPlaneKDTree(); - - /// - /// Searchable tree of the current floor planes. - /// - private BoundedPlaneKDTree m_FloorPlanes = new BoundedPlaneKDTree(); - - /// - /// Searchable tree of the current table planes. - /// - private BoundedPlaneKDTree m_TablePlanes = new BoundedPlaneKDTree(); - - /// - /// Searchable tree of the current ceiling planes. - /// - private BoundedPlaneKDTree m_CeilingPlanes = new BoundedPlaneKDTree(); - - /// - /// Empty game object used to contain all planes created by the SurfaceToPlanes class. - /// - private GameObject planesParent; - - /// - /// Used to align planes with gravity so that they appear more level. - /// - private float snapToGravityThreshold = 5.0f; - - /// - /// The current plane id to assign to the next created plane. - /// - private int m_PlaneId = 1; - - private SpatialMappingObserver m_MappingObserver = null; - - -#if UNITY_EDITOR - /// - /// How much time (in sec), while running in the Unity Editor, to allow RemoveSurfaceVertices to consume before returning control to the main program. - /// - private static readonly float FrameTime = .016f; -#else - /// - /// How much time (in sec) to allow RemoveSurfaceVertices to consume before returning control to the main program. - /// - private static readonly float FrameTime = .008f; -#endif - -#endregion - - //----------------------------------------------------------------------------------------------- - -#region Accessors - - /// - /// Floor y value, which corresponds to the maximum horizontal area found below the user's head position. - /// This value is reset by SurfaceMeshesToPlanes when the max floor plane has been found. - /// - public float FloorYPosition { get; private set; } - - /// - /// Ceiling y value, which corresponds to the maximum horizontal area found above the user's head position. - /// This value is reset by SurfaceMeshesToPlanes when the max ceiling plane has been found. - /// - public float CeilingYPosition { get; private set; } - - /// - /// The minimum threshold for being considered pointing up. - /// - public float UpNormalThreshold - { - get - { - return m_UpNormalThreshold; - } - } - - /// - /// If true the planes set to draw will be shown, otherwise no plane will be shown. - /// - public bool ShowPlanes - { - get - { - return m_ShowPlanes; - } - - set - { - m_ShowPlanes = value; - foreach (SurfacePlane plane in m_ActivePlanes) - { - SetPlaneVisibility(plane); - } - } - } - - /// - /// Indicates if SurfaceToPlanes is currently creating planes based on the Spatial Mapping Mesh. - /// - public bool MakingPlanes - { - get; set; - } - -#endregion - - //----------------------------------------------------------------------------------------------- - -#region MonoBehaviour Functions - - /// - /// Standard Start function. - /// - private void Start() - { - MakingPlanes = false; - planesParent = new GameObject("SurfacePlanes"); - planesParent.transform.position = Vector3.zero; - planesParent.transform.rotation = Quaternion.identity; - - m_MappingObserver = SpatialMappingManager.Instance.Source as SpatialMappingObserver; - } - - private void OnEnable() - { - StartCoroutine(RebuildPlanesFromMeshCoroutine()); - } - - #endregion - - //----------------------------------------------------------------------------------------------- - - #region Public Static Functions - /// - /// Gets the possible types a plane might be. This does not take into account the current floor or ceiling height. - /// - /// - /// - /// - public static PlaneTypes GetPossibleType(BoundedPlane bounds, float upNormalThreshold = 0.9f) - { - PlaneTypes type; - - Vector3 surfaceNormal = bounds.Plane.normal; - // Determine what type of plane this is. - // Use the upNormalThreshold to help determine if we have a horizontal or vertical surface. - if (surfaceNormal.y >= upNormalThreshold) - { - type = PlaneTypes.Floor | PlaneTypes.Table; - } - else if (surfaceNormal.y <= -(upNormalThreshold)) - { - type = PlaneTypes.Ceiling | PlaneTypes.Table; - } - else if (Mathf.Abs(surfaceNormal.y) <= (1 - upNormalThreshold)) - { - // If the plane is vertical, then classify it as a wall. - type = PlaneTypes.Wall; - } - else - { - // The plane has a strange angle, classify it as 'unknown'. - type = PlaneTypes.Unknown; - } - - return type; - } - #endregion - - //----------------------------------------------------------------------------------------------- - - #region Public Functions - - /// - /// Coroutine for RebuildPlanesFromMesh. - /// - /// List of meshes to use to build planes. - public IEnumerator RebuildPlanesFromMeshCoroutine() - { - yield return new WaitForSeconds(1.0f); - - while (isActiveAndEnabled) - { - if (m_MappingObserver != null) - { - List meshFilters = m_MappingObserver.GetMeshFilters(); - var convertedMeshes = new List(meshFilters.Count); - - for (int i = 0; i < meshFilters.Count; i++) - { - convertedMeshes.Add(new PlaneFinding.MeshData(meshFilters[i])); - } - - if (convertedMeshes.Count > 0) - { - yield return MakePlanesRoutine(convertedMeshes); - } - - yield return new WaitForSeconds(2.0f); - } - } - } - - /// - /// Returns all currently active planes. - /// - /// - public List GetActivePlanes() - { - return new List(m_ActivePlanes); - } - - /// - /// Gets all active planes of the specified type(s). - /// - /// A flag which includes all plane type(s) that should be returned. - /// A collection of planes that match the expected type(s). - public List GetActivePlanes(PlaneTypes planeTypes) - { - List typePlanes = new List(); - - foreach (SurfacePlane plane in m_ActivePlanes) - { - if ((planeTypes & plane.PlaneType) == plane.PlaneType) - { - typePlanes.Add(plane); - } - } - - return typePlanes; - } - - /// - /// Gets the closest plane to a provided world position of one of the types defined by validTypes - /// - /// - /// - /// - public SurfacePlane GetClosestPlane(Vector3 pos, PlaneTypes validTypes) - { - SurfacePlane closestPlane = null; - List possiblePlanes = new List(); - - if ((validTypes & PlaneTypes.Ceiling) == PlaneTypes.Ceiling) - { - SurfacePlane possiblePlane = null; - BoundedPlane possibleBounds = new BoundedPlane(); - if (m_CeilingPlanes.FindClosestBoundedPlane(pos, out possibleBounds, out possiblePlane)) - { - possiblePlanes.Add(possiblePlane); - } - } - - if ((validTypes & PlaneTypes.Floor) == PlaneTypes.Floor) - { - SurfacePlane possiblePlane = null; - BoundedPlane possibleBounds = new BoundedPlane(); - if (m_FloorPlanes.FindClosestBoundedPlane(pos, out possibleBounds, out possiblePlane)) - { - possiblePlanes.Add(possiblePlane); - } - } - - if ((validTypes & PlaneTypes.Table) == PlaneTypes.Table) - { - SurfacePlane possiblePlane = null; - BoundedPlane possibleBounds = new BoundedPlane(); - if (m_TablePlanes.FindClosestBoundedPlane(pos, out possibleBounds, out possiblePlane)) - { - possiblePlanes.Add(possiblePlane); - } - } - - if ((validTypes & PlaneTypes.Wall) == PlaneTypes.Wall) - { - SurfacePlane possiblePlane = null; - BoundedPlane possibleBounds = new BoundedPlane(); - if (m_WallPlanes.FindClosestBoundedPlane(pos, out possibleBounds, out possiblePlane)) - { - possiblePlanes.Add(possiblePlane); - } - } - - - float closestDist = float.MaxValue; - //Of the possible planes figure out which is closest. - foreach (SurfacePlane possiblePlane in possiblePlanes) - { - if ((possiblePlane.PlaneType & validTypes) == possiblePlane.PlaneType) - { - float dist = possiblePlane.Plane.GetSqrDistance(pos); - if (closestPlane == null || dist < closestDist) - { - closestDist = dist; - closestPlane = possiblePlane; - } - } - } - - if (closestPlane != null) - { - float distanceToPlane = closestPlane.Plane.Plane.GetDistanceToPoint(pos); - Vector3 worldPosOnPlane = pos - closestPlane.Plane.Plane.normal * distanceToPlane; - - Debug.DrawLine(pos, worldPosOnPlane, Color.red, 15); - Debug.DrawLine(pos, closestPlane.Plane.GetClosestWorldPoint(pos), Color.green, 15); - } - - return closestPlane; - } - - /// - /// Classifies the surface as a floor, wall, ceiling, table, etc. - /// - public PlaneTypes GetPlaneType(BoundedPlane plane) - { - Vector3 surfaceNormal = plane.Plane.normal; - PlaneTypes planeType = PlaneTypes.Unknown; - - // Determine what type of plane this is. - // Use the upNormalThreshold to help determine if we have a horizontal or vertical surface. - if (surfaceNormal.y >= UpNormalThreshold) - { - // If we have a horizontal surface with a normal pointing up, classify it as a floor. - planeType = PlaneTypes.Floor; - - if (plane.Bounds.Center.y > (FloorYPosition + m_FloorBuffer)) - { - // If the plane is too high to be considered part of the floor, classify it as a table. - planeType = PlaneTypes.Table; - } - } - else if (surfaceNormal.y <= -(UpNormalThreshold)) - { - // If we have a horizontal surface with a normal pointing down, classify it as a ceiling. - planeType = PlaneTypes.Ceiling; - - if (plane.Bounds.Center.y < (CeilingYPosition - m_CeilingBuffer)) - { - // If the plane is not high enough to be considered part of the ceiling, classify it as a table. - planeType = PlaneTypes.Table; - } - } - else if (Mathf.Abs(surfaceNormal.y) <= (1 - UpNormalThreshold)) - { - // If the plane is vertical, then classify it as a wall. - planeType = PlaneTypes.Wall; - } - else - { - // The plane has a strange angle, classify it as 'unknown'. - planeType = PlaneTypes.Unknown; - } - - return planeType; - } -#endregion - - //----------------------------------------------------------------------------------------------- - -#region Private Functions - - /// - /// Iterator block, analyzes surface meshes to find planes and create new 3D cubes to represent each plane. - /// - /// Yield result. - private IEnumerator MakePlanesRoutine(List meshData) - { - MakingPlanes = true; -#if UNITY_WSA && !UNITY_EDITOR - // When not in the unity editor we can use a cool background task to help manage FindPlanes(). - Task planeTask = Task.Run(() => PlaneFinding.FindPlanes(meshData, snapToGravityThreshold, m_MinArea)); - - while (planeTask.IsCompleted == false) - { - yield return null; - } - - BoundedPlane[] planes = planeTask.Result; -#else - // In the unity editor, the task class isn't available, but perf is usually good, so we'll just wait for FindPlanes to complete. - BoundedPlane[] planes = PlaneFinding.FindPlanes(meshData, snapToGravityThreshold, m_MinArea); -#endif - - // Pause our work here, and continue on the next frame. - yield return null; - float start = Time.realtimeSinceStartup; - - float maxFloorArea = 0.0f; - float maxCeilingArea = 0.0f; - FloorYPosition = 0.0f; - CeilingYPosition = 0.0f; - - // Find the floor and ceiling. - // We classify the floor as the maximum horizontal surface below the user's head. - // We classify the ceiling as the maximum horizontal surface above the user's head. - for (int i = 0; i < planes.Length; i++) - { - BoundedPlane boundedPlane = planes[i]; - if (boundedPlane.Bounds.Center.y < 0 && boundedPlane.Plane.normal.y >= m_UpNormalThreshold) - { - maxFloorArea = Mathf.Max(maxFloorArea, boundedPlane.Area); - if (maxFloorArea == boundedPlane.Area) - { - FloorYPosition = boundedPlane.Bounds.Center.y; - } - } - else if (boundedPlane.Bounds.Center.y > 0 && boundedPlane.Plane.normal.y <= -(m_UpNormalThreshold)) - { - maxCeilingArea = Mathf.Max(maxCeilingArea, boundedPlane.Area); - if (maxCeilingArea == boundedPlane.Area) - { - CeilingYPosition = boundedPlane.Bounds.Center.y; - } - } - } - - int newPlanes = 0; - List oldPlanes = new List(m_ActivePlanes); - // Create SurfacePlane objects to represent each plane found in the Spatial Mapping mesh. - for (int index = 0; index < planes.Length; index++) - { - BoundedPlane boundedPlane = planes[index]; - boundedPlane.Bounds.Extents.z = m_PlaneThickness /2.0f; - SurfacePlane plane = CheckForExistingPlane(oldPlanes, boundedPlane); - bool planeExisted = plane != null; - - if (plane == null) - { - newPlanes++; - // This is a new plane. - GameObject newPlaneObj = GameObject.CreatePrimitive(PrimitiveType.Cube); - plane = newPlaneObj.AddComponent(); - newPlaneObj.GetComponent().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; - newPlaneObj.transform.parent = planesParent.transform; - - newPlaneObj.name = "Plane " + m_PlaneId; - m_PlaneId++; - - plane.PlaneType = GetPlaneType(boundedPlane); - SetPlaneMaterial(plane); - } - else - { - oldPlanes.Remove(plane); - } - - // Set the Plane property to adjust transform position/scale/rotation and determine plane type. - plane.PlaneThickness = m_PlaneThickness; - plane.Plane = boundedPlane; - - // Set the plane to use the same layer as the SpatialMapping mesh. Do this every time incase the layer has changed. - plane.gameObject.layer = SpatialMappingManager.Instance.PhysicsLayer; - - - SetPlaneVisibility(plane); - - if ((m_DestroyPlanesMask & plane.PlaneType) == plane.PlaneType) - { - DestroyImmediate(plane.gameObject); - } - else if (!planeExisted) - { - AddPlane(plane); - } - - // If too much time has passed, we need to return control to the main game loop. - if ((Time.realtimeSinceStartup - start) > FrameTime) - { - // Pause our work here, and continue making additional planes on the next frame. - yield return null; - start = Time.realtimeSinceStartup; - } - } - - for (int index = 0; index < oldPlanes.Count; index++) - { - RemovePlane(oldPlanes[index]); - Destroy(oldPlanes[index].gameObject); - } - - // We are done creating planes, trigger an event. - EventHandler handler = MakePlanesComplete; - if (handler != null) - { - handler(this, EventArgs.Empty); - } - - MakingPlanes = false; - } - - /// - /// Adds the plane to tracking. - /// - /// - private void AddPlane(SurfacePlane plane) - { - m_ActivePlanes.Add(plane); - - switch (plane.PlaneType) - { - case PlaneTypes.Ceiling: - { - m_CeilingPlanes.Add(plane.Plane, plane); - break; - } - - case PlaneTypes.Floor: - { - m_FloorPlanes.Add(plane.Plane, plane); - break; - } - - case PlaneTypes.Table: - { - m_TablePlanes.Add(plane.Plane, plane); - break; - } - - case PlaneTypes.Wall: - { - m_WallPlanes.Add(plane.Plane, plane); - break; - } - } - } - - /// - /// Removes the plane from tracking. - /// - /// - private void RemovePlane(SurfacePlane plane) - { - m_ActivePlanes.Remove(plane); - - switch (plane.PlaneType) - { - case PlaneTypes.Ceiling: - { - m_CeilingPlanes.Remove(plane); - break; - } - - case PlaneTypes.Floor: - { - m_FloorPlanes.Remove(plane); - break; - } - - case PlaneTypes.Table: - { - m_TablePlanes.Remove(plane); - break; - } - - case PlaneTypes.Wall: - { - m_WallPlanes.Remove(plane); - break; - } - } - } - - /// - /// Checks the list of passed in planes for one that might match the passed in bounding plane. - /// - /// - /// - /// - private SurfacePlane CheckForExistingPlane(List planes, BoundedPlane plane) - { - SurfacePlane bestMatch = null; - float bestAreaDiff = float.MaxValue; - float bestDistance = float.MaxValue; - float bestDistPercent = float.MaxValue; - - PlaneTypes type = GetPossibleType(plane, m_UpNormalThreshold); - - foreach (SurfacePlane possiblePlane in planes) - { - if ((possiblePlane.PlaneType & type) == 0) - { - //Skip this one. - continue; - } - - //What is the area difference? - float areaDiff = Mathf.Abs(possiblePlane.Plane.Area - plane.Area); - float areaDiffPercent = areaDiff / ((possiblePlane.Plane.Area + plane.Area ) / 2); - - //What is the distance difference? - float distDiff = (possiblePlane.Plane.Bounds.Center - plane.Bounds.Center).sqrMagnitude; - float distChangePercent = distDiff /(possiblePlane.Plane.Bounds.Center.sqrMagnitude + plane.Bounds.Center.sqrMagnitude) / 2; - - if (areaDiffPercent >= m_MaxAreaDiffPercent || distDiff > m_MaxDistChange) - { - //The difference in these planes are to different so we can ignore this one. - continue; - } - else if (areaDiffPercent < bestAreaDiff && distDiff < bestDistance) - { - bestMatch = possiblePlane; - bestAreaDiff = areaDiffPercent; - bestDistPercent = distChangePercent; - distDiff = bestDistance; - } - else if (areaDiffPercent < bestAreaDiff && areaDiffPercent <= bestDistPercent) - { - bestMatch = possiblePlane; - bestAreaDiff = areaDiffPercent; - bestDistPercent = distChangePercent; - distDiff = bestDistance; - } - else if (distDiff < bestDistance && distChangePercent <= areaDiffPercent) - { - bestMatch = possiblePlane; - bestAreaDiff = areaDiffPercent; - bestDistPercent = distChangePercent; - distDiff = bestDistance; - } - - } - - return bestMatch; - } - - /// - /// Sets the material on the renderer based on the type of plane it is. - /// - /// - private void SetPlaneMaterial(SurfacePlane plane) - { - Material mat = null; - switch (plane.PlaneType) - { - case PlaneTypes.Ceiling: - { - mat = m_CeilingMaterial; - break; - } - - case PlaneTypes.Floor: - { - mat = m_FloorMaterial; - break; - } - - case PlaneTypes.Table: - { - mat = m_TableMaterial; - break; - } - - case PlaneTypes.Wall: - { - mat = m_WallMaterial; - break; - } - } - - plane.SetPlaneMaterial(mat); - } - - /// - /// Sets visibility of planes based on their type. - /// - /// - private void SetPlaneVisibility(SurfacePlane surfacePlane) - { - surfacePlane.IsVisible = m_ShowPlanes && ((m_DrawPlanesMask & surfacePlane.PlaneType) == surfacePlane.PlaneType); - } -#endregion - } - -#region Inspector -#if UNITY_EDITOR - /// - /// Editor extension class to enable multi-selection of the 'Draw Planes' and 'Destroy Planes' options in the Inspector. - /// - [CustomEditor(typeof(SurfacePlaneManager))] - public class PlaneTypesEnumEditor : Editor - { - public SerializedProperty drawPlanesMask; - public SerializedProperty destroyPlanesMask; - - void OnEnable() - { - drawPlanesMask = serializedObject.FindProperty("m_DrawPlanesMask"); - destroyPlanesMask = serializedObject.FindProperty("m_DestroyPlanesMask"); - } - - public override void OnInspectorGUI() - { - base.OnInspectorGUI(); - serializedObject.Update(); - - drawPlanesMask.intValue = (int)((PlaneTypes)EditorGUILayout.EnumMaskField - ("Draw Planes", (PlaneTypes)drawPlanesMask.intValue)); - - destroyPlanesMask.intValue = (int)((PlaneTypes)EditorGUILayout.EnumMaskField - ("Destroy Planes", (PlaneTypes)destroyPlanesMask.intValue)); - - serializedObject.ApplyModifiedProperties(); - } - } -#endif - -#endregion -} +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using HUX.Utility; +using HoloToolkit.Unity.SpatialMapping; + +#if UNITY_WSA +using UnityEngine.XR.WSA; +#endif + +#if UNITY_WSA && !UNITY_EDITOR +using System.Threading; +using System.Threading.Tasks; +#endif + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace HUX.Spatial +{ + /// + /// SurfaceMeshesToPlanes will find and create planes based on the meshes returned by the SpatialMappingManager's Observer. + /// + public class SurfacePlaneManager : HoloToolkit.Unity.Singleton + { + +#region Editor Variables + [Header("Plane Generation")] + /// + /// Minimum area required for a plane to be created. + /// + [SerializeField, Tooltip("Minimum area required for a plane to be created.")] + private float m_MinArea = 0.025f; + + /// + /// Threshold for acceptable normals (the closer to 1, the stricter the standard). Used when determining plane type. + /// + [SerializeField, Range(0.0f, 1.0f), Tooltip("Threshold for acceptable normals (the closer to 1, the stricter the standard). Used when determining plane type.")] + private float m_UpNormalThreshold = 0.9f; + + /// + /// The thickness to create the planes at. + /// + [SerializeField, Range(0.0f, 1.0f), Tooltip("Thickness to make each plane.")] + private float m_PlaneThickness = 0.01f; + + /// + /// Buffer to use when determining if a horizontal plane near the floor should be considered part of the floor. + /// + [SerializeField, Range(0.0f, 1.0f), Tooltip("Buffer to use when determining if a horizontal plane near the floor should be considered part of the floor.")] + private float m_FloorBuffer = 0.1f; + + /// + /// Buffer to use when determining if a horizontal plane near the ceiling should be considered part of the ceiling. + /// + [SerializeField, Range(0.0f, 1.0f), Tooltip("Buffer to use when determining if a horizontal plane near the ceiling should be considered part of the ceiling.")] + private float m_CeilingBuffer = 0.1f; + + /// + /// The maximum percentage different a plane can have before it will no longer be considered as possibly the same plane. + /// + [SerializeField, Range(0.0f, 1.0f), Tooltip("The maximum percentage different a surface can have before it will no longer be considered as possibly the same plane.")] + private float m_MaxAreaDiffPercent = 0.2f; + + /// + /// The maximum distance a plane can be from another surface before it will no longer be considered as possibly the same plane. + /// + [SerializeField, Tooltip("The maximum distance a plane can be from another surface before it will no longer be considered as possibly the same plane.")] + private float m_MaxDistChange = 0.15f; + + /// + /// Determines which plane types should be discarded. + /// Use this when the spatial mapping mesh is a better fit for the surface (ex: round tables). + /// + [SerializeField, HideInInspector] + private PlaneTypes m_DestroyPlanesMask = PlaneTypes.Unknown; + + [Header("Plane Rendering")] + /// + /// Toggle for turn the drawing of the planes on and off. + /// + [SerializeField] + private bool m_ShowPlanes = true; + + /// + /// Determines which plane types should be rendered. + /// + [SerializeField, HideInInspector] + private PlaneTypes m_DrawPlanesMask = (PlaneTypes.Wall | PlaneTypes.Floor | PlaneTypes.Ceiling | PlaneTypes.Table); + + /// + /// The Material to use for Wall Planes. + /// + [SerializeField] + private Material m_WallMaterial; + + /// + /// The Material to use for Floor planes. + /// + [SerializeField] + private Material m_FloorMaterial; + + /// + /// The Material to use for Ceiling planes. + /// + [SerializeField] + private Material m_CeilingMaterial; + + /// + /// The Material to use for Table planes. + /// + [SerializeField] + private Material m_TableMaterial; + + #endregion + + //----------------------------------------------------------------------------------------------- + + #region Event Handlers + /// + /// Delegate which is called when the MakePlanesCompleted event is triggered. + /// + /// + /// + public delegate void EventHandler(object source, EventArgs args); + + /// + /// EventHandler which is triggered when the MakePlanesRoutine is finished. + /// + public event EventHandler MakePlanesComplete; +#endregion + + //----------------------------------------------------------------------------------------------- + +#region Private Variables + /// + /// All of the currently active planes. + /// + private List m_ActivePlanes = new List(); + + /// + /// Searchable tree of the current wall planes. + /// + private BoundedPlaneKDTree m_WallPlanes = new BoundedPlaneKDTree(); + + /// + /// Searchable tree of the current floor planes. + /// + private BoundedPlaneKDTree m_FloorPlanes = new BoundedPlaneKDTree(); + + /// + /// Searchable tree of the current table planes. + /// + private BoundedPlaneKDTree m_TablePlanes = new BoundedPlaneKDTree(); + + /// + /// Searchable tree of the current ceiling planes. + /// + private BoundedPlaneKDTree m_CeilingPlanes = new BoundedPlaneKDTree(); + + /// + /// Empty game object used to contain all planes created by the SurfaceToPlanes class. + /// + private GameObject planesParent; + + /// + /// Used to align planes with gravity so that they appear more level. + /// + private float snapToGravityThreshold = 5.0f; + + /// + /// The current plane id to assign to the next created plane. + /// + private int m_PlaneId = 1; + + private SpatialMappingObserver m_MappingObserver = null; + + +#if UNITY_EDITOR + /// + /// How much time (in sec), while running in the Unity Editor, to allow RemoveSurfaceVertices to consume before returning control to the main program. + /// + private static readonly float FrameTime = .016f; +#else + /// + /// How much time (in sec) to allow RemoveSurfaceVertices to consume before returning control to the main program. + /// + private static readonly float FrameTime = .008f; +#endif + +#endregion + + //----------------------------------------------------------------------------------------------- + +#region Accessors + + /// + /// Floor y value, which corresponds to the maximum horizontal area found below the user's head position. + /// This value is reset by SurfaceMeshesToPlanes when the max floor plane has been found. + /// + public float FloorYPosition { get; private set; } + + /// + /// Ceiling y value, which corresponds to the maximum horizontal area found above the user's head position. + /// This value is reset by SurfaceMeshesToPlanes when the max ceiling plane has been found. + /// + public float CeilingYPosition { get; private set; } + + /// + /// The minimum threshold for being considered pointing up. + /// + public float UpNormalThreshold + { + get + { + return m_UpNormalThreshold; + } + } + + /// + /// If true the planes set to draw will be shown, otherwise no plane will be shown. + /// + public bool ShowPlanes + { + get + { + return m_ShowPlanes; + } + + set + { + m_ShowPlanes = value; + foreach (SurfacePlane plane in m_ActivePlanes) + { + SetPlaneVisibility(plane); + } + } + } + + /// + /// Indicates if SurfaceToPlanes is currently creating planes based on the Spatial Mapping Mesh. + /// + public bool MakingPlanes + { + get; set; + } + +#endregion + + //----------------------------------------------------------------------------------------------- + +#region MonoBehaviour Functions + + /// + /// Standard Start function. + /// + private void Start() + { + MakingPlanes = false; + planesParent = new GameObject("SurfacePlanes"); + planesParent.transform.position = Vector3.zero; + planesParent.transform.rotation = Quaternion.identity; + + m_MappingObserver = SpatialMappingManager.Instance.Source as SpatialMappingObserver; + } + + private void OnEnable() + { + StartCoroutine(RebuildPlanesFromMeshCoroutine()); + } + + #endregion + + //----------------------------------------------------------------------------------------------- + + #region Public Static Functions + /// + /// Gets the possible types a plane might be. This does not take into account the current floor or ceiling height. + /// + /// + /// + /// + public static PlaneTypes GetPossibleType(BoundedPlane bounds, float upNormalThreshold = 0.9f) + { + PlaneTypes type; + + Vector3 surfaceNormal = bounds.Plane.normal; + // Determine what type of plane this is. + // Use the upNormalThreshold to help determine if we have a horizontal or vertical surface. + if (surfaceNormal.y >= upNormalThreshold) + { + type = PlaneTypes.Floor | PlaneTypes.Table; + } + else if (surfaceNormal.y <= -(upNormalThreshold)) + { + type = PlaneTypes.Ceiling | PlaneTypes.Table; + } + else if (Mathf.Abs(surfaceNormal.y) <= (1 - upNormalThreshold)) + { + // If the plane is vertical, then classify it as a wall. + type = PlaneTypes.Wall; + } + else + { + // The plane has a strange angle, classify it as 'unknown'. + type = PlaneTypes.Unknown; + } + + return type; + } + #endregion + + //----------------------------------------------------------------------------------------------- + + #region Public Functions + + /// + /// Coroutine for RebuildPlanesFromMesh. + /// + /// List of meshes to use to build planes. + public IEnumerator RebuildPlanesFromMeshCoroutine() + { + yield return new WaitForSeconds(1.0f); + + while (isActiveAndEnabled) + { + if (m_MappingObserver != null) + { + List meshFilters = m_MappingObserver.GetMeshFilters(); + var convertedMeshes = new List(meshFilters.Count); + + for (int i = 0; i < meshFilters.Count; i++) + { + convertedMeshes.Add(new PlaneFinding.MeshData(meshFilters[i])); + } + + if (convertedMeshes.Count > 0) + { + yield return MakePlanesRoutine(convertedMeshes); + } + + yield return new WaitForSeconds(2.0f); + } + } + } + + /// + /// Returns all currently active planes. + /// + /// + public List GetActivePlanes() + { + return new List(m_ActivePlanes); + } + + /// + /// Gets all active planes of the specified type(s). + /// + /// A flag which includes all plane type(s) that should be returned. + /// A collection of planes that match the expected type(s). + public List GetActivePlanes(PlaneTypes planeTypes) + { + List typePlanes = new List(); + + foreach (SurfacePlane plane in m_ActivePlanes) + { + if ((planeTypes & plane.PlaneType) == plane.PlaneType) + { + typePlanes.Add(plane); + } + } + + return typePlanes; + } + + /// + /// Gets the closest plane to a provided world position of one of the types defined by validTypes + /// + /// + /// + /// + public SurfacePlane GetClosestPlane(Vector3 pos, PlaneTypes validTypes) + { + SurfacePlane closestPlane = null; + List possiblePlanes = new List(); + + if ((validTypes & PlaneTypes.Ceiling) == PlaneTypes.Ceiling) + { + SurfacePlane possiblePlane = null; + BoundedPlane possibleBounds = new BoundedPlane(); + if (m_CeilingPlanes.FindClosestBoundedPlane(pos, out possibleBounds, out possiblePlane)) + { + possiblePlanes.Add(possiblePlane); + } + } + + if ((validTypes & PlaneTypes.Floor) == PlaneTypes.Floor) + { + SurfacePlane possiblePlane = null; + BoundedPlane possibleBounds = new BoundedPlane(); + if (m_FloorPlanes.FindClosestBoundedPlane(pos, out possibleBounds, out possiblePlane)) + { + possiblePlanes.Add(possiblePlane); + } + } + + if ((validTypes & PlaneTypes.Table) == PlaneTypes.Table) + { + SurfacePlane possiblePlane = null; + BoundedPlane possibleBounds = new BoundedPlane(); + if (m_TablePlanes.FindClosestBoundedPlane(pos, out possibleBounds, out possiblePlane)) + { + possiblePlanes.Add(possiblePlane); + } + } + + if ((validTypes & PlaneTypes.Wall) == PlaneTypes.Wall) + { + SurfacePlane possiblePlane = null; + BoundedPlane possibleBounds = new BoundedPlane(); + if (m_WallPlanes.FindClosestBoundedPlane(pos, out possibleBounds, out possiblePlane)) + { + possiblePlanes.Add(possiblePlane); + } + } + + + float closestDist = float.MaxValue; + //Of the possible planes figure out which is closest. + foreach (SurfacePlane possiblePlane in possiblePlanes) + { + if ((possiblePlane.PlaneType & validTypes) == possiblePlane.PlaneType) + { + float dist = possiblePlane.Plane.GetSqrDistance(pos); + if (closestPlane == null || dist < closestDist) + { + closestDist = dist; + closestPlane = possiblePlane; + } + } + } + + if (closestPlane != null) + { + float distanceToPlane = closestPlane.Plane.Plane.GetDistanceToPoint(pos); + Vector3 worldPosOnPlane = pos - closestPlane.Plane.Plane.normal * distanceToPlane; + + Debug.DrawLine(pos, worldPosOnPlane, Color.red, 15); + Debug.DrawLine(pos, closestPlane.Plane.GetClosestWorldPoint(pos), Color.green, 15); + } + + return closestPlane; + } + + /// + /// Classifies the surface as a floor, wall, ceiling, table, etc. + /// + public PlaneTypes GetPlaneType(BoundedPlane plane) + { + Vector3 surfaceNormal = plane.Plane.normal; + PlaneTypes planeType = PlaneTypes.Unknown; + + // Determine what type of plane this is. + // Use the upNormalThreshold to help determine if we have a horizontal or vertical surface. + if (surfaceNormal.y >= UpNormalThreshold) + { + // If we have a horizontal surface with a normal pointing up, classify it as a floor. + planeType = PlaneTypes.Floor; + + if (plane.Bounds.Center.y > (FloorYPosition + m_FloorBuffer)) + { + // If the plane is too high to be considered part of the floor, classify it as a table. + planeType = PlaneTypes.Table; + } + } + else if (surfaceNormal.y <= -(UpNormalThreshold)) + { + // If we have a horizontal surface with a normal pointing down, classify it as a ceiling. + planeType = PlaneTypes.Ceiling; + + if (plane.Bounds.Center.y < (CeilingYPosition - m_CeilingBuffer)) + { + // If the plane is not high enough to be considered part of the ceiling, classify it as a table. + planeType = PlaneTypes.Table; + } + } + else if (Mathf.Abs(surfaceNormal.y) <= (1 - UpNormalThreshold)) + { + // If the plane is vertical, then classify it as a wall. + planeType = PlaneTypes.Wall; + } + else + { + // The plane has a strange angle, classify it as 'unknown'. + planeType = PlaneTypes.Unknown; + } + + return planeType; + } +#endregion + + //----------------------------------------------------------------------------------------------- + +#region Private Functions + + /// + /// Iterator block, analyzes surface meshes to find planes and create new 3D cubes to represent each plane. + /// + /// Yield result. + private IEnumerator MakePlanesRoutine(List meshData) + { + MakingPlanes = true; +#if UNITY_WSA && !UNITY_EDITOR + // When not in the unity editor we can use a cool background task to help manage FindPlanes(). + Task planeTask = Task.Run(() => PlaneFinding.FindPlanes(meshData, snapToGravityThreshold, m_MinArea)); + + while (planeTask.IsCompleted == false) + { + yield return null; + } + + BoundedPlane[] planes = planeTask.Result; +#else + // In the unity editor, the task class isn't available, but perf is usually good, so we'll just wait for FindPlanes to complete. + BoundedPlane[] planes = PlaneFinding.FindPlanes(meshData, snapToGravityThreshold, m_MinArea); +#endif + + // Pause our work here, and continue on the next frame. + yield return null; + float start = Time.realtimeSinceStartup; + + float maxFloorArea = 0.0f; + float maxCeilingArea = 0.0f; + FloorYPosition = 0.0f; + CeilingYPosition = 0.0f; + + // Find the floor and ceiling. + // We classify the floor as the maximum horizontal surface below the user's head. + // We classify the ceiling as the maximum horizontal surface above the user's head. + for (int i = 0; i < planes.Length; i++) + { + BoundedPlane boundedPlane = planes[i]; + if (boundedPlane.Bounds.Center.y < 0 && boundedPlane.Plane.normal.y >= m_UpNormalThreshold) + { + maxFloorArea = Mathf.Max(maxFloorArea, boundedPlane.Area); + if (maxFloorArea == boundedPlane.Area) + { + FloorYPosition = boundedPlane.Bounds.Center.y; + } + } + else if (boundedPlane.Bounds.Center.y > 0 && boundedPlane.Plane.normal.y <= -(m_UpNormalThreshold)) + { + maxCeilingArea = Mathf.Max(maxCeilingArea, boundedPlane.Area); + if (maxCeilingArea == boundedPlane.Area) + { + CeilingYPosition = boundedPlane.Bounds.Center.y; + } + } + } + + int newPlanes = 0; + List oldPlanes = new List(m_ActivePlanes); + // Create SurfacePlane objects to represent each plane found in the Spatial Mapping mesh. + for (int index = 0; index < planes.Length; index++) + { + BoundedPlane boundedPlane = planes[index]; + boundedPlane.Bounds.Extents.z = m_PlaneThickness /2.0f; + SurfacePlane plane = CheckForExistingPlane(oldPlanes, boundedPlane); + bool planeExisted = plane != null; + + if (plane == null) + { + newPlanes++; + // This is a new plane. + GameObject newPlaneObj = GameObject.CreatePrimitive(PrimitiveType.Cube); + plane = newPlaneObj.AddComponent(); + newPlaneObj.GetComponent().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; + newPlaneObj.transform.parent = planesParent.transform; + + newPlaneObj.name = "Plane " + m_PlaneId; + m_PlaneId++; + + plane.PlaneType = GetPlaneType(boundedPlane); + SetPlaneMaterial(plane); + } + else + { + oldPlanes.Remove(plane); + } + + // Set the Plane property to adjust transform position/scale/rotation and determine plane type. + plane.PlaneThickness = m_PlaneThickness; + plane.Plane = boundedPlane; + + // Set the plane to use the same layer as the SpatialMapping mesh. Do this every time incase the layer has changed. + plane.gameObject.layer = SpatialMappingManager.Instance.PhysicsLayer; + + + SetPlaneVisibility(plane); + + if ((m_DestroyPlanesMask & plane.PlaneType) == plane.PlaneType) + { + DestroyImmediate(plane.gameObject); + } + else if (!planeExisted) + { + AddPlane(plane); + } + + // If too much time has passed, we need to return control to the main game loop. + if ((Time.realtimeSinceStartup - start) > FrameTime) + { + // Pause our work here, and continue making additional planes on the next frame. + yield return null; + start = Time.realtimeSinceStartup; + } + } + + for (int index = 0; index < oldPlanes.Count; index++) + { + RemovePlane(oldPlanes[index]); + Destroy(oldPlanes[index].gameObject); + } + + // We are done creating planes, trigger an event. + EventHandler handler = MakePlanesComplete; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + + MakingPlanes = false; + } + + /// + /// Adds the plane to tracking. + /// + /// + private void AddPlane(SurfacePlane plane) + { + m_ActivePlanes.Add(plane); + + switch (plane.PlaneType) + { + case PlaneTypes.Ceiling: + { + m_CeilingPlanes.Add(plane.Plane, plane); + break; + } + + case PlaneTypes.Floor: + { + m_FloorPlanes.Add(plane.Plane, plane); + break; + } + + case PlaneTypes.Table: + { + m_TablePlanes.Add(plane.Plane, plane); + break; + } + + case PlaneTypes.Wall: + { + m_WallPlanes.Add(plane.Plane, plane); + break; + } + } + } + + /// + /// Removes the plane from tracking. + /// + /// + private void RemovePlane(SurfacePlane plane) + { + m_ActivePlanes.Remove(plane); + + switch (plane.PlaneType) + { + case PlaneTypes.Ceiling: + { + m_CeilingPlanes.Remove(plane); + break; + } + + case PlaneTypes.Floor: + { + m_FloorPlanes.Remove(plane); + break; + } + + case PlaneTypes.Table: + { + m_TablePlanes.Remove(plane); + break; + } + + case PlaneTypes.Wall: + { + m_WallPlanes.Remove(plane); + break; + } + } + } + + /// + /// Checks the list of passed in planes for one that might match the passed in bounding plane. + /// + /// + /// + /// + private SurfacePlane CheckForExistingPlane(List planes, BoundedPlane plane) + { + SurfacePlane bestMatch = null; + float bestAreaDiff = float.MaxValue; + float bestDistance = float.MaxValue; + float bestDistPercent = float.MaxValue; + + PlaneTypes type = GetPossibleType(plane, m_UpNormalThreshold); + + foreach (SurfacePlane possiblePlane in planes) + { + if ((possiblePlane.PlaneType & type) == 0) + { + //Skip this one. + continue; + } + + //What is the area difference? + float areaDiff = Mathf.Abs(possiblePlane.Plane.Area - plane.Area); + float areaDiffPercent = areaDiff / ((possiblePlane.Plane.Area + plane.Area ) / 2); + + //What is the distance difference? + float distDiff = (possiblePlane.Plane.Bounds.Center - plane.Bounds.Center).sqrMagnitude; + float distChangePercent = distDiff /(possiblePlane.Plane.Bounds.Center.sqrMagnitude + plane.Bounds.Center.sqrMagnitude) / 2; + + if (areaDiffPercent >= m_MaxAreaDiffPercent || distDiff > m_MaxDistChange) + { + //The difference in these planes are to different so we can ignore this one. + continue; + } + else if (areaDiffPercent < bestAreaDiff && distDiff < bestDistance) + { + bestMatch = possiblePlane; + bestAreaDiff = areaDiffPercent; + bestDistPercent = distChangePercent; + distDiff = bestDistance; + } + else if (areaDiffPercent < bestAreaDiff && areaDiffPercent <= bestDistPercent) + { + bestMatch = possiblePlane; + bestAreaDiff = areaDiffPercent; + bestDistPercent = distChangePercent; + distDiff = bestDistance; + } + else if (distDiff < bestDistance && distChangePercent <= areaDiffPercent) + { + bestMatch = possiblePlane; + bestAreaDiff = areaDiffPercent; + bestDistPercent = distChangePercent; + distDiff = bestDistance; + } + + } + + return bestMatch; + } + + /// + /// Sets the material on the renderer based on the type of plane it is. + /// + /// + private void SetPlaneMaterial(SurfacePlane plane) + { + Material mat = null; + switch (plane.PlaneType) + { + case PlaneTypes.Ceiling: + { + mat = m_CeilingMaterial; + break; + } + + case PlaneTypes.Floor: + { + mat = m_FloorMaterial; + break; + } + + case PlaneTypes.Table: + { + mat = m_TableMaterial; + break; + } + + case PlaneTypes.Wall: + { + mat = m_WallMaterial; + break; + } + } + + plane.SetPlaneMaterial(mat); + } + + /// + /// Sets visibility of planes based on their type. + /// + /// + private void SetPlaneVisibility(SurfacePlane surfacePlane) + { + surfacePlane.IsVisible = m_ShowPlanes && ((m_DrawPlanesMask & surfacePlane.PlaneType) == surfacePlane.PlaneType); + } +#endregion + } + +#region Inspector +#if UNITY_EDITOR + /// + /// Editor extension class to enable multi-selection of the 'Draw Planes' and 'Destroy Planes' options in the Inspector. + /// + [CustomEditor(typeof(SurfacePlaneManager))] + public class PlaneTypesEnumEditor : Editor + { + public SerializedProperty drawPlanesMask; + public SerializedProperty destroyPlanesMask; + + void OnEnable() + { + drawPlanesMask = serializedObject.FindProperty("m_DrawPlanesMask"); + destroyPlanesMask = serializedObject.FindProperty("m_DestroyPlanesMask"); + } + + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + serializedObject.Update(); + + drawPlanesMask.intValue = (int)((PlaneTypes)EditorGUILayout.EnumMaskField + ("Draw Planes", (PlaneTypes)drawPlanesMask.intValue)); + + destroyPlanesMask.intValue = (int)((PlaneTypes)EditorGUILayout.EnumMaskField + ("Destroy Planes", (PlaneTypes)destroyPlanesMask.intValue)); + + serializedObject.ApplyModifiedProperties(); + } + } +#endif + +#endregion +} diff --git a/HUX/Scripts/Spatial/PointOfReferenceManager.cs b/HUX/Scripts/Spatial/PointOfReferenceManager.cs index 615dfe6..30102fa 100644 --- a/HUX/Scripts/Spatial/PointOfReferenceManager.cs +++ b/HUX/Scripts/Spatial/PointOfReferenceManager.cs @@ -1,360 +1,360 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. -// -using UnityEngine; - -#if UNITY_WSA -using UnityEngine.VR.WSA; -using UnityEngine.VR.WSA.Persistence; -using UnityEngine.VR.WSA.Sharing; -#endif - -using System.Collections; -using System.Collections.Generic; -using HUX.Receivers; -using HUX.Dialogs.Debug; -using HUX.Utility; - -public class PointOfReferenceManager : Singleton -{ - public GameObject m_PointOfReferenceCube; - - -#if UNITY_WSA - List m_MessagesAsync = new List(); - public event System.Action OnPlacement; - WorldAnchorStore store = null; - private WorldAnchor m_PointOfReference = null; - public WorldAnchor PointOfReference - { - get { return m_PointOfReference; } - } -#endif - public const string PointOfReferenceID = "PointOfReference"; - - private bool m_LoadedAndPlaced = false; - public bool LoadedAndPlaced - { - get { return m_LoadedAndPlaced; } - } - - enum ManualPointOfReferenceAcquisitionStage - { - Idle, - FirstPoint, - SecondPoint, - Acquired, - } - ManualPointOfReferenceAcquisitionStage m_ManualAnchorAcquisitionState = ManualPointOfReferenceAcquisitionStage.Idle; - Vector3 m_ManualAnchorPos = Vector3.zero; - Vector3 m_ManualAnchorForward = Vector3.forward; - -#if UNITY_WSA - // Use this for initialization - void Start() - { - if (DebugMenu.Instance) - { - DebugMenu.Instance.AddButtonItem("Point Of Reference\\Generate Point of Reference", "Generate New", CreateManualPointOfReference); - } -#if UNITY_EDITOR - m_LoadedAndPlaced = true; -#else - WorldAnchorStore.GetAsync(StoreLoaded); -#endif - - InputSources.Instance.hands.OnFingerPressed += OnFingerPressed; - } - - // Update is called once per frame - void Update () - { - lock (m_MessagesAsync) - { - while (m_MessagesAsync.Count > 0) - { - Debug.Log("(async)" + m_MessagesAsync[0]); - m_MessagesAsync.RemoveAt(0); - } - } - - if ((m_PointOfReference != null) && (m_PointOfReference.isLocated)) - { - m_LoadedAndPlaced = true; - } - } -#endif - - public void CreatePointOfReference(Vector3 pos, Quaternion rot) - { - m_PointOfReferenceCube.transform.position = pos; - m_PointOfReferenceCube.transform.rotation = rot; - -#if UNITY_WSA - if (store != null) - { - store.Delete(PointOfReferenceID); - } - - if (m_PointOfReference) - { - DestroyImmediate(m_PointOfReference); - } - - m_PointOfReference = m_PointOfReferenceCube.AddComponent(); - if (StatusText.Instance) - { - StatusText.Instance.SetText("Point of Reference Created"); - } - - // SaveAnchorToStore(m_PointOfReferenceID, m_PointOfReference); - StartCoroutine(SaveAnchor()); -#endif - } - - -#if UNITY_WSA - void LogAsync(string message) - { - lock (m_MessagesAsync) - { - m_MessagesAsync.Add(message); - } - } - - private void StoreLoaded(WorldAnchorStore store) - { - this.store = store; - - // We've loaded. Wait to place. - StartCoroutine(PlaceWorldAnchor()); - } - - private IEnumerator PlaceWorldAnchor() - { - // Wait while the SR loads a bit. - yield return new WaitForSeconds(0.5f); - - if (m_PointOfReference) - { - DestroyImmediate(m_PointOfReference); - } - - m_PointOfReferenceCube.transform.position = Vector3.zero; - m_PointOfReferenceCube.transform.localScale = Vector3.one * 0.1f; - - m_PointOfReference = store.Load(PointOfReferenceID, m_PointOfReferenceCube); - if (m_PointOfReference == null) - { - if (StatusText.Instance) - { - StatusText.Instance.SetText("No Point of Reference created."); - } - } - else - { - Debug.Log("Created anchor from WorldAnchorStore: " + PointOfReferenceID); - if (StatusText.Instance) - { - StatusText.Instance.SetText("Loaded WorldAnchorStore"); - } - } - } - - private IEnumerator SaveAnchor() - { -#if !UNITY_EDITOR - while (!m_PointOfReference.isLocated) - { - yield return new WaitForEndOfFrame(); - } -#else - yield return 0; -#endif - - SaveAnchorToStore(PointOfReferenceID, m_PointOfReference); - Debug.Log("SaveAnchor: Point of Reference Saved."); - } - - private void SaveAnchorToStore(string id, WorldAnchor worldAnchor) - { -#if !UNITY_EDITOR - if ((store != null) && (store.Save(id, worldAnchor))) - { - Debug.Log("WorldAnchor Saved: " + id); - if (OnPlacement != null) - { - OnPlacement(); - } - } - else - { - Debug.Log("Failed to Save WorldAnchor " + id); - } -#else - if (OnPlacement != null) - { - OnPlacement(); - } -#endif - if (StatusText.Instance) - { - StatusText.Instance.SetText("Anchor Saved"); - } - } - - public void SetPointOfReference(byte[] pointOfReferenceData) - { - WorldAnchorTransferBatch.ImportAsync(pointOfReferenceData, OnImportComplete); - } - - private void OnImportComplete(SerializationCompletionReason completionReason, WorldAnchorTransferBatch deserializedTransferBatch) - { - if (completionReason != SerializationCompletionReason.Succeeded) - { - LogAsync("Failed to import: " + completionReason.ToString()); - return; - } - - string[] ids = deserializedTransferBatch.GetAllIds(); - if (ids.Length > 0) - { - if (m_PointOfReference) - { - DestroyImmediate(m_PointOfReference); - } - - m_PointOfReferenceCube.transform.position = Vector3.zero; - m_PointOfReferenceCube.transform.localScale = Vector3.one * 0.1f; - - m_PointOfReference = deserializedTransferBatch.LockObject(ids[0], m_PointOfReferenceCube); - - if (StatusText.Instance) - { - StatusText.Instance.SetText("Anchor Created"); - } - Debug.Log("Anchor Created"); - - if (store != null) - { - store.Delete(PointOfReferenceID); - } - - StartCoroutine(SaveAnchor()); - - if (StatusText.Instance) - { - StatusText.Instance.SetText("Anchor Saved to Store"); - } - - Debug.Log("Anchor Saved to Store"); - } - } - - private void ClearAnchors() - { - if (store != null) - { - store.Clear(); - } - - Debug.Log("Cleared WorldAnchorStore"); - - m_PointOfReference = null; - - if (StatusText.Instance) - { - StatusText.Instance.SetText("Anchors Cleared"); - } - } -#endif - - public void CreateManualPointOfReference() - { - m_ManualAnchorAcquisitionState = ManualPointOfReferenceAcquisitionStage.FirstPoint; - if (StatusText.Instance) - { - StatusText.Instance.SetTextUntimed("Click First Point for Point-of-Reference"); - } - } - - public void OnFingerPressed(InputSourceHands.CurrentHandState state) - { - switch (m_ManualAnchorAcquisitionState) - { - case ManualPointOfReferenceAcquisitionStage.Idle: - { - break; - } - - case ManualPointOfReferenceAcquisitionStage.FirstPoint: - { - if (StatusText.Instance) - { - StatusText.Instance.SetTextUntimed("Click Second Point for Point-of-Reference"); - } - m_ManualAnchorPos = HUX.Focus.FocusManager.Instance.GazeFocuser.Cursor.transform.position; - m_ManualAnchorAcquisitionState = ManualPointOfReferenceAcquisitionStage.SecondPoint; - break; - } - - case ManualPointOfReferenceAcquisitionStage.SecondPoint: - { - if (StatusText.Instance) - { - StatusText.Instance.SetText("Point of Reference Created!"); - } - m_ManualAnchorForward = (HUX.Focus.FocusManager.Instance.GazeFocuser.Cursor.transform.position - m_ManualAnchorPos).normalized; - m_ManualAnchorAcquisitionState = ManualPointOfReferenceAcquisitionStage.Acquired; - - CreatePointOfReference(m_ManualAnchorPos, Quaternion.LookRotation(m_ManualAnchorForward)); - break; - } - - case ManualPointOfReferenceAcquisitionStage.Acquired: - { - break; - } - } - } - - - public void CalculateOffsetFromPointOfReference(Vector3 pos, Quaternion rot, out Vector3 posOffset, out Quaternion rotOffset) - { - if (m_PointOfReferenceCube != null) - { - Vector3 flattenedDir = m_PointOfReferenceCube.transform.forward; - flattenedDir.y = 0.0f; - - rotOffset = Quaternion.Inverse(m_PointOfReferenceCube.transform.rotation) * rot; - - Quaternion localRot = Quaternion.LookRotation(flattenedDir, Vector3.up); - posOffset = Quaternion.Inverse(localRot) * (pos - m_PointOfReferenceCube.transform.position); - } - else - { - posOffset = pos; - rotOffset = rot; - } - } - - public void CalculatePosRotFromPointOfReference(Vector3 posOffset, Quaternion rotOffset, out Vector3 pos, out Quaternion rot) - { - if (m_PointOfReferenceCube != null) - { - Vector3 flattenedDir = m_PointOfReferenceCube.transform.forward; - flattenedDir.y = 0.0f; - - rot = m_PointOfReferenceCube.transform.rotation * rotOffset; - - Quaternion localRot = Quaternion.LookRotation(flattenedDir, Vector3.up); - pos = m_PointOfReferenceCube.transform.position + localRot * posOffset; - } - else - { - pos = posOffset; - rot = rotOffset; - } - } -} +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// +using UnityEngine; + +#if UNITY_WSA + + + +#endif + +using System.Collections; +using System.Collections.Generic; +using HUX.Receivers; +using HUX.Dialogs.Debug; +using HUX.Utility; + +public class PointOfReferenceManager : Singleton +{ + public GameObject m_PointOfReferenceCube; + + +#if UNITY_WSA + List m_MessagesAsync = new List(); + public event System.Action OnPlacement; + UnityEngine.XR.WSA.Persistence.WorldAnchorStore store = null; + private UnityEngine.XR.WSA.WorldAnchor m_PointOfReference = null; + public UnityEngine.XR.WSA.WorldAnchor PointOfReference + { + get { return m_PointOfReference; } + } +#endif + public const string PointOfReferenceID = "PointOfReference"; + + private bool m_LoadedAndPlaced = false; + public bool LoadedAndPlaced + { + get { return m_LoadedAndPlaced; } + } + + enum ManualPointOfReferenceAcquisitionStage + { + Idle, + FirstPoint, + SecondPoint, + Acquired, + } + ManualPointOfReferenceAcquisitionStage m_ManualAnchorAcquisitionState = ManualPointOfReferenceAcquisitionStage.Idle; + Vector3 m_ManualAnchorPos = Vector3.zero; + Vector3 m_ManualAnchorForward = Vector3.forward; + +#if UNITY_WSA + // Use this for initialization + void Start() + { + if (DebugMenu.Instance) + { + DebugMenu.Instance.AddButtonItem("Point Of Reference\\Generate Point of Reference", "Generate New", CreateManualPointOfReference); + } +#if UNITY_EDITOR + m_LoadedAndPlaced = true; +#else + UnityEngine.XR.WSA.Persistence.WorldAnchorStore.GetAsync(StoreLoaded); +#endif + + InputSources.Instance.hands.OnFingerPressed += OnFingerPressed; + } + + // Update is called once per frame + void Update () + { + lock (m_MessagesAsync) + { + while (m_MessagesAsync.Count > 0) + { + Debug.Log("(async)" + m_MessagesAsync[0]); + m_MessagesAsync.RemoveAt(0); + } + } + + if ((m_PointOfReference != null) && (m_PointOfReference.isLocated)) + { + m_LoadedAndPlaced = true; + } + } +#endif + + public void CreatePointOfReference(Vector3 pos, Quaternion rot) + { + m_PointOfReferenceCube.transform.position = pos; + m_PointOfReferenceCube.transform.rotation = rot; + +#if UNITY_WSA + if (store != null) + { + store.Delete(PointOfReferenceID); + } + + if (m_PointOfReference) + { + DestroyImmediate(m_PointOfReference); + } + + m_PointOfReference = m_PointOfReferenceCube.AddComponent(); + if (StatusText.Instance) + { + StatusText.Instance.SetText("Point of Reference Created"); + } + + // SaveAnchorToStore(m_PointOfReferenceID, m_PointOfReference); + StartCoroutine(SaveAnchor()); +#endif + } + + +#if UNITY_WSA + void LogAsync(string message) + { + lock (m_MessagesAsync) + { + m_MessagesAsync.Add(message); + } + } + + private void StoreLoaded(UnityEngine.XR.WSA.Persistence.WorldAnchorStore store) + { + this.store = store; + + // We've loaded. Wait to place. + StartCoroutine(PlaceWorldAnchor()); + } + + private IEnumerator PlaceWorldAnchor() + { + // Wait while the SR loads a bit. + yield return new WaitForSeconds(0.5f); + + if (m_PointOfReference) + { + DestroyImmediate(m_PointOfReference); + } + + m_PointOfReferenceCube.transform.position = Vector3.zero; + m_PointOfReferenceCube.transform.localScale = Vector3.one * 0.1f; + + m_PointOfReference = store.Load(PointOfReferenceID, m_PointOfReferenceCube); + if (m_PointOfReference == null) + { + if (StatusText.Instance) + { + StatusText.Instance.SetText("No Point of Reference created."); + } + } + else + { + Debug.Log("Created anchor from WorldAnchorStore: " + PointOfReferenceID); + if (StatusText.Instance) + { + StatusText.Instance.SetText("Loaded WorldAnchorStore"); + } + } + } + + private IEnumerator SaveAnchor() + { +#if !UNITY_EDITOR + while (!m_PointOfReference.isLocated) + { + yield return new WaitForEndOfFrame(); + } +#else + yield return 0; +#endif + + SaveAnchorToStore(PointOfReferenceID, m_PointOfReference); + Debug.Log("SaveAnchor: Point of Reference Saved."); + } + + private void SaveAnchorToStore(string id, UnityEngine.XR.WSA.WorldAnchor worldAnchor) + { +#if !UNITY_EDITOR + if ((store != null) && (store.Save(id, worldAnchor))) + { + Debug.Log("WorldAnchor Saved: " + id); + if (OnPlacement != null) + { + OnPlacement(); + } + } + else + { + Debug.Log("Failed to Save WorldAnchor " + id); + } +#else + if (OnPlacement != null) + { + OnPlacement(); + } +#endif + if (StatusText.Instance) + { + StatusText.Instance.SetText("Anchor Saved"); + } + } + + public void SetPointOfReference(byte[] pointOfReferenceData) + { + UnityEngine.XR.WSA.Sharing.WorldAnchorTransferBatch.ImportAsync(pointOfReferenceData, OnImportComplete); + } + + private void OnImportComplete(UnityEngine.XR.WSA.Sharing.SerializationCompletionReason completionReason, UnityEngine.XR.WSA.Sharing.WorldAnchorTransferBatch deserializedTransferBatch) + { + if (completionReason != UnityEngine.XR.WSA.Sharing.SerializationCompletionReason.Succeeded) + { + LogAsync("Failed to import: " + completionReason.ToString()); + return; + } + + string[] ids = deserializedTransferBatch.GetAllIds(); + if (ids.Length > 0) + { + if (m_PointOfReference) + { + DestroyImmediate(m_PointOfReference); + } + + m_PointOfReferenceCube.transform.position = Vector3.zero; + m_PointOfReferenceCube.transform.localScale = Vector3.one * 0.1f; + + m_PointOfReference = deserializedTransferBatch.LockObject(ids[0], m_PointOfReferenceCube); + + if (StatusText.Instance) + { + StatusText.Instance.SetText("Anchor Created"); + } + Debug.Log("Anchor Created"); + + if (store != null) + { + store.Delete(PointOfReferenceID); + } + + StartCoroutine(SaveAnchor()); + + if (StatusText.Instance) + { + StatusText.Instance.SetText("Anchor Saved to Store"); + } + + Debug.Log("Anchor Saved to Store"); + } + } + + private void ClearAnchors() + { + if (store != null) + { + store.Clear(); + } + + Debug.Log("Cleared WorldAnchorStore"); + + m_PointOfReference = null; + + if (StatusText.Instance) + { + StatusText.Instance.SetText("Anchors Cleared"); + } + } +#endif + + public void CreateManualPointOfReference() + { + m_ManualAnchorAcquisitionState = ManualPointOfReferenceAcquisitionStage.FirstPoint; + if (StatusText.Instance) + { + StatusText.Instance.SetTextUntimed("Click First Point for Point-of-Reference"); + } + } + + public void OnFingerPressed(InputSourceHands.CurrentHandState state) + { + switch (m_ManualAnchorAcquisitionState) + { + case ManualPointOfReferenceAcquisitionStage.Idle: + { + break; + } + + case ManualPointOfReferenceAcquisitionStage.FirstPoint: + { + if (StatusText.Instance) + { + StatusText.Instance.SetTextUntimed("Click Second Point for Point-of-Reference"); + } + m_ManualAnchorPos = HUX.Focus.FocusManager.Instance.GazeFocuser.Cursor.transform.position; + m_ManualAnchorAcquisitionState = ManualPointOfReferenceAcquisitionStage.SecondPoint; + break; + } + + case ManualPointOfReferenceAcquisitionStage.SecondPoint: + { + if (StatusText.Instance) + { + StatusText.Instance.SetText("Point of Reference Created!"); + } + m_ManualAnchorForward = (HUX.Focus.FocusManager.Instance.GazeFocuser.Cursor.transform.position - m_ManualAnchorPos).normalized; + m_ManualAnchorAcquisitionState = ManualPointOfReferenceAcquisitionStage.Acquired; + + CreatePointOfReference(m_ManualAnchorPos, Quaternion.LookRotation(m_ManualAnchorForward)); + break; + } + + case ManualPointOfReferenceAcquisitionStage.Acquired: + { + break; + } + } + } + + + public void CalculateOffsetFromPointOfReference(Vector3 pos, Quaternion rot, out Vector3 posOffset, out Quaternion rotOffset) + { + if (m_PointOfReferenceCube != null) + { + Vector3 flattenedDir = m_PointOfReferenceCube.transform.forward; + flattenedDir.y = 0.0f; + + rotOffset = Quaternion.Inverse(m_PointOfReferenceCube.transform.rotation) * rot; + + Quaternion localRot = Quaternion.LookRotation(flattenedDir, Vector3.up); + posOffset = Quaternion.Inverse(localRot) * (pos - m_PointOfReferenceCube.transform.position); + } + else + { + posOffset = pos; + rotOffset = rot; + } + } + + public void CalculatePosRotFromPointOfReference(Vector3 posOffset, Quaternion rotOffset, out Vector3 pos, out Quaternion rot) + { + if (m_PointOfReferenceCube != null) + { + Vector3 flattenedDir = m_PointOfReferenceCube.transform.forward; + flattenedDir.y = 0.0f; + + rot = m_PointOfReferenceCube.transform.rotation * rotOffset; + + Quaternion localRot = Quaternion.LookRotation(flattenedDir, Vector3.up); + pos = m_PointOfReferenceCube.transform.position + localRot * posOffset; + } + else + { + pos = posOffset; + rot = rotOffset; + } + } +} diff --git a/HUX/Scripts/Utility/ManualCameraControl.cs b/HUX/Scripts/Utility/ManualCameraControl.cs index 1c33e8a..bacc7db 100644 --- a/HUX/Scripts/Utility/ManualCameraControl.cs +++ b/HUX/Scripts/Utility/ManualCameraControl.cs @@ -79,9 +79,9 @@ private void Start() m_InitialRotation = m_Veil.transform.rotation; // VR Mode - if (VRSettings.loadedDeviceName == "Oculus" || VRSettings.loadedDeviceName == "PlayStationVR") + if (UnityEngine.XR.XRSettings.loadedDeviceName == "Oculus" || UnityEngine.XR.XRSettings.loadedDeviceName == "PlayStationVR") { - InputTracking.Recenter(); + UnityEngine.XR.InputTracking.Recenter(); } }