diff --git a/Runtime/Interaction/HPUIContinuousInteractable.cs b/Runtime/Interaction/HPUIContinuousInteractable.cs index f5fd82a..d142680 100644 --- a/Runtime/Interaction/HPUIContinuousInteractable.cs +++ b/Runtime/Interaction/HPUIContinuousInteractable.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using ubco.ovilab.HPUI.Tracking; +using ubco.ovilab.HPUI.UI; using UnityEngine; using UnityEngine.XR.Hands; using UnityEngine.XR.Interaction.Toolkit; @@ -36,6 +37,8 @@ public class HPUIContinuousInteractable: HPUIBaseInteractable public Material defaultMaterial; [Tooltip("(Optional) the MeshFilter of the corresponding SkinnedMeshRenderer. If not set, will create a child object with the MeshFilter and SkinnedMeshRenderer.")] public MeshFilter filter; + [Tooltip("(Optional) Will be used to provide feedback during setup.")] + [SerializeField] public HPUIContinuousInteractableUI ui; /// public override Vector2 boundsMax { get => surfaceCollidersManager?.boundsMax ?? Vector2.zero; } @@ -260,7 +263,8 @@ public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase up } if (jointPositionApproximation.TryComputePoseForKeyPoints(keypointsUsed.ToList(), - out Dictionary keypointPoses)) + out Dictionary keypointPoses, + out float percentageDone)) { keypointsCache = SetupKeypoints(); @@ -307,6 +311,10 @@ public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase up jointFollower.enabled = true; finishedApproximatingJoints = true; } + else + { + // TODO: display progress + } } } } diff --git a/Runtime/Prefabs.meta b/Runtime/Prefabs.meta new file mode 100644 index 0000000..a6e676a --- /dev/null +++ b/Runtime/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 20fbe3e1dcc15714f8441ad546a01cd8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Prefabs/HPUIContinousUI.prefab b/Runtime/Prefabs/HPUIContinousUI.prefab new file mode 100644 index 0000000..294ee17 --- /dev/null +++ b/Runtime/Prefabs/HPUIContinousUI.prefab @@ -0,0 +1,317 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &2961770035072034218 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8629184124075420591} + - component: {fileID: 3135720285395849235} + - component: {fileID: 2420295586894504013} + m_Layer: 5 + m_Name: backplate + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8629184124075420591 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2961770035072034218} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8825315508195386409} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 100, y: 10} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &3135720285395849235 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2961770035072034218} + m_CullTransparentMesh: 1 +--- !u!114 &2420295586894504013 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2961770035072034218} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 2bf62cea73f52f7448758b2229ac7a2c, type: 3} + m_Type: 3 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 0 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &3373608917536836410 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8770058598125140} + - component: {fileID: 5091855221251318508} + - component: {fileID: 8593948679709192792} + m_Layer: 5 + m_Name: frontplate + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8770058598125140 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3373608917536836410} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8825315508195386409} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 95, y: 8} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &5091855221251318508 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3373608917536836410} + m_CullTransparentMesh: 1 +--- !u!114 &8593948679709192792 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3373608917536836410} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.15752938, g: 0.5660378, b: 0.18210375, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 2bf62cea73f52f7448758b2229ac7a2c, type: 3} + m_Type: 3 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 0 + m_FillAmount: 0 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &3522842689121506704 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 947454274434934731} + - component: {fileID: 2442874235928380245} + - component: {fileID: 732220008833224774} + - component: {fileID: 7253316176162991818} + - component: {fileID: 3157601004788341374} + m_Layer: 5 + m_Name: HPUIContinousUI + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &947454274434934731 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3522842689121506704} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 8825315508195386409} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!223 &2442874235928380245 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3522842689121506704} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!114 &732220008833224774 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3522842689121506704} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!114 &7253316176162991818 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3522842689121506704} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &3157601004788341374 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3522842689121506704} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0f267dc9e93377f4897f95ac98ef2889, type: 3} + m_Name: + m_EditorClassIdentifier: + jointFollowerData: + m_UseConstant: 1 + m_ConstantValue: + handedness: 2 + jointID: 12 + useSecondJointID: 0 + secondJointID: 0 + defaultJointRadius: 0.01 + offsetAngle: 0 + offsetAsRatioToRadius: 1 + longitudinalOffset: 0 + m_Variable: {fileID: 0} + targetTransform: {fileID: 0} +--- !u!1 &8898444606526160354 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8825315508195386409} + m_Layer: 5 + m_Name: Progress + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8825315508195386409 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8898444606526160354} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 8629184124075420591} + - {fileID: 8770058598125140} + m_Father: {fileID: 947454274434934731} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 100, y: 100} + m_Pivot: {x: 0.5, y: 0.5} diff --git a/Runtime/Prefabs/HPUIContinousUI.prefab.meta b/Runtime/Prefabs/HPUIContinousUI.prefab.meta new file mode 100644 index 0000000..e7a1e76 --- /dev/null +++ b/Runtime/Prefabs/HPUIContinousUI.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d26f41e57bf50cd4d8d3a53dc066981e +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Tracking/JointPositionApproximation.cs b/Runtime/Tracking/JointPositionApproximation.cs index 9e49899..b9b1266 100644 --- a/Runtime/Tracking/JointPositionApproximation.cs +++ b/Runtime/Tracking/JointPositionApproximation.cs @@ -150,9 +150,10 @@ protected override void ProcessJointData(XRHandSubsystem subsystem) } } - public bool TryComputePoseForKeyPoints(List keypoints, out Dictionary keypointPoses) + public bool TryComputePoseForKeyPoints(List keypoints, out Dictionary keypointPoses, out float percentageDone) { keypointPoses = null; + percentageDone = 0; // Was just initiated if (jointsLengthEsitmation.Count == 0) @@ -162,10 +163,11 @@ public bool TryComputePoseForKeyPoints(List keypoints, out Dictio // Checking of all joints // FIXME: Optimization - Avoid computing for all joints if not necessary - bool jointLengthsStable = jointsLengthEsitmation.All(kvp => kvp.Value.stable); - bool computeKeypointsStable = computeKeypointJointsData.All(kvp => kvp.Value.stable); + float jointLengthsStableRatio = (float)jointsLengthEsitmation.Where(kvp => kvp.Value.stable).Count() / (float)windowSize; + float computeKeypointsStableRatio = (float)computeKeypointJointsData.Where(kvp => kvp.Value.stable).Count() / (float)windowSize; - if (!jointLengthsStable || !computeKeypointsStable || !recievedLastWristPose) + percentageDone = (jointLengthsStableRatio + computeKeypointsStableRatio) * 0.5f; + if (jointLengthsStableRatio == 1 || computeKeypointsStableRatio == 1 || !recievedLastWristPose) { return false; } diff --git a/Runtime/UI.meta b/Runtime/UI.meta new file mode 100644 index 0000000..63648c6 --- /dev/null +++ b/Runtime/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8cf6037dcca42b94eba4558c780577ea +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/UI/HPUIContinuousInteractableUI.cs b/Runtime/UI/HPUIContinuousInteractableUI.cs new file mode 100644 index 0000000..b65ec07 --- /dev/null +++ b/Runtime/UI/HPUIContinuousInteractableUI.cs @@ -0,0 +1,80 @@ +using ubco.ovilab.HPUI.Tracking; +using UnityEngine; +using UnityEngine.UI; +using UnityEngine.XR.Hands; + +namespace ubco.ovilab.HPUI.UI +{ + public class HPUIContinuousInteractableUI : MonoBehaviour + { + private const string UIPrefab = "Packages/ubc.ok.ovilab.hpui-core/Runtime/Prefabs/HPUIContinousUI.prefab"; + [SerializeField] private JointFollower jointFollower; + [SerializeField] private Transform UIRoot; + [SerializeField] private Image progressBarImage; + [SerializeField] private Image inProgressImage; + [SerializeField] private Text textMessage; + + private bool usingInProgress = false; + + public Handedness Handedness + { + get => jointFollower?.JointFollowerDatumProperty.Value.handedness ?? Handedness.Invalid; + set { + if (jointFollower != null) + { + jointFollower.JointFollowerDatumProperty.Value.handedness = value; + } + } + } + + public string TextMessage { + set => textMessage.text = value; + } + + /// + /// Set the progress bar ratio. Expecting to be a value between 0 and 1. + /// This wil also disable the in progress visual. + /// + public void SetProgress(float progress) + { + usingInProgress = false; + progressBarImage.transform.parent.gameObject.SetActive(true); + inProgressImage.transform.parent.gameObject.SetActive(false); + progressBarImage.fillAmount = progress; + } + + /// + /// Show the in progress visual. + /// This wil also disable the progress bar visual. + /// + public void InProgress() + { + usingInProgress = true; + progressBarImage.transform.parent.gameObject.SetActive(false); + inProgressImage.transform.parent.gameObject.SetActive(true); + } + + /// + private void Update() + { + if (usingInProgress) + { + // Full rotation every 3 seconds. + inProgressImage.fillAmount = Time.time / 3; + } + + UIRoot.localPosition = transform.position + Vector3.up * 0.1f; + UIRoot.LookAt(Camera.main.transform); + } + + public void Show() + { + gameObject.SetActive(true); + } + + public void Hide() + { + gameObject.SetActive(false); + } + } +} diff --git a/Runtime/UI/HPUIContinuousInteractableUI.cs.meta b/Runtime/UI/HPUIContinuousInteractableUI.cs.meta new file mode 100644 index 0000000..def883e --- /dev/null +++ b/Runtime/UI/HPUIContinuousInteractableUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 15ae6f2cc8c7dc648b5ee5f8aa7db9e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: