diff --git a/Editor/ConditionalFieldAttributeDrawer.cs b/Editor/ConditionalFieldAttributeDrawer.cs deleted file mode 100644 index 5ab8f3c..0000000 --- a/Editor/ConditionalFieldAttributeDrawer.cs +++ /dev/null @@ -1,31 +0,0 @@ -using ubco.ovilab.HPUI.Utils; -using UnityEditor; -using UnityEngine; - -namespace ubco.ovilab.HPUI.Editor -{ - [CustomPropertyDrawer(typeof(ConditionalFieldAttribute))] - public class ConditionalFieldAttributeDrawer : PropertyDrawer - { - private SerializedProperty conditionalProp; - - public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) - { - ConditionalFieldAttribute labelAttribute = attribute as ConditionalFieldAttribute; - - if (conditionalProp == null) - { - conditionalProp = property.serializedObject.FindProperty(labelAttribute.conditionalProp); - if (conditionalProp.propertyType != SerializedPropertyType.Boolean) - { - conditionalProp = null; - Debug.LogError($"The property {labelAttribute.conditionalProp} is not of type bool."); - } - } - - GUI.enabled = conditionalProp.boolValue; - EditorGUI.PropertyField(position, property); - GUI.enabled = true; - } - } -} diff --git a/Editor/JointFollowerDataPropertyDrawer.cs b/Editor/JointFollowerDataPropertyDrawer.cs new file mode 100644 index 0000000..aae2554 --- /dev/null +++ b/Editor/JointFollowerDataPropertyDrawer.cs @@ -0,0 +1,35 @@ +using UnityEditor; +using ubco.ovilab.HPUI.Tracking; +using UnityEngine; + +namespace ubco.ovilab.HPUI.Editor +{ + [CustomPropertyDrawer(typeof(JointFollowerData))] + public class JointFollowerDataPropertyDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginProperty(position, label, property); + + position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); + + EditorGUI.indentLevel++; + + EditorGUILayout.PropertyField(property.FindPropertyRelative("handedness")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("jointID")); + SerializedProperty useSecondJointIDProp = property.FindPropertyRelative("useSecondJointID"); + EditorGUILayout.PropertyField(useSecondJointIDProp); + bool guiEnabled = GUI.enabled; + GUI.enabled = useSecondJointIDProp.boolValue; + EditorGUILayout.PropertyField(property.FindPropertyRelative("secondJointID")); + GUI.enabled = guiEnabled; + EditorGUILayout.PropertyField(property.FindPropertyRelative("defaultJointRadius")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("offsetAngle")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("offsetAsRatioToRadius")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("longitudinalOffset")); + + EditorGUI.indentLevel--; + EditorGUI.EndProperty(); + } + } +} diff --git a/Runtime/utils/ConditionalFieldAttribute.cs.meta b/Editor/JointFollowerDataPropertyDrawer.cs.meta similarity index 83% rename from Runtime/utils/ConditionalFieldAttribute.cs.meta rename to Editor/JointFollowerDataPropertyDrawer.cs.meta index 1744789..3c89c25 100644 --- a/Runtime/utils/ConditionalFieldAttribute.cs.meta +++ b/Editor/JointFollowerDataPropertyDrawer.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 105f7002305e6bc4fb344aea955f34c3 +guid: c5a9df57ff4e5ed4ebea6ae6e98991f6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Editor/JointFollowerDatumPropertyDrawer.cs b/Editor/JointFollowerDatumPropertyDrawer.cs new file mode 100644 index 0000000..f5b3b9a --- /dev/null +++ b/Editor/JointFollowerDatumPropertyDrawer.cs @@ -0,0 +1,11 @@ +using UnityEditor; +using Unity.XR.CoreUtils.Datums.Editor; +using ubco.ovilab.HPUI.Tracking; + +namespace ubco.ovilab.HPUI.Editor +{ + [CustomPropertyDrawer(typeof(JointFollowerDatumProperty))] + public class JointFollowerDatumPropertyDrawer : DatumPropertyDrawer + { + } +} diff --git a/Editor/ConditionalFieldAttributeDrawer.cs.meta b/Editor/JointFollowerDatumPropertyDrawer.cs.meta similarity index 83% rename from Editor/ConditionalFieldAttributeDrawer.cs.meta rename to Editor/JointFollowerDatumPropertyDrawer.cs.meta index 4b520a4..8b675a2 100644 --- a/Editor/ConditionalFieldAttributeDrawer.cs.meta +++ b/Editor/JointFollowerDatumPropertyDrawer.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7004dcbc6ef1f584aa61a5b00e776ee5 +guid: a8e4122b25c65474e8dbb79ef977e900 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Editor/ubco.ovilab.HPUI.Editor.asmdef b/Editor/ubco.ovilab.HPUI.Editor.asmdef index 7931e24..5cacc04 100644 --- a/Editor/ubco.ovilab.HPUI.Editor.asmdef +++ b/Editor/ubco.ovilab.HPUI.Editor.asmdef @@ -4,7 +4,8 @@ "references": [ "ubco.ovilab.HPUI", "Unity.XR.Interaction.Toolkit", - "Unity.XR.Interaction.Toolkit.Editor" + "Unity.XR.Interaction.Toolkit.Editor", + "Unity.XR.CoreUtils.Editor" ], "includePlatforms": [ "Editor" diff --git a/Runtime/Interaction/HPUIContinuousInteractable.cs b/Runtime/Interaction/HPUIContinuousInteractable.cs index a538c0c..3930cdb 100644 --- a/Runtime/Interaction/HPUIContinuousInteractable.cs +++ b/Runtime/Interaction/HPUIContinuousInteractable.cs @@ -96,7 +96,7 @@ private List SetupKeypoints() { GameObject obj = new GameObject($"{Handedness}_{jointID}"); JointFollower jointFollower = obj.AddComponent(); - jointFollower.SetParams(Handedness, jointID, 0, 0, 0); + jointFollower.SetData(new JointFollowerData(Handedness, jointID, 0, 0, 0)); Transform keypoint = obj.transform; keypoint.parent = this.transform; diff --git a/Runtime/Tracking/JointFollower.cs b/Runtime/Tracking/JointFollower.cs index aab6f5e..a9d5ad2 100644 --- a/Runtime/Tracking/JointFollower.cs +++ b/Runtime/Tracking/JointFollower.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using ubco.ovilab.HPUI.Utils; using UnityEngine; using UnityEngine.XR.Hands; @@ -10,28 +9,21 @@ namespace ubco.ovilab.HPUI.Tracking /// public class JointFollower: MonoBehaviour { - [Tooltip("The handedness of the joint to follow")] - public Handedness handedness; - [Tooltip("The joint to follow.")] - public XRHandJointID jointID; - [Tooltip("Should a second joint be used. If `useSecondJointID` is true, offsetAlongJoint behaves differently.")] - public bool useSecondJointID; - [Tooltip("Second joint to use as reference. If `useSecondJointID` is true, offsetAlongJoint behaves differently.")] - [ConditionalField("useSecondJointID")] - public XRHandJointID secondJointID; - [Tooltip("Default joint radius to use when joint radius is not provided by XR Hands. In unity units.")] - public float defaultJointRadius = 0.01f; - - [Tooltip("(optional) The target transform to use. If not set, use this transform.")] - public Transform targetTransform; + [SerializeField] + [Tooltip("Joint follower data to use for this Joint.")] + private JointFollowerDatumProperty jointFollowerData = new JointFollowerDatumProperty(new JointFollowerData()); + /// + /// Joint follower data to use for this Joint. + /// + public JointFollowerDatumProperty JointFollowerDatumProperty { get => jointFollowerData; set => jointFollowerData = value; } - [Tooltip("The offset angle.")][SerializeField] - public float offsetAngle = 0f; - [Tooltip("The offset as a ratio of the joint radius.")][SerializeField] - public float offsetAsRatioToRadius = 1f; - [Tooltip("The offset along joint (the joint's up) if no secondJoint is set. Otherwise, the position along joint as a ratio to the distance between jointID and secondJointID. In unity units.")] [SerializeField] - public float longitudinalOffset = 0f; + [Tooltip("(optional) The target transform to use. If not set, use this transform.")] + private Transform targetTransform; + /// + /// The target transform to use. If not set, use this transform. + /// + public Transform TargetTransform { get => targetTransform; set => targetTransform = value; } private float cachedRadius = 0f; private XRHandSubsystem handSubsystem; @@ -70,9 +62,9 @@ protected void Update() /// protected void OnEnable() { - if (targetTransform == null) + if (TargetTransform == null) { - targetTransform = transform; + TargetTransform = transform; } SubscribeHandSubsystem(); @@ -124,13 +116,9 @@ private void UnsubscribeHandSubsystem() /// /// Set the parameters of this JointFollower. /// - public void SetParams(Handedness handedness, XRHandJointID jointID, float offsetAngle, float offsetAsRationToRadius, float longitudinalOffset) + public void SetData(JointFollowerData jointFollowerData) { - this.handedness = handedness; - this.jointID = jointID; - this.offsetAngle = offsetAngle; - this.offsetAsRatioToRadius = offsetAsRationToRadius; - this.longitudinalOffset = longitudinalOffset; + this.jointFollowerData.Value = jointFollowerData; } private void OnUpdatedHands(XRHandSubsystem subsystem, @@ -143,11 +131,11 @@ private void OnUpdatedHands(XRHandSubsystem subsystem, // Update game logic that uses hand data break; case XRHandSubsystem.UpdateType.BeforeRender: - if (handedness == Handedness.Left) + if (jointFollowerData.Value.handedness == Handedness.Left) { ProcessJointData(subsystem.leftHand); } - else if (handedness == Handedness.Right) + else if (jointFollowerData.Value.handedness == Handedness.Right) { ProcessJointData(subsystem.rightHand); } @@ -160,16 +148,17 @@ private void OnUpdatedHands(XRHandSubsystem subsystem, /// private void ProcessJointData(XRHand hand) { - XRHandJoint mainJoint = hand.GetJoint(jointID); + JointFollowerData jointFollowerDataValue = jointFollowerData.Value; + XRHandJoint mainJoint = hand.GetJoint(jointFollowerDataValue.jointID); bool mainPoseSuccess = mainJoint.TryGetPose(out Pose mainJointPose); bool mainRadiusSuccess = mainJoint.TryGetRadius(out float mainRadius); XRHandJoint secondJoint; bool secondPoseSuccess = false; Pose secondJointPose = default; - if (useSecondJointID) + if (jointFollowerDataValue.useSecondJointID) { - secondJoint = hand.GetJoint(secondJointID); + secondJoint = hand.GetJoint(jointFollowerDataValue.secondJointID); secondPoseSuccess = secondJoint.TryGetPose(out secondJointPose); } @@ -179,26 +168,26 @@ private void ProcessJointData(XRHand hand) } else if (cachedRadius == 0) { - cachedRadius = defaultJointRadius; + cachedRadius = jointFollowerDataValue.defaultJointRadius; } - if (mainPoseSuccess && (!useSecondJointID || secondPoseSuccess)) + if (mainPoseSuccess && (!jointFollowerDataValue.useSecondJointID || secondPoseSuccess)) { Vector3 poseForward = mainJointPose.forward; Vector3 jointPlaneOffset; - if (offsetAngle == 0 || offsetAsRatioToRadius == 0) + if (jointFollowerDataValue.offsetAngle == 0 || jointFollowerDataValue.offsetAsRatioToRadius == 0) { jointPlaneOffset = -mainJointPose.up; } else { - jointPlaneOffset = Quaternion.AngleAxis(offsetAngle, poseForward) * -mainJointPose.up; + jointPlaneOffset = Quaternion.AngleAxis(jointFollowerDataValue.offsetAngle, poseForward) * -mainJointPose.up; } - Vector3 jointLongitudianlOffset = secondPoseSuccess ? (secondJointPose.position - mainJointPose.position) * longitudinalOffset : poseForward * longitudinalOffset; + Vector3 jointLongitudianlOffset = secondPoseSuccess ? (secondJointPose.position - mainJointPose.position) * jointFollowerDataValue.longitudinalOffset : poseForward * jointFollowerDataValue.longitudinalOffset; - targetTransform.rotation = Quaternion.LookRotation(poseForward, jointPlaneOffset); - targetTransform.position = mainJointPose.position + jointPlaneOffset * (cachedRadius * offsetAsRatioToRadius) + jointLongitudianlOffset; + TargetTransform.rotation = Quaternion.LookRotation(poseForward, jointPlaneOffset); + TargetTransform.position = mainJointPose.position + jointPlaneOffset * (cachedRadius * jointFollowerDataValue.offsetAsRatioToRadius) + jointLongitudianlOffset; } } } diff --git a/Runtime/Tracking/JointFollowerData.cs b/Runtime/Tracking/JointFollowerData.cs new file mode 100644 index 0000000..cddb39c --- /dev/null +++ b/Runtime/Tracking/JointFollowerData.cs @@ -0,0 +1,44 @@ +using System; +using UnityEngine; +using UnityEngine.XR.Hands; + +namespace ubco.ovilab.HPUI.Tracking +{ + /// + /// Represents the joint follower data to be used. + /// + [Serializable] + public class JointFollowerData + { + [Tooltip("The handedness of the joint to follow")] + public Handedness handedness; + [Tooltip("The joint to follow.")] + public XRHandJointID jointID; + [Tooltip("Should a second joint be used. If `useSecondJointID` is true, offsetAlongJoint behaves differently.")] + public bool useSecondJointID; + [Tooltip("Second joint to use as reference. If `useSecondJointID` is true, offsetAlongJoint behaves differently.")] + public XRHandJointID secondJointID; + [Tooltip("Default joint radius to use when joint radius is not provided by XR Hands. In unity units.")] + public float defaultJointRadius = 0.01f; + + [Tooltip("The offset angle.")][SerializeField] + public float offsetAngle = 0f; + [Tooltip("The offset as a ratio of the joint radius.")][SerializeField] + public float offsetAsRatioToRadius = 1f; + [Tooltip("The offset along joint (the joint's up) if no secondJoint is set. Otherwise, the position along joint as a ratio to the distance between jointID and secondJointID. In unity units.")] + [SerializeField] + public float longitudinalOffset = 0f; + + public JointFollowerData() + {} + + public JointFollowerData(Handedness handedness, XRHandJointID jointID, float offsetAngle, float offsetAsRationToRadius, float longitudinalOffset) + { + this.handedness = handedness; + this.jointID = jointID; + this.offsetAngle = offsetAngle; + this.offsetAsRatioToRadius = offsetAsRationToRadius; + this.longitudinalOffset = longitudinalOffset; + } + } +} diff --git a/Runtime/Tracking/JointFollowerData.cs.meta b/Runtime/Tracking/JointFollowerData.cs.meta new file mode 100644 index 0000000..8bde006 --- /dev/null +++ b/Runtime/Tracking/JointFollowerData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bcb3586f29ad6df40bab7d9b7ef46def +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Tracking/JointFollowerDatum.cs b/Runtime/Tracking/JointFollowerDatum.cs new file mode 100644 index 0000000..f35f0ec --- /dev/null +++ b/Runtime/Tracking/JointFollowerDatum.cs @@ -0,0 +1,32 @@ +using System; +using UnityEngine; +using Unity.XR.CoreUtils.Datums; + +namespace ubco.ovilab.HPUI.Tracking +{ + /// + /// container class that holds a value. + /// + [CreateAssetMenu(fileName = "JointFollowerData", menuName = "HPUI/JointFollowerData", order = 1)] + public class JointFollowerDatum: Datum + { + } + + /// + /// Serializable container class that holds a JointFollower data value or container asset reference. + /// + /// + [Serializable] + public class JointFollowerDatumProperty: DatumProperty + { + /// + public JointFollowerDatumProperty(JointFollowerData value) : base(value) + { + } + + /// + public JointFollowerDatumProperty(JointFollowerDatum datum) : base(datum) + { + } + } +} diff --git a/Runtime/Tracking/JointFollowerDatum.cs.meta b/Runtime/Tracking/JointFollowerDatum.cs.meta new file mode 100644 index 0000000..447be6b --- /dev/null +++ b/Runtime/Tracking/JointFollowerDatum.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0cc04d49291e1fe40a785592a6470821 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/utils.meta b/Runtime/utils.meta deleted file mode 100644 index 4859157..0000000 --- a/Runtime/utils.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 880ddb861642d484b9a778a447262e76 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/utils/ConditionalFieldAttribute.cs b/Runtime/utils/ConditionalFieldAttribute.cs deleted file mode 100644 index 98bb8aa..0000000 --- a/Runtime/utils/ConditionalFieldAttribute.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using UnityEngine; - -namespace ubco.ovilab.HPUI.Utils -{ - // Based on https://github.com/Deadcows/MyBox/blob/master/Attributes/ConditionalFieldAttribute.cs - /// - /// Conditionally Show/Hide field in inspector, based on some other field or property value - /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] - public class ConditionalFieldAttribute : PropertyAttribute - { - public string conditionalProp; - - public ConditionalFieldAttribute(string conditionalProp) - { - this.conditionalProp = conditionalProp; - } - } -}