From 69c849ecfdaa244d6365ead06c8455710af65ad9 Mon Sep 17 00:00:00 2001 From: Enrique Tromp Date: Thu, 13 Oct 2022 03:09:34 +0200 Subject: [PATCH] Add new UxrGrabbableObject constraints system, improve manipulation and general bugfixes. --- CHANGELOG.md | 60 +- Examples/FullScene/Prefabs/Lab/Battery.prefab | 35 +- .../FullScene/Prefabs/Lab/Generator.prefab | 70 +- Examples/FullScene/Scripts/GlobalLogic.cs | 2 +- Examples/FullScene/Scripts/Lab/Battery.cs | 2 +- .../FullScene/Scripts/Lab/GeneratorDoor.cs | 14 +- Examples/FullScene/UltimateXR_URP.unity | 128 +- Prefabs/Avatars/CyborgAvatar_BRP.prefab | 20 + Prefabs/Avatars/CyborgAvatar_URP.prefab | 460 +++++-- .../Avatars/ControllersCyborgAvatar.prefab | 241 ++-- Scripts/Animation/IK/UxrArmIKSolver.cs | 12 +- .../Animation/IK/UxrWristTorsionIKSolver.cs | 4 +- .../UxrStandardAvatarController.cs | 30 +- Scripts/Avatar/Editor/UxrAvatarEditor.cs | 37 + Scripts/Avatar/Rig/UxrAvatarArmInfo.cs | 302 +++++ Scripts/Avatar/Rig/UxrAvatarArmInfo.cs.meta | 3 + Scripts/Avatar/Rig/UxrAvatarFingerBoneInfo.cs | 75 ++ .../Rig/UxrAvatarFingerBoneInfo.cs.meta | 3 + Scripts/Avatar/Rig/UxrAvatarFingerInfo.cs | 190 +++ .../Avatar/Rig/UxrAvatarFingerInfo.cs.meta | 11 + .../UxrAvatarRig.HandRuntimeTransformation.cs | 2 +- .../Rig/UxrAvatarRig.HandTransformation.cs | 12 +- .../Rig/UxrAvatarRig.ReferenceSolving.cs | 145 +-- Scripts/Avatar/Rig/UxrAvatarRigInfo.cs | 314 +---- .../Avatar/Rig/UxrRuntimeFingerDescriptor.cs | 4 +- Scripts/Avatar/Rig/UxrWristTorsionInfo.cs | 20 +- Scripts/Avatar/UxrAvatar.cs | 90 +- Scripts/Avatar/UxrAvatarMoveEventArgs.cs | 5 +- Scripts/CameraUtils/UxrCameraWallFade.cs | 5 + Scripts/Core/Components/UxrComponent.cs | 63 +- Scripts/Core/IUxrLogger.cs | 23 + Scripts/Core/IUxrLogger.cs.meta | 11 + .../Core/Math/Editor/UxrAxisPropertyDrawer.cs | 14 +- Scripts/Core/Math/UxrAxis.cs | 150 ++- Scripts/Core/Math/UxrMathUtils.cs | 10 +- Scripts/Core/UxrConstants.Geometry.cs | 27 + Scripts/Core/UxrConstants.Geometry.cs.meta | 3 + Scripts/Core/UxrConstants.Hand.cs | 26 + Scripts/Core/UxrConstants.Hand.cs.meta | 3 + Scripts/Core/UxrConstants.cs | 5 +- Scripts/Core/UxrManager.cs | 287 ++++- .../Integrations/Meta/UxrMetaHandTracking.cs | 30 +- Scripts/Devices/UxrControllerInput.cs | 7 +- Scripts/Devices/UxrInputButtons.cs | 2 +- .../Editor/UxrControllerHandEditor.cs | 4 +- Scripts/Editor/UxrEditorUtils.UI.cs | 14 + .../Extensions/System/Collections/ListExt.cs | 2 +- Scripts/Extensions/System/Math/FloatExt.cs | 68 + Scripts/Extensions/System/Math/IntExt.cs | 27 + Scripts/Extensions/Unity/GameObjectExt.cs | 80 +- Scripts/Extensions/Unity/Math/Vector3Ext.cs | 61 +- .../Extensions/Unity/Math/Vector3IntExt.cs | 16 +- Scripts/Extensions/Unity/Render/MeshExt.cs | 172 +++ Scripts/Extensions/Unity/TransformExt.cs | 115 +- .../Editor/UxrLocomotionTeleportBaseEditor.cs | 4 + Scripts/Locomotion/UxrLocomotion.cs | 88 ++ .../Locomotion/UxrParentAvatarDestination.cs | 42 + .../UxrParentAvatarDestination.cs.meta | 11 + Scripts/Locomotion/UxrSmoothLocomotion.cs | 53 +- Scripts/Locomotion/UxrTeleportLocomotion.cs | 9 +- .../Locomotion/UxrTeleportLocomotionBase.cs | 191 +-- .../Editor/UxrGrabbableObjectEditor.cs | 522 +++++--- .../Editor/UxrHandPoseEditorWindow.cs | 12 +- .../HandPoses/UxrHandDescriptor.cs | 5 +- .../UxrGrabManager.HandTransitionInfo.cs | 14 +- .../UxrGrabManager.RuntimeGrabInfo.cs | 12 +- Scripts/Manipulation/UxrGrabManager.cs | 188 ++- Scripts/Manipulation/UxrGrabbableObject.cs | 1114 +++++++++++------ Scripts/Manipulation/UxrGrabber.cs | 40 +- Scripts/Manipulation/UxrManipulationMode.cs | 23 - .../Manipulation/UxrManipulationMode.cs.meta | 3 - .../Manipulation/UxrRotationConstraintMode.cs | 7 +- Scripts/Manipulation/UxrRotationProvider.cs | 24 + .../Manipulation/UxrRotationProvider.cs.meta | 11 + Scripts/Manipulation/UxrRuntimeGripInfo.cs | 23 +- .../UxrTranslationConstraintMode.cs | 9 +- Scripts/Mechanics/Weapons/UxrActor.cs | 26 +- .../Mechanics/Weapons/UxrFirearmTrigger.cs | 17 +- Scripts/Mechanics/Weapons/UxrFirearmWeapon.cs | 8 +- Scripts/Mechanics/Weapons/UxrWeaponManager.cs | 18 +- Scripts/Rendering/FX/UxrMagnifyingGlassUrp.cs | 2 + .../Rendering/FX/UxrPlanarReflectionUrp.cs | 2 + Shaders/FX/LaserDot.shader | 8 +- 83 files changed, 4283 insertions(+), 1719 deletions(-) create mode 100644 Scripts/Avatar/Rig/UxrAvatarArmInfo.cs create mode 100644 Scripts/Avatar/Rig/UxrAvatarArmInfo.cs.meta create mode 100644 Scripts/Avatar/Rig/UxrAvatarFingerBoneInfo.cs create mode 100644 Scripts/Avatar/Rig/UxrAvatarFingerBoneInfo.cs.meta create mode 100644 Scripts/Avatar/Rig/UxrAvatarFingerInfo.cs create mode 100644 Scripts/Avatar/Rig/UxrAvatarFingerInfo.cs.meta create mode 100644 Scripts/Core/IUxrLogger.cs create mode 100644 Scripts/Core/IUxrLogger.cs.meta create mode 100644 Scripts/Core/UxrConstants.Geometry.cs create mode 100644 Scripts/Core/UxrConstants.Geometry.cs.meta create mode 100644 Scripts/Core/UxrConstants.Hand.cs create mode 100644 Scripts/Core/UxrConstants.Hand.cs.meta create mode 100644 Scripts/Locomotion/UxrParentAvatarDestination.cs create mode 100644 Scripts/Locomotion/UxrParentAvatarDestination.cs.meta delete mode 100644 Scripts/Manipulation/UxrManipulationMode.cs delete mode 100644 Scripts/Manipulation/UxrManipulationMode.cs.meta create mode 100644 Scripts/Manipulation/UxrRotationProvider.cs create mode 100644 Scripts/Manipulation/UxrRotationProvider.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index d50c3e54..90d8b5d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.9.0] - 2022-10-13 + +### Added + +- Add new UxrGrabbableObject constraints functionality with improved manipulation. +- Add UxrGrabbableObject gizmos to visualize rotation/translation constraints. +- Add new UxrGrabbableObject rotation/translation constraint modes. +- Add support to UxrGrabbableObject for rotation constraints on all 3 axes. +- Add support to UxrGrabbableObject for a single rotation constraint over 360 degrees. +- Improve manipulation behavior when grabbing objects to detect the grip and know which part + of the hand creates more leverage. +- Add possibility to parent to destination in locomotion components: UxrTeleportLocomotion and + UxrSmoothLocomotion. +- Add new teleport methods to UxrManager to teleport relative to moving objects. +- Add new functionality to GameObjectExt to compute bounds recursively. +- Add new functionality to MeshExt to compute skinned mesh vertices and bone influences. +- Add new misc functionality to FloatExt, IntExt, Vector3Ext, Vector3IntExt and TransformExt. +- Add new data to UxrAvatarRigInfo. +- Add versioning to avatar rig info serialization and automatic updating. +- Add IUxrLogger interface to unify logging in managers. +- Add logging to UxrWeaponManager. +- Add new properties to UxrComponent with initial Transform data. +- Add new UxrAxis properties and functionality. +- Add possibility to access avatar grabbers at edit-time. + +### Changed + +- Improve all UxrGrabbableObject and hand grab/release/constrain transitions. +- Move UxrGrabbableObject constraints to the top of the inspector. +- Replace GrabAndMove/RotateAroundAxis manipulation modes by new constraint system. +- Change UxrGrabbableObject rotation and translation constraints reference. + Rotations are performed around the grabbable object local axes. + Translations are performed along the initial grabbable object local axes. +- Improve UxrAvatarRig reference solving. + +### Removed + +- Remove parent reference to UxrGrabbableObject rotation/translation constraints. +- Remove UxrManipulationMode. New constraint system and UxrRotationProvider is used instead. + +### Fixed + +- Fix manipulation not working correctly on moving platforms. +- Fix incorrect manipulation release on objects with non-default grab button(s). +- Fix UxrGrabbableObject release multipliers not working correctly with values less than 1. +- Fix UxrGrabbableObject Constrain events not being called in some cases. +- Fix UxrAvatarEditor throwing exception when using Fix button to save prefab variant. +- Fix UxrCameraWallFade throwing exception when there are no avatars. +- Fix constrained rotations not being able to go over 180 degrees. +- Fix pre-caching triggered by non-local avatars. Only local avatar triggers pre-caching now. +- Fix locomotion components detecting avatar or grabbed objects as obstacles. +- Fix locomotion not working correctly on moving platforms. +- Fix UxrWeaponManager not tracking actors correctly. +- Fix UxrMagnifyingGlassUrp error when not using URP. +- Fix CyborgAvatar_URP base to use index controllers correctly. +- Fix some CyborgAvatar_BRP base materials that were using the URP variants. + ## [0.8.4] - 2022-08-05 ### Added @@ -71,7 +128,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - First public release! -[Unreleased]: https://github.com/VRMADA/ultimatexr-unity/compare/v0.8.4...HEAD +[Unreleased]: https://github.com/VRMADA/ultimatexr-unity/compare/v0.9.0...HEAD +[0.9.0]: https://github.com/VRMADA/ultimatexr-unity/releases/tag/v0.9.0 [0.8.4]: https://github.com/VRMADA/ultimatexr-unity/releases/tag/v0.8.4 [0.8.3]: https://github.com/VRMADA/ultimatexr-unity/releases/tag/v0.8.3 [0.8.2]: https://github.com/VRMADA/ultimatexr-unity/releases/tag/v0.8.2 diff --git a/Examples/FullScene/Prefabs/Lab/Battery.prefab b/Examples/FullScene/Prefabs/Lab/Battery.prefab index 5f431740..09b53d10 100644 --- a/Examples/FullScene/Prefabs/Lab/Battery.prefab +++ b/Examples/FullScene/Prefabs/Lab/Battery.prefab @@ -654,7 +654,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: f9d66e1c4a827a345a4c670b5cbf2d14, type: 3} m_Name: m_EditorClassIdentifier: - _grabbableObject: {fileID: 4029760754406280085} _batteryDiameter: 0.2 _batteryInsertOffset: 0.34 --- !u!114 &4029760754406280085 @@ -669,16 +668,29 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 79f5a30897a3c024c805c866226dd743, type: 3} m_Name: m_EditorClassIdentifier: - _manipulationMode: 0 _controlParentDirection: 0 _ignoreGrabbableParentDependency: 1 _priority: 0 + _allowMultiGrab: 0 + _translationConstraintMode: 2 + _restrictToBox: {fileID: 0} + _restrictToSphere: {fileID: 0} + _translationLimitsMin: {x: 0, y: 0, z: 0} + _translationLimitsMax: {x: 0, y: 0, z: 0.35} + _rotationConstraintMode: 2 + _rotationAngleLimitsMin: {x: 0, y: 0, z: 0} + _rotationAngleLimitsMax: {x: 0, y: 0, z: 0} + _rotationProvider: 0 + _rotationLongitudinalAxis: + _axis: 2 + _needsTwoHandsToRotate: 0 + _lockedGrabReleaseDistance: 0.4 + _translationResistance: 0.07 + _rotationResistance: 0.07 _rigidBodySource: {fileID: 2295813357377736856} _rigidBodyDynamicOnRelease: 1 _verticalReleaseMultiplier: 1 _horizontalReleaseMultiplier: 1 - _needsTwoHandsToRotate: 0 - _allowMultiGrab: 0 _previewGrabPosesMode: 3 _previewPosesRegenerationType: 0 _previewPosesRegenerationIndex: -1 @@ -792,21 +804,6 @@ MonoBehaviour: _dropSnapMode: 0 _dropProximityTransformUseSelf: 1 _dropProximityTransform: {fileID: 0} - _translationConstraintMode: 2 - _restrictToBox: {fileID: 0} - _restrictToSphere: {fileID: 0} - _translationLimitsMin: {x: 0, y: 0, z: 0} - _translationLimitsMax: {x: 0, y: 0, z: 0.35} - _translationLimitsReferenceIsParent: 1 - _translationLimitsParent: {fileID: 0} - _rotationConstraintMode: 1 - _rotationAngleLimitsMin: {x: 0, y: 0, z: 0} - _rotationAngleLimitsMax: {x: 0, y: 0, z: 0} - _rotationLimitsReferenceIsParent: 1 - _rotationLimitsParent: {fileID: 0} - _lockedGrabReleaseDistance: 0.4 - _translationResistance: 0.07 - _rotationResistance: 0.07 --- !u!114 &2781093687404522855 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Examples/FullScene/Prefabs/Lab/Generator.prefab b/Examples/FullScene/Prefabs/Lab/Generator.prefab index 8be26d2a..c739c8f1 100644 --- a/Examples/FullScene/Prefabs/Lab/Generator.prefab +++ b/Examples/FullScene/Prefabs/Lab/Generator.prefab @@ -1680,16 +1680,29 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 79f5a30897a3c024c805c866226dd743, type: 3} m_Name: m_EditorClassIdentifier: - _manipulationMode: 1 _controlParentDirection: 1 _ignoreGrabbableParentDependency: 0 _priority: 0 + _allowMultiGrab: 0 + _translationConstraintMode: 4 + _restrictToBox: {fileID: 0} + _restrictToSphere: {fileID: 0} + _translationLimitsMin: {x: 0, y: 0, z: 0} + _translationLimitsMax: {x: 0, y: 0, z: 0} + _rotationConstraintMode: 1 + _rotationAngleLimitsMin: {x: 0, y: 0, z: 0} + _rotationAngleLimitsMax: {x: 0, y: 45, z: 0} + _rotationProvider: 1 + _rotationLongitudinalAxis: + _axis: 2 + _needsTwoHandsToRotate: 0 + _lockedGrabReleaseDistance: 0.4 + _translationResistance: 0 + _rotationResistance: 0 _rigidBodySource: {fileID: 0} _rigidBodyDynamicOnRelease: 1 _verticalReleaseMultiplier: 1 _horizontalReleaseMultiplier: 1 - _needsTwoHandsToRotate: 0 - _allowMultiGrab: 0 _previewGrabPosesMode: 3 _previewPosesRegenerationType: 0 _previewPosesRegenerationIndex: -1 @@ -1750,21 +1763,6 @@ MonoBehaviour: _dropSnapMode: 0 _dropProximityTransformUseSelf: 1 _dropProximityTransform: {fileID: 0} - _translationConstraintMode: 0 - _restrictToBox: {fileID: 0} - _restrictToSphere: {fileID: 0} - _translationLimitsMin: {x: 0, y: 0, z: 0} - _translationLimitsMax: {x: 0, y: 0, z: 0} - _translationLimitsReferenceIsParent: 1 - _translationLimitsParent: {fileID: 0} - _rotationConstraintMode: 0 - _rotationAngleLimitsMin: {x: 0, y: 0, z: 0} - _rotationAngleLimitsMax: {x: 0, y: 45, z: 0} - _rotationLimitsReferenceIsParent: 1 - _rotationLimitsParent: {fileID: 0} - _lockedGrabReleaseDistance: 0.4 - _translationResistance: 0 - _rotationResistance: 0 --- !u!114 &7499747384943950240 MonoBehaviour: m_ObjectHideFlags: 0 @@ -1858,16 +1856,29 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 79f5a30897a3c024c805c866226dd743, type: 3} m_Name: m_EditorClassIdentifier: - _manipulationMode: 1 - _controlParentDirection: 1 + _controlParentDirection: 0 _ignoreGrabbableParentDependency: 0 _priority: 0 + _allowMultiGrab: 1 + _translationConstraintMode: 4 + _restrictToBox: {fileID: 0} + _restrictToSphere: {fileID: 0} + _translationLimitsMin: {x: 0, y: 0, z: 0} + _translationLimitsMax: {x: 0, y: 0, z: 0} + _rotationConstraintMode: 1 + _rotationAngleLimitsMin: {x: 0, y: 0, z: 0} + _rotationAngleLimitsMax: {x: 0, y: 0, z: 45} + _rotationProvider: 1 + _rotationLongitudinalAxis: + _axis: 2 + _needsTwoHandsToRotate: 1 + _lockedGrabReleaseDistance: 0.4 + _translationResistance: 0 + _rotationResistance: 0 _rigidBodySource: {fileID: 0} _rigidBodyDynamicOnRelease: 1 _verticalReleaseMultiplier: 1 _horizontalReleaseMultiplier: 1 - _needsTwoHandsToRotate: 1 - _allowMultiGrab: 1 _previewGrabPosesMode: 3 _previewPosesRegenerationType: 0 _previewPosesRegenerationIndex: -1 @@ -1971,21 +1982,6 @@ MonoBehaviour: _dropSnapMode: 0 _dropProximityTransformUseSelf: 1 _dropProximityTransform: {fileID: 0} - _translationConstraintMode: 0 - _restrictToBox: {fileID: 0} - _restrictToSphere: {fileID: 0} - _translationLimitsMin: {x: 0, y: 0, z: 0} - _translationLimitsMax: {x: 0, y: 0, z: 0} - _translationLimitsReferenceIsParent: 1 - _translationLimitsParent: {fileID: 0} - _rotationConstraintMode: 0 - _rotationAngleLimitsMin: {x: 0, y: 0, z: 0} - _rotationAngleLimitsMax: {x: 0, y: 0, z: 45} - _rotationLimitsReferenceIsParent: 1 - _rotationLimitsParent: {fileID: 0} - _lockedGrabReleaseDistance: 0.4 - _translationResistance: 0 - _rotationResistance: 0 --- !u!114 &5205576574750625359 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Examples/FullScene/Scripts/GlobalLogic.cs b/Examples/FullScene/Scripts/GlobalLogic.cs index a7c30b2d..399e7807 100644 --- a/Examples/FullScene/Scripts/GlobalLogic.cs +++ b/Examples/FullScene/Scripts/GlobalLogic.cs @@ -126,7 +126,7 @@ private void UxrManager_AvatarMoved(object sender, UxrAvatarMoveEventArgs e) /// private void UxrManager_AvatarsUpdated() { - if (UxrCameraWallFade.IsAvatarPeekingThroughGeometry(UxrAvatar.LocalAvatar)) + if (UxrAvatar.LocalAvatar == null || UxrCameraWallFade.IsAvatarPeekingThroughGeometry(UxrAvatar.LocalAvatar)) { return; } diff --git a/Examples/FullScene/Scripts/Lab/Battery.cs b/Examples/FullScene/Scripts/Lab/Battery.cs index 543ff09c..6db9cc30 100644 --- a/Examples/FullScene/Scripts/Lab/Battery.cs +++ b/Examples/FullScene/Scripts/Lab/Battery.cs @@ -81,7 +81,7 @@ private void UxrManager_AvatarsUpdated() // Add constraints and place GrabbableObject.TranslationConstraint = UxrTranslationConstraintMode.RestrictLocalOffset; - GrabbableObject.RotationConstraint = UxrRotationConstraintMode.RestrictLocalRotation; + GrabbableObject.RotationConstraint = UxrRotationConstraintMode.Locked; UxrGrabManager.Instance.PlaceObject(GrabbableObject, batteryAnchor.Anchor, UxrPlacementType.Smooth, false); diff --git a/Examples/FullScene/Scripts/Lab/GeneratorDoor.cs b/Examples/FullScene/Scripts/Lab/GeneratorDoor.cs index 7663c93e..9c77a7db 100644 --- a/Examples/FullScene/Scripts/Lab/GeneratorDoor.cs +++ b/Examples/FullScene/Scripts/Lab/GeneratorDoor.cs @@ -35,7 +35,8 @@ public bool IsLockOpen get => LockHandleOpenValue > 0.5f; private set { - _grabbableLock.transform.localRotation = _grabbableLock.InitialLocalRotation * Quaternion.AngleAxis(value ? _lockHandleAngleOpen : _lockHandleAngleClosed, Vector3.forward); + // Set rotation using the correct property to avoid interference between grabbable object constraint calculation and manually setting its transform. + _grabbableLock.SingleRotationAxisDegrees = value ? _lockHandleAngleOpen : _lockHandleAngleClosed; for (int i = 0; i < _locks.Length; ++i) { @@ -69,9 +70,6 @@ protected override void Awake() _lockInitialRotation[i] = _locks[i].localRotation; } - _lockHandleAngleConstraintMin = _grabbableLock.RotationAngleLimitsMin; - _lockHandleAngleConstraintMax = _grabbableLock.RotationAngleLimitsMax; - IsBatteryInContact = _batteryAnchor.Anchor.CurrentPlacedObject != null; } @@ -150,13 +148,11 @@ private void Lock_ConstraintsApplied(object sender, UxrApplyConstraintsEventArgs if (_batteryAnchor.Anchor.CurrentPlacedObject != null && _batteryAnchor.Anchor.CurrentPlacedObject.transform.localPosition.z > 0.01f) { - _grabbableLock.RotationAngleLimitsMin = _grabbableLock.transform.localRotation.eulerAngles; - _grabbableLock.RotationAngleLimitsMax = _grabbableLock.transform.localRotation.eulerAngles; + _grabbableLock.RotationConstraint = UxrRotationConstraintMode.Locked; } else { - _grabbableLock.RotationAngleLimitsMin = _lockHandleAngleConstraintMin; - _grabbableLock.RotationAngleLimitsMax = _lockHandleAngleConstraintMax; + _grabbableLock.RotationConstraint = UxrRotationConstraintMode.RestrictLocalRotation; } } @@ -198,8 +194,6 @@ private float LockHandleOpenValue private bool _isBatteryInContact; private Quaternion[] _lockInitialRotation; - private Vector3 _lockHandleAngleConstraintMin; - private Vector3 _lockHandleAngleConstraintMax; #endregion } diff --git a/Examples/FullScene/UltimateXR_URP.unity b/Examples/FullScene/UltimateXR_URP.unity index 1f988140..4beab776 100644 --- a/Examples/FullScene/UltimateXR_URP.unity +++ b/Examples/FullScene/UltimateXR_URP.unity @@ -2066,6 +2066,75 @@ Transform: type: 3} m_PrefabInstance: {fileID: 3502982943006601856} m_PrefabAsset: {fileID: 0} +--- !u!1001 &1226285097 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_RootOrder + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6800616272644675311, guid: af480974a3e0c9a438abc245d0558ac5, + type: 3} + propertyPath: m_Name + value: CyborgAvatarExample + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: af480974a3e0c9a438abc245d0558ac5, type: 3} --- !u!1 &1244240751 GameObject: m_ObjectHideFlags: 0 @@ -3388,45 +3457,6 @@ MeshCollider: m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: -1157319304542580218, guid: 23860c1757a60804685d37eed9849403, type: 3} ---- !u!1001 &1927973734 -PrefabInstance: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Modification: - m_TransformParent: {fileID: 0} - m_Modifications: - - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, - type: 3} - propertyPath: m_RootOrder - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, - type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, - type: 3} - propertyPath: m_LocalEulerAnglesHint.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 4794776476062498322, guid: af480974a3e0c9a438abc245d0558ac5, - type: 3} - propertyPath: m_LocalEulerAnglesHint.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 6800616272644675311, guid: af480974a3e0c9a438abc245d0558ac5, - type: 3} - propertyPath: m_Name - value: CyborgAvatarExample - objectReference: {fileID: 0} - - target: {fileID: 6800616272644675311, guid: af480974a3e0c9a438abc245d0558ac5, - type: 3} - propertyPath: m_IsActive - value: 1 - objectReference: {fileID: 0} - m_RemovedComponents: [] - m_SourcePrefab: {fileID: 100100000, guid: af480974a3e0c9a438abc245d0558ac5, type: 3} --- !u!1001 &1942674603 PrefabInstance: m_ObjectHideFlags: 0 @@ -4161,6 +4191,11 @@ PrefabInstance: m_Modification: m_TransformParent: {fileID: 1256970156} m_Modifications: + - target: {fileID: 675595156093723294, guid: 69e22c8e738ecdc47a07a18994425fcd, + type: 3} + propertyPath: _previewPosesRegenerationType + value: 0 + objectReference: {fileID: 0} - target: {fileID: 1054261244907589379, guid: 69e22c8e738ecdc47a07a18994425fcd, type: 3} propertyPath: _previewGrabPosesMode @@ -4181,6 +4216,16 @@ PrefabInstance: propertyPath: _grabPoint._avatarGripPoseEntries.Array.data[2]._poseBlendValue value: 0 objectReference: {fileID: 0} + - target: {fileID: 2751430945757735379, guid: 69e22c8e738ecdc47a07a18994425fcd, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 25 + objectReference: {fileID: 0} + - target: {fileID: 2751430945757735379, guid: 69e22c8e738ecdc47a07a18994425fcd, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 180 + objectReference: {fileID: 0} - target: {fileID: 7360594947471282968, guid: 69e22c8e738ecdc47a07a18994425fcd, type: 3} propertyPath: m_Name @@ -4241,6 +4286,11 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 8085224605727417135, guid: 69e22c8e738ecdc47a07a18994425fcd, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 90 + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 69e22c8e738ecdc47a07a18994425fcd, type: 3} --- !u!65 &7360594946285476476 stripped diff --git a/Prefabs/Avatars/CyborgAvatar_BRP.prefab b/Prefabs/Avatars/CyborgAvatar_BRP.prefab index 125a5886..b890efc8 100644 --- a/Prefabs/Avatars/CyborgAvatar_BRP.prefab +++ b/Prefabs/Avatars/CyborgAvatar_BRP.prefab @@ -452,6 +452,26 @@ PrefabInstance: propertyPath: m_Materials.Array.data[0] value: objectReference: {fileID: 2100000, guid: 27c7eda1954669e4b940666ffbbcb8b7, type: 2} + - target: {fileID: 4220686947205364464, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} + propertyPath: m_Materials.Array.data[0] + value: + objectReference: {fileID: 2100000, guid: 183df7b1b99e79849b7042cb2e47a4fb, type: 2} + - target: {fileID: 4220686947338437332, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} + propertyPath: m_Materials.Array.data[0] + value: + objectReference: {fileID: 2100000, guid: 183df7b1b99e79849b7042cb2e47a4fb, type: 2} + - target: {fileID: 4220686947524422218, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} + propertyPath: m_Materials.Array.data[0] + value: + objectReference: {fileID: 2100000, guid: 183df7b1b99e79849b7042cb2e47a4fb, type: 2} + - target: {fileID: 4220686948171980878, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} + propertyPath: m_Materials.Array.data[0] + value: + objectReference: {fileID: 2100000, guid: 183df7b1b99e79849b7042cb2e47a4fb, type: 2} - target: {fileID: 4313512905551311841, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} propertyPath: m_Materials.Array.data[0] diff --git a/Prefabs/Avatars/CyborgAvatar_URP.prefab b/Prefabs/Avatars/CyborgAvatar_URP.prefab index dfd7b5fc..72f0c325 100644 --- a/Prefabs/Avatars/CyborgAvatar_URP.prefab +++ b/Prefabs/Avatars/CyborgAvatar_URP.prefab @@ -1718,140 +1718,329 @@ MonoBehaviour: _foot: {fileID: 0} _toes: {fileID: 0} _rigInfo: + _version: 1 _avatar: {fileID: 6665688241830784974} - _leftUpperArmLength: 0.27935857 - _rightUpperArmLength: 0.2793581 - _leftForearmLength: 0.3065355 - _rightForearmLength: 0.30653545 - _leftFingerUniversalLocalAxes: - _transform: {fileID: 4323921785033573269} - _localRight: {x: -0, y: -0, z: -1} - _localUp: {x: 0, y: -1, z: 0} - _localForward: {x: -1, y: -0, z: -0} - _universalToActualAxesRotation: {x: -0.7071068, y: -0, z: 0.7071068, w: 0} - _initialRotation: {x: 0.823743, y: 0.31098327, z: -0.4476826, w: 0.15593979} - _initialLocalRotation: {x: 0.00000026822107, y: 0.000000476837, z: -0.00000038743028, - w: 1} - _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialPosition: {x: -0.613647, y: 1.1427922, z: 0.22197597} - _initialLocalPosition: {x: -0.039260976, y: 0, z: 0.00000022888183} - _rightFingerUniversalLocalAxes: - _transform: {fileID: 7966351054458962406} - _localRight: {x: 0, y: 0, z: 1} - _localUp: {x: 0, y: 1, z: 0} - _localForward: {x: -1, y: -0, z: -0} - _universalToActualAxesRotation: {x: -0, y: 0.7071068, z: -0, w: 0.7071068} - _initialRotation: {x: -0.31098256, y: -0.8237433, z: 0.15593779, w: -0.44768342} - _initialLocalRotation: {x: 0.00000005960483, y: 0.0000019073489, z: -0.00000010430826, - w: 1} - _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialPosition: {x: 0.61368513, y: 1.1428132, z: 0.2219456} - _initialLocalPosition: {x: -0.039260786, y: 0, z: 0.000000076293944} - _leftHandUniversalLocalAxes: - _transform: {fileID: 2522387165088284687} - _localRight: {x: 0.16737479, y: -0.032215413, z: 0.9853669} - _localUp: {x: 0.18900555, y: 0.98197603, z: -0} - _localForward: {x: -0.9676066, y: 0.18623981, z: 0.17044692} - _universalToActualAxesRotation: {x: 0.061138876, y: 0.6411229, z: 0.07262251, - w: 0.7615441} - _initialRotation: {x: -0.10653144, y: 0.22051816, z: 0.39302343, w: 0.8863157} - _initialLocalRotation: {x: 0.00000010430805, y: -0.0000003054738, z: -0.00000029802317, - w: 1} - _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialPosition: {x: -0.5719679, y: 1.1619437, z: 0.1519392} - _initialLocalPosition: {x: -0.30653533, y: 0.000000076293944, z: 0} - _rightHandUniversalLocalAxes: - _transform: {fileID: 77979469672119307} - _localRight: {x: -0.16893815, y: -0.032817062, z: -0.9850801} - _localUp: {x: 0.19069037, y: -0.98165023, z: 0} - _localForward: {x: -0.96700424, y: -0.18784532, z: 0.17209609} - _universalToActualAxesRotation: {x: 0.64043266, y: 0.061627604, z: -0.7620178, - w: -0.0733275} - _initialRotation: {x: 0.22051883, y: -0.10653114, z: -0.8863156, w: -0.3930236} - _initialLocalRotation: {x: 0.00022651485, y: 0.00015760117, z: 0.00012733944, - w: 0.99999994} - _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialPosition: {x: 0.57192886, y: 1.161829, z: 0.15176894} - _initialLocalPosition: {x: -0.30653533, y: 0.00000015258789, z: -0.00000022888183} - _leftArmUniversalLocalAxes: - _transform: {fileID: 5156971107299104229} - _localRight: {x: 0.00000026077032, y: -0, z: 1} - _localUp: {x: 0, y: 1, z: 0} - _localForward: {x: -1, y: -0.00006082654, z: 0.00000026077032} - _universalToActualAxesRotation: {x: -0.000021505426, y: 0.70710665, z: 0, w: 0.7071069} - _initialRotation: {x: -0.03886342, y: 0.068199605, z: 0.40534702, w: 0.9107866} - _initialLocalRotation: {x: -0.030921718, y: 0.064092405, z: 0.46274257, w: 0.8836319} - _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialPosition: {x: -0.20499808, y: 1.5659004, z: -0.03706063} - _initialLocalPosition: {x: -0.16805698, y: -0.00000015258789, z: -0.00000029087067} - _rightArmUniversalLocalAxes: - _transform: {fileID: 1835798567124492442} - _localRight: {x: -0.00000010430813, y: 0, z: -1.0000001} - _localUp: {x: -0, y: -1, z: -0} - _localForward: {x: -1.0000001, y: 0.000060141087, z: 0.00000010430813} - _universalToActualAxesRotation: {x: 0.70710677, y: -0.000021263082, z: -0.7071068, - w: 0} - _initialRotation: {x: 0.06816208, y: -0.038562983, z: -0.91078913, w: -0.40537667} - _initialLocalRotation: {x: 0.030619562, y: 0.06407281, z: -0.4627709, w: 0.883629} - _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialPosition: {x: 0.2049983, y: 1.5659006, z: -0.037060212} - _initialLocalPosition: {x: -0.16805704, y: 0, z: 0.0000001502037} - _leftForearmUniversalLocalAxes: - _transform: {fileID: 3970094588317566289} - _localRight: {x: 0.00000024586916, y: -0, z: 1.0000001} - _localUp: {x: 0, y: 1, z: 0} - _localForward: {x: -1.0000001, y: 0.00000011920942, z: 0.00000024586916} - _universalToActualAxesRotation: {x: 0.000000042146887, y: 0.7071067, z: 0, w: 0.7071069} - _initialRotation: {x: -0.10653147, y: 0.22051847, z: 0.39302367, w: 0.88631546} - _initialLocalRotation: {x: 0.00000024343393, y: 0.16830686, z: 0.000000079369926, - w: 0.98573464} - _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialPosition: {x: -0.3899446, y: 1.3611002, z: 0.0064461567} - _initialLocalPosition: {x: -0.2793586, y: -0.00001701355, z: 0.000000009536743} - _rightForearmUniversalLocalAxes: - _transform: {fileID: 2512099166448472843} - _localRight: {x: 0.0000009760261, y: 0, z: -1.0000001} - _localUp: {x: -0, y: -1, z: -0} - _localForward: {x: -1.0000001, y: 0.0000007450583, z: -0.0000009760261} - _universalToActualAxesRotation: {x: -0.7071071, y: -0, z: 0.7071065, w: -0.00000026341775} - _initialRotation: {x: 0.22048172, y: -0.10624033, z: -0.88632435, w: -0.39310315} - _initialLocalRotation: {x: -0.0000000671276, y: 0.16830678, z: 0.00000023821366, - w: 0.9857347} - _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialPosition: {x: 0.38993412, y: 1.3610729, z: 0.006359998} - _initialLocalPosition: {x: -0.27935812, y: 0.000016784668, z: -0.000000019073486} - _leftClavicleUniversalLocalAxes: - _transform: {fileID: 6609533846315169039} - _localRight: {x: 0, y: 0, z: 1} - _localUp: {x: 0, y: 1, z: 0} - _localForward: {x: -1, y: -0, z: -0} - _universalToActualAxesRotation: {x: -0, y: 0.7071068, z: -0, w: 0.7071068} - _initialRotation: {x: -0.011757046, y: -0.0035609417, z: -0.06290017, w: 0.99794436} - _initialLocalRotation: {x: 0.06121901, y: 0.06522117, z: 0.65836143, w: 0.7473678} - _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialPosition: {x: -0.038275164, y: 1.5448165, z: -0.035617363} - _initialLocalPosition: {x: -0.15479766, y: -0.038275376, z: -0.03128269} - _rightClavicleUniversalLocalAxes: - _transform: {fileID: 5378592488698474173} - _localRight: {x: 0, y: 0, z: -1} - _localUp: {x: -0, y: -1, z: -0} - _localForward: {x: -1, y: -0, z: -0} - _universalToActualAxesRotation: {x: -0.7071068, y: -0, z: 0.7071068, w: 0} - _initialRotation: {x: -0.0035601892, y: -0.011757046, z: -0.99794436, w: 0.062900424} - _initialLocalRotation: {x: -0.061218485, y: 0.06522168, z: -0.6583609, w: 0.7473683} - _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} - _initialPosition: {x: 0.03827533, y: 1.5448164, z: -0.03561763} - _initialLocalPosition: {x: -0.15479766, y: 0.038275123, z: -0.031282976} + _leftArmInfo: + _avatar: {fileID: 6665688241830784974} + _side: 0 + _upperArmLength: 0.27935857 + _forearmLength: 0.3065355 + _fingerUniversalLocalAxes: + _transform: {fileID: 4323921785033573269} + _localRight: {x: -0, y: -0, z: -1} + _localUp: {x: 0, y: -1, z: 0} + _localForward: {x: -1, y: -0, z: -0} + _universalToActualAxesRotation: {x: -0.7071068, y: -0, z: 0.7071068, w: 0} + _initialRotation: {x: 0.823743, y: 0.31098327, z: -0.4476826, w: 0.15593979} + _initialLocalRotation: {x: 0.00000026822107, y: 0.000000476837, z: -0.00000038743028, + w: 1} + _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialPosition: {x: -0.613647, y: 1.1427922, z: 0.22197597} + _initialLocalPosition: {x: -0.039260976, y: 0, z: 0.00000022888183} + _handUniversalLocalAxes: + _transform: {fileID: 2522387165088284687} + _localRight: {x: 0.16737479, y: -0.032215413, z: 0.9853669} + _localUp: {x: 0.18900555, y: 0.98197603, z: -0} + _localForward: {x: -0.9676066, y: 0.18623981, z: 0.17044692} + _universalToActualAxesRotation: {x: 0.061138876, y: 0.6411229, z: 0.07262251, + w: 0.7615441} + _initialRotation: {x: -0.10653144, y: 0.22051816, z: 0.39302343, w: 0.8863157} + _initialLocalRotation: {x: 0.00000010430805, y: -0.0000003054738, z: -0.00000029802317, + w: 1} + _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialPosition: {x: -0.5719679, y: 1.1619437, z: 0.1519392} + _initialLocalPosition: {x: -0.30653533, y: 0.000000076293944, z: 0} + _armUniversalLocalAxes: + _transform: {fileID: 5156971107299104229} + _localRight: {x: 0.00000026077032, y: -0, z: 1} + _localUp: {x: 0, y: 1, z: 0} + _localForward: {x: -1, y: -0.00006082654, z: 0.00000026077032} + _universalToActualAxesRotation: {x: -0.000021505426, y: 0.70710665, z: 0, + w: 0.7071069} + _initialRotation: {x: -0.03886342, y: 0.068199605, z: 0.40534702, w: 0.9107866} + _initialLocalRotation: {x: -0.030921718, y: 0.064092405, z: 0.46274257, w: 0.8836319} + _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialPosition: {x: -0.20499808, y: 1.5659004, z: -0.03706063} + _initialLocalPosition: {x: -0.16805698, y: -0.00000015258789, z: -0.00000029087067} + _forearmUniversalLocalAxes: + _transform: {fileID: 3970094588317566289} + _localRight: {x: 0.00000024586916, y: -0, z: 1.0000001} + _localUp: {x: 0, y: 1, z: 0} + _localForward: {x: -1.0000001, y: 0.00000011920942, z: 0.00000024586916} + _universalToActualAxesRotation: {x: 0.000000042146887, y: 0.7071067, z: 0, + w: 0.7071069} + _initialRotation: {x: -0.10653147, y: 0.22051847, z: 0.39302367, w: 0.88631546} + _initialLocalRotation: {x: 0.00000024343393, y: 0.16830686, z: 0.000000079369926, + w: 0.98573464} + _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialPosition: {x: -0.3899446, y: 1.3611002, z: 0.0064461567} + _initialLocalPosition: {x: -0.2793586, y: -0.00001701355, z: 0.000000009536743} + _clavicleUniversalLocalAxes: + _transform: {fileID: 6609533846315169039} + _localRight: {x: 0, y: 0, z: 1} + _localUp: {x: 0, y: 1, z: 0} + _localForward: {x: -1, y: -0, z: -0} + _universalToActualAxesRotation: {x: -0, y: 0.7071068, z: -0, w: 0.7071068} + _initialRotation: {x: -0.011757046, y: -0.0035609417, z: -0.06290017, w: 0.99794436} + _initialLocalRotation: {x: 0.06121901, y: 0.06522117, z: 0.65836143, w: 0.7473678} + _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialPosition: {x: -0.038275164, y: 1.5448165, z: -0.035617363} + _initialLocalPosition: {x: -0.15479766, y: -0.038275376, z: -0.03128269} + _thumbInfo: + _avatar: {fileID: 6665688241830784974} + _side: 0 + _finger: 1 + _metacarpalInfo: + _length: 0 + _radius: 0 + _proximalInfo: + _length: 0.029156702 + _radius: 0.018627401 + _intermediateInfo: + _length: 0.029087255 + _radius: 0.017593157 + _distalInfo: + _length: 0.0347857 + _radius: 0.01721128 + _distalLocalTip: {x: -0.0347857, y: -0, z: -0} + _distalLocalFingerPrintCenter: {x: -0.022958562, y: 0.011359446, z: -0} + _indexInfo: + _avatar: {fileID: 6665688241830784974} + _side: 0 + _finger: 2 + _metacarpalInfo: + _length: 0.039261155 + _radius: 0.020717436 + _proximalInfo: + _length: 0.039258577 + _radius: 0.01952646 + _intermediateInfo: + _length: 0.02379825 + _radius: 0.014249392 + _distalInfo: + _length: 0.023836892 + _radius: 0.011162487 + _distalLocalTip: {x: -0.023836892, y: -0, z: -0} + _distalLocalFingerPrintCenter: {x: -0.01573235, y: 0.0073672417, z: -0} + _middleInfo: + _avatar: {fileID: 6665688241830784974} + _side: 0 + _finger: 3 + _metacarpalInfo: + _length: 0.039260875 + _radius: 0 + _proximalInfo: + _length: 0.04354455 + _radius: 0.02113616 + _intermediateInfo: + _length: 0.030379474 + _radius: 0.013460865 + _distalInfo: + _length: 0.024240006 + _radius: 0.012489015 + _distalLocalTip: {x: -0.024240006, y: -0, z: -0} + _distalLocalFingerPrintCenter: {x: -0.015998404, y: 0.008242751, z: -0} + _ringInfo: + _avatar: {fileID: 6665688241830784974} + _side: 0 + _finger: 4 + _metacarpalInfo: + _length: 0.039260916 + _radius: 0 + _proximalInfo: + _length: 0.040291034 + _radius: 0.019430816 + _intermediateInfo: + _length: 0.028662736 + _radius: 0.013051865 + _distalInfo: + _length: 0.024692938 + _radius: 0.012223251 + _distalLocalTip: {x: -0.024692938, y: -0, z: -0} + _distalLocalFingerPrintCenter: {x: -0.01629734, y: 0.008067346, z: -0} + _littleInfo: + _avatar: {fileID: 6665688241830784974} + _side: 0 + _finger: 5 + _metacarpalInfo: + _length: 0.039260563 + _radius: 0.026297221 + _proximalInfo: + _length: 0.032816872 + _radius: 0.021182533 + _intermediateInfo: + _length: 0.02166212 + _radius: 0.013877094 + _distalInfo: + _length: 0.021128409 + _radius: 0.011504291 + _distalLocalTip: {x: -0.021128409, y: -0, z: -0} + _distalLocalFingerPrintCenter: {x: -0.013944751, y: 0.007592832, z: -0} + _rightArmInfo: + _avatar: {fileID: 6665688241830784974} + _side: 1 + _upperArmLength: 0.2793581 + _forearmLength: 0.30653545 + _fingerUniversalLocalAxes: + _transform: {fileID: 7966351054458962406} + _localRight: {x: 0, y: 0, z: 1} + _localUp: {x: 0, y: 1, z: 0} + _localForward: {x: -1, y: -0, z: -0} + _universalToActualAxesRotation: {x: -0, y: 0.7071068, z: -0, w: 0.7071068} + _initialRotation: {x: -0.31098256, y: -0.8237433, z: 0.15593779, w: -0.44768342} + _initialLocalRotation: {x: 0.00000005960483, y: 0.0000019073489, z: -0.00000010430826, + w: 1} + _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialPosition: {x: 0.61368513, y: 1.1428132, z: 0.2219456} + _initialLocalPosition: {x: -0.039260786, y: 0, z: 0.000000076293944} + _handUniversalLocalAxes: + _transform: {fileID: 77979469672119307} + _localRight: {x: -0.16893815, y: -0.032817062, z: -0.9850801} + _localUp: {x: 0.19069037, y: -0.98165023, z: 0} + _localForward: {x: -0.96700424, y: -0.18784532, z: 0.17209609} + _universalToActualAxesRotation: {x: 0.64043266, y: 0.061627604, z: -0.7620178, + w: -0.0733275} + _initialRotation: {x: 0.22051883, y: -0.10653114, z: -0.8863156, w: -0.3930236} + _initialLocalRotation: {x: 0.00022651485, y: 0.00015760117, z: 0.00012733944, + w: 0.99999994} + _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialPosition: {x: 0.57192886, y: 1.161829, z: 0.15176894} + _initialLocalPosition: {x: -0.30653533, y: 0.00000015258789, z: -0.00000022888183} + _armUniversalLocalAxes: + _transform: {fileID: 1835798567124492442} + _localRight: {x: -0.00000010430813, y: 0, z: -1.0000001} + _localUp: {x: -0, y: -1, z: -0} + _localForward: {x: -1.0000001, y: 0.000060141087, z: 0.00000010430813} + _universalToActualAxesRotation: {x: 0.70710677, y: -0.000021263082, z: -0.7071068, + w: 0} + _initialRotation: {x: 0.06816208, y: -0.038562983, z: -0.91078913, w: -0.40537667} + _initialLocalRotation: {x: 0.030619562, y: 0.06407281, z: -0.4627709, w: 0.883629} + _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialPosition: {x: 0.2049983, y: 1.5659006, z: -0.037060212} + _initialLocalPosition: {x: -0.16805704, y: 0, z: 0.0000001502037} + _forearmUniversalLocalAxes: + _transform: {fileID: 2512099166448472843} + _localRight: {x: 0.0000009760261, y: 0, z: -1.0000001} + _localUp: {x: -0, y: -1, z: -0} + _localForward: {x: -1.0000001, y: 0.0000007450583, z: -0.0000009760261} + _universalToActualAxesRotation: {x: -0.7071071, y: -0, z: 0.7071065, w: -0.00000026341775} + _initialRotation: {x: 0.22048172, y: -0.10624033, z: -0.88632435, w: -0.39310315} + _initialLocalRotation: {x: -0.0000000671276, y: 0.16830678, z: 0.00000023821366, + w: 0.9857347} + _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialPosition: {x: 0.38993412, y: 1.3610729, z: 0.006359998} + _initialLocalPosition: {x: -0.27935812, y: 0.000016784668, z: -0.000000019073486} + _clavicleUniversalLocalAxes: + _transform: {fileID: 5378592488698474173} + _localRight: {x: 0, y: 0, z: -1} + _localUp: {x: -0, y: -1, z: -0} + _localForward: {x: -1, y: -0, z: -0} + _universalToActualAxesRotation: {x: -0.7071068, y: -0, z: 0.7071068, w: 0} + _initialRotation: {x: -0.0035601892, y: -0.011757046, z: -0.99794436, w: 0.062900424} + _initialLocalRotation: {x: -0.061218485, y: 0.06522168, z: -0.6583609, w: 0.7473683} + _initialLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialUniversalLocalReferenceRotation: {x: 0, y: 0, z: 0, w: 1} + _initialPosition: {x: 0.03827533, y: 1.5448164, z: -0.03561763} + _initialLocalPosition: {x: -0.15479766, y: 0.038275123, z: -0.031282976} + _thumbInfo: + _avatar: {fileID: 6665688241830784974} + _side: 1 + _finger: 1 + _metacarpalInfo: + _length: 0 + _radius: 0 + _proximalInfo: + _length: 0.029156936 + _radius: 0.018627752 + _intermediateInfo: + _length: 0.029087206 + _radius: 0.018092817 + _distalInfo: + _length: 0.035219863 + _radius: 0.015569918 + _distalLocalTip: {x: -0.035219863, y: -0, z: -0} + _distalLocalFingerPrintCenter: {x: -0.023245111, y: -0.010276146, z: -0} + _indexInfo: + _avatar: {fileID: 6665688241830784974} + _side: 1 + _finger: 2 + _metacarpalInfo: + _length: 0.039260894 + _radius: 0.020859204 + _proximalInfo: + _length: 0.039258614 + _radius: 0.01951724 + _intermediateInfo: + _length: 0.023798266 + _radius: 0.014554845 + _distalInfo: + _length: 0.023836749 + _radius: 0.011760101 + _distalLocalTip: {x: -0.023836749, y: -0, z: -0} + _distalLocalFingerPrintCenter: {x: -0.015732255, y: -0.007761667, z: -0} + _middleInfo: + _avatar: {fileID: 6665688241830784974} + _side: 1 + _finger: 3 + _metacarpalInfo: + _length: 0.03926101 + _radius: 0 + _proximalInfo: + _length: 0.043544296 + _radius: 0.02085619 + _intermediateInfo: + _length: 0.030379485 + _radius: 0.013910446 + _distalInfo: + _length: 0.024240147 + _radius: 0.012973296 + _distalLocalTip: {x: -0.024240147, y: -0, z: -0} + _distalLocalFingerPrintCenter: {x: -0.015998498, y: -0.008562376, z: -0} + _ringInfo: + _avatar: {fileID: 6665688241830784974} + _side: 1 + _finger: 4 + _metacarpalInfo: + _length: 0.039260853 + _radius: 0 + _proximalInfo: + _length: 0.040291347 + _radius: 0.019982887 + _intermediateInfo: + _length: 0.028662995 + _radius: 0.01345507 + _distalInfo: + _length: 0.024327036 + _radius: 0.012795657 + _distalLocalTip: {x: -0.024327036, y: -0, z: -0} + _distalLocalFingerPrintCenter: {x: -0.016055845, y: -0.008445133, z: -0} + _littleInfo: + _avatar: {fileID: 6665688241830784974} + _side: 1 + _finger: 5 + _metacarpalInfo: + _length: 0.039260805 + _radius: 0.026841842 + _proximalInfo: + _length: 0.032817073 + _radius: 0.02189231 + _intermediateInfo: + _length: 0.021661868 + _radius: 0.014396792 + _distalInfo: + _length: 0.02112832 + _radius: 0.00786847 + _distalLocalTip: {x: -0.02112832, y: -0, z: -0} + _distalLocalFingerPrintCenter: {x: -0.013944691, y: -0.00519319, z: -0} _handPosesFoldout: 1 _handPoses: - {fileID: 11400000, guid: dbb57ee8b96b9c041b60a1b6a35b0494, type: 2} @@ -3335,6 +3524,11 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 4770505293191965900, guid: 2f7a5d0166ab0d041bed38f7cc6affef, + type: 3} + propertyPath: _openHandPoseName + value: ControllerValveIndexTracking + objectReference: {fileID: 0} - target: {fileID: 6332531002992653792, guid: 2f7a5d0166ab0d041bed38f7cc6affef, type: 3} propertyPath: m_LocalPosition.x diff --git a/Prefabs/Internal/Avatars/ControllersCyborgAvatar.prefab b/Prefabs/Internal/Avatars/ControllersCyborgAvatar.prefab index a074bf5e..5789ff27 100644 --- a/Prefabs/Internal/Avatars/ControllersCyborgAvatar.prefab +++ b/Prefabs/Internal/Avatars/ControllersCyborgAvatar.prefab @@ -7,255 +7,330 @@ PrefabInstance: m_Modification: m_TransformParent: {fileID: 0} m_Modifications: - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _prefabGuid value: 4eff023a7e941cd4cb62dd10b4b5469e objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _parentPrefab value: - objectReference: {fileID: 9064259894265353843, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 9064259894265353843, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.size value: 14 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[0] value: - objectReference: {fileID: 11400000, guid: ac1f5e617474a3a46af3ee10e094e016, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: ac1f5e617474a3a46af3ee10e094e016, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[1] value: - objectReference: {fileID: 11400000, guid: 4328f3ca6a4aa4a4eb6330ce6bdf8a3d, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: 4328f3ca6a4aa4a4eb6330ce6bdf8a3d, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[2] value: - objectReference: {fileID: 11400000, guid: c157dd6730f11534f8d9716db0f1acbc, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: c157dd6730f11534f8d9716db0f1acbc, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[3] value: - objectReference: {fileID: 11400000, guid: c3eeace805620e647aa31cb8ce93fd23, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: c3eeace805620e647aa31cb8ce93fd23, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[4] value: - objectReference: {fileID: 11400000, guid: 260fce01e924d374392f6b19acdcbee4, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: 260fce01e924d374392f6b19acdcbee4, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[5] value: - objectReference: {fileID: 11400000, guid: 35d1272eb78bded458c5bd2c1ab6de06, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: 35d1272eb78bded458c5bd2c1ab6de06, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[6] value: - objectReference: {fileID: 11400000, guid: a97321595cc43c84eae7e44ace2372b6, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: a97321595cc43c84eae7e44ace2372b6, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[7] value: - objectReference: {fileID: 11400000, guid: 1bd4608dd84147e4fbe5926a9fd50944, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: 1bd4608dd84147e4fbe5926a9fd50944, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[8] value: - objectReference: {fileID: 11400000, guid: bc075099a961fc948894bdb1070e9517, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: bc075099a961fc948894bdb1070e9517, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[9] value: - objectReference: {fileID: 11400000, guid: b37e3204d1e8c924d9e6a7be826df7e2, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: b37e3204d1e8c924d9e6a7be826df7e2, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[10] value: - objectReference: {fileID: 11400000, guid: 813db6a8ed095564aac562471aac78d0, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: 813db6a8ed095564aac562471aac78d0, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[11] value: - objectReference: {fileID: 11400000, guid: b8d441f919755034f8664d9370b7aff2, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: b8d441f919755034f8664d9370b7aff2, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[12] value: - objectReference: {fileID: 11400000, guid: 4354f147728470247b7f8fcd0440cd95, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: 4354f147728470247b7f8fcd0440cd95, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[13] value: - objectReference: {fileID: 11400000, guid: 42d62e4997b50e24987ef68571977ce3, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: 42d62e4997b50e24987ef68571977ce3, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[14] value: - objectReference: {fileID: 11400000, guid: 42d62e4997b50e24987ef68571977ce3, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: 42d62e4997b50e24987ef68571977ce3, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _handPoses.Array.data[15] value: - objectReference: {fileID: 11400000, guid: 42d62e4997b50e24987ef68571977ce3, type: 2} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + objectReference: {fileID: 11400000, guid: 42d62e4997b50e24987ef68571977ce3, + type: 2} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightForearmLength value: 0.30653545 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftArmUniversalLocalAxes._localRight.x value: 0.00000026077032 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightArmUniversalLocalAxes._localRight.x value: -0.00000010430813 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftArmUniversalLocalAxes._localForward.z value: 0.00000026077032 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightArmUniversalLocalAxes._localForward.z value: 0.00000010430813 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftForearmUniversalLocalAxes._localRight.x value: 0.00000024586916 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftForearmUniversalLocalAxes._localRight.z value: 1.0000001 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftArmUniversalLocalAxes._initialPosition.z value: -0.03706063 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightForearmUniversalLocalAxes._localRight.x value: 0.0000009760261 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftForearmUniversalLocalAxes._localForward.x value: -1.0000001 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftForearmUniversalLocalAxes._localForward.y value: 0.00000011920942 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftForearmUniversalLocalAxes._localForward.z value: 0.00000024586916 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftHandUniversalLocalAxes._initialPosition.z value: 0.1519392 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightArmUniversalLocalAxes._initialPosition.z value: -0.037060212 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightForearmUniversalLocalAxes._localForward.y value: 0.0000007450583 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightForearmUniversalLocalAxes._localForward.z value: -0.0000009760261 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightHandUniversalLocalAxes._initialPosition.z value: 0.15176894 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftFingerUniversalLocalAxes._initialPosition.z value: 0.22197597 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftForearmUniversalLocalAxes._initialPosition.z value: 0.0064461567 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightFingerUniversalLocalAxes._initialPosition.z value: 0.2219456 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftClavicleUniversalLocalAxes._initialPosition.z value: -0.035617363 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightForearmUniversalLocalAxes._initialPosition.z value: 0.006359998 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightClavicleUniversalLocalAxes._initialPosition.z value: -0.03561763 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightArmUniversalLocalAxes._universalToActualAxesRotation.x value: 0.70710677 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightArmUniversalLocalAxes._universalToActualAxesRotation.y value: -0.000021263082 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightArmUniversalLocalAxes._universalToActualAxesRotation.z value: -0.7071068 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftForearmUniversalLocalAxes._universalToActualAxesRotation.x value: 0.000000042146887 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._leftForearmUniversalLocalAxes._universalToActualAxesRotation.y value: 0.7071067 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightForearmUniversalLocalAxes._universalToActualAxesRotation.w value: -0.00000026341775 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightForearmUniversalLocalAxes._universalToActualAxesRotation.x value: -0.7071071 objectReference: {fileID: 0} - - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6665688241830784974, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: _rigInfo._rightForearmUniversalLocalAxes._universalToActualAxesRotation.z value: 0.7071065 objectReference: {fileID: 0} - - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_RootOrder value: 0 objectReference: {fileID: 0} - - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_LocalPosition.x value: 0 objectReference: {fileID: 0} - - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_LocalPosition.y value: 0 objectReference: {fileID: 0} - - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_LocalPosition.z value: 0 objectReference: {fileID: 0} - - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_LocalRotation.w value: 1 objectReference: {fileID: 0} - - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_LocalRotation.x value: 0 objectReference: {fileID: 0} - - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_LocalRotation.y value: 0 objectReference: {fileID: 0} - - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_LocalRotation.z value: 0 objectReference: {fileID: 0} - - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_LocalEulerAnglesHint.x value: 0 objectReference: {fileID: 0} - - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_LocalEulerAnglesHint.y value: 0 objectReference: {fileID: 0} - - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 6998712484238540430, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} - - target: {fileID: 7307018016531419596, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} - propertyPath: _openHandPoseName - value: ControllerValveIndexTracking - objectReference: {fileID: 0} - - target: {fileID: 9064259894265353843, guid: e7468ec6b7af89c4d94d451ec3c1807b, type: 3} + - target: {fileID: 9064259894265353843, guid: e7468ec6b7af89c4d94d451ec3c1807b, + type: 3} propertyPath: m_Name value: ControllersCyborgAvatar objectReference: {fileID: 0} diff --git a/Scripts/Animation/IK/UxrArmIKSolver.cs b/Scripts/Animation/IK/UxrArmIKSolver.cs index 032bd943..78afe1e1 100644 --- a/Scripts/Animation/IK/UxrArmIKSolver.cs +++ b/Scripts/Animation/IK/UxrArmIKSolver.cs @@ -229,7 +229,7 @@ public void SolveIKPass(UxrArmSolveOptions armSolveOptions, UxrArmOverExtendMode } // Compute elbow aperture value [0.0, 1.0] depending on the relaxedElbowAperture parameter and the current wrist torsion - float wristDegrees = _side == UxrHandSide.Left ? -Avatar.AvatarRigInfo.LeftWristTorsionInfo.WristTorsionAngle : Avatar.AvatarRigInfo.RightWristTorsionInfo.WristTorsionAngle; + float wristDegrees = _side == UxrHandSide.Left ? -Avatar.AvatarRigInfo.GetArmInfo(UxrHandSide.Left).WristTorsionInfo.WristTorsionAngle : Avatar.AvatarRigInfo.GetArmInfo(UxrHandSide.Right).WristTorsionInfo.WristTorsionAngle; float elbowApertureBiasDueToWrist = wristDegrees / WristTorsionDegreesFactor * _elbowApertureRotation; float elbowAperture = Mathf.Clamp01(_relaxedElbowAperture + elbowApertureBiasDueToWrist); @@ -413,12 +413,12 @@ private void ComputeParameters() Hand = Avatar.GetHandBone(_side); } - _upperArmLength = _side == UxrHandSide.Left ? Avatar.AvatarRigInfo.LeftUpperArmLength : Avatar.AvatarRigInfo.RightUpperArmLength; - _forearmLength = _side == UxrHandSide.Left ? Avatar.AvatarRigInfo.LeftForearmLength : Avatar.AvatarRigInfo.RightForearmLength; + _upperArmLength = Avatar.AvatarRigInfo.GetArmInfo(_side).UpperArmLength; + _forearmLength = Avatar.AvatarRigInfo.GetArmInfo(_side).ForearmLength; - _clavicleUniversalLocalAxes = Avatar.AvatarRigInfo.GetClavicleUniversalLocalAxes(_side); - _armUniversalLocalAxes = Avatar.AvatarRigInfo.GetArmUniversalLocalAxes(_side); - _forearmUniversalLocalAxes = Avatar.AvatarRigInfo.GetForearmUniversalLocalAxes(_side); + _clavicleUniversalLocalAxes = Avatar.AvatarRigInfo.GetArmInfo(_side).ClavicleUniversalLocalAxes; + _armUniversalLocalAxes = Avatar.AvatarRigInfo.GetArmInfo(_side).ArmUniversalLocalAxes; + _forearmUniversalLocalAxes = Avatar.AvatarRigInfo.GetArmInfo(_side).ForearmUniversalLocalAxes; // Compute arm range of motion neutral direction _armNeutralForwardInParent = Vector3.forward; diff --git a/Scripts/Animation/IK/UxrWristTorsionIKSolver.cs b/Scripts/Animation/IK/UxrWristTorsionIKSolver.cs index f011892a..e6f81463 100644 --- a/Scripts/Animation/IK/UxrWristTorsionIKSolver.cs +++ b/Scripts/Animation/IK/UxrWristTorsionIKSolver.cs @@ -34,7 +34,7 @@ protected override void Awake() _handSide = transform.HasParent(Avatar.AvatarRig.LeftArm.UpperArm) ? UxrHandSide.Left : UxrHandSide.Right; _startLocalRotation = transform.localRotation; - UxrUniversalLocalAxes handUniversalLocalAxes = Avatar.AvatarRigInfo.GetHandUniversalLocalAxes(_handSide); + UxrUniversalLocalAxes handUniversalLocalAxes = Avatar.AvatarRigInfo.GetArmInfo(_handSide).HandUniversalLocalAxes; _torsionLocalAxis = transform.InverseTransformDirection(handUniversalLocalAxes.WorldForward).GetClosestAxis(); } @@ -47,7 +47,7 @@ protected override void Awake() /// protected override void InternalSolveIK() { - float angle = _handSide == UxrHandSide.Left ? Avatar.AvatarRigInfo.LeftWristTorsionInfo.WristTorsionAngle : Avatar.AvatarRigInfo.RightWristTorsionInfo.WristTorsionAngle; + float angle = Avatar.AvatarRigInfo.GetArmInfo(_handSide).WristTorsionInfo.WristTorsionAngle; transform.localRotation = Quaternion.AngleAxis(-angle * _amount, _torsionLocalAxis) * _startLocalRotation; } diff --git a/Scripts/Avatar/Controllers/UxrStandardAvatarController.cs b/Scripts/Avatar/Controllers/UxrStandardAvatarController.cs index f6d44cc4..bbb3f786 100644 --- a/Scripts/Avatar/Controllers/UxrStandardAvatarController.cs +++ b/Scripts/Avatar/Controllers/UxrStandardAvatarController.cs @@ -390,22 +390,28 @@ protected override void UpdateAvatar() // Check with the help of the grab manager if we need to override the grab buttons based on proximity to a grabbable object that used non-default grab buttons. - if (UxrGrabManager.Instance.GetClosestGrabbableObject(Avatar, UxrHandSide.Left, out UxrGrabbableObject grabbableObjectLeft, out int grabPointLeft)) + if (!UxrGrabManager.Instance.IsHandGrabbing(Avatar, UxrHandSide.Left)) { - LeftHandGrabButtonsOverride = grabbableObjectLeft.GetGrabPoint(grabPointLeft).UseDefaultGrabButtons ? UxrInputButtons.Everything : grabbableObjectLeft.GetGrabPoint(grabPointLeft).InputButtons; - } - else - { - LeftHandGrabButtonsOverride = UxrInputButtons.Everything; + if (UxrGrabManager.Instance.GetClosestGrabbableObject(Avatar, UxrHandSide.Left, out UxrGrabbableObject grabbableObjectLeft, out int grabPointLeft)) + { + LeftHandGrabButtonsOverride = grabbableObjectLeft.GetGrabPoint(grabPointLeft).UseDefaultGrabButtons ? UxrInputButtons.Everything : grabbableObjectLeft.GetGrabPoint(grabPointLeft).InputButtons; + } + else + { + LeftHandGrabButtonsOverride = UxrInputButtons.Everything; + } } - if (UxrGrabManager.Instance.GetClosestGrabbableObject(Avatar, UxrHandSide.Right, out UxrGrabbableObject grabbableObjectRight, out int grabPointRight)) + if (!UxrGrabManager.Instance.IsHandGrabbing(Avatar, UxrHandSide.Right)) { - RightHandGrabButtonsOverride = grabbableObjectRight.GetGrabPoint(grabPointRight).UseDefaultGrabButtons ? UxrInputButtons.Everything : grabbableObjectRight.GetGrabPoint(grabPointRight).InputButtons; - } - else - { - RightHandGrabButtonsOverride = UxrInputButtons.Everything; + if (UxrGrabManager.Instance.GetClosestGrabbableObject(Avatar, UxrHandSide.Right, out UxrGrabbableObject grabbableObjectRight, out int grabPointRight)) + { + RightHandGrabButtonsOverride = grabbableObjectRight.GetGrabPoint(grabPointRight).UseDefaultGrabButtons ? UxrInputButtons.Everything : grabbableObjectRight.GetGrabPoint(grabPointRight).InputButtons; + } + else + { + RightHandGrabButtonsOverride = UxrInputButtons.Everything; + } } // Update only internal vars (are we grabbing and/or pointing?) but don't execute the actions. We might change things like the grab pose name at runtime diff --git a/Scripts/Avatar/Editor/UxrAvatarEditor.cs b/Scripts/Avatar/Editor/UxrAvatarEditor.cs index b1a09bb2..5cf8d703 100644 --- a/Scripts/Avatar/Editor/UxrAvatarEditor.cs +++ b/Scripts/Avatar/Editor/UxrAvatarEditor.cs @@ -7,10 +7,12 @@ using System.Linq; using UltimateXR.Avatar.Controllers; using UltimateXR.Avatar.Rig; +using UltimateXR.Core; using UltimateXR.Devices; using UltimateXR.Editor; using UltimateXR.Extensions.System.Collections; using UltimateXR.Extensions.Unity; +using UltimateXR.Extensions.Unity.Render; using UltimateXR.Manipulation.HandPoses; using UltimateXR.Manipulation.HandPoses.Editor; using UnityEditor; @@ -206,6 +208,8 @@ public override void OnInspectorGUI() Selection.activeGameObject = newInstance.gameObject; avatar = newInstance.GetComponent(); } + + return; } } } @@ -569,6 +573,39 @@ public override void OnInspectorGUI() } } + /// + /// Draws visual guides on the avatar. + /// + private void OnSceneGUI() + { + UxrAvatar avatar = (UxrAvatar)serializedObject.targetObject; + + if (avatar == null) + { + return; + } + + /* + // Draw finger tips and finger print positions + + Color handlesColor = Handles.color; + Handles.matrix = Matrix4x4.identity; + + foreach (UxrAvatarArmInfo arm in avatar.AvatarRigInfo.Arms) + { + foreach (UxrAvatarFingerInfo finger in arm.Fingers) + { + Handles.color = ColorExt.ColorAlpha(Color.blue, UxrEditorUtils.HandlesAlpha); + Handles.DrawSolidDisc(finger.TipPosition, finger.TipDirection, 0.004f); + Handles.color = ColorExt.ColorAlpha(Color.green, UxrEditorUtils.HandlesAlpha); + Handles.DrawSolidDisc(finger.FingerPrintPosition, finger.FingerPrintDirection, 0.004f); + } + } + + Handles.color = handlesColor; + */ + } + #endregion #region Private Methods diff --git a/Scripts/Avatar/Rig/UxrAvatarArmInfo.cs b/Scripts/Avatar/Rig/UxrAvatarArmInfo.cs new file mode 100644 index 00000000..ebd0ffb9 --- /dev/null +++ b/Scripts/Avatar/Rig/UxrAvatarArmInfo.cs @@ -0,0 +1,302 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) VRMADA, All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using UltimateXR.Core; +using UltimateXR.Core.Math; +using UltimateXR.Extensions.Unity.Math; +using UltimateXR.Manipulation; +using UnityEngine; + +namespace UltimateXR.Avatar.Rig +{ + /// + /// Stores information of an avatar rig's arm. + /// + [Serializable] + public class UxrAvatarArmInfo + { + #region Inspector Properties/Serialized Fields + + [SerializeField] private UxrAvatar _avatar; + [SerializeField] private UxrHandSide _side; + [SerializeField] private float _upperArmLength; + [SerializeField] private float _forearmLength; + [SerializeField] private UxrUniversalLocalAxes _fingerUniversalLocalAxes; + [SerializeField] private UxrUniversalLocalAxes _handUniversalLocalAxes; + [SerializeField] private UxrUniversalLocalAxes _armUniversalLocalAxes; + [SerializeField] private UxrUniversalLocalAxes _forearmUniversalLocalAxes; + [SerializeField] private UxrUniversalLocalAxes _clavicleUniversalLocalAxes; + [SerializeField] private UxrAvatarFingerInfo _thumbInfo; + [SerializeField] private UxrAvatarFingerInfo _indexInfo; + [SerializeField] private UxrAvatarFingerInfo _middleInfo; + [SerializeField] private UxrAvatarFingerInfo _ringInfo; + [SerializeField] private UxrAvatarFingerInfo _littleInfo; + + #endregion + + #region Public Types & Data + + /// + /// Gets the thumb finger information. + /// + public UxrAvatarFingerInfo ThumbInfo => _thumbInfo; + + /// + /// Gets the index finger information. + /// + public UxrAvatarFingerInfo IndexInfo => _indexInfo; + + /// + /// Gets the middle finger information. + /// + public UxrAvatarFingerInfo MiddleInfo => _middleInfo; + + /// + /// Gets the ring finger information. + /// + public UxrAvatarFingerInfo RingInfo => _ringInfo; + + /// + /// Gets the little finger information. + /// + public UxrAvatarFingerInfo LittleInfo => _littleInfo; + + /// + /// Enumerates all the finger information. + /// + public IEnumerable Fingers + { + get + { + yield return _thumbInfo; + yield return _indexInfo; + yield return _middleInfo; + yield return _ringInfo; + yield return _littleInfo; + } + } + + /// + /// Gets the upper arm length. From shoulder to elbow. + /// + public float UpperArmLength + { + get => _upperArmLength; + private set => _upperArmLength = value; + } + + /// + /// Gets the forearm length. From elbow to wrist. + /// + public float ForearmLength + { + get => _forearmLength; + private set => _forearmLength = value; + } + + /// + /// Gets the universal coordinate system for the fingers: right = axis around which the finger curls, up = + /// knuckles up, forward = finger direction. + /// + public UxrUniversalLocalAxes FingerUniversalLocalAxes + { + get => _fingerUniversalLocalAxes; + private set => _fingerUniversalLocalAxes = value; + } + + /// + /// Gets the universal coordinate system for the hand: right = from wrist to right (thumb direction), up = -palm + /// facing vector, forward = finger direction. + /// + public UxrUniversalLocalAxes HandUniversalLocalAxes + { + get => _handUniversalLocalAxes; + private set => _handUniversalLocalAxes = value; + } + + /// + /// Gets the universal coordinate system for the arm: forward is arm->elbow, up is elbow rotation axis + /// + public UxrUniversalLocalAxes ArmUniversalLocalAxes + { + get => _armUniversalLocalAxes; + private set => _armUniversalLocalAxes = value; + } + + /// + /// Gets the universal coordinate system for the forearm: forward is arm->hand, up is elbow rotation axis + /// + public UxrUniversalLocalAxes ForearmUniversalLocalAxes + { + get => _forearmUniversalLocalAxes; + private set => _forearmUniversalLocalAxes = value; + } + + /// + /// Gets the universal coordinate system for the clavicle: forward is clavicle->arm, up is avatar up axis + /// + public UxrUniversalLocalAxes ClavicleUniversalLocalAxes + { + get => _clavicleUniversalLocalAxes; + private set => _clavicleUniversalLocalAxes = value; + } + + // Updated every frame + + /// + /// Gets the wrist torsion info. + /// + public UxrWristTorsionInfo WristTorsionInfo { get; private set; } = new UxrWristTorsionInfo(); + + #endregion + + #region Internal Methods + + /// + /// Computes and stores all the arm information of an avatar. + /// + /// Avatar whose arm to compute the information of + /// Which side to compute + internal void Compute(UxrAvatar avatar, UxrHandSide side) + { + _avatar = avatar; + _side = side; + SolveHandAndFingerAxes(avatar.GetHand(side), side); + ComputeArmRigInfo(avatar, avatar.GetArm(side), side); + } + + #endregion + + #region Private Methods + + /// + /// Gets the outwards-pointing elbow rotation axis in world coordinates. + /// + /// The avatar the arm nodes belong to + /// The arm's forearm transform + /// The arm forward looking vector, the one pointing from shoulder to elbow + /// The forearm forward looking vector, the one pointing from elbow to hand + /// Elbow rotation axis + private static Vector3 GetWorldElbowAxis(UxrAvatar avatar, Transform forearm, Vector3 armForward, Vector3 forearmForward) + { + bool isLeft = avatar.transform.InverseTransformPoint(forearm.position).x < 0.0f; + + float elbowAngle = Vector3.Angle(armForward, forearmForward); + Vector3 elbowAxis = Vector3.Cross(forearmForward, armForward).normalized; + + if (elbowAngle > ElbowMinAngleThreshold) + { + elbowAxis = Vector3.up; // Assume T-pose if elbow angle is too small + } + else + { + elbowAxis = isLeft ? -elbowAxis : elbowAxis; + } + + return forearm.TransformDirection(forearm.InverseTransformDirection(elbowAxis).GetClosestAxis()); + } + + /// + /// Tries to find out which axes are pointing right/up/forward in the hand and finger nodes. These "universal" axes + /// will be used to rotate the nodes, so that any coordinate system can be used no matter how the hand was authored. + /// + /// The hand to compute the axes for + /// Whether it is a left hand or right hand + /// Boolean telling whether the axes could be solved. If any necessary transform is missing it will fail + private bool SolveHandAndFingerAxes(UxrAvatarHand hand, UxrHandSide side) + { + Transform indexProximal = hand.Index.Proximal; + Transform indexDistal = hand.Index.Distal; + Transform middleProximal = hand.Middle.Proximal; + Transform ringProximal = hand.Ring.Proximal; + + if (!hand.Wrist || !indexProximal || !indexDistal || !middleProximal || !ringProximal) + { + return false; + } + + float handCenter = 0.5f; // [0, 1] + + Vector3 handAxesRight = hand.Wrist.InverseTransformDirection(indexProximal.position - middleProximal.position).GetClosestAxis() * (side == UxrHandSide.Left ? 1.0f : -1.0f); + Vector3 handAxesForward = hand.Wrist.InverseTransformDirection((Vector3.Lerp(ringProximal.position, middleProximal.position, handCenter) - hand.Wrist.position).normalized); + Vector3 handAxesUp = Vector3.Cross(handAxesForward, handAxesRight).normalized; + handAxesRight = Vector3.Cross(handAxesUp, handAxesForward).normalized; + + HandUniversalLocalAxes = UxrUniversalLocalAxes.FromAxes(hand.Wrist, handAxesRight, handAxesUp, handAxesForward); + + Vector3 fingerAxesRight = indexProximal.InverseTransformDirection(indexProximal.position - middleProximal.position).GetClosestAxis() * (side == UxrHandSide.Left ? 1.0f : -1.0f); + Vector3 fingerAxesForward = indexProximal.InverseTransformDirection(indexDistal.position - indexProximal.position).GetClosestAxis(); + Vector3 fingerAxesUp = Vector3.Cross(fingerAxesForward, fingerAxesRight); + + FingerUniversalLocalAxes = UxrUniversalLocalAxes.FromAxes(indexProximal, fingerAxesRight, fingerAxesUp, fingerAxesForward); + + return true; + } + + /// + /// Computes and stores information of the arm's rig. + /// + /// Avatar whose right arm to compute information of + /// Arm to compute the information of + /// Which side it is + private void ComputeArmRigInfo(UxrAvatar avatar, UxrAvatarArm arm, UxrHandSide side) + { + if (arm.UpperArm != null && arm.Forearm != null && arm.Hand.Wrist != null) + { + Vector3 armForward = (arm.Forearm.position - arm.UpperArm.position).normalized; + Vector3 forearmForward = (arm.Hand.Wrist.position - arm.Forearm.position).normalized; + Vector3 elbowAxis = GetWorldElbowAxis(avatar, arm.Forearm, armForward, forearmForward); + Vector3 armLocalForward = arm.UpperArm.InverseTransformDirection(armForward); + Vector3 armLocalElbowAxis = arm.UpperArm.InverseTransformDirection(elbowAxis); + Vector3 forearmLocalForward = arm.Forearm.InverseTransformDirection(forearmForward); + Vector3 forearmLocalElbowAxis = arm.Forearm.InverseTransformDirection(elbowAxis); + + ArmUniversalLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.UpperArm, armLocalElbowAxis.GetClosestAxis(), armLocalForward); + ForearmUniversalLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.Forearm, armLocalElbowAxis.GetClosestAxis(), forearmLocalForward); + UpperArmLength = Vector3.Distance(arm.UpperArm.position, arm.Forearm.position); + ForearmLength = Vector3.Distance(arm.Forearm.position, arm.Hand.Wrist.position); + + if (arm.Clavicle != null) + { + Vector3 clavicleForward = (arm.UpperArm.position - arm.Clavicle.position).normalized; + Vector3 clavicleLocalForwardAxis = arm.Clavicle.InverseTransformDirection(clavicleForward).GetClosestAxis(); + Vector3 clavicleLocalUpAxis = arm.Clavicle.InverseTransformDirection(avatar.transform.up).GetClosestAxis(); + + ClavicleUniversalLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.Clavicle, clavicleLocalUpAxis, clavicleLocalForwardAxis); + } + } + + UxrGrabber grabber = avatar.GetGrabber(side); + + if (grabber && grabber.HandRenderer && grabber.HandRenderer is SkinnedMeshRenderer handRenderer) + { + _thumbInfo = new UxrAvatarFingerInfo(); + _indexInfo = new UxrAvatarFingerInfo(); + _middleInfo = new UxrAvatarFingerInfo(); + _ringInfo = new UxrAvatarFingerInfo(); + _littleInfo = new UxrAvatarFingerInfo(); + + _thumbInfo.Compute(avatar, handRenderer, side, UxrFingerType.Thumb); + _indexInfo.Compute(avatar, handRenderer, side, UxrFingerType.Index); + _middleInfo.Compute(avatar, handRenderer, side, UxrFingerType.Middle); + _ringInfo.Compute(avatar, handRenderer, side, UxrFingerType.Ring); + _littleInfo.Compute(avatar, handRenderer, side, UxrFingerType.Little); + } + } + + #endregion + + #region Private Types & Data + + /// + /// Minimum angle between arm and forearm to compute elbow axis using cross product. + /// + private const float ElbowMinAngleThreshold = 3.0f; + + #endregion + } +} \ No newline at end of file diff --git a/Scripts/Avatar/Rig/UxrAvatarArmInfo.cs.meta b/Scripts/Avatar/Rig/UxrAvatarArmInfo.cs.meta new file mode 100644 index 00000000..1ce74e37 --- /dev/null +++ b/Scripts/Avatar/Rig/UxrAvatarArmInfo.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b35d6ec8a0474d098337128d5632355a +timeCreated: 1665136779 \ No newline at end of file diff --git a/Scripts/Avatar/Rig/UxrAvatarFingerBoneInfo.cs b/Scripts/Avatar/Rig/UxrAvatarFingerBoneInfo.cs new file mode 100644 index 00000000..4f40e42d --- /dev/null +++ b/Scripts/Avatar/Rig/UxrAvatarFingerBoneInfo.cs @@ -0,0 +1,75 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) VRMADA, All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- +using System; +using UltimateXR.Core.Math; +using UltimateXR.Extensions.Unity.Render; +using UnityEngine; + +namespace UltimateXR.Avatar.Rig +{ + /// + /// Stores information of the bone in an . + /// + [Serializable] + public class UxrAvatarFingerBoneInfo + { + #region Inspector Properties/Serialized Fields + + [SerializeField] private float _length; + [SerializeField] private float _radius; + + #endregion + + #region Public Types & Data + + /// + /// Gets the bone length. + /// + public float Length => _length; + + /// + /// Gets the radius of the finger along this bone. + /// + public float Radius => _radius; + + #endregion + + #region Internal Methods + + /// + /// Computes the bone length and radius. + /// + /// Hand renderer + /// Finger bone + /// Next finger bone downwards the hierarchy or null for the last bone + /// Finger universal coordinate system + internal void Compute(SkinnedMeshRenderer handRenderer, Transform bone, Transform nextBone, UxrUniversalLocalAxes universalFingerAxes) + { + // Compute bounds representing the influenced part in the mesh in local bone coordinates. + Bounds localBounds = MeshExt.GetBoneInfluenceBounds(handRenderer, bone); + + _length = 0.0f; + + // Compute bone length: + + if (bone && nextBone) + { + // If we have a next bone, simply compute distance. + _length = Vector3.Distance(bone.position, nextBone.position); + } + else if (bone) + { + // If we have no next bone (for example, the distal bone is the last in the hierarchy), we compute the length using the influenced mesh part's local bounds. + _length = Vector3.Scale(universalFingerAxes.LocalForward, localBounds.size).magnitude; + } + + // Compute radius using the influenced part of the mesh's bounds computed earlier. + _radius = 0.5f * Mathf.Max(Vector3.Scale(universalFingerAxes.LocalRight, localBounds.size).magnitude, Vector3.Scale(universalFingerAxes.LocalUp, localBounds.size).magnitude); + } + + #endregion + } +} \ No newline at end of file diff --git a/Scripts/Avatar/Rig/UxrAvatarFingerBoneInfo.cs.meta b/Scripts/Avatar/Rig/UxrAvatarFingerBoneInfo.cs.meta new file mode 100644 index 00000000..678d53f9 --- /dev/null +++ b/Scripts/Avatar/Rig/UxrAvatarFingerBoneInfo.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 017ad516096f4f4d83da5739003991c1 +timeCreated: 1665409719 \ No newline at end of file diff --git a/Scripts/Avatar/Rig/UxrAvatarFingerInfo.cs b/Scripts/Avatar/Rig/UxrAvatarFingerInfo.cs new file mode 100644 index 00000000..8c9e50ec --- /dev/null +++ b/Scripts/Avatar/Rig/UxrAvatarFingerInfo.cs @@ -0,0 +1,190 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) VRMADA, All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- +using System; +using UltimateXR.Core; +using UltimateXR.Core.Math; +using UnityEngine; + +namespace UltimateXR.Avatar.Rig +{ + /// + /// Stores information of an avatar rig's finger. + /// + [Serializable] + public class UxrAvatarFingerInfo + { + #region Inspector Properties/Serialized Fields + + [SerializeField] private UxrAvatar _avatar; + [SerializeField] private UxrHandSide _side; + [SerializeField] private UxrFingerType _finger; + [SerializeField] private UxrAvatarFingerBoneInfo _metacarpalInfo; + [SerializeField] private UxrAvatarFingerBoneInfo _proximalInfo; + [SerializeField] private UxrAvatarFingerBoneInfo _intermediateInfo; + [SerializeField] private UxrAvatarFingerBoneInfo _distalInfo; + [SerializeField] private Vector3 _distalLocalTip; + [SerializeField] private Vector3 _distalLocalFingerPrintCenter; + + #endregion + + #region Public Types & Data + + /// + /// Gets the metacarpal bone info. + /// + public UxrAvatarFingerBoneInfo MetacarpalInfo => _metacarpalInfo; + + /// + /// Gets the proximal bone info. + /// + public UxrAvatarFingerBoneInfo ProximalInfo => _proximalInfo; + + /// + /// Gets the intermediate bone info. + /// + public UxrAvatarFingerBoneInfo IntermediateInfo => _intermediateInfo; + + /// + /// Gets the distal bone info. + /// + public UxrAvatarFingerBoneInfo DistalInfo => _distalInfo; + + /// + /// Gets the finger tip in local coordinates of the distal bone. + /// + public Vector3 DistalLocalTip => _distalLocalTip; + + /// + /// Gets an approximate position of the finger print center in local coordinates of the distal bone. The position is + /// computed as a position at 2/3 of the distance between the distal bone start and the tip and at the bottom part of + /// the distal using the distal radius. + /// + public Vector3 DistalLocalFingerPrintCenter => _distalLocalFingerPrintCenter; + + /// + /// Gets the tip position in world-space. + /// + public Vector3 TipPosition + { + get + { + if (_avatar) + { + UxrAvatarFinger avatarFinger = _avatar.GetHand(_side).GetFinger(_finger); + + if (avatarFinger.Distal) + { + return avatarFinger.Distal.TransformPoint(_distalLocalTip); + } + } + + return Vector3.zero; + } + } + + /// + /// Gets the tip forward direction in world-space. + /// + public Vector3 TipDirection + { + get + { + if (_avatar) + { + UxrAvatarFinger avatarFinger = _avatar.GetHand(_side).GetFinger(_finger); + + if (avatarFinger.Distal) + { + UxrUniversalLocalAxes fingerAxes = _avatar.AvatarRigInfo.GetArmInfo(_side).FingerUniversalLocalAxes; + return avatarFinger.Distal.TransformVector(fingerAxes.LocalForward); + } + } + + return Vector3.zero; + } + } + + /// + /// Gets the finger print approximate position. The position is computed as a position at 2/3 of the distance between + /// the distal bone start and the tip and at the bottom part of the distal using the distal radius. + /// + public Vector3 FingerPrintPosition + { + get + { + if (_avatar) + { + UxrAvatarFinger avatarFinger = _avatar.GetHand(_side).GetFinger(_finger); + + if (avatarFinger.Distal) + { + return avatarFinger.Distal.TransformPoint(_distalLocalFingerPrintCenter); + } + } + + return Vector3.zero; + } + } + + /// + /// Gets the finger print direction in world-space. The direction points from the finger print center downwards. + /// + public Vector3 FingerPrintDirection + { + get + { + if (_avatar) + { + UxrAvatarFinger avatarFinger = _avatar.GetHand(_side).GetFinger(_finger); + + if (avatarFinger.Distal) + { + UxrUniversalLocalAxes fingerAxes = _avatar.AvatarRigInfo.GetArmInfo(_side).FingerUniversalLocalAxes; + return -avatarFinger.Distal.TransformVector(fingerAxes.LocalUp); + } + } + + return Vector3.zero; + } + } + + #endregion + + #region Internal Methods + + /// + /// Computes the finger information. + /// + /// Avatar whose finger information to compute + /// Hand renderer + /// Which hand side the finger belongs to + /// Which finger to compute + internal void Compute(UxrAvatar avatar, SkinnedMeshRenderer handRenderer, UxrHandSide side, UxrFingerType finger) + { + _avatar = avatar; + _side = side; + _finger = finger; + + UxrUniversalLocalAxes fingerAxes = avatar.AvatarRigInfo.GetArmInfo(side).FingerUniversalLocalAxes; + UxrAvatarFinger avatarFinger = avatar.GetHand(side).GetFinger(finger); + + _metacarpalInfo = new UxrAvatarFingerBoneInfo(); + _proximalInfo = new UxrAvatarFingerBoneInfo(); + _intermediateInfo = new UxrAvatarFingerBoneInfo(); + _distalInfo = new UxrAvatarFingerBoneInfo(); + + _metacarpalInfo.Compute(handRenderer, avatarFinger.Metacarpal, avatarFinger.Proximal, fingerAxes); + _proximalInfo.Compute(handRenderer, avatarFinger.Proximal, avatarFinger.Intermediate, fingerAxes); + _intermediateInfo.Compute(handRenderer, avatarFinger.Intermediate, avatarFinger.Distal, fingerAxes); + _distalInfo.Compute(handRenderer, avatarFinger.Distal, null, fingerAxes); + + _distalLocalTip = fingerAxes.LocalForward * _distalInfo.Length; + _distalLocalFingerPrintCenter = fingerAxes.LocalForward * (_distalInfo.Length * 0.66f) - fingerAxes.LocalUp * (_distalInfo.Radius * 0.66f); + } + + #endregion + } +} \ No newline at end of file diff --git a/Scripts/Avatar/Rig/UxrAvatarFingerInfo.cs.meta b/Scripts/Avatar/Rig/UxrAvatarFingerInfo.cs.meta new file mode 100644 index 00000000..97a6362f --- /dev/null +++ b/Scripts/Avatar/Rig/UxrAvatarFingerInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 78a4d19d90f3ad74ba6d15bad66d317e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Avatar/Rig/UxrAvatarRig.HandRuntimeTransformation.cs b/Scripts/Avatar/Rig/UxrAvatarRig.HandRuntimeTransformation.cs index 5093a388..676c7700 100644 --- a/Scripts/Avatar/Rig/UxrAvatarRig.HandRuntimeTransformation.cs +++ b/Scripts/Avatar/Rig/UxrAvatarRig.HandRuntimeTransformation.cs @@ -58,7 +58,7 @@ public static void PopHandTransforms(UxrAvatarHand hand, DictionarySpread angle in degrees for the finger (finger "left" or "right" amount with respect to the wrist) public static void CurlFinger(UxrAvatar avatar, UxrHandSide handSide, UxrAvatarFinger finger, float proximalCurl, float intermediateCurl, float distalCurl, float spread = 0.0f) { - UxrUniversalLocalAxes fingerAxes = handSide == UxrHandSide.Left ? avatar.AvatarRigInfo.LeftFingerUniversalLocalAxes : avatar.AvatarRigInfo.RightFingerUniversalLocalAxes; + UxrUniversalLocalAxes fingerAxes = avatar.AvatarRigInfo.GetArmInfo(handSide).FingerUniversalLocalAxes; if (avatar.GetInitialBoneLocalRotation(finger.Proximal, out Quaternion localRotationProximal)) { diff --git a/Scripts/Avatar/Rig/UxrAvatarRig.HandTransformation.cs b/Scripts/Avatar/Rig/UxrAvatarRig.HandTransformation.cs index c4bebf17..09cb37fd 100644 --- a/Scripts/Avatar/Rig/UxrAvatarRig.HandTransformation.cs +++ b/Scripts/Avatar/Rig/UxrAvatarRig.HandTransformation.cs @@ -22,10 +22,7 @@ partial class UxrAvatarRig /// The descriptor to get the data from public static void UpdateHandUsingDescriptor(UxrAvatar avatar, UxrHandSide handSide, UxrHandDescriptor handDescriptor) { - UpdateHandUsingDescriptor(avatar.GetHand(handSide), - handDescriptor, - handSide == UxrHandSide.Left ? avatar.AvatarRigInfo.LeftHandUniversalLocalAxes : avatar.AvatarRigInfo.RightHandUniversalLocalAxes, - handSide == UxrHandSide.Left ? avatar.AvatarRigInfo.LeftFingerUniversalLocalAxes : avatar.AvatarRigInfo.RightFingerUniversalLocalAxes); + UpdateHandUsingDescriptor(avatar.GetHand(handSide), handDescriptor, avatar.AvatarRigInfo.GetArmInfo(handSide).HandUniversalLocalAxes, avatar.AvatarRigInfo.GetArmInfo(handSide).FingerUniversalLocalAxes); } /// @@ -38,12 +35,7 @@ public static void UpdateHandUsingDescriptor(UxrAvatar avatar, UxrHandSide handS /// The interpolation value [0.0, 1.0] public static void UpdateHandUsingDescriptor(UxrAvatar avatar, UxrHandSide handSide, UxrHandDescriptor handDescriptorA, UxrHandDescriptor handDescriptorB, float blend) { - UpdateHandUsingDescriptor(avatar.GetHand(handSide), - handDescriptorA, - handDescriptorB, - blend, - handSide == UxrHandSide.Left ? avatar.AvatarRigInfo.LeftHandUniversalLocalAxes : avatar.AvatarRigInfo.RightHandUniversalLocalAxes, - handSide == UxrHandSide.Left ? avatar.AvatarRigInfo.LeftFingerUniversalLocalAxes : avatar.AvatarRigInfo.RightFingerUniversalLocalAxes); + UpdateHandUsingDescriptor(avatar.GetHand(handSide), handDescriptorA, handDescriptorB, blend, avatar.AvatarRigInfo.GetArmInfo(handSide).HandUniversalLocalAxes, avatar.AvatarRigInfo.GetArmInfo(handSide).FingerUniversalLocalAxes); } /// diff --git a/Scripts/Avatar/Rig/UxrAvatarRig.ReferenceSolving.cs b/Scripts/Avatar/Rig/UxrAvatarRig.ReferenceSolving.cs index 8214b3df..018ef800 100644 --- a/Scripts/Avatar/Rig/UxrAvatarRig.ReferenceSolving.cs +++ b/Scripts/Avatar/Rig/UxrAvatarRig.ReferenceSolving.cs @@ -6,10 +6,10 @@ using System.Collections.Generic; using System.Linq; using UltimateXR.Core; -using UltimateXR.Devices.Visualization; using UltimateXR.Extensions.System; using UltimateXR.Extensions.System.Collections; using UltimateXR.Extensions.Unity; +using UltimateXR.Extensions.Unity.Render; using UnityEngine; namespace UltimateXR.Avatar.Rig @@ -31,11 +31,10 @@ public static SkinnedMeshRenderer TryToGetHandRenderer(UxrAvatar avatar, UxrHand return null; } - SkinnedMeshRenderer[] skins = avatar.GetComponentsInChildren(); - SkinnedMeshRenderer mostInfluentialSkin = null; - int maxInfluenceCount = 0; + SkinnedMeshRenderer mostInfluentialSkin = null; + int maxInfluenceCount = 0; - foreach (SkinnedMeshRenderer skin in skins) + foreach (SkinnedMeshRenderer skin in avatar.GetAllAvatarRendererComponents()) { if (!skin.gameObject.activeInHierarchy) { @@ -46,33 +45,7 @@ public static SkinnedMeshRenderer TryToGetHandRenderer(UxrAvatar avatar, UxrHand foreach (Transform bone in avatar.GetHand(handSide).Transforms) { - Transform[] skinBones = skin.bones; - - if (skinBones.Contains(bone)) - { - int boneIndex = skinBones.IndexOf(bone); - BoneWeight[] boneWeights = skin.sharedMesh.boneWeights; - - foreach (BoneWeight boneWeight in boneWeights) - { - if (boneWeight.boneIndex0 == boneIndex && boneWeight.weight0 > SignificantWeightInfluence) - { - influenceCount++; - } - if (boneWeight.boneIndex1 == boneIndex && boneWeight.weight1 > SignificantWeightInfluence) - { - influenceCount++; - } - if (boneWeight.boneIndex2 == boneIndex && boneWeight.weight2 > SignificantWeightInfluence) - { - influenceCount++; - } - if (boneWeight.boneIndex3 == boneIndex && boneWeight.weight3 > SignificantWeightInfluence) - { - influenceCount++; - } - } - } + influenceCount += MeshExt.GetBoneInfluenceVertexCount(skin, bone); } if (influenceCount > maxInfluenceCount) @@ -280,7 +253,7 @@ public static bool TryToResolveHand(UxrAvatarHand hand, Transform root, Transfor /// Tries to infer rig elements by doing some checks on names and bone hierarchy. /// This is useful when we have a rig that has no full humanoid avatar set up on its animator . /// - public static void TryToInferMissingRigElements(UxrAvatarRig rig, SkinnedMeshRenderer[] skins) + public static void TryToInferMissingRigElements(UxrAvatarRig rig, IEnumerable skins) { if (rig != null) { @@ -434,13 +407,10 @@ public static void TryToInferMissingRigElements(UxrAvatarRig rig, SkinnedMeshRen rig.RightArm.Hand.Wrist = TryToResolveBoneUniqueAnd(skins, "wrist", "r"); } - for (int i = 0; i < skins.Length; ++i) + foreach (SkinnedMeshRenderer skin in skins) { - if (skins[i]) - { - TryToResolveArm(rig._leftArm, skins[i]); - TryToResolveArm(rig._rightArm, skins[i]); - } + TryToResolveArm(rig._leftArm, skin); + TryToResolveArm(rig._rightArm, skin); } // Legs @@ -681,19 +651,6 @@ public static bool SetupRigElementsFromAnimator(UxrAvatarRig rig, Animator anima #region Private Methods - /// - /// Checks whether the bone is a valid bone when trying to infer rig elements. - /// - /// Bone to check - /// Whether it is a valid bone - private static bool IsValidAvatarBone(Transform bone) - { - // Is it part of an enabled controller hand or a hand integration? -> Ignore - - UxrControllerHand controllerHand = bone.GetComponentInParent(); - return !(controllerHand != null && controllerHand.enabled) && bone.GetComponentInParent() == null; - } - /// /// Tries to fine a bone with a unique name in the hierarchy. /// @@ -704,12 +661,9 @@ private static bool IsValidAvatarBone(Transform bone) /// /// Different alternative names to use in case isn't found /// The transform or null if it wasn't found or there were two or more candidates - private static Transform TryToResolveBoneUniqueOr(SkinnedMeshRenderer[] skins, string name, params string[] alternatives) + private static Transform TryToResolveBoneUniqueOr(IEnumerable skins, string name, params string[] alternatives) { - Transform candidate; - int candidateOccurrences; - - TryToResolveNonControllerHandBoneUniqueOr(skins, out candidate, out candidateOccurrences, name, alternatives); + TryToResolveNonControllerHandBoneUniqueOr(skins, out Transform candidate, out int candidateOccurrences, name, alternatives); return candidate != null && candidateOccurrences == 1 ? candidate : null; } @@ -725,7 +679,7 @@ private static Transform TryToResolveBoneUniqueOr(SkinnedMeshRenderer[] skins, s /// name, ends with the name or contains the name but always uniquely. /// /// Different alternative names to use in case isn't found - private static void TryToResolveNonControllerHandBoneUniqueOr(SkinnedMeshRenderer[] skins, out Transform candidate, out int candidateCount, string name, params string[] alternatives) + private static void TryToResolveNonControllerHandBoneUniqueOr(IEnumerable skins, out Transform candidate, out int candidateCount, string name, params string[] alternatives) { candidate = null; candidateCount = 0; @@ -734,39 +688,31 @@ private static void TryToResolveNonControllerHandBoneUniqueOr(SkinnedMeshRendere Dictionary dictionaryProcessed = new Dictionary(); - for (int skinIndex = 0; skinIndex < skins.Length; ++skinIndex) + foreach (SkinnedMeshRenderer skin in skins) { - SkinnedMeshRenderer skin = skins[skinIndex]; - - for (int i = 0; i < skin.bones.Length; ++i) + foreach (Transform bone in skin.bones) { - if (dictionaryProcessed.ContainsKey(skin.bones[i])) + if (dictionaryProcessed.ContainsKey(bone)) { continue; } - dictionaryProcessed.Add(skin.bones[i], 1); - // Invalid bone? -> Ignore - - if (!IsValidAvatarBone(skin.bones[i])) - { - continue; - } + dictionaryProcessed.Add(bone, 1); // Look for name or alternatives - string nameToLower = skin.bones[i].name.ToLower(); + string nameToLower = bone.name.ToLower(); if (IsWordEnd(nameToLower, name.ToLower())) { candidateCountClean++; candidateCount = candidateCountClean; - candidate = skin.bones[i]; + candidate = bone; continue; } if (nameToLower.Contains(name.ToLower()) && candidateCountClean == 0) { - candidate = skin.bones[i]; + candidate = bone; candidateCount++; continue; } @@ -775,20 +721,20 @@ private static void TryToResolveNonControllerHandBoneUniqueOr(SkinnedMeshRendere { if (IsWordEnd(nameToLower, alternatives[j].ToLower())) { - if (candidate != skin.bones[i]) + if (candidate != bone) { candidateCountClean++; candidateCount = candidateCountClean; - candidate = skin.bones[i]; + candidate = bone; break; } } else if (nameToLower.Contains(alternatives[j].ToLower()) && candidateCountClean == 0) { - if (candidate != skin.bones[i]) + if (candidate != bone) { candidateCount++; - candidate = skin.bones[i]; + candidate = bone; break; } } @@ -840,12 +786,9 @@ private static bool IsWordEnd(string name, string part) /// . /// /// The transform or null if it wasn't found or there were two or more candidates - private static Transform TryToResolveBoneUniqueAnd(SkinnedMeshRenderer[] skins, string name, params string[] additionalStrings) + private static Transform TryToResolveBoneUniqueAnd(IEnumerable skins, string name, params string[] additionalStrings) { - Transform candidate; - int candidateCount; - - TryToResolveNonControllerHandBoneUniqueAnd(skins, out candidate, out candidateCount, name, additionalStrings); + TryToResolveNonControllerHandBoneUniqueAnd(skins, out Transform candidate, out int candidateCount, name, additionalStrings); return candidate != null && candidateCount == 1 ? candidate : null; } @@ -865,7 +808,7 @@ private static Transform TryToResolveBoneUniqueAnd(SkinnedMeshRenderer[] skins, /// Additional strings that also need to meet the same requirement as /// . /// - private static void TryToResolveNonControllerHandBoneUniqueAnd(SkinnedMeshRenderer[] skins, out Transform candidate, out int candidateCount, string name, params string[] additionalStrings) + private static void TryToResolveNonControllerHandBoneUniqueAnd(IEnumerable skins, out Transform candidate, out int candidateCount, string name, params string[] additionalStrings) { candidate = null; candidateCount = 0; @@ -874,28 +817,20 @@ private static void TryToResolveNonControllerHandBoneUniqueAnd(SkinnedMeshRender Dictionary dictionaryProcessed = new Dictionary(); - for (int skinIndex = 0; skinIndex < skins.Length; ++skinIndex) + foreach (SkinnedMeshRenderer skin in skins) { - SkinnedMeshRenderer skin = skins[skinIndex]; - - for (int i = 0; i < skin.bones.Length; ++i) + foreach (Transform bone in skin.bones) { - if (dictionaryProcessed.ContainsKey(skin.bones[i])) + if (dictionaryProcessed.ContainsKey(bone)) { continue; } - dictionaryProcessed.Add(skin.bones[i], 1); - - // Invalid bone? - if (!IsValidAvatarBone(skin.bones[i])) - { - continue; - } + dictionaryProcessed.Add(bone, 1); // Find occurrences of the given name - int occurrences = skin.bones[i].name.GetOccurrenceCount(name, false); + int occurrences = bone.name.GetOccurrenceCount(name, false); if (occurrences == 0) { @@ -908,7 +843,7 @@ private static void TryToResolveNonControllerHandBoneUniqueAnd(SkinnedMeshRender foreach (string additionalString in additionalStrings) { - int additionalOccurrences = skin.bones[i].name.GetOccurrenceCount(additionalString, false); + int additionalOccurrences = bone.name.GetOccurrenceCount(additionalString, false); if (additionalOccurrences == 0) { @@ -925,24 +860,24 @@ private static void TryToResolveNonControllerHandBoneUniqueAnd(SkinnedMeshRender { if (candidate == null) { - candidate = skin.bones[i]; + candidate = bone; candidateCount = 1; maxOccurrences = occurrences; } - else if (candidate.HasParent(skin.bones[i])) + else if (candidate.HasParent(bone)) { - candidate = skin.bones[i]; + candidate = bone; candidateCount = 1; maxOccurrences = occurrences; } - else if (skin.bones[i].HasParent(candidate)) + else if (bone.HasParent(candidate)) { } else { if (occurrences > maxOccurrences) { - candidate = skin.bones[i]; + candidate = bone; candidateCount = 1; maxOccurrences = occurrences; } @@ -1201,11 +1136,5 @@ private static bool IsBoneInList(SkinnedMeshRenderer skin, Transform transformTo } #endregion - - #region Private Types & Data - - private const float SignificantWeightInfluence = 0.5f; - - #endregion } } \ No newline at end of file diff --git a/Scripts/Avatar/Rig/UxrAvatarRigInfo.cs b/Scripts/Avatar/Rig/UxrAvatarRigInfo.cs index 48f69368..f7ccb77d 100644 --- a/Scripts/Avatar/Rig/UxrAvatarRigInfo.cs +++ b/Scripts/Avatar/Rig/UxrAvatarRigInfo.cs @@ -4,9 +4,9 @@ // // -------------------------------------------------------------------------------------------------------------------- using System; +using System.Collections.Generic; using UltimateXR.Core; using UltimateXR.Core.Math; -using UltimateXR.Extensions.Unity.Math; using UnityEngine; namespace UltimateXR.Avatar.Rig @@ -23,324 +23,78 @@ public class UxrAvatarRigInfo { #region Inspector Properties/Serialized Fields - [SerializeField] private UxrAvatar _avatar; - [SerializeField] private float _leftUpperArmLength; - [SerializeField] private float _rightUpperArmLength; - [SerializeField] private float _leftForearmLength; - [SerializeField] private float _rightForearmLength; - [SerializeField] private UxrUniversalLocalAxes _leftFingerUniversalLocalAxes; - [SerializeField] private UxrUniversalLocalAxes _rightFingerUniversalLocalAxes; - [SerializeField] private UxrUniversalLocalAxes _leftHandUniversalLocalAxes; - [SerializeField] private UxrUniversalLocalAxes _rightHandUniversalLocalAxes; - [SerializeField] private UxrUniversalLocalAxes _leftArmUniversalLocalAxes; - [SerializeField] private UxrUniversalLocalAxes _rightArmUniversalLocalAxes; - [SerializeField] private UxrUniversalLocalAxes _leftForearmUniversalLocalAxes; - [SerializeField] private UxrUniversalLocalAxes _rightForearmUniversalLocalAxes; - [SerializeField] private UxrUniversalLocalAxes _leftClavicleUniversalLocalAxes; - [SerializeField] private UxrUniversalLocalAxes _rightClavicleUniversalLocalAxes; + [SerializeField] private int _version; + [SerializeField] private UxrAvatar _avatar; + [SerializeField] private UxrAvatarArmInfo _leftArmInfo = new UxrAvatarArmInfo(); + [SerializeField] private UxrAvatarArmInfo _rightArmInfo = new UxrAvatarArmInfo(); #endregion #region Public Types & Data /// - /// Gets the left upper arm length. From shoulder to elbow. + /// Enumerates the arm information. /// - public float LeftUpperArmLength => _leftUpperArmLength; - - /// - /// Gets the right upper arm length. From shoulder to elbow. - /// - public float RightUpperArmLength => _rightUpperArmLength; - - /// - /// Gets the left forearm length. From elbow to wrist. - /// - public float LeftForearmLength => _leftForearmLength; - - /// - /// Gets the right forearm length. From elbow to wrist. - /// - public float RightForearmLength => _rightForearmLength; - - /// - /// Gets the universal coordinate system for the left fingers: right = axis around which the finger curls, up = - /// knuckles up, forward = finger direction. - /// - public UxrUniversalLocalAxes LeftFingerUniversalLocalAxes => _leftFingerUniversalLocalAxes; - - /// - /// Gets the universal coordinate system for the right fingers: right = axis around which the finger curls, up = - /// knuckles up, forward = finger direction. - /// - public UxrUniversalLocalAxes RightFingerUniversalLocalAxes => _rightFingerUniversalLocalAxes; - - /// - /// Gets the universal coordinate system for the left hand: right = from wrist to right (thumb direction), up = -palm - /// facing vector, forward = finger direction. - /// - public UxrUniversalLocalAxes LeftHandUniversalLocalAxes => _leftHandUniversalLocalAxes; - - /// - /// Gets the universal coordinate system for the left hand: right = from wrist to right (opposite of thumb direction), - /// up = -palm facing vector, forward = finger direction. - /// - public UxrUniversalLocalAxes RightHandUniversalLocalAxes => _rightHandUniversalLocalAxes; - - /// - /// Gets the universal coordinate system for the left arm: forward is arm->elbow, up is elbow rotation axis - /// - public UxrUniversalLocalAxes LeftArmUniversalLocalAxes => _leftArmUniversalLocalAxes; - - /// - /// Gets the universal coordinate system for the right arm: forward is arm->elbow, up is elbow rotation axis - /// - public UxrUniversalLocalAxes RightArmUniversalLocalAxes => _rightArmUniversalLocalAxes; - - /// - /// Gets the universal coordinate system for the left forearm: forward is arm->hand, up is elbow rotation axis - /// - public UxrUniversalLocalAxes LeftForearmUniversalLocalAxes => _leftForearmUniversalLocalAxes; - - /// - /// Gets the universal coordinate system for the right forearm: forward is arm->hand, up is elbow rotation axis - /// - public UxrUniversalLocalAxes RightForearmUniversalLocalAxes => _rightForearmUniversalLocalAxes; - - /// - /// Gets the universal coordinate system for the left clavicle: forward is clavicle->arm, up is avatar up axis - /// - public UxrUniversalLocalAxes LeftClavicleUniversalLocalAxes => _leftClavicleUniversalLocalAxes; - - /// - /// Gets the universal coordinate system for the right clavicle: forward is clavicle->arm, up is avatar up axis - /// - public UxrUniversalLocalAxes RightClavicleUniversalLocalAxes => _rightClavicleUniversalLocalAxes; - - // Updated every frame - - /// - /// Gets the left wrist torsion info. - /// - public UxrWristTorsionInfo LeftWristTorsionInfo { get; private set; } = new UxrWristTorsionInfo(); - - /// - /// Gets the right wrist torsion info. - /// - public UxrWristTorsionInfo RightWristTorsionInfo { get; private set; } = new UxrWristTorsionInfo(); - - #endregion - - #region Public Methods - - /// - /// Tries to find out which axes are pointing right/up/forward in the hand and finger nodes. These "universal" axes - /// will be used to rotate the nodes, so that any coordinate system can be used no matter how the hand was authored. - /// - /// The hand to compute the axes for - /// Whether it is a left hand or right hand - /// Universal axes for the hand - /// Universal axes for the fingers - /// Boolean telling whether the axes could be solved. If any necessary transform is missing it will fail - public static bool SolveHandAndFingerAxes(UxrAvatarHand hand, UxrHandSide handSide, out UxrUniversalLocalAxes handAxes, out UxrUniversalLocalAxes fingerAxes) + public IEnumerable Arms { - Transform indexProximal = hand.Index.Proximal; - Transform indexDistal = hand.Index.Distal; - Transform middleProximal = hand.Middle.Proximal; - Transform ringProximal = hand.Ring.Proximal; - - handAxes = null; - fingerAxes = null; - - if (!hand.Wrist || !indexProximal || !indexDistal || !middleProximal || !ringProximal) + get { - return false; + yield return _leftArmInfo; + yield return _rightArmInfo; } - - float handCenter = 0.5f; // [0, 1] - - Vector3 handAxesRight = hand.Wrist.InverseTransformDirection(indexProximal.position - middleProximal.position).GetClosestAxis() * (handSide == UxrHandSide.Left ? 1.0f : -1.0f); - Vector3 handAxesForward = hand.Wrist.InverseTransformDirection((Vector3.Lerp(ringProximal.position, middleProximal.position, handCenter) - hand.Wrist.position).normalized); - Vector3 handAxesUp = Vector3.Cross(handAxesForward, handAxesRight).normalized; - handAxesRight = Vector3.Cross(handAxesUp, handAxesForward).normalized; - - handAxes = UxrUniversalLocalAxes.FromAxes(hand.Wrist, handAxesRight, handAxesUp, handAxesForward); - - Vector3 fingerAxesRight = indexProximal.InverseTransformDirection(indexProximal.position - middleProximal.position).GetClosestAxis() * (handSide == UxrHandSide.Left ? 1.0f : -1.0f); - Vector3 fingerAxesForward = indexProximal.InverseTransformDirection(indexDistal.position - indexProximal.position).GetClosestAxis(); - Vector3 fingerAxesUp = Vector3.Cross(fingerAxesForward, fingerAxesRight); - - fingerAxes = UxrUniversalLocalAxes.FromAxes(indexProximal, fingerAxesRight, fingerAxesUp, fingerAxesForward); - - return true; } - /// - /// Gets a hand's universal local axes. - /// - /// Which side to get - /// Hand universal local axes - /// - /// - public UxrUniversalLocalAxes GetHandUniversalLocalAxes(UxrHandSide side) - { - return side == UxrHandSide.Left ? _leftHandUniversalLocalAxes : _rightHandUniversalLocalAxes; - } + #endregion - /// - /// Gets the finger universal local axes. - /// - /// Which side to get - /// Finger universal local axes - /// - /// - public UxrUniversalLocalAxes GetFingerUniversalLocalAxes(UxrHandSide side) - { - return side == UxrHandSide.Left ? _leftFingerUniversalLocalAxes : _rightFingerUniversalLocalAxes; - } + #region Internal Types & Data - /// - /// Gets an arm's universal local axes. - /// - /// Which side to get - /// Arm universal local axes - /// - /// - public UxrUniversalLocalAxes GetArmUniversalLocalAxes(UxrHandSide side) - { - return side == UxrHandSide.Left ? _leftArmUniversalLocalAxes : _rightArmUniversalLocalAxes; - } + internal const int CurrentVersion = 1; /// - /// Gets a forearm's universal local axes. + /// Gets the version this data was serialized for. It allows to control if new data needs to be computed. /// - /// Which side to get - /// Forearm universal local axes - /// - /// - public UxrUniversalLocalAxes GetForearmUniversalLocalAxes(UxrHandSide side) - { - return side == UxrHandSide.Left ? _leftForearmUniversalLocalAxes : _rightForearmUniversalLocalAxes; - } + internal int SerializedVersion => _version; - /// - /// Gets a clavicle's universal local axes. - /// - /// Which side to get - /// Clavicle's universal local axes - /// - /// - public UxrUniversalLocalAxes GetClavicleUniversalLocalAxes(UxrHandSide side) - { - return side == UxrHandSide.Left ? _leftClavicleUniversalLocalAxes : _rightClavicleUniversalLocalAxes; - } - - /// - /// Computes the information of an avatar and a rig. - /// - /// Avatar to compute the information of - /// Rig to compute the information of - public void ComputeFromAvatar(UxrAvatar avatar, UxrAvatarRig rig) - { - _avatar = avatar; - - SolveHandAndFingerAxes(rig.LeftArm.Hand, UxrHandSide.Left, out _leftHandUniversalLocalAxes, out _leftFingerUniversalLocalAxes); - SolveHandAndFingerAxes(rig.RightArm.Hand, UxrHandSide.Right, out _rightHandUniversalLocalAxes, out _rightFingerUniversalLocalAxes); + #endregion - ComputeArmRigInfo(avatar, rig.LeftArm, ref _leftClavicleUniversalLocalAxes, ref _leftArmUniversalLocalAxes, ref _leftForearmUniversalLocalAxes, ref _leftUpperArmLength, ref _leftForearmLength); - ComputeArmRigInfo(avatar, rig.RightArm, ref _rightClavicleUniversalLocalAxes, ref _rightArmUniversalLocalAxes, ref _rightForearmUniversalLocalAxes, ref _rightUpperArmLength, ref _rightForearmLength); - } + #region Public Methods /// - /// Updates information for the current frame. + /// Gets the arm information. /// - public void UpdateInfo() + /// Which side to retrieve + /// Arm information + public UxrAvatarArmInfo GetArmInfo(UxrHandSide side) { - LeftWristTorsionInfo.UpdateInfo(_avatar.AvatarRig.LeftArm.Forearm, _avatar.LeftHandBone, LeftForearmUniversalLocalAxes, LeftHandUniversalLocalAxes); - RightWristTorsionInfo.UpdateInfo(_avatar.AvatarRig.RightArm.Forearm, _avatar.RightHandBone, RightForearmUniversalLocalAxes, RightHandUniversalLocalAxes); + return side == UxrHandSide.Left ? _leftArmInfo : _rightArmInfo; } #endregion - #region Private Methods + #region Internal Methods /// - /// Computes information of the arm. + /// Computes the information of an avatar's rig. /// - /// Avatar whose right arm to compute information of - /// Arm to compute the information of - /// Will return the universal coordinate system for the clavicle - /// Will return the universal coordinate system for the arm - /// Will return the universal coordinate system for the forearm - /// Will return the upper arm length - /// Will return the forearm length - private static void ComputeArmRigInfo(UxrAvatar avatar, - UxrAvatarArm arm, - ref UxrUniversalLocalAxes clavicleLocalAxes, - ref UxrUniversalLocalAxes armLocalAxes, - ref UxrUniversalLocalAxes forearmLocalAxes, - ref float upperArmLength, - ref float forearmLength) + /// Avatar whose rig to compute the information of + internal void Compute(UxrAvatar avatar) { - if (arm.UpperArm != null && arm.Forearm != null && arm.Hand.Wrist != null) - { - Vector3 armForward = (arm.Forearm.position - arm.UpperArm.position).normalized; - Vector3 forearmForward = (arm.Hand.Wrist.position - arm.Forearm.position).normalized; - Vector3 elbowAxis = GetWorldElbowAxis(avatar, arm.Forearm, armForward, forearmForward); - Vector3 armLocalForward = arm.UpperArm.InverseTransformDirection(armForward); - Vector3 armLocalElbowAxis = arm.UpperArm.InverseTransformDirection(elbowAxis); - Vector3 forearmLocalForward = arm.Forearm.InverseTransformDirection(forearmForward); - Vector3 forearmLocalElbowAxis = arm.Forearm.InverseTransformDirection(elbowAxis); - - armLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.UpperArm, armLocalElbowAxis.GetClosestAxis(), armLocalForward); - forearmLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.Forearm, armLocalElbowAxis.GetClosestAxis(), forearmLocalForward); - upperArmLength = Vector3.Distance(arm.UpperArm.position, arm.Forearm.position); - forearmLength = Vector3.Distance(arm.Forearm.position, arm.Hand.Wrist.position); - - if (arm.Clavicle != null) - { - Vector3 clavicleForward = (arm.UpperArm.position - arm.Clavicle.position).normalized; - Vector3 clavicleLocalForwardAxis = arm.Clavicle.InverseTransformDirection(clavicleForward).GetClosestAxis(); - Vector3 clavicleLocalUpAxis = arm.Clavicle.InverseTransformDirection(avatar.transform.up).GetClosestAxis(); + _version = CurrentVersion; + _avatar = avatar; - clavicleLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.Clavicle, clavicleLocalUpAxis, clavicleLocalForwardAxis); - } - } + _leftArmInfo.Compute(avatar, UxrHandSide.Left); + _rightArmInfo.Compute(avatar, UxrHandSide.Right); } /// - /// Gets the outwards-pointing elbow rotation axis in world coordinates. + /// Updates information to the current frame. /// - /// The avatar the arm nodes belong to - /// The arm's forearm transform - /// The arm forward looking vector, the one pointing from shoulder to elbow - /// The forearm forward looking vector, the one pointing from elbow to hand - /// Elbow rotation axis - private static Vector3 GetWorldElbowAxis(UxrAvatar avatar, Transform forearm, Vector3 armForward, Vector3 forearmForward) + internal void UpdateInfo() { - bool isLeft = avatar.transform.InverseTransformPoint(forearm.position).x < 0.0f; - - float elbowAngle = Vector3.Angle(armForward, forearmForward); - Vector3 elbowAxis = Vector3.Cross(forearmForward, armForward).normalized; - - if (elbowAngle > ElbowMinAngleThreshold) - { - elbowAxis = Vector3.up; // Assume T-pose if elbow angle is too small - } - else - { - elbowAxis = isLeft ? -elbowAxis : elbowAxis; - } - - return forearm.TransformDirection(forearm.InverseTransformDirection(elbowAxis).GetClosestAxis()); + GetArmInfo(UxrHandSide.Left).WristTorsionInfo.UpdateInfo(_avatar.AvatarRig.LeftArm.Forearm, _avatar.LeftHandBone, GetArmInfo(UxrHandSide.Left)); + GetArmInfo(UxrHandSide.Right).WristTorsionInfo.UpdateInfo(_avatar.AvatarRig.RightArm.Forearm, _avatar.RightHandBone, GetArmInfo(UxrHandSide.Right)); } #endregion - - #region Private Types & Data - - private const float ElbowMinAngleThreshold = 3.0f; // Minimum angle between arm and forearm to compute elbow axis using cross product - - #endregion } } \ No newline at end of file diff --git a/Scripts/Avatar/Rig/UxrRuntimeFingerDescriptor.cs b/Scripts/Avatar/Rig/UxrRuntimeFingerDescriptor.cs index bd1a21ef..ab199671 100644 --- a/Scripts/Avatar/Rig/UxrRuntimeFingerDescriptor.cs +++ b/Scripts/Avatar/Rig/UxrRuntimeFingerDescriptor.cs @@ -64,8 +64,8 @@ public UxrRuntimeFingerDescriptor(UxrAvatar avatar, UxrHandSide handSide, UxrHan { UxrAvatarHand avatarHand = avatar.GetHand(handSide); UxrAvatarFinger avatarFinger = avatarHand.GetFinger(fingerType); - UxrUniversalLocalAxes handLocalAxes = handSide == UxrHandSide.Left ? avatar.AvatarRigInfo.LeftHandUniversalLocalAxes : avatar.AvatarRigInfo.RightHandUniversalLocalAxes; - UxrUniversalLocalAxes fingerLocalAxes = handSide == UxrHandSide.Left ? avatar.AvatarRigInfo.LeftFingerUniversalLocalAxes : avatar.AvatarRigInfo.RightFingerUniversalLocalAxes; + UxrUniversalLocalAxes handLocalAxes = avatar.AvatarRigInfo.GetArmInfo(handSide).HandUniversalLocalAxes; + UxrUniversalLocalAxes fingerLocalAxes = avatar.AvatarRigInfo.GetArmInfo(handSide).FingerUniversalLocalAxes; UxrFingerDescriptor fingerDescriptor = handDescriptor.GetFinger(fingerType); HasMetacarpalInfo = fingerDescriptor.HasMetacarpalInfo && avatarFinger.Metacarpal != null; diff --git a/Scripts/Avatar/Rig/UxrWristTorsionInfo.cs b/Scripts/Avatar/Rig/UxrWristTorsionInfo.cs index 2b4adfd1..26ce9025 100644 --- a/Scripts/Avatar/Rig/UxrWristTorsionInfo.cs +++ b/Scripts/Avatar/Rig/UxrWristTorsionInfo.cs @@ -3,7 +3,6 @@ // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- -using UltimateXR.Core.Math; using UnityEngine; namespace UltimateXR.Avatar.Rig @@ -42,20 +41,19 @@ public UxrWristTorsionInfo() /// /// Forearm bone /// Hand bone - /// Forearm universal axes - /// Hand universal axes - public void UpdateInfo(Transform forearm, Transform hand, UxrUniversalLocalAxes forearmUniversalLocalAxes, UxrUniversalLocalAxes handUniversalLocalAxes) + /// Arm information + public void UpdateInfo(Transform forearm, Transform hand, UxrAvatarArmInfo armInfo) { - if (hand && forearm && forearmUniversalLocalAxes != null && handUniversalLocalAxes != null) + if (hand && forearm && armInfo.ForearmUniversalLocalAxes != null && armInfo.HandUniversalLocalAxes != null) { - Vector3 currentHandForwardInForearm = forearm.InverseTransformDirection(handUniversalLocalAxes.WorldForward); - Vector3 currentHandUpInForearm = forearm.InverseTransformDirection(handUniversalLocalAxes.WorldUp); - Vector3 currentHandRightInForearm = forearm.InverseTransformDirection(handUniversalLocalAxes.WorldRight); + Vector3 currentHandForwardInForearm = forearm.InverseTransformDirection(armInfo.HandUniversalLocalAxes.WorldForward); + Vector3 currentHandUpInForearm = forearm.InverseTransformDirection(armInfo.HandUniversalLocalAxes.WorldUp); + Vector3 currentHandRightInForearm = forearm.InverseTransformDirection(armInfo.HandUniversalLocalAxes.WorldRight); - currentHandUpInForearm = Vector3.ProjectOnPlane(currentHandUpInForearm, forearmUniversalLocalAxes.LocalForward); - currentHandRightInForearm = Vector3.ProjectOnPlane(currentHandRightInForearm, forearmUniversalLocalAxes.LocalForward); + currentHandUpInForearm = Vector3.ProjectOnPlane(currentHandUpInForearm, armInfo.ForearmUniversalLocalAxes.LocalForward); + currentHandRightInForearm = Vector3.ProjectOnPlane(currentHandRightInForearm, armInfo.ForearmUniversalLocalAxes.LocalForward); - float angleRight = Vector3.SignedAngle(forearmUniversalLocalAxes.LocalRight, currentHandRightInForearm, forearmUniversalLocalAxes.LocalForward); + float angleRight = Vector3.SignedAngle(armInfo.ForearmUniversalLocalAxes.LocalRight, currentHandRightInForearm, armInfo.ForearmUniversalLocalAxes.LocalForward); float angle = angleRight; // Works better than angleUp because of hand movement constraints // Check for twists greater than 180 degrees, to know which way to turn to diff --git a/Scripts/Avatar/UxrAvatar.cs b/Scripts/Avatar/UxrAvatar.cs index f1a56545..22d667bf 100644 --- a/Scripts/Avatar/UxrAvatar.cs +++ b/Scripts/Avatar/UxrAvatar.cs @@ -3,6 +3,7 @@ // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- + using System; using System.Collections.Generic; using System.Linq; @@ -22,7 +23,6 @@ using UltimateXR.Manipulation.HandPoses; using UltimateXR.UI; using UnityEngine; - #if ULTIMATEXR_UNITY_XR_MANAGEMENT using UnityEngine.SpatialTracking; #endif @@ -147,7 +147,19 @@ public static Camera LocalAvatarCamera /// /// Gets the avatar rig information. /// - public UxrAvatarRigInfo AvatarRigInfo => _rigInfo; + public UxrAvatarRigInfo AvatarRigInfo + { + get + { + if (_rigInfo.SerializedVersion != UxrAvatarRigInfo.CurrentVersion) + { + // We have new serialization data from a newer version, so compute it. + CreateRigInfo(); + } + + return _rigInfo; + } + } /// /// Gets the first enabled controller input belonging to the avatar. If there is no enabled @@ -322,12 +334,40 @@ public Camera CameraComponent /// /// Gets the left hand's grabber component. Null if no left component was found. /// - public UxrGrabber LeftGrabber => UxrGrabber.GetComponents(this).FirstOrDefault(g => g.Side == UxrHandSide.Left); + public UxrGrabber LeftGrabber + { + get + { +#if UNITY_EDITOR + + if (Application.isEditor && !Application.isPlaying) + { + return GetComponentsInChildren().FirstOrDefault(g => g.Side == UxrHandSide.Left); + } + +#endif + return UxrGrabber.GetComponents(this).FirstOrDefault(g => g.Side == UxrHandSide.Left); + } + } /// /// Gets the right hand's grabber component. Null if no right component was found. /// - public UxrGrabber RightGrabber => UxrGrabber.GetComponents(this).FirstOrDefault(g => g.Side == UxrHandSide.Right); + public UxrGrabber RightGrabber + { + get + { +#if UNITY_EDITOR + + if (Application.isEditor && !Application.isPlaying) + { + return GetComponentsInChildren().FirstOrDefault(g => g.Side == UxrHandSide.Right); + } + +#endif + return UxrGrabber.GetComponents(this).FirstOrDefault(g => g.Side == UxrHandSide.Right); + } + } /// /// Gets the default hand pose name or null if there isn't any default hand pose set. @@ -373,10 +413,7 @@ public UxrAvatarRenderModes RenderMode // Enable or disable avatar renderers - if (_avatarRenderers != null) - { - _avatarRenderers.ForEach(r => r.enabled = value.HasFlag(UxrAvatarRenderModes.Avatar)); - } + _avatarRenderers?.ForEach(r => r.enabled = value.HasFlag(UxrAvatarRenderModes.Avatar)); // Enable/disable controller 3d models (and controller hands) depending on if their input component is active @@ -596,6 +633,16 @@ public void SetCameraAtFloorLevel() } } + /// + /// Gets the rig information for the given arm. + /// + /// Which arm to get + /// rig information + public UxrAvatarArm GetArm(UxrHandSide handSide) + { + return handSide == UxrHandSide.Left ? AvatarRig.LeftArm : AvatarRig.RightArm; + } + /// /// Gets the rig information for the given hand. /// @@ -660,7 +707,18 @@ public void ClearRigElements() /// public void TryToInferMissingRigElements() { - UxrAvatarRig.TryToInferMissingRigElements(_rig, GetComponentsInChildren(true)); + UxrAvatarRig.TryToInferMissingRigElements(_rig, GetAllAvatarRendererComponents()); + } + + /// + /// Gets all components except those hanging from a object, + /// which are renderers that are not part of the avatar itself but part of the supported input controllers / controller + /// hands that can be rendered too. + /// + public IEnumerable GetAllAvatarRendererComponents() + { + SkinnedMeshRenderer[] skins = GetComponentsInChildren(true); + return skins.Where(s => s.SafeGetComponentInParent() == null); } /// @@ -968,7 +1026,7 @@ public bool SetCurrentHandPose(UxrHandSide handSide, string poseName, float blen { OnHandPoseChanging(avatarHandPoseChangeArgs); } - + handState.SetPose(handPose, blendValue); if (propagateEvents) @@ -1049,8 +1107,8 @@ public void SetCurrentHandPoseImmediately(UxrHandSide handSide, UxrHandPoseAsset { UxrAvatarRig.UpdateHandUsingDescriptor(GetHand(handSide), handPoseAsset.GetHandDescriptor(handSide, handPoseAsset.PoseType, blendPoseType), - AvatarRigInfo.GetHandUniversalLocalAxes(handSide), - AvatarRigInfo.GetFingerUniversalLocalAxes(handSide)); + AvatarRigInfo.GetArmInfo(handSide).HandUniversalLocalAxes, + AvatarRigInfo.GetArmInfo(handSide).FingerUniversalLocalAxes); } #endregion @@ -1151,8 +1209,14 @@ protected override void Awake() } // Try to infer missing elements + TryToInferMissingRigElements(); + if (AvatarRigInfo.SerializedVersion != UxrAvatarRigInfo.CurrentVersion) + { + CreateRigInfo(); + } + // Subscribe to device events foreach (UxrTrackingDevice tracking in UxrTrackingDevice.GetComponents(this, true)) @@ -1322,7 +1386,7 @@ private UxrHandPoseAsset GetDefaultPoseInHierarchy() /// private void CreateRigInfo() { - _rigInfo.ComputeFromAvatar(this, _rig); + _rigInfo.Compute(this); } /// diff --git a/Scripts/Avatar/UxrAvatarMoveEventArgs.cs b/Scripts/Avatar/UxrAvatarMoveEventArgs.cs index c408bcd4..7fa1d88a 100644 --- a/Scripts/Avatar/UxrAvatarMoveEventArgs.cs +++ b/Scripts/Avatar/UxrAvatarMoveEventArgs.cs @@ -3,6 +3,7 @@ // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- +using UltimateXR.Core; using UnityEngine; namespace UltimateXR.Avatar @@ -13,7 +14,7 @@ namespace UltimateXR.Avatar /// /// /// + /// cref="UxrManager.MoveAvatarTo(UltimateXR.Avatar.UxrAvatar,UnityEngine.Vector3,UnityEngine.Vector3,bool)"> /// UxrManager.Instance.MoveAvatarTo /// /// @@ -22,7 +23,7 @@ namespace UltimateXR.Avatar /// /// /// + /// cref="UxrManager.TeleportLocalAvatar"> /// UxrManager.Instance.TeleportLocalAvatar /// /// diff --git a/Scripts/CameraUtils/UxrCameraWallFade.cs b/Scripts/CameraUtils/UxrCameraWallFade.cs index a031f2d0..3a0a9718 100644 --- a/Scripts/CameraUtils/UxrCameraWallFade.cs +++ b/Scripts/CameraUtils/UxrCameraWallFade.cs @@ -71,6 +71,11 @@ public UxrWallFadeMode Mode /// public static bool IsAvatarPeekingThroughGeometry(UxrAvatar avatar) { + if (avatar == null) + { + return false; + } + UxrCameraWallFade wallFade = avatar.CameraComponent != null ? avatar.CameraComponent.GetComponent() : null; return wallFade && wallFade._quadObject.activeSelf; //.IsInsideWall; } diff --git a/Scripts/Core/Components/UxrComponent.cs b/Scripts/Core/Components/UxrComponent.cs index bb39e34f..23e6a44b 100644 --- a/Scripts/Core/Components/UxrComponent.cs +++ b/Scripts/Core/Components/UxrComponent.cs @@ -99,50 +99,60 @@ public abstract class UxrComponent : MonoBehaviour public bool IsApplicationQuitting { get; private set; } /// - /// The value at the moment of + /// Gets the value at the moment of /// public Transform InitialParent { get; private set; } /// - /// The value at the moment of + /// Gets the value at the moment of /// public Vector3 InitialLocalPosition { get; private set; } = Vector3.zero; /// - /// The value at the moment of + /// Gets the value at the moment of /// public Quaternion InitialLocalRotation { get; private set; } = Quaternion.identity; /// - /// The value at the moment of + /// Gets the value at the moment of /// public Vector3 InitialLocalEulerAngles { get; private set; } = Vector3.zero; /// - /// The value at the moment of + /// Gets the value at the moment of /// public Vector3 InitialLocalScale { get; private set; } = Vector3.zero; /// - /// The value at the moment of + /// Gets the value at the moment of /// public Vector3 InitialLossyScale { get; private set; } = Vector3.zero; /// - /// The value at the moment of + /// Gets the value at the moment of /// public Vector3 InitialPosition { get; private set; } = Vector3.zero; /// - /// The value at the moment of + /// Gets the value at the moment of /// public Quaternion InitialRotation { get; private set; } = Quaternion.identity; /// - /// The value at the moment of + /// Gets the value at the moment of /// public Vector3 InitialEulerAngles { get; private set; } = Vector3.zero; + /// + /// Gets the transformation matrix relative to the parent transform at the moment of + /// + public Matrix4x4 InitialRelativeMatrix { get; private set; } = Matrix4x4.identity; + + /// + /// Gets the value at the moment of + /// + public Matrix4x4 InitialLocalToWorldMatrix { get; private set; } = Matrix4x4.identity; + #endregion #region Public Methods @@ -203,6 +213,28 @@ public bool TrySetUniqueId(string newUniqueId) return false; } + /// + /// Caches the data of the GameObject's component. + /// This is called on Awake() but can be called by the user at any point of the program to re-compute the values again + /// using the current state. This can be useful when an object is re-parented and the data using the new parenting + /// is more meaningful. + /// + public void RecomputeInitialTransformData() + { + Transform tf = transform; + InitialParent = tf.parent; + InitialLocalPosition = tf.localPosition; + InitialLocalRotation = tf.localRotation; + InitialLocalEulerAngles = tf.localEulerAngles; + InitialLocalScale = tf.localScale; + InitialLossyScale = tf.lossyScale; + InitialPosition = tf.position; + InitialRotation = tf.rotation; + InitialEulerAngles = tf.eulerAngles; + InitialRelativeMatrix = Matrix4x4.TRS(tf.localPosition, tf.localRotation, tf.localScale); + InitialLocalToWorldMatrix = tf.localToWorldMatrix; + } + /// /// Returns a Unity cached by type given that there is only one in the GameObject. /// If there is more than one, it will return the first that gets. @@ -238,16 +270,7 @@ protected virtual void Awake() { // Store initial transform data - Transform tf = transform; - InitialParent = tf.parent; - InitialLocalPosition = tf.localPosition; - InitialLocalRotation = tf.localRotation; - InitialLocalEulerAngles = tf.localEulerAngles; - InitialLocalScale = tf.localScale; - InitialLossyScale = tf.lossyScale; - InitialPosition = tf.position; - InitialRotation = tf.rotation; - InitialEulerAngles = tf.eulerAngles; + RecomputeInitialTransformData(); // Compute Id using unique scene path and handling collisions @@ -399,7 +422,7 @@ private void OnUniqueIdChanged(string oldId, string newId) /// /// Static dictionary of path collisions so that unique ids are generated. /// - private static Dictionary s_idCollisions = new Dictionary(); + private static readonly Dictionary s_idCollisions = new Dictionary(); /// /// Dictionary of cached components by type. diff --git a/Scripts/Core/IUxrLogger.cs b/Scripts/Core/IUxrLogger.cs new file mode 100644 index 00000000..ce1de796 --- /dev/null +++ b/Scripts/Core/IUxrLogger.cs @@ -0,0 +1,23 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) VRMADA, All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- +namespace UltimateXR.Core +{ + /// + /// Interface for all components that output log messages and want to provide a way to control the amount of + /// information sent. + /// + public interface IUxrLogger + { + #region Public Types & Data + + /// + /// Gets or sets the current log level. This controls the amount of information sent. + /// + public UxrLogLevel LogLevel { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/Scripts/Core/IUxrLogger.cs.meta b/Scripts/Core/IUxrLogger.cs.meta new file mode 100644 index 00000000..210a79cf --- /dev/null +++ b/Scripts/Core/IUxrLogger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7bf26f5c10f88e49b6eed38ff130e65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Core/Math/Editor/UxrAxisPropertyDrawer.cs b/Scripts/Core/Math/Editor/UxrAxisPropertyDrawer.cs index eec3d4db..1b8cfeae 100644 --- a/Scripts/Core/Math/Editor/UxrAxisPropertyDrawer.cs +++ b/Scripts/Core/Math/Editor/UxrAxisPropertyDrawer.cs @@ -49,7 +49,19 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten SerializedProperty propertyAxis = property.FindPropertyRelative("_axis"); - propertyAxis.intValue = EditorGUI.Popup(UxrEditorUtils.GetRect(position, 0), property.displayName, propertyAxis.intValue, AxesAsStrings); + if (property.serializedObject.isEditingMultipleObjects) + { + // Multi-selection doesn't work correctly with PropertyDrawers when not using PropertyFields. Disable UI. + // https://answers.unity.com/questions/1214493/custompropertydrawer-cant-restrict-multi-editing.html + bool isGuiEnabled = GUI.enabled; + GUI.enabled = false; + EditorGUI.PropertyField(UxrEditorUtils.GetRect(position, 0), propertyAxis, label); + GUI.enabled = isGuiEnabled; + } + else + { + propertyAxis.intValue = EditorGUI.Popup(UxrEditorUtils.GetRect(position, 0), label, propertyAxis.intValue, UxrEditorUtils.ToGUIContentArray(AxesAsStrings)); + } } #endregion diff --git a/Scripts/Core/Math/UxrAxis.cs b/Scripts/Core/Math/UxrAxis.cs index 128f32f7..376d294f 100644 --- a/Scripts/Core/Math/UxrAxis.cs +++ b/Scripts/Core/Math/UxrAxis.cs @@ -14,7 +14,7 @@ namespace UltimateXR.Core.Math /// See the UxrAxisPropertyDrawer editor class for the integration with Unity Editor. /// [Serializable] - public class UxrAxis + public class UxrAxis : IEquatable { #region Inspector Properties/Serialized Fields @@ -33,6 +33,11 @@ public class UxrAxis /// public UxrAxis Perpendicular => (_axis + 1) % 3; + /// + /// Returns the other perpendicular axis. + /// + public UxrAxis OtherPerpendicular => (_axis + 2) % 3; + #endregion #region Constructors & Finalizer @@ -43,11 +48,122 @@ public class UxrAxis /// Axis as an integer value public UxrAxis(int axis) { +#if UNITY_EDITOR + + if (axis < 0 || axis > 3) + { + Debug.LogError($"Assigning invalid value to axis: {axis}"); + } + +#endif _axis = Mathf.Clamp(axis, 0, 2); } #endregion + #region Implicit IEquatable + + /// + public bool Equals(UxrAxis other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + if (ReferenceEquals(this, other)) + { + return true; + } + + return _axis == other._axis; + } + + #endregion + + #region Public Overrides object + + /// + public override string ToString() + { + return _axis switch + { + 0 => "Right", + 1 => "Up", + _ => "Forward" + }; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((UxrAxis)obj); + } + + /// + public override int GetHashCode() + { + return _axis; + } + + #endregion + + #region Public Methods + + /// + /// Returns the remaining axis which is not axis1 nor axis2. + /// + /// Axis 1 + /// Axis 2 + /// The remaining axis which is not axis1 nor axis2 + public static UxrAxis OtherThan(UxrAxis axis1, UxrAxis axis2) + { + if (axis1 == axis2) + { + Debug.LogError($"{nameof(UxrAxis)}: Got same axis for {nameof(OtherThan)} (axis1)"); + return axis1.Perpendicular; + } + + int smaller = axis1._axis < axis2._axis ? axis1._axis : axis2._axis; + int bigger = axis1._axis > axis2._axis ? axis1._axis : axis2._axis; + + if (smaller == 0 && bigger == 1) + { + return 2; + } + if (smaller == 0 && bigger == 2) + { + return 1; + } + + return 0; + } + + /// + /// Gets the color representing + /// + /// + /// + public Color GetColor(float alpha) + { + Vector3 axis = this; + return new Color(Mathf.Abs(axis.x), Mathf.Abs(axis.y), Mathf.Abs(axis.z), alpha); + } + + #endregion + #region Event Handling Methods /// @@ -85,6 +201,38 @@ public static implicit operator UxrAxis(int axis) return new UxrAxis(axis); } + /// + /// Equality operator. + /// + /// Operand A + /// Operand B + /// Whether the two operands are equal + public static bool operator ==(UxrAxis a, UxrAxis b) + { + if (ReferenceEquals(a, null) && ReferenceEquals(b, null)) + { + return true; + } + + if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) + { + return false; + } + + return a.Equals(b); + } + + /// + /// Inequality operator. + /// + /// Operand A + /// Operand B + /// Whether the two operands are different + public static bool operator !=(UxrAxis a, UxrAxis b) + { + return !(a == b); + } + /// /// Implicit conversion from an to an integer. /// diff --git a/Scripts/Core/Math/UxrMathUtils.cs b/Scripts/Core/Math/UxrMathUtils.cs index e50bc83c..174124aa 100644 --- a/Scripts/Core/Math/UxrMathUtils.cs +++ b/Scripts/Core/Math/UxrMathUtils.cs @@ -11,12 +11,12 @@ namespace UltimateXR.Core.Math /// /// Contains math computations involving elements that do not belong to a specific class. /// Most math is available through extensions classes in namespaces such as - /// UltimateXR.Extensions.System.Math or - /// UltimateXR.Extensions.Unity.Math. + /// UltimateXR.Extensions.System.Math or + /// UltimateXR.Extensions.Unity.Math. /// Math related to animation is also available through classes in namespaces such as - /// UltimateXR.Animation.IK, - /// UltimateXRAnimation.Interpolation or - /// UltimateXR.Animation.Splines. + /// UltimateXR.Animation.IK, + /// UltimateXRAnimation.Interpolation or + /// UltimateXR.Animation.Splines. /// This class will contain math functionality that cannot be assigned to any extensions class. /// public static class UxrMathUtils diff --git a/Scripts/Core/UxrConstants.Geometry.cs b/Scripts/Core/UxrConstants.Geometry.cs new file mode 100644 index 00000000..cacbc79c --- /dev/null +++ b/Scripts/Core/UxrConstants.Geometry.cs @@ -0,0 +1,27 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) VRMADA, All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- +namespace UltimateXR.Core +{ + public static partial class UxrConstants + { + #region Public Types & Data + + /// + /// Geometry constants. + /// + public static class Geometry + { + #region Public Types & Data + + public const float SignificantBoneWeight = 0.5f; + public const float SmallestBoneWeight = 0.01f; + + #endregion + } + + #endregion + } +} \ No newline at end of file diff --git a/Scripts/Core/UxrConstants.Geometry.cs.meta b/Scripts/Core/UxrConstants.Geometry.cs.meta new file mode 100644 index 00000000..dcb860cb --- /dev/null +++ b/Scripts/Core/UxrConstants.Geometry.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 469b2100d3fca8248bcead3e13d4a2f3 +timeCreated: 1644311409 \ No newline at end of file diff --git a/Scripts/Core/UxrConstants.Hand.cs b/Scripts/Core/UxrConstants.Hand.cs new file mode 100644 index 00000000..58fc991f --- /dev/null +++ b/Scripts/Core/UxrConstants.Hand.cs @@ -0,0 +1,26 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) VRMADA, All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- +namespace UltimateXR.Core +{ + public static partial class UxrConstants + { + #region Public Types & Data + + /// + /// Hand constants. + /// + public static class Hand + { + #region Public Types & Data + + public const float HandWidth = 0.08f; + + #endregion + } + + #endregion + } +} \ No newline at end of file diff --git a/Scripts/Core/UxrConstants.Hand.cs.meta b/Scripts/Core/UxrConstants.Hand.cs.meta new file mode 100644 index 00000000..b66ef96a --- /dev/null +++ b/Scripts/Core/UxrConstants.Hand.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1e3fa063640da40498e6c018b4883d1e +timeCreated: 1644311409 \ No newline at end of file diff --git a/Scripts/Core/UxrConstants.cs b/Scripts/Core/UxrConstants.cs index b3a7e93b..d4e93d84 100644 --- a/Scripts/Core/UxrConstants.cs +++ b/Scripts/Core/UxrConstants.cs @@ -13,13 +13,14 @@ public static partial class UxrConstants #region Public Types & Data public const int MajorVersion = 0; - public const int MinorVersion = 8; - public const int PatchVersion = 4; + public const int MinorVersion = 9; + public const int PatchVersion = 0; public const string CoreModule = "Core"; public const string LocomotionModule = "Locomotion"; public const string ManipulationModule = "Manipulation"; public const string UiModule = "UI"; + public const string WeaponsModule = "Weapons"; public const float TeleportTranslationSeconds = 0.2f; public const float TeleportRotationSeconds = 0.1f; diff --git a/Scripts/Core/UxrManager.cs b/Scripts/Core/UxrManager.cs index 62f75095..be907512 100644 --- a/Scripts/Core/UxrManager.cs +++ b/Scripts/Core/UxrManager.cs @@ -290,11 +290,11 @@ public void RotateAvatar(UxrAvatar avatar, float degrees, bool propagateEvents = /// the user, either other users through the network or other scenarios such as automated replays. /// /// - /// Floor-level position the avatar will be teleported over. The camera position will be on top of the floor position, - /// keeping the original eye-level. + /// World-space floor-level position the avatar will be teleported over. The camera position will be on top of the + /// floor position, keeping the original eye-level. /// /// - /// Rotation the avatar will be teleported to. The camera will point in the rotation's forward direction. + /// World-space rotation the avatar will be teleported to. The camera will point in the rotation's forward direction. /// /// The type of translation to use. By default it will teleport immediately /// @@ -342,25 +342,99 @@ public void TeleportLocalAvatar(Vector3 newFloorPosition, finishedCallback?.Invoke(hasFinished); } + /// + /// Teleports the local while making sure to keep relative position/orientation on moving + /// objects. Some values have a transition before the teleport to avoid motion + /// sickness. On worlds with moving platforms it is important to specify the destination transform so that: + /// + /// Relative position/orientation to the destination is preserved. + /// Optionally the local avatar can be parented to the new destination. + /// + /// The local avatar is the avatar controlled by the user using the headset and input controllers. Non-local avatars + /// are other avatars instantiated in the scene but not controlled by the user, either other users through the network + /// or other scenarios such as automated replays. + /// + /// + /// The object the avatar should keep relative position/orientation to. This should be the moving object the avatar has + /// teleported on top of + /// + /// + /// Whether to parent the avatar to . The avatar should be parented if it's being + /// teleported to a moving hierarchy it is not part of + /// + /// + /// World-space floor-level position the avatar will be teleported over. The camera position will be on top of the + /// floor position, keeping the original eye-level. + /// + /// + /// World-space rotation the avatar will be teleported to. The camera will point in the rotation's forward direction. + /// + /// The type of translation to use. By default it will teleport immediately + /// + /// If has a duration, it will specify how long the + /// teleport transition will take in seconds. By default it is + /// + /// + /// Optional callback executed depending on the teleportation mode: + /// + /// : Right after finishing the teleportation. + /// + /// : When the screen is completely faded out and the avatar has been + /// moved, before fading back in. This can be used to enable/disable/change GameObjects in the scene since the + /// screen at this point is fully rendered using the fade color. + /// + /// : Right after finishing the teleportation. + /// + /// + /// + /// Optional callback executed right after the teleportation finished. It will receive a boolean parameter telling + /// whether the teleport finished completely (true) or was cancelled (false). If a fade effect has been requested, the + /// callback is executed right after the screen has faded back in. + /// + /// Whether to propagate / events + /// Coroutine enumerator + /// + /// If translation mode was specified, the default black fade color can be + /// changed using . + /// + public void TeleportLocalAvatarRelative(Transform referenceTransform, + bool parentToReference, + Vector3 newFloorPosition, + Quaternion newRotation, + UxrTranslationType translationType = UxrTranslationType.Immediate, + float transitionSeconds = UxrConstants.TeleportTranslationSeconds, + Action teleportedCallback = null, + Action finishedCallback = null, + bool propagateEvents = true) + { + if (_teleportCoroutine != null) + { + StopCoroutine(_teleportCoroutine); + } + + Vector3 newRelativeFloorPosition = referenceTransform != null ? referenceTransform.InverseTransformPoint(newFloorPosition) : newFloorPosition; + Quaternion newRelativeRotation = referenceTransform != null ? Quaternion.Inverse(referenceTransform.rotation) * newRotation : newRotation; + bool hasFinished = false; + + _teleportCoroutine = StartCoroutine(TeleportLocalAvatarRelativeCoroutine(referenceTransform, parentToReference, newRelativeFloorPosition, newRelativeRotation, translationType, transitionSeconds, teleportedCallback, () => hasFinished = true, propagateEvents)); + + finishedCallback?.Invoke(hasFinished); + } + /// /// - /// Asynchronous version of - /// - /// TeleportLocalAvatar - /// - /// . + /// Asynchronous version of TeleportLocalAvatar. /// /// Teleports the local . The local avatar is the avatar controlled by the user using the /// headset and input controllers. Non-local avatars are other avatars instantiated in the scene but not controlled by /// the user, either other users through the network or other scenarios such as automated replays. /// /// - /// Floor-level position the avatar will be teleported over. The camera position will be on top of the floor position, - /// keeping the original eye-level. + /// World-space floor-level position the avatar will be teleported over. The camera position will be on top of the + /// floor position, keeping the original eye-level. /// /// - /// Rotation the avatar will be teleported to. The camera will point in the rotation's forward direction. + /// World-space rotation the avatar will be teleported to. The camera will point in the rotation's forward direction. /// /// The type of translation to use. By default it will teleport immediately /// @@ -406,6 +480,77 @@ public async Task TeleportLocalAvatarAsync(Vector3 newFloorPosition, } } + /// + /// + /// Asynchronous version of TeleportLocalAvatar. + /// + /// Teleports the local . The local avatar is the avatar controlled by the user using the + /// headset and input controllers. Non-local avatars are other avatars instantiated in the scene but not controlled by + /// the user, either other users through the network or other scenarios such as automated replays. + /// + /// + /// The object the avatar should keep relative position/orientation to. This should be the moving object the avatar has + /// teleported on top of + /// + /// + /// Whether to parent the avatar to . The avatar should be parented if it's being + /// teleported to a moving hierarchy it is not part of + /// + /// + /// World-space floor-level position the avatar will be teleported over. The camera position will be on top of the + /// floor position, keeping the original eye-level. + /// + /// + /// World-space rotation the avatar will be teleported to. The camera will point in the rotation's forward direction. + /// + /// The type of translation to use. By default it will teleport immediately + /// + /// If has a duration, it will specify how long the + /// teleport transition will take in seconds. By default it is + /// + /// + /// Optional callback executed depending on the teleportation mode: + /// + /// : Right after finishing the teleportation. + /// + /// : When the screen is completely faded out and the avatar has been + /// moved, before fading back in. This can be used to enable/disable/change GameObjects in the scene since the + /// screen at this point is fully rendered using the fade color. + /// + /// : Right after finishing the teleportation. + /// + /// + /// Optional cancellation token that can be used to cancel the task + /// Whether to propagate / events + /// Awaitable that will finish after the avatar was teleported or if it was cancelled + /// Task was canceled using + /// + /// If translation mode was specified, the default black fade color can be + /// changed using . + /// + public async Task TeleportLocalAvatarRelativeAsync(Transform referenceTransform, + bool parentToReference, + Vector3 newFloorPosition, + Quaternion newRotation, + UxrTranslationType translationType = UxrTranslationType.Immediate, + float transitionSeconds = UxrConstants.TeleportTranslationSeconds, + Action teleportedCallback = null, + CancellationToken ct = default, + bool propagateEvents = true) + { + Vector3 newRelativeFloorPosition = referenceTransform != null ? referenceTransform.InverseTransformPoint(newFloorPosition) : newFloorPosition; + Quaternion newRelativeRotation = referenceTransform != null ? Quaternion.Inverse(referenceTransform.rotation) * newRotation : newRotation; + bool hasFinished = false; + + Coroutine teleportCoroutine = StartCoroutine(TeleportLocalAvatarRelativeCoroutine(referenceTransform, parentToReference, newRelativeFloorPosition, newRelativeRotation, translationType, transitionSeconds, teleportedCallback, () => hasFinished = true, propagateEvents)); + await TaskExt.WaitUntil(() => hasFinished, ct); + + if (ct.IsCancellationRequested) + { + StopCoroutine(teleportCoroutine); + } + } + /// /// Rotates the local avatar around its vertical axis, where a positive angle turns it to the right and a negative /// angle to the left. The rotation can be performed in different ways using . @@ -523,6 +668,8 @@ protected override void Awake() /// protected override void OnDestroy() { + base.OnDestroy(); + UxrAvatar.GlobalEnabled -= Avatar_Enabled; SceneManager.sceneLoaded -= SceneManager_SceneLoaded; SceneManager.sceneUnloaded -= SceneManager_SceneUnloaded; @@ -583,6 +730,9 @@ private void LateUpdate() /// /// Public teleporting coroutine that can be yielded from an external coroutine. + /// Teleports the local . The local avatar is the avatar controlled by the user using the + /// headset and input controllers. Non-local avatars are other avatars instantiated in the scene but not controlled by + /// the user, either other users through the network or other scenarios such as automated replays. /// /// /// Floor-level position the avatar will be teleported over. The camera position will be on top of the floor position, @@ -626,25 +776,105 @@ public IEnumerator TeleportLocalAvatarCoroutine(Vector3 newFloorPosit Action teleportedCallback = null, Action finishedCallback = null, bool propagateEvents = true) + { + yield return TeleportLocalAvatarRelativeCoroutine(null, false, newFloorPosition, newRotation, translationType, transitionSeconds, teleportedCallback, finishedCallback, propagateEvents); + } + + /// + /// Public teleporting coroutine that can be yielded from an external coroutine. + /// Teleports the local while making sure to keep relative position/orientation on moving + /// objects. Some values have a transition before the teleport to avoid motion + /// sickness. On worlds with moving platforms it is important to specify the destination transform so that: + /// + /// Relative position/orientation to the destination is preserved. + /// Optionally the local avatar can be parented to the new destination. + /// + /// The local avatar is the avatar controlled by the user using the headset and input controllers. Non-local avatars + /// are other avatars instantiated in the scene but not controlled by the user, either other users through the network + /// or other scenarios such as automated replays. + /// + /// + /// The object the avatar should keep relative position/orientation to. This should be the moving object the avatar has + /// teleported on top of. If null, and + /// will be interpreted as world coordinates. + /// + /// + /// Whether to parent the avatar to . The avatar should be parented if it's being + /// teleported to a moving hierarchy it is not part of + /// + /// + /// New floor-level position the avatar will be teleported over in local + /// coordinates. If is null, coordinates will be interpreted as being in + /// world-space. The camera position will be on top of the floor position, keeping the original eye-level. + /// + /// + /// Local rotation the avatar will be teleported to with respect to . If + /// is null, rotation will be in world-space. The camera will point in the + /// rotation's forward direction. + /// + /// The type of translation to use. By default it will teleport immediately + /// + /// If has a duration, it will specify how long the + /// teleport transition will take in seconds. By default it is + /// + /// + /// Optional callback executed depending on the teleportation mode: + /// + /// : Right after finishing the teleportation. + /// + /// : When the screen is completely faded out and the avatar has been + /// moved, before fading back in. This can be used to enable/disable/change GameObjects in the scene since the + /// screen at this point is fully rendered using the fade color. + /// + /// : Right after finishing the teleportation. + /// + /// + /// + /// Optional callback executed right after the teleportation finished. If a fade effect has been requested, the + /// callback is executed right after the screen has faded back in. + /// + /// Whether to propagate / events + /// Coroutine enumerator + /// + /// If translation mode was specified, the default black fade color can be + /// changed using . + /// + public IEnumerator TeleportLocalAvatarRelativeCoroutine(Transform referenceTransform, + bool parentToReference, + Vector3 newRelativeFloorPosition, + Quaternion newRelativeRotation, + UxrTranslationType translationType = UxrTranslationType.Immediate, + float transitionSeconds = UxrConstants.TeleportTranslationSeconds, + Action teleportedCallback = null, + Action finishedCallback = null, + bool propagateEvents = true) { if (UxrAvatar.LocalAvatar) { - Transform avatarTransform = UxrAvatar.LocalAvatar.transform; - - Vector3 oldAvatarPosition = avatarTransform.position; - Quaternion oldAvatarRotation = avatarTransform.rotation; + Vector3 oldFloorPosition = UxrAvatar.LocalAvatar.CameraFloorPosition; + Quaternion oldFloorRotation = Quaternion.LookRotation(UxrAvatar.LocalAvatar.ProjectedCameraForward); + Quaternion inverseReferenceRotation = referenceTransform != null ? Quaternion.Inverse(referenceTransform.rotation) : Quaternion.identity; + Matrix4x4 inverseReferenceMatrix = referenceTransform != null ? referenceTransform.localToWorldMatrix.inverse : Matrix4x4.identity; + Vector3 oldRelativePosition = inverseReferenceMatrix * oldFloorPosition; + Quaternion oldRelativeRotation = inverseReferenceRotation * oldFloorRotation; void TranslateAvatarInternal(float t = 1.0f) { - Vector3 newPositionInternal = Vector3.Lerp(oldAvatarPosition, newFloorPosition, t); - Quaternion newRotationInternal = oldAvatarRotation; + Vector3 newPos = Vector3.Lerp(oldRelativePosition, newRelativeFloorPosition, t); + Quaternion newRot = oldRelativeRotation; if (Mathf.Approximately(t, 1.0f)) { - newRotationInternal = newRotation; + newRot = newRelativeRotation; } - MoveAvatarTo(UxrAvatar.LocalAvatar, newPositionInternal, newRotationInternal * Vector3.forward, propagateEvents); + if (referenceTransform != null) + { + newPos = referenceTransform.TransformPoint(newPos); + newRot = referenceTransform.rotation * newRot; + } + + MoveAvatarTo(UxrAvatar.LocalAvatar, newPos, newRot * Vector3.forward, propagateEvents); } switch (translationType) @@ -672,6 +902,11 @@ void TranslateAvatarInternal(float t = 1.0f) break; } + + if (parentToReference && referenceTransform != null) + { + UxrAvatar.LocalAvatar.transform.SetParent(referenceTransform); + } } _teleportCoroutine = null; @@ -720,11 +955,11 @@ public IEnumerator RotateLocalAvatarCoroutine(float degrees, { if (UxrAvatar.LocalAvatar) { - Transform avatarTransform = UxrAvatar.LocalAvatar.transform; - Vector3 initialForward = UxrAvatar.LocalAvatar.ProjectedCameraForward; - void RotateAvatarInternal(float t = 1.0f) { + Transform avatarTransform = UxrAvatar.LocalAvatar.transform; + Vector3 initialForward = UxrAvatar.LocalAvatar.ProjectedCameraForward; + MoveAvatarTo(UxrAvatar.LocalAvatar, UxrAvatar.LocalAvatar.CameraFloorPosition, initialForward.GetRotationAround(avatarTransform.up, degrees * t), propagateEvents); } @@ -836,9 +1071,9 @@ private void Avatar_Enabled(UxrAvatar avatar) } } } - } - TryPrecaching(); + TryPrecaching(); + } } /// @@ -962,7 +1197,7 @@ private void PostUpdate() ((IUxrAvatarControllerUpdater)avatarController).UpdateAvatarAnimation(); OnAvatarUpdated(avatar, new UxrAvatarUpdateEventArgs(avatar, UxrUpdateStage.Animation)); } - else if(avatar.AvatarMode == UxrAvatarMode.UpdateExternally) + else if (avatar.AvatarMode == UxrAvatarMode.UpdateExternally) { // This makes sure that hand poses are updated OnAvatarUpdating(avatar, new UxrAvatarUpdateEventArgs(avatar, UxrUpdateStage.Animation)); diff --git a/Scripts/Devices/Integrations/Meta/UxrMetaHandTracking.cs b/Scripts/Devices/Integrations/Meta/UxrMetaHandTracking.cs index fa44430d..d0fa0c5d 100644 --- a/Scripts/Devices/Integrations/Meta/UxrMetaHandTracking.cs +++ b/Scripts/Devices/Integrations/Meta/UxrMetaHandTracking.cs @@ -111,19 +111,20 @@ protected override void UpdateAvatar() SetCalibrationPose(UxrHandSide.Left); } - UxrUniversalLocalAxes leftHandParentAxes = wristLeft.parent == Avatar.AvatarRig.LeftArm.Forearm ? Avatar.AvatarRigInfo.LeftForearmUniversalLocalAxes : Avatar.AvatarRigInfo.LeftHandUniversalLocalAxes; + UxrAvatarArmInfo leftArmInfo = Avatar.AvatarRigInfo.GetArmInfo(UxrHandSide.Left); + UxrUniversalLocalAxes leftHandParentAxes = wristLeft.parent == Avatar.AvatarRig.LeftArm.Forearm ? leftArmInfo.ForearmUniversalLocalAxes : leftArmInfo.HandUniversalLocalAxes; Vector3 sensorLeftPos = Avatar.transform.TransformPoint(_leftHandState.RootPose.Position.FromFlippedZVector3f()); - Quaternion sensorLeftRot = Avatar.transform.rotation * ToCorrectCoordinateSystem(_leftHandState.RootPose.Orientation, FlipMode.FlipZ, _leftHandOculusRotation, leftHandParentAxes, Avatar.AvatarRigInfo.LeftHandUniversalLocalAxes); + Quaternion sensorLeftRot = Avatar.transform.rotation * ToCorrectCoordinateSystem(_leftHandState.RootPose.Orientation, FlipMode.FlipZ, _leftHandOculusRotation, leftHandParentAxes, leftArmInfo.HandUniversalLocalAxes); wristLeft.position = sensorLeftPos; wristLeft.rotation = sensorLeftRot; - UpdateFinger(UxrHandSide.Left, Avatar.LeftHand.Index, OVRPlugin.BoneId.Hand_Index1, 3, _leftFingerOculusRotation, Avatar.AvatarRigInfo.LeftHandUniversalLocalAxes, Avatar.AvatarRigInfo.LeftFingerUniversalLocalAxes); - UpdateFinger(UxrHandSide.Left, Avatar.LeftHand.Middle, OVRPlugin.BoneId.Hand_Middle1, 3, _leftFingerOculusRotation, Avatar.AvatarRigInfo.LeftHandUniversalLocalAxes, Avatar.AvatarRigInfo.LeftFingerUniversalLocalAxes); - UpdateFinger(UxrHandSide.Left, Avatar.LeftHand.Ring, OVRPlugin.BoneId.Hand_Ring1, 3, _leftFingerOculusRotation, Avatar.AvatarRigInfo.LeftHandUniversalLocalAxes, Avatar.AvatarRigInfo.LeftFingerUniversalLocalAxes); - UpdateFinger(UxrHandSide.Left, Avatar.LeftHand.Little, OVRPlugin.BoneId.Hand_Pinky0, 4, _leftFingerOculusRotation, Avatar.AvatarRigInfo.LeftHandUniversalLocalAxes, Avatar.AvatarRigInfo.LeftFingerUniversalLocalAxes); - UpdateFinger(UxrHandSide.Left, Avatar.LeftHand.Thumb, OVRPlugin.BoneId.Hand_Thumb0, 4, _leftFingerOculusRotation, Avatar.AvatarRigInfo.LeftHandUniversalLocalAxes, Avatar.AvatarRigInfo.LeftFingerUniversalLocalAxes); + UpdateFinger(UxrHandSide.Left, Avatar.LeftHand.Index, OVRPlugin.BoneId.Hand_Index1, 3, _leftFingerOculusRotation, leftArmInfo.HandUniversalLocalAxes, leftArmInfo.FingerUniversalLocalAxes); + UpdateFinger(UxrHandSide.Left, Avatar.LeftHand.Middle, OVRPlugin.BoneId.Hand_Middle1, 3, _leftFingerOculusRotation, leftArmInfo.HandUniversalLocalAxes, leftArmInfo.FingerUniversalLocalAxes); + UpdateFinger(UxrHandSide.Left, Avatar.LeftHand.Ring, OVRPlugin.BoneId.Hand_Ring1, 3, _leftFingerOculusRotation, leftArmInfo.HandUniversalLocalAxes, leftArmInfo.FingerUniversalLocalAxes); + UpdateFinger(UxrHandSide.Left, Avatar.LeftHand.Little, OVRPlugin.BoneId.Hand_Pinky0, 4, _leftFingerOculusRotation, leftArmInfo.HandUniversalLocalAxes, leftArmInfo.FingerUniversalLocalAxes); + UpdateFinger(UxrHandSide.Left, Avatar.LeftHand.Thumb, OVRPlugin.BoneId.Hand_Thumb0, 4, _leftFingerOculusRotation, leftArmInfo.HandUniversalLocalAxes, leftArmInfo.FingerUniversalLocalAxes); } if (_isRightHandAvailable && wristRight != null) @@ -133,19 +134,20 @@ protected override void UpdateAvatar() SetCalibrationPose(UxrHandSide.Right); } - UxrUniversalLocalAxes rightHandParentAxes = wristRight.parent == Avatar.AvatarRig.RightArm.Forearm ? Avatar.AvatarRigInfo.RightForearmUniversalLocalAxes : Avatar.AvatarRigInfo.RightHandUniversalLocalAxes; + UxrAvatarArmInfo rightArmInfo = Avatar.AvatarRigInfo.GetArmInfo(UxrHandSide.Right); + UxrUniversalLocalAxes rightHandParentAxes = wristRight.parent == Avatar.AvatarRig.RightArm.Forearm ? rightArmInfo.ForearmUniversalLocalAxes : rightArmInfo.HandUniversalLocalAxes; Vector3 sensorRightPos = Avatar.transform.TransformPoint(_rightHandState.RootPose.Position.FromFlippedZVector3f()); - Quaternion sensorRightRot = Avatar.transform.rotation * ToCorrectCoordinateSystem(_rightHandState.RootPose.Orientation, FlipMode.FlipZ, _rightHandOculusRotation, rightHandParentAxes, Avatar.AvatarRigInfo.RightHandUniversalLocalAxes); + Quaternion sensorRightRot = Avatar.transform.rotation * ToCorrectCoordinateSystem(_rightHandState.RootPose.Orientation, FlipMode.FlipZ, _rightHandOculusRotation, rightHandParentAxes, rightArmInfo.HandUniversalLocalAxes); wristRight.position = sensorRightPos; wristRight.rotation = sensorRightRot; - UpdateFinger(UxrHandSide.Right, Avatar.RightHand.Index, OVRPlugin.BoneId.Hand_Index1, 3, _rightFingerOculusRotation, Avatar.AvatarRigInfo.RightHandUniversalLocalAxes, Avatar.AvatarRigInfo.RightFingerUniversalLocalAxes); - UpdateFinger(UxrHandSide.Right, Avatar.RightHand.Middle, OVRPlugin.BoneId.Hand_Middle1, 3, _rightFingerOculusRotation, Avatar.AvatarRigInfo.RightHandUniversalLocalAxes, Avatar.AvatarRigInfo.RightFingerUniversalLocalAxes); - UpdateFinger(UxrHandSide.Right, Avatar.RightHand.Ring, OVRPlugin.BoneId.Hand_Ring1, 3, _rightFingerOculusRotation, Avatar.AvatarRigInfo.RightHandUniversalLocalAxes, Avatar.AvatarRigInfo.RightFingerUniversalLocalAxes); - UpdateFinger(UxrHandSide.Right, Avatar.RightHand.Little, OVRPlugin.BoneId.Hand_Pinky1, 3, _rightFingerOculusRotation, Avatar.AvatarRigInfo.RightHandUniversalLocalAxes, Avatar.AvatarRigInfo.RightFingerUniversalLocalAxes); - UpdateFinger(UxrHandSide.Right, Avatar.RightHand.Thumb, OVRPlugin.BoneId.Hand_Thumb0, 4, _rightFingerOculusRotation, Avatar.AvatarRigInfo.RightHandUniversalLocalAxes, Avatar.AvatarRigInfo.RightFingerUniversalLocalAxes); + UpdateFinger(UxrHandSide.Right, Avatar.RightHand.Index, OVRPlugin.BoneId.Hand_Index1, 3, _rightFingerOculusRotation, rightArmInfo.HandUniversalLocalAxes, rightArmInfo.FingerUniversalLocalAxes); + UpdateFinger(UxrHandSide.Right, Avatar.RightHand.Middle, OVRPlugin.BoneId.Hand_Middle1, 3, _rightFingerOculusRotation, rightArmInfo.HandUniversalLocalAxes, rightArmInfo.FingerUniversalLocalAxes); + UpdateFinger(UxrHandSide.Right, Avatar.RightHand.Ring, OVRPlugin.BoneId.Hand_Ring1, 3, _rightFingerOculusRotation, rightArmInfo.HandUniversalLocalAxes, rightArmInfo.FingerUniversalLocalAxes); + UpdateFinger(UxrHandSide.Right, Avatar.RightHand.Little, OVRPlugin.BoneId.Hand_Pinky1, 3, _rightFingerOculusRotation, rightArmInfo.HandUniversalLocalAxes, rightArmInfo.FingerUniversalLocalAxes); + UpdateFinger(UxrHandSide.Right, Avatar.RightHand.Thumb, OVRPlugin.BoneId.Hand_Thumb0, 4, _rightFingerOculusRotation, rightArmInfo.HandUniversalLocalAxes, rightArmInfo.FingerUniversalLocalAxes); } #endif diff --git a/Scripts/Devices/UxrControllerInput.cs b/Scripts/Devices/UxrControllerInput.cs index 2a30823d..1bdb861b 100644 --- a/Scripts/Devices/UxrControllerInput.cs +++ b/Scripts/Devices/UxrControllerInput.cs @@ -39,8 +39,6 @@ public abstract partial class UxrControllerInput : UxrAvatarComponent @@ -69,6 +67,11 @@ public static UxrControllerInput Current } } + /// + /// Gets or sets the current log level. This controls the amount of information sent. + /// + public static UxrLogLevel LogLevel { get; set; } = UxrLogLevel.Relevant; + #endregion #region Implicit IUxrControllerInput diff --git a/Scripts/Devices/UxrInputButtons.cs b/Scripts/Devices/UxrInputButtons.cs index d6556255..cf45c54f 100644 --- a/Scripts/Devices/UxrInputButtons.cs +++ b/Scripts/Devices/UxrInputButtons.cs @@ -48,4 +48,4 @@ public enum UxrInputButtons Any = 1 << 31, Everything = ~None } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Scripts/Devices/Visualization/Editor/UxrControllerHandEditor.cs b/Scripts/Devices/Visualization/Editor/UxrControllerHandEditor.cs index c48433cc..77034a1e 100644 --- a/Scripts/Devices/Visualization/Editor/UxrControllerHandEditor.cs +++ b/Scripts/Devices/Visualization/Editor/UxrControllerHandEditor.cs @@ -97,8 +97,8 @@ public override void OnInspectorGUI() { UxrAvatarRig.UpdateHandUsingDescriptor(controllerHand.Hand, selectedHandPose.GetHandDescriptor(handSide, selectedHandPose.PoseType, UxrBlendPoseType.OpenGrip), - avatar.AvatarRigInfo.GetHandUniversalLocalAxes(handSide), - avatar.AvatarRigInfo.GetFingerUniversalLocalAxes(handSide)); + avatar.AvatarRigInfo.GetArmInfo(handSide).HandUniversalLocalAxes, + avatar.AvatarRigInfo.GetArmInfo(handSide).FingerUniversalLocalAxes); } GUI.enabled = true; diff --git a/Scripts/Editor/UxrEditorUtils.UI.cs b/Scripts/Editor/UxrEditorUtils.UI.cs index 59cf55ad..cdb61ec2 100644 --- a/Scripts/Editor/UxrEditorUtils.UI.cs +++ b/Scripts/Editor/UxrEditorUtils.UI.cs @@ -14,6 +14,20 @@ namespace UltimateXR.Editor { public static partial class UxrEditorUtils { + #region Public Types & Data + + /// + /// Default value. + /// + public const float DefaultHandlesAlpha = 0.3f; + + /// + /// Gets or sets the transparency value for handles used in Editor.OnSceneGUI. + /// + public static float HandlesAlpha { get; set; } = DefaultHandlesAlpha; + + #endregion + #region Public Methods /// diff --git a/Scripts/Extensions/System/Collections/ListExt.cs b/Scripts/Extensions/System/Collections/ListExt.cs index 87e4ffaf..867ca7a0 100644 --- a/Scripts/Extensions/System/Collections/ListExt.cs +++ b/Scripts/Extensions/System/Collections/ListExt.cs @@ -22,7 +22,7 @@ public static class ListExt /// List where to look for the item /// Item to look for /// Element type - /// Element index + /// Element index or -1 if not found /// Equals() is used for comparison public static int IndexOf(this IReadOnlyList self, T item) { diff --git a/Scripts/Extensions/System/Math/FloatExt.cs b/Scripts/Extensions/System/Math/FloatExt.cs index ec1a3d3c..ef5775d6 100644 --- a/Scripts/Extensions/System/Math/FloatExt.cs +++ b/Scripts/Extensions/System/Math/FloatExt.cs @@ -38,6 +38,74 @@ public static bool IsAlmostZero(this float self) return Mathf.Approximately(self, 0.0f); } + /// + /// Given a value in degrees, returns the same angle making sure it's in range [-180, 180]. For example, an + /// input of -380.3 would return -20.3. + /// + /// Value to process + /// Degrees in range between [-180, 180] + public static float ToEuler180(this float self) + { + float angle = self % 360.0f; + + if (angle > 180.0f) + { + angle -= 360.0f; + } + else if (angle < -180.0f) + { + angle += 360.0f; + } + + return angle; + } + + /// + /// Clamps a value so that it doesn't go beyond a given range. + /// + /// Value to clamp + /// Minimum value + /// Maximum value + /// Clamped value between [min, max] + public static float Clamp(this ref float self, float min, float max) + { + self = Mathf.Clamp(self, min, max); + return self; + } + + /// + /// Returns a clamped value. + /// + /// Value to clamp + /// Minimum value + /// Maximum value + /// Clamped value between [min, max] + public static float Clamped(this float self, float min, float max) + { + return Mathf.Clamp(self, min, max); + } + + /// + /// Clamps a value to [0.0, 1.0]. + /// + /// Value to clamp + /// Clamped value between [0.0, 1.0] + public static float Clamp(this ref float self) + { + self = Mathf.Clamp01(self); + return self; + } + + /// + /// Returns a clamped value in range [0.0, 1.0]. + /// + /// Value to clamp + /// Clamped value between [0.0, 1.0] + public static float Clamped(this float self) + { + return Mathf.Clamp01(self); + } + #endregion } } \ No newline at end of file diff --git a/Scripts/Extensions/System/Math/IntExt.cs b/Scripts/Extensions/System/Math/IntExt.cs index 44ead1af..968c3a04 100644 --- a/Scripts/Extensions/System/Math/IntExt.cs +++ b/Scripts/Extensions/System/Math/IntExt.cs @@ -3,6 +3,8 @@ // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- +using UnityEngine; + namespace UltimateXR.Extensions.System.Math { /// @@ -78,6 +80,31 @@ public static uint WithoutFlags(this uint self, uint flags) return self & ~flags; } + /// + /// Clamps a value so that it doesn't go beyond a given range. + /// + /// Value to clamp + /// Minimum value + /// Maximum value + /// Clamped value between [min, max] + public static int Clamp(this ref int self, int min, int max) + { + self = Mathf.Clamp(self, min, max); + return self; + } + + /// + /// Returns a clamped value. + /// + /// Value to clamp + /// Minimum value + /// Maximum value + /// Clamped value between [min, max] + public static int Clamped(this int self, int min, int max) + { + return Mathf.Clamp(self, min, max); + } + #endregion } } \ No newline at end of file diff --git a/Scripts/Extensions/Unity/GameObjectExt.cs b/Scripts/Extensions/Unity/GameObjectExt.cs index 043cd3ac..cc44ca9f 100644 --- a/Scripts/Extensions/Unity/GameObjectExt.cs +++ b/Scripts/Extensions/Unity/GameObjectExt.cs @@ -3,9 +3,11 @@ // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- +using System.Collections.Generic; using System.Linq; using UltimateXR.Avatar; using UltimateXR.Extensions.System; +using UltimateXR.Extensions.Unity.Math; using UltimateXR.Manipulation; using UltimateXR.UI.UnityInputModule; using UnityEngine; @@ -148,8 +150,9 @@ public static GameObject CreateGameObjectAndParentSameTransform(GameObject paren public static Vector3 GetGeometricCenter(this GameObject self) { MeshRenderer[] meshRenderers = self.GetComponentsInChildren(); - Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); - Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue); + bool initialized = false; + Vector3 min = Vector3.zero; + Vector3 max = Vector3.zero; if (meshRenderers.Length == 0) { @@ -158,13 +161,82 @@ public static Vector3 GetGeometricCenter(this GameObject self) foreach (MeshRenderer renderer in meshRenderers) { - min = Vector3.Min(min, renderer.bounds.min); - max = Vector3.Max(max, renderer.bounds.max); + if (!initialized) + { + initialized = true; + min = renderer.bounds.min; + max = renderer.bounds.max; + } + else + { + min = Vector3.Min(min, renderer.bounds.min); + max = Vector3.Max(max, renderer.bounds.max); + } } return (min + max) * 0.5f; } + /// + /// Calculates the . The bounds are the 's bounds + /// if there is one in the GameObject. Otherwise it will encapsulate all renderers found in the children. + /// + /// The GameObject whose to get + /// + /// of the GameObject if there is a component in it, or the + /// that encapsulates all children renderers otherwise + /// + public static Bounds GetBounds(this GameObject self) + { + Renderer renderer = self.GetComponent(); + + if (renderer != null) + { + return renderer.bounds; + } + + Renderer[] renderers = self.GetComponentsInChildren(); + + Vector3 min = Vector3Ext.Min(renderers.Select(r => r.bounds.min)); + Vector3 max = Vector3Ext.Max(renderers.Select(r => r.bounds.max)); + + return new Bounds((max - min) * 0.5f, max - min); + } + + /// + /// Calculates the in local space. The bounds are the + /// 's bounds if there is one in the GameObject. Otherwise it will encapsulate all renderers + /// found in the children. + /// If is true, it will also encapsulate all renderers found in + /// the children no matter if the GameObject has a Renderer component or not. + /// + /// The GameObject whose local to get + /// + /// Whether to also encapsulate all renderers found in the children no matter if the + /// GameObject has a Renderer component or not + /// + /// + /// Local of the GameObject if there is a component in it, or the + /// that encapsulates all children renderers otherwise + /// + public static Bounds GetLocalBounds(this GameObject self, bool forceRecurseIntoChildren) + { + Renderer renderer = self.GetComponent(); + + if (renderer != null && !forceRecurseIntoChildren) + { + return renderer.localBounds; + } + + IEnumerable renderers = self.GetComponentsInChildren().Where(r => !r.hideFlags.HasFlag(HideFlags.HideInHierarchy)); + + IEnumerable allMinMaxToLocal = renderers.Select(r => self.transform.InverseTransformPoint(r.transform.TransformPoint(r.localBounds.min))).Concat(renderers.Select(r => self.transform.InverseTransformPoint(r.transform.TransformPoint(r.localBounds.max)))); + Vector3 min = Vector3Ext.Min(allMinMaxToLocal); + Vector3 max = Vector3Ext.Max(allMinMaxToLocal); + + return new Bounds((max + min) * 0.5f, max - min); + } + /// /// Sets the layer of a GameObject and all its children. /// diff --git a/Scripts/Extensions/Unity/Math/Vector3Ext.cs b/Scripts/Extensions/Unity/Math/Vector3Ext.cs index 815795d9..06b28f86 100644 --- a/Scripts/Extensions/Unity/Math/Vector3Ext.cs +++ b/Scripts/Extensions/Unity/Math/Vector3Ext.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using UltimateXR.Extensions.System; +using UltimateXR.Extensions.System.Math; using UnityEngine; namespace UltimateXR.Extensions.Unity.Math @@ -25,6 +26,16 @@ public static class Vector3Ext /// public static ref readonly Vector3 NaN => ref s_nan; + /// + /// Represents the Vector3 with minimum float values per component. + /// + public static ref readonly Vector3 MinValue => ref s_minValue; + + /// + /// Represents the Vector3 with maximum float values per component. + /// + public static ref readonly Vector3 MaxValue => ref s_maxValue; + #endregion #region Public Methods @@ -115,18 +126,7 @@ public static Vector3 ToEuler180(this in Vector3 self) for (int i = 0; i < VectorLength; ++i) { - float angle = self[i] % 360.0f; - - if (angle > 180.0f) - { - angle -= 360.0f; - } - else if (angle < -180.0f) - { - angle += 360.0f; - } - - result[i] = angle; + result[i] = self[i].ToEuler180(); } return result.ToVector3(); @@ -217,6 +217,27 @@ public static Vector3 Inverse(this in Vector3 self) Mathf.Approximately(self.z, 0f) ? 0f : 1f / self.z); } + /// + /// Gets the number of components that are different between two vectors. + /// + /// First vector + /// Second vector + /// The number of components [0, 3] that are different + public static int DifferentComponentCount(Vector3 a, Vector3 b) + { + int count = 0; + + for (int axisIndex = 0; axisIndex < 3; ++axisIndex) + { + if (!Mathf.Approximately(a[axisIndex], b[axisIndex])) + { + count++; + } + } + + return count; + } + /// /// Multiplies two component by component. /// @@ -262,7 +283,7 @@ public static Vector3 ToVector3(this float[] data) /// Tries to parse a from a string. /// /// Source string - /// Parsed vector or NaN if there was an error + /// Parsed vector or if there was an error /// Whether the vector was parsed successfully public static bool TryParse(string s, out Vector3 result) { @@ -362,6 +383,18 @@ public static Vector3 GetPerpendicularVector(this Vector3 vector) return new Vector3(vector.z, 0.0f, -vector.y); } + /// + /// Computes the signed distance from a point to a plane. + /// + /// The point to compute the distance from + /// Point in a plane + /// Plane normal + /// Signed distance from a point to a plane + public static float DistanceToPlane(this Vector3 point, Vector3 planePoint, Vector3 planeNormal) + { + return new Plane(planeNormal, planePoint).GetDistanceToPoint(point); + } + /// /// Computes the distance from a point to a line. /// @@ -632,6 +665,8 @@ public static Vector3 GetRotationAround(this Vector3 point, Vector3 pivot, Vecto private static readonly char[] s_cardinalSeparator = CardinalSeparator.ToCharArray(); private static readonly Vector3 s_nan = float.NaN * Vector3.one; + private static readonly Vector3 s_minValue = new Vector3(float.MinValue, float.MinValue, float.MinValue); + private static readonly Vector3 s_maxValue = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); #endregion } diff --git a/Scripts/Extensions/Unity/Math/Vector3IntExt.cs b/Scripts/Extensions/Unity/Math/Vector3IntExt.cs index 3550c64b..6be809cf 100644 --- a/Scripts/Extensions/Unity/Math/Vector3IntExt.cs +++ b/Scripts/Extensions/Unity/Math/Vector3IntExt.cs @@ -20,14 +20,14 @@ public static class Vector3IntExt #region Public Types & Data /// - /// Representation of the negative infinity vector. + /// Representation of the minimum int values per component. /// - public static ref readonly Vector3Int NegativeInfinity => ref s_negativeInfinity; + public static ref readonly Vector3Int MinValue => ref s_minValue; /// - /// Representation of the positive infinity vector. + /// Representation of the maximum int values per component. /// - public static ref readonly Vector3Int PositiveInfinity => ref s_positiveInfinity; + public static ref readonly Vector3Int MaxValue => ref s_maxValue; #endregion @@ -105,7 +105,7 @@ public static Vector3Int ToVector3Int(this int[] data) /// Tries to parse a from a string. /// /// Source string - /// Parsed vector or if there was an error + /// Parsed vector or if there was an error /// Whether the vector was parsed successfully public static bool TryParse(string s, out Vector3Int result) { @@ -116,7 +116,7 @@ public static bool TryParse(string s, out Vector3Int result) } catch { - result = PositiveInfinity; + result = MaxValue; return false; } } @@ -166,8 +166,8 @@ public static Vector3Int Parse(string s) private const string CardinalSeparator = ","; private static readonly char[] s_cardinalSeparator = CardinalSeparator.ToCharArray(); - private static readonly Vector3Int s_negativeInfinity = int.MinValue * Vector3Int.one; - private static readonly Vector3Int s_positiveInfinity = int.MaxValue * Vector3Int.one; + private static readonly Vector3Int s_minValue = int.MinValue * Vector3Int.one; + private static readonly Vector3Int s_maxValue = int.MaxValue * Vector3Int.one; #endregion } diff --git a/Scripts/Extensions/Unity/Render/MeshExt.cs b/Scripts/Extensions/Unity/Render/MeshExt.cs index c99cead8..534dd48b 100644 --- a/Scripts/Extensions/Unity/Render/MeshExt.cs +++ b/Scripts/Extensions/Unity/Render/MeshExt.cs @@ -3,7 +3,10 @@ // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- +using System.Linq; using UltimateXR.Animation.Splines; +using UltimateXR.Core; +using UltimateXR.Extensions.System.Collections; using UltimateXR.Extensions.Unity.Math; using UnityEngine; @@ -97,6 +100,175 @@ public static Mesh CreateSpline(UxrSpline spline, int subdivisions, int sides, f return splineMesh; } + /// + /// Computes the number of vertices that a bone influences in a skinned mesh. + /// + /// Skinned mesh + /// Bone to check + /// Weight above which will be considered significant influence + /// + /// Number of vertices influenced by with a weight above + /// . + /// + public static int GetBoneInfluenceVertexCount(SkinnedMeshRenderer skin, Transform bone, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight) + { + Transform[] skinBones = skin.bones; + int boneIndex = skinBones.IndexOf(bone); + + if (boneIndex == -1) + { + return 0; + } + + BoneWeight[] boneWeights = skin.sharedMesh.boneWeights; + + return boneWeights.Count(w => HasBoneInfluence(w, boneIndex, weightThreshold)); + } + + /// + /// Computes the number of vertices that a bone influences in a skinned mesh. + /// + /// Skinned mesh + /// Bone to check + /// Weight above which to consider significant influence + /// + /// Number of vertices influenced by with a weight above + /// + /// + public static bool HasBoneInfluence(SkinnedMeshRenderer skin, Transform bone, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight) + { + Transform[] skinBones = skin.bones; + int boneIndex = skinBones.IndexOf(bone); + + if (boneIndex == -1) + { + return false; + } + + BoneWeight[] boneWeights = skin.sharedMesh.boneWeights; + + return boneWeights.Any(w => HasBoneInfluence(w, boneIndex, weightThreshold)); + } + + /// + /// Checks whether a given bone index has influence on a skinned mesh vertex. + /// + /// Vertex's bone weight information + /// Bone index + /// Weight above which will be considered significant influence + /// Whether the bone influences the vertex in a significant amount + public static bool HasBoneInfluence(in BoneWeight boneWeight, int boneIndex, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight) + { + if (boneWeight.boneIndex0 == boneIndex && boneWeight.weight0 > weightThreshold) + { + return true; + } + + if (boneWeight.boneIndex1 == boneIndex && boneWeight.weight1 > weightThreshold) + { + return true; + } + + if (boneWeight.boneIndex2 == boneIndex && boneWeight.weight2 > weightThreshold) + { + return true; + } + + if (boneWeight.boneIndex3 == boneIndex && boneWeight.weight3 > weightThreshold) + { + return true; + } + + return false; + } + + /// + /// Computes the bounding box that contains all the vertices that a bone has influence on in a skinned mesh. The + /// bounding box is computed in local bone space. + /// + /// Skinned mesh + /// Bone to check + /// Weight above which to consider significant influence + /// + /// Bounding box in local coordinates. + /// + public static Bounds GetBoneInfluenceBounds(SkinnedMeshRenderer skin, Transform bone, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight) + { + Transform[] skinBones = skin.bones; + int boneIndex = skinBones.IndexOf(bone); + + if (boneIndex == -1) + { + return new Bounds(); + } + + Vector3[] vertices = skin.sharedMesh.vertices; + BoneWeight[] boneWeights = skin.sharedMesh.boneWeights; + Transform[] bones = skin.bones; + Matrix4x4[] boneBindPoses = skin.sharedMesh.bindposes; + Vector3 min = Vector3.zero; + Vector3 max = Vector3.zero; + bool initialized = false; + + for (int i = 0; i < boneWeights.Length; ++i) + { + if (HasBoneInfluence(boneWeights[i], boneIndex, weightThreshold)) + { + Vector3 localVertex = bones[boneIndex].InverseTransformPoint(GetSkinnedWorldVertex(skin, boneWeights[i], vertices[i], bones, boneBindPoses)); + + if (!initialized) + { + initialized = true; + min = localVertex; + max = localVertex; + } + else + { + min = Vector3Ext.Min(localVertex, min); + max = Vector3Ext.Max(localVertex, max); + } + } + } + + return new Bounds((min + max) * 0.5f, max - min); + } + + /// + /// Gets a skinned vertex in world coordinates. + /// + /// Skin + /// Vertex bone weights info + /// Vertex in local skin coordinates when the skin is in the bind pose + /// Bone list + /// Bone bind poses + /// Vertex in world coordinates + public static Vector3 GetSkinnedWorldVertex(SkinnedMeshRenderer skin, BoneWeight boneWeight, Vector3 vertex, Transform[] bones, Matrix4x4[] boneBindPoses) + { + Vector3 result = Vector3.zero; + + if (boneWeight.weight0 > UxrConstants.Geometry.SmallestBoneWeight) + { + result += bones[boneWeight.boneIndex0].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex0].MultiplyPoint(vertex)) * boneWeight.weight0; + } + + if (boneWeight.weight1 > UxrConstants.Geometry.SmallestBoneWeight) + { + result += bones[boneWeight.boneIndex1].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex1].MultiplyPoint(vertex)) * boneWeight.weight1; + } + + if (boneWeight.weight2 > UxrConstants.Geometry.SmallestBoneWeight) + { + result += bones[boneWeight.boneIndex2].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex2].MultiplyPoint(vertex)) * boneWeight.weight2; + } + + if (boneWeight.weight3 > UxrConstants.Geometry.SmallestBoneWeight) + { + result += bones[boneWeight.boneIndex3].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex3].MultiplyPoint(vertex)) * boneWeight.weight3; + } + + return result; + } + #endregion } } \ No newline at end of file diff --git a/Scripts/Extensions/Unity/TransformExt.cs b/Scripts/Extensions/Unity/TransformExt.cs index 626ab1ba..5e44cba7 100644 --- a/Scripts/Extensions/Unity/TransformExt.cs +++ b/Scripts/Extensions/Unity/TransformExt.cs @@ -11,11 +11,10 @@ using UltimateXR.Extensions.Unity.Math; using UltimateXR.Extensions.Unity.Render; using UnityEngine; -using Object = UnityEngine.Object; namespace UltimateXR.Extensions.Unity { - using UnityObject = Object; + using UnityObject = UnityEngine.Object; /// /// extensions. @@ -296,7 +295,7 @@ public static void ClampPositionToBox(this Transform self, BoxCollider box) /// Pivot separation between the different children /// Axis where the children will be aligned /// Space to use for the alignment axis - /// Transform is null + /// Transform is null public static void AlignChildrenOnAxis(this Transform transform, float padding, Vector3 axis, Space space = Space.World) { if (transform == null) @@ -330,7 +329,7 @@ public static void AlignChildrenOnAxis(this Transform transform, float padding, /// Pivot separation between the different children /// Axis where the children will be aligned /// Space to use for the alignment axis - /// Transform is null + /// Transform is null public static void AlignOnAxis(this IEnumerable transforms, float padding, Vector3 axis, Space space = Space.World) { if (transforms == null) @@ -777,6 +776,98 @@ public static string GetUniqueScenePath(this Transform self) return $"{prePath}/{self.GetSiblingIndex():00}.-{self.name}"; } + /// + /// Gets the parent local to world transform matrix. + /// + /// Transform to get the parent matrix of + /// Parent local-to-world matrix or Identity if it has no parent + public static Matrix4x4 GetParentWorldMatrix(this Transform self) + { + return self.parent != null ? self.parent.localToWorldMatrix : Matrix4x4.identity; + } + + /// + /// Gets the parent rotation or the identity Quaternion if it doesn't exist. + /// + /// Transform to get the parent rotation of + /// Parent rotation or Identity if it has no parent + public static Quaternion GetParentRotation(this Transform self) + { + return self.parent != null ? self.parent.rotation : Quaternion.identity; + } + + /// + /// Gets the given position in local coordinates. If is + /// null, will be returned. + /// + /// Transform to get the local coordinates in + /// Position + /// Coordinates in local space or if is null + public static Vector3 GetLocalPosition(Transform transform, Vector3 position) + { + return transform != null ? transform.InverseTransformPoint(position) : position; + } + + /// + /// Transforms a position to world space coordinates. If is null, + /// will be returned. + /// + /// Transform with the space of the local coordinates + /// Position in local coordinates + /// Coordinates in world space or if is null + public static Vector3 GetWorldPosition(Transform transform, Vector3 localPosition) + { + return transform != null ? transform.TransformPoint(localPosition) : localPosition; + } + + /// + /// Gets the given rotation in local coordinates. If is + /// null, will be returned. + /// + /// Transform to get the local coordinates in + /// Rotation + /// Rotation in local space or if is null + public static Quaternion GetLocalRotation(Transform transform, Quaternion rotation) + { + return transform != null ? Quaternion.Inverse(transform.rotation) : rotation; + } + + /// + /// Transforms a rotation to world space. If is null, + /// will be returned. + /// + /// Transform with the space of the local rotation + /// Local rotation + /// Rotation in world space or if is null + public static Quaternion GetWorldRotation(Transform transform, Quaternion localRotation) + { + return transform != null ? transform.rotation * localRotation : localRotation; + } + + /// + /// Gets the given direction in local coordinates. If is + /// null, will be returned. + /// + /// Transform to get the local coordinates in + /// Direction + /// Direction in local space or if is null + public static Vector3 GetLocalDirection(Transform transform, Vector3 direction) + { + return transform != null ? Quaternion.Inverse(transform.rotation) * direction : direction; + } + + /// + /// Transforms a direction to world space. If is null, + /// will be returned. + /// + /// Transform with the space of the local direction + /// Direction + /// Rotation in world space or if is null + public static Vector3 GetWorldDirection(Transform transform, Vector3 localDirection) + { + return transform != null ? transform.rotation * localDirection : localDirection; + } + /// /// Computes the bounds of all MeshRenderers that hang from a parent transform. /// @@ -784,7 +875,7 @@ public static string GetUniqueScenePath(this Transform self) /// Space in which to retrieve the bounds /// Whether to include inactive MeshRenderers /// Bounds containing all MeshRenderers - /// Transform is null + /// Transform is null public static Bounds CalculateBounds(this Transform self, Space space = Space.World, bool includeInactive = false) { self.ThrowIfNull(nameof(self)); @@ -817,7 +908,7 @@ public static Bounds CalculateBounds(this Transform self, Space space = Space.Wo /// RectTransform to process /// Space in which to retrieve the bounds /// Bounds of the RectTransform - /// Transform is null + /// Transform is null public static Bounds CalculateRectBounds(this RectTransform transform, Space space = Space.World) { if (transform == null) @@ -836,7 +927,7 @@ public static Bounds CalculateRectBounds(this RectTransform transform, Space spa /// BoxCollider to get the bounds of /// Space in which to retrieve the bounds /// BoxCollider bounds - /// BoxCollider is null + /// BoxCollider is null public static Bounds CalculateBounds(this BoxCollider boxCollider, Space space = Space.World) { if (boxCollider == null) @@ -865,7 +956,7 @@ public static Bounds CalculateBounds(this BoxCollider boxCollider, Space space = /// Space in which to retrieve the bounds /// Whether to include inactive MeshRenderers /// Bounds containing all MeshRenderers - /// transforms is null + /// transforms is null public static Bounds CalculateBounds(this IEnumerable transforms, Space space = Space.World, bool includeInactive = false) { if (transforms == null) @@ -906,7 +997,7 @@ public static Bounds CalculateBounds(this IEnumerable transforms, Spa /// Transform to process all children of /// Whether to include inactive MeshRenderers /// Bounds with the largest squared length size vector - /// transform is null + /// transform is null public static Bounds GetWiderBoundsInChildren(this Transform transform, bool includeInactive = false) { if (transform == null) @@ -939,7 +1030,7 @@ public static Bounds GetWiderBoundsInChildren(this Transform transform, bool inc /// Transforms to process all children of /// Whether to include inactive MeshRenderers /// Bounds with the largest squared length size vector - /// transforms is null + /// transforms is null public static Bounds GetWiderBounds(this IEnumerable transforms, bool includeInactive = false) { if (transforms == null) @@ -971,7 +1062,7 @@ public static Bounds GetWiderBounds(this IEnumerable transforms, bool /// Transform applied to the bounds /// Bounds in local space /// Transformed bounds - /// transform is null + /// transform is null public static Bounds TransformBounds(this Transform transform, Bounds localBounds) { if (transform == null) @@ -1001,7 +1092,7 @@ public static Bounds TransformBounds(this Transform transform, Bounds localBound /// Transform defining the local space where to move the bounds to /// Bounds in world space /// Transformed bounds - /// transform is null + /// transform is null public static Bounds InverseTransformBounds(this Transform transform, Bounds worldBounds) { if (transform == null) diff --git a/Scripts/Locomotion/Editor/UxrLocomotionTeleportBaseEditor.cs b/Scripts/Locomotion/Editor/UxrLocomotionTeleportBaseEditor.cs index a9679088..3113a097 100644 --- a/Scripts/Locomotion/Editor/UxrLocomotionTeleportBaseEditor.cs +++ b/Scripts/Locomotion/Editor/UxrLocomotionTeleportBaseEditor.cs @@ -25,6 +25,7 @@ protected virtual void OnEnable() { _propControllerHand = serializedObject.FindProperty("_controllerHand"); _propUseControllerForward = serializedObject.FindProperty("_useControllerForward"); + _propParentToDestination = serializedObject.FindProperty("_parentToDestination"); _propShakeFilter = serializedObject.FindProperty("_shakeFilter"); _propTranslationType = serializedObject.FindProperty("_translationType"); _propFadeTranslationColor = serializedObject.FindProperty("_fadeTranslationColor"); @@ -69,6 +70,7 @@ public override void OnInspectorGUI() { EditorGUILayout.PropertyField(_propControllerHand, ContentControllerHand); EditorGUILayout.PropertyField(_propUseControllerForward, ContentUseControllerForward); + EditorGUILayout.PropertyField(_propParentToDestination, ContentParentToDestination); EditorGUILayout.Slider(_propShakeFilter, 0.0f, 1.0f, ContentShakeFilter); } @@ -173,6 +175,7 @@ public override void OnInspectorGUI() private GUIContent ContentControllerHand { get; } = new GUIContent("Controller Hand", "Which hand controls the input"); private GUIContent ContentUseControllerForward { get; } = new GUIContent("Use Controller Forward", "Will the teleport use the controller's forward vector instead of its own transform forward?"); + private GUIContent ContentParentToDestination { get; } = new GUIContent("Parent To Destination", "Whether to parent the avatar to the destination object after teleport. Use it when building applications with moving vehicles or platforms the avatar can move on, so that the avatar keeps the relative position/orientation after teleporting."); private GUIContent ContentShakeFilter { get; } = new GUIContent("Shake Filter", "The amount of filtering to apply to the hand movement to smooth it out"); private GUIContent ContentTranslationType { get; } = new GUIContent("Translation Type", "Which translation method to use"); private GUIContent ContentFadeTranslationColor { get; } = new GUIContent("Translation Fade Color", "The fade color when Fade translation type is used"); @@ -201,6 +204,7 @@ public override void OnInspectorGUI() private SerializedProperty _propControllerHand; private SerializedProperty _propUseControllerForward; + private SerializedProperty _propParentToDestination; private SerializedProperty _propShakeFilter; private SerializedProperty _propTranslationType; private SerializedProperty _propFadeTranslationColor; diff --git a/Scripts/Locomotion/UxrLocomotion.cs b/Scripts/Locomotion/UxrLocomotion.cs index b492129b..76b951f6 100644 --- a/Scripts/Locomotion/UxrLocomotion.cs +++ b/Scripts/Locomotion/UxrLocomotion.cs @@ -3,9 +3,13 @@ // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- +using System; +using System.Linq; using UltimateXR.Avatar; using UltimateXR.Core; using UltimateXR.Core.Components.Composite; +using UltimateXR.Manipulation; +using UnityEngine; namespace UltimateXR.Locomotion { @@ -74,6 +78,90 @@ protected override void Awake() /// protected abstract void UpdateLocomotion(); + /// + /// Checks whether a raycast has anything that is blocking. It filters out invalid raycasts such as against anything + /// part of the avatar or a grabbed object. + /// + /// The avatar to compute the raycast for + /// Ray origin + /// Ray direction + /// Raycast maximum distance + /// Raycast layer mask + /// Behaviour against trigger colliders + /// Result blocking raycast + /// Whether there is a blocking raycast returned in + protected bool HasBlockingRaycastHit(UxrAvatar avatar, Vector3 origin, Vector3 direction, float maxDistance, int layerMaskRaycast, QueryTriggerInteraction queryTriggerInteraction, out RaycastHit outputHit) + { + RaycastHit[] hits = Physics.RaycastAll(origin, direction.normalized, maxDistance, layerMaskRaycast, queryTriggerInteraction); + return HasBlockingRaycastHit(avatar, hits, out outputHit); + } + + /// + /// Checks whether a capsule cast has anything that is blocking. It filters out invalid positives such as against + /// anything part of the avatar or a grabbed object. + /// + /// The avatar to compute the capsule cast for + /// The center of the sphere at the start of the capsule + /// The center of the sphere at the end of the capsule + /// The radius of the capsule + /// The direction into which to sweep the capsule + /// The max length of the sweep + /// A that is used to selectively ignore colliders when casting a capsule + /// Specifies whether this query should hit Triggers + /// Result blocking raycast + /// Whether there is a blocking raycast returned in + protected bool HasBlockingCapsuleCastHit(UxrAvatar avatar, Vector3 point1, Vector3 point2, float radius, Vector3 direction, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction, out RaycastHit outputHit) + { + RaycastHit[] hits = Physics.CapsuleCastAll(point1, point2, radius, direction, maxDistance, layerMask, queryTriggerInteraction); + return HasBlockingRaycastHit(avatar, hits, out outputHit); + } + + #endregion + + #region Private Methods + + /// + /// Checks whether the given raycast hits have any that are blocking. + /// This method filters out invalid raycasts such as against anything part the avatar or a grabbed object. + /// + /// The avatar the ray-casting was computed for + /// Set of raycast hits to check + /// Result blocking raycast + /// Whether there is a blocking raycast returned in + private bool HasBlockingRaycastHit(UxrAvatar avatar, RaycastHit[] inputHits, out RaycastHit outputHit) + { + bool hasBlockingHit = false; + outputHit = default; + + if (inputHits.Count() > 1) + { + Array.Sort(inputHits, (a, b) => a.distance.CompareTo(b.distance)); + } + + foreach (RaycastHit singleHit in inputHits) + { + if (singleHit.collider.GetComponentInParent() == avatar) + { + // Filter out colliding against part of the avatar + continue; + } + + UxrGrabbableObject grabbableObject = singleHit.collider.GetComponentInParent(); + + if (grabbableObject != null && UxrGrabManager.Instance.IsBeingGrabbedBy(grabbableObject, avatar)) + { + // Filter out colliding against a grabbed object + continue; + } + + outputHit = singleHit; + hasBlockingHit = true; + break; + } + + return hasBlockingHit; + } + #endregion } } \ No newline at end of file diff --git a/Scripts/Locomotion/UxrParentAvatarDestination.cs b/Scripts/Locomotion/UxrParentAvatarDestination.cs new file mode 100644 index 00000000..5134dc8b --- /dev/null +++ b/Scripts/Locomotion/UxrParentAvatarDestination.cs @@ -0,0 +1,42 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) VRMADA, All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- +using UltimateXR.Core.Components; +using UnityEngine; + +namespace UltimateXR.Locomotion +{ + /// + /// Component that tells an avatar should be re-parented to the GameObject whenever any locomotion takes the avatar to + /// the object or any of its children. + /// If a hierarchy contains more than a single , the closest object or parent + /// upwards will be selected. + /// Some components, such as , have a setting that controls the default behaviour ( + /// ). The can in + /// this case be used to override the default behaviour. + /// + public class UxrParentAvatarDestination : UxrComponent + { + #region Inspector Properties/Serialized Fields + + [SerializeField] private bool _parentAvatar; + + #endregion + + #region Public Types & Data + + /// + /// Whether the avatar should be re-parented to the object containing the component whenever a locomotion takes the + /// avatar to the object or any of its children. + /// + public bool ParentAvatar + { + get => _parentAvatar; + set => _parentAvatar = value; + } + + #endregion + } +} \ No newline at end of file diff --git a/Scripts/Locomotion/UxrParentAvatarDestination.cs.meta b/Scripts/Locomotion/UxrParentAvatarDestination.cs.meta new file mode 100644 index 00000000..9a978942 --- /dev/null +++ b/Scripts/Locomotion/UxrParentAvatarDestination.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff0d03b59ffa05f41b5da59946ee5830 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Locomotion/UxrSmoothLocomotion.cs b/Scripts/Locomotion/UxrSmoothLocomotion.cs index e1b00241..82f1049a 100644 --- a/Scripts/Locomotion/UxrSmoothLocomotion.cs +++ b/Scripts/Locomotion/UxrSmoothLocomotion.cs @@ -17,7 +17,8 @@ public class UxrSmoothLocomotion : UxrLocomotion { #region Inspector Properties/Serialized Fields - [Header("General parameters")] [SerializeField] private float _metersPerSecondNormal = 2.0f; + [Header("General parameters")] [SerializeField] private bool _parentToDestination; + [SerializeField] private float _metersPerSecondNormal = 2.0f; [SerializeField] private float _metersPerSecondSprint = 4.0f; [SerializeField] private UxrWalkDirection _walkDirection = UxrWalkDirection.ControllerForward; [SerializeField] private float _rotationDegreesPerSecondNormal = 120.0f; @@ -27,11 +28,12 @@ public class UxrSmoothLocomotion : UxrLocomotion [Header("Input parameters")] [SerializeField] private UxrHandSide _sprintButtonHand = UxrHandSide.Left; [SerializeField] private UxrInputButtons _sprintButton = UxrInputButtons.Joystick; - [Header("Constraints")] [SerializeField] private LayerMask _collisionLayerMask = ~0; - [SerializeField] private float _capsuleRadius = 0.25f; - [SerializeField] private float _maxStepHeight = 0.2f; - [SerializeField] [Range(0.0f, 80.0f)] private float _maxSlopeDegrees = 35.0f; - [SerializeField] private float _stepDistanceCheck = 0.2f; + [Header("Constraints")] [SerializeField] private QueryTriggerInteraction _triggerCollidersInteraction = QueryTriggerInteraction.Ignore; + [SerializeField] private LayerMask _collisionLayerMask = ~0; + [SerializeField] private float _capsuleRadius = 0.25f; + [SerializeField] private float _maxStepHeight = 0.2f; + [SerializeField] [Range(0.0f, 80.0f)] private float _maxSlopeDegrees = 35.0f; + [SerializeField] private float _stepDistanceCheck = 0.2f; #endregion @@ -180,8 +182,11 @@ protected override void UpdateLocomotion() // Rotation. We perform it here since it doesn't require any collision checks. - float rotationSpeed = isSprinting ? _rotationDegreesPerSecondSprint : _rotationDegreesPerSecondNormal; - UxrManager.Instance.RotateAvatar(Avatar, joystickRight.x * rotationSpeed * Time.deltaTime); + if (!Mathf.Approximately(joystickRight.x, 0.0f)) + { + float rotationSpeed = isSprinting ? _rotationDegreesPerSecondSprint : _rotationDegreesPerSecondNormal; + UxrManager.Instance.RotateAvatar(Avatar, joystickRight.x * rotationSpeed * Time.deltaTime); + } UpdateLocomotionPhysics(Time.deltaTime); } @@ -210,10 +215,10 @@ private void UpdateLocomotionPhysics(float deltaTime) Vector3 newRequestedCameraPos = cameraPos + _translationSpeed * deltaTime; - if (!Physics.CapsuleCast(capsuleTop, capsuleBottom, _capsuleRadius, _translationSpeed.normalized, (newRequestedCameraPos - capsuleTop).magnitude, _collisionLayerMask)) + if (!HasBlockingCapsuleCastHit(Avatar, capsuleTop, capsuleBottom, _capsuleRadius, _translationSpeed.normalized, (newRequestedCameraPos - capsuleTop).magnitude, _collisionLayerMask, _triggerCollidersInteraction, out RaycastHit _)) { // Nothing in front. Now check for slope and maximum step height - if (Physics.Raycast(cameraPos + _translationSpeed.normalized * _stepDistanceCheck, -Vector3.up, out RaycastHit hitInfo, cameraHeight + _maxStepHeight, _collisionLayerMask)) + if (HasBlockingRaycastHit(Avatar, cameraPos + _translationSpeed.normalized * _stepDistanceCheck, -Vector3.up, cameraHeight + _maxStepHeight, _collisionLayerMask, _triggerCollidersInteraction, out RaycastHit hitInfo)) { float heightIncrement = hitInfo.point.y - avatarPos.y; float slopeDegrees = Mathf.Atan(heightIncrement / _stepDistanceCheck) * Mathf.Rad2Deg; @@ -225,11 +230,13 @@ private void UpdateLocomotionPhysics(float deltaTime) UxrManager.Instance.TranslateAvatar(Avatar, translation); } + + CheckSetAvatarParent(hitInfo); } else { // No collisions found, just keep walking. Probably to a fall. - UxrManager.Instance.TranslateAvatar(Avatar, _translationSpeed * Time.fixedDeltaTime); + UxrManager.Instance.TranslateAvatar(Avatar, _translationSpeed * deltaTime); } } } @@ -240,23 +247,24 @@ private void UpdateLocomotionPhysics(float deltaTime) { // Falling - if (Physics.Raycast(avatarPos + Vector3.up * SafeFloorDistance, -Vector3.up, out RaycastHit hitInfo, Mathf.Abs(_fallSpeed * Time.fixedDeltaTime) + SafeFloorDistance * 2.0f, _collisionLayerMask)) + if (HasBlockingRaycastHit(Avatar, avatarPos + Vector3.up * SafeFloorDistance, -Vector3.up, Mathf.Abs(_fallSpeed * deltaTime) + SafeFloorDistance * 2.0f, _collisionLayerMask, _triggerCollidersInteraction, out RaycastHit hitInfo)) { // Hit ground _isFalling = false; _fallSpeed = 0.0f; UxrManager.Instance.MoveAvatarTo(Avatar, hitInfo.point.y); + CheckSetAvatarParent(hitInfo); } else { // Keep falling - _fallSpeed += Time.deltaTime * _gravity; + _fallSpeed += deltaTime * _gravity; - UxrManager.Instance.MoveAvatarTo(Avatar, Avatar.transform.position.y + _fallSpeed * Time.fixedDeltaTime); + UxrManager.Instance.MoveAvatarTo(Avatar, Avatar.transform.position.y + _fallSpeed * deltaTime); } } - else if (!_isFalling && !Physics.Raycast(cameraPos, -Vector3.up, cameraPos.y - avatarPos.y + SafeFloorDistance, _collisionLayerMask)) + else if (!_isFalling && !HasBlockingRaycastHit(Avatar, cameraPos, -Vector3.up, cameraPos.y - avatarPos.y + SafeFloorDistance, _collisionLayerMask, _triggerCollidersInteraction, out RaycastHit _)) { // Start falling _isFalling = true; @@ -269,6 +277,18 @@ private void UpdateLocomotionPhysics(float deltaTime) } } + /// + /// Checks whether to parent the avatar to a new transform. + /// + /// Raycast hit information with the potential parent collider + private void CheckSetAvatarParent(RaycastHit hitInfo) + { + if (_parentToDestination && hitInfo.collider.transform != null && Avatar.transform.parent != hitInfo.collider.transform) + { + Avatar.transform.SetParent(hitInfo.collider.transform); + } + } + /// /// Tries to place the user on the ground. /// @@ -279,9 +299,10 @@ private void TryGround() _translationSpeed = Vector3.zero; _fallSpeed = 0.0f; - if (Physics.Raycast(Avatar.transform.position + Vector3.up, -Vector3.up, out RaycastHit hitInfo, 2.0f, _collisionLayerMask, QueryTriggerInteraction.Ignore)) + if (HasBlockingRaycastHit(Avatar, Avatar.transform.position + Vector3.up, -Vector3.up, 2.0f, _collisionLayerMask, _triggerCollidersInteraction, out RaycastHit hitInfo)) { UxrManager.Instance.MoveAvatarTo(Avatar, hitInfo.point); + CheckSetAvatarParent(hitInfo); } } } diff --git a/Scripts/Locomotion/UxrTeleportLocomotion.cs b/Scripts/Locomotion/UxrTeleportLocomotion.cs index 05d4f06f..14b335d4 100644 --- a/Scripts/Locomotion/UxrTeleportLocomotion.cs +++ b/Scripts/Locomotion/UxrTeleportLocomotion.cs @@ -196,12 +196,11 @@ protected override void UpdateTeleportLocomotion() // Process blocking hit. // Use RaycastAll to avoid putting "permitted" objects in between "blocking" objects to teleport through walls or any other cheats. - if (HasBlockingRaycastHit(Physics.RaycastAll(point1, direction, distanceBetweenPoints, LayerMaskRaycast, TriggerCollidersInteraction), out RaycastHit hit)) + if (HasBlockingRaycastHit(point1, direction, distanceBetweenPoints, out RaycastHit hit)) { - endTime = time + deltaTime * (hit.distance / distanceBetweenPoints); - hitSomething = true; - - isValidTeleport = NotifyDestinationRaycast(hit); + endTime = time + deltaTime * (hit.distance / distanceBetweenPoints); + hitSomething = true; + isValidTeleport = NotifyDestinationRaycast(hit, false); break; } } diff --git a/Scripts/Locomotion/UxrTeleportLocomotionBase.cs b/Scripts/Locomotion/UxrTeleportLocomotionBase.cs index 4f2a1a86..21760541 100644 --- a/Scripts/Locomotion/UxrTeleportLocomotionBase.cs +++ b/Scripts/Locomotion/UxrTeleportLocomotionBase.cs @@ -3,7 +3,6 @@ // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- -using System; using System.Collections.Generic; using System.Linq; using UltimateXR.Avatar; @@ -12,7 +11,6 @@ using UltimateXR.Core.Caching; using UltimateXR.Devices; using UltimateXR.Extensions.Unity; -using UltimateXR.Manipulation; using UnityEngine; namespace UltimateXR.Locomotion @@ -26,8 +24,9 @@ public abstract class UxrTeleportLocomotionBase : UxrLocomotion, IUxrPrecacheabl // General parameters - [SerializeField] private UxrHandSide _controllerHand = UxrHandSide.Left; - [SerializeField] private bool _useControllerForward = true; + [SerializeField] private UxrHandSide _controllerHand = UxrHandSide.Left; + [SerializeField] private bool _useControllerForward = true; + [SerializeField] private bool _parentToDestination; [SerializeField] private float _shakeFilter = 0.4f; [SerializeField] private UxrTranslationType _translationType = UxrTranslationType.Fade; [SerializeField] private Color _fadeTranslationColor = Color.black; @@ -330,8 +329,9 @@ protected override void Awake() if (_teleportTarget != null) { _teleportTarget.transform.rotation = Avatar.transform.rotation; - TeleportPosition = Avatar.transform.position; - TeleportDirection = Avatar.ProjectedCameraForward; + TeleportReference = null; + TeleportLocalPosition = Avatar.transform.position; + TeleportLocalDirection = Avatar.ProjectedCameraForward; } _layerMaskRaycast.value = BlockingTargetLayers.value | ValidTargetLayers.value; @@ -432,14 +432,15 @@ protected override void UpdateLocomotion() { Vector3 newPosition = Avatar.CameraFloorPosition - Avatar.ProjectedCameraForward * _backStepDistance; - if (IsValidTeleport(true, ref newPosition, Avatar.transform.up, out bool _)) + if (Physics.Raycast(newPosition + Vector3.up * RaycastAboveGround, -Vector3.up, out RaycastHit backStepRaycast, _maxAllowedHeightDifference > 0.0f ? _maxAllowedHeightDifference : RaycastLongDistance, BlockingTargetLayers, TriggerCollidersInteraction)) { - _isBackStepAvailable = false; - _isValidTeleport = true; - TeleportPosition = newPosition; - TeleportDirection = Avatar.ProjectedCameraForward; - TryTeleportUsingCurrentTarget(); - return; + if (NotifyDestinationRaycast(backStepRaycast, true)) + { + _isBackStepAvailable = false; + TeleportLocalDirection = TransformExt.GetLocalDirection(TeleportReference, Avatar.ProjectedCameraForward); + TryTeleportUsingCurrentTarget(); + return; + } } } @@ -519,7 +520,7 @@ protected bool IsValidTeleport(bool checkBlockingInBetween, ref Vector3 newPosit { Vector3 direction = eyePosEnd - eyePosStart; - if (Physics.Raycast(eyePosStart, direction.normalized, out RaycastHit _, direction.magnitude, BlockingTargetLayers, TriggerCollidersInteraction)) + if (HasBlockingRaycastHit(eyePosStart, direction.normalized, direction.magnitude, out RaycastHit _)) { // There is something blocking in between return false; @@ -593,42 +594,16 @@ protected void CancelOtherTeleportTargets() /// Checks whether the given raycast hits have any that are blocking. A blocking raycast can either be a valid or /// invalid teleport destination depending on many factors. Use to check whether the /// given position is valid. + /// This method filters out invalid raycasts such as against anything part of an avatar or a grabbed object. /// - /// Set of raycast hits to check + /// Ray origin + /// Ray direction + /// Raycast maximum distance /// Result blocking raycast /// Whether there is a blocking raycast returned in - protected bool HasBlockingRaycastHit(RaycastHit[] inputHits, out RaycastHit outputHit) + protected bool HasBlockingRaycastHit(Vector3 origin, Vector3 direction, float maxDistance, out RaycastHit outputHit) { - bool hasBlockingHit = false; - outputHit = default; - - if (inputHits.Count() > 1) - { - Array.Sort(inputHits, (a, b) => a.distance.CompareTo(b.distance)); - } - - foreach (RaycastHit singleHit in inputHits) - { - if (singleHit.collider.GetComponentInParent() != null) - { - // Filter out colliding against part of an avatar - continue; - } - - UxrGrabbableObject grabbableObject = singleHit.collider.GetComponentInParent(); - - if (grabbableObject != null && grabbableObject.IsBeingGrabbed) - { - // Filter out colliding against a grabbed object - continue; - } - - outputHit = singleHit; - hasBlockingHit = true; - break; - } - - return hasBlockingHit; + return HasBlockingRaycastHit(Avatar, origin, direction, maxDistance, LayerMaskRaycast, TriggerCollidersInteraction, out outputHit); } /// @@ -636,12 +611,16 @@ protected bool HasBlockingRaycastHit(RaycastHit[] inputHits, out RaycastHit outp /// sets the appropriate internal state that can later be executed using . /// /// Raycast that will be processed as a potential teleport destination + /// + /// Should it check for blocking elements in a straight line from the current position to the new position? + /// /// Whether the destination is a valid teleport location - protected bool NotifyDestinationRaycast(RaycastHit hit) + protected bool NotifyDestinationRaycast(RaycastHit hit, bool checkBlockingInBetween) { _isValidTeleport = true; - + bool ignoreDestination = hit.collider.GetComponentInParent() != null; + TeleportReference = hit.collider != null ? hit.collider.transform : null; // Check for UxrTeleportSpawnCollider component @@ -655,8 +634,9 @@ protected bool NotifyDestinationRaycast(RaycastHit hit) if (spawnPos != null) { - TeleportPosition = spawnPos.position; - TeleportDirection = Vector3.ProjectOnPlane(spawnPos.forward, Vector3.up); + TeleportReference = spawnPos; + TeleportLocalPosition = TransformExt.GetLocalPosition(TeleportReference, spawnPos.position); + TeleportLocalDirection = TransformExt.GetLocalDirection(TeleportReference, Vector3.ProjectOnPlane(spawnPos.forward, Vector3.up)); EnableTeleportObjects(true, true); } @@ -666,7 +646,7 @@ protected bool NotifyDestinationRaycast(RaycastHit hit) Vector3 teleportPos = hit.point; bool showTarget = true; - _isValidTeleport = IsValidTeleport(false, ref teleportPos, hit.normal, out bool validSlope) && !ignoreDestination; + _isValidTeleport = IsValidTeleport(checkBlockingInBetween, ref teleportPos, hit.normal, out bool validSlope) && !ignoreDestination; if (_isValidTeleport && validSlope) { @@ -685,15 +665,15 @@ protected bool NotifyDestinationRaycast(RaycastHit hit) { // Place target - TeleportPosition = teleportPos; + TeleportLocalPosition = TransformExt.GetLocalPosition(TeleportReference, teleportPos); if (ReorientationType == UxrReorientationType.KeepOrientation) { - TeleportDirection = Avatar.ProjectedCameraForward; + TeleportLocalDirection = TransformExt.GetLocalDirection(TeleportReference, Avatar.ProjectedCameraForward); } else if (ReorientationType == UxrReorientationType.UseTeleportFromToDirection) { - TeleportDirection = Vector3.ProjectOnPlane(TeleportPosition - Avatar.CameraPosition, Vector3.up); + TeleportLocalDirection = TransformExt.GetLocalDirection(TeleportReference, Vector3.ProjectOnPlane(teleportPos - Avatar.CameraPosition, Vector3.up)); } else if (ReorientationType == UxrReorientationType.AllowUserJoystickRedirect) { @@ -701,7 +681,7 @@ protected bool NotifyDestinationRaycast(RaycastHit hit) Vector3 projectedForward = Vector3.ProjectOnPlane(ControllerForward, Vector3.up).normalized; Vector3 joystickDirection = new Vector3(joystickValue.x, 0.0f, joystickValue.y).normalized; - TeleportDirection = Quaternion.LookRotation(projectedForward, Vector3.up) * Quaternion.LookRotation(joystickDirection, Vector3.up) * Vector3.forward; + TeleportLocalDirection = TransformExt.GetLocalDirection(TeleportReference, Quaternion.LookRotation(projectedForward, Vector3.up) * Quaternion.LookRotation(joystickDirection, Vector3.up) * Vector3.forward); } } } @@ -741,24 +721,33 @@ protected void TryTeleportUsingCurrentTarget() UxrManager.Instance.TeleportFadeColor = FadeTranslationColor; } - UxrManager.Instance.TeleportLocalAvatar(TeleportPosition, - Quaternion.LookRotation(TeleportDirection), - _translationType, - TranslationSeconds, - () => - { - if (_lastSpawnCollider != null) - { - _lastSpawnCollider.RaiseTeleported(new UxrAvatarMoveEventArgs(Avatar, avatarPos, avatarRot, TeleportPosition, Quaternion.LookRotation(TeleportDirection, avatarUp))); - } - }, - finished => - { - _isValidTeleport = false; - IsTeleporting = false; - ControllerStart = RawControllerStart; - ControllerForward = RawControllerForward; - }); + bool parentToDestination = ParentToDestination; + + if (TeleportReference != null && TeleportReference.TryGetComponent(out UxrParentAvatarDestination parentAvatarDestination)) + { + parentToDestination = parentAvatarDestination.ParentAvatar; + } + + UxrManager.Instance.TeleportLocalAvatarRelative(TeleportReference, + parentToDestination, + TransformExt.GetWorldPosition(TeleportReference, TeleportLocalPosition), + Quaternion.LookRotation(TransformExt.GetWorldDirection(TeleportReference, TeleportLocalDirection)), + _translationType, + TranslationSeconds, + () => + { + if (_lastSpawnCollider != null) + { + _lastSpawnCollider.RaiseTeleported(new UxrAvatarMoveEventArgs(Avatar, avatarPos, avatarRot, Avatar.CameraFloorPosition, Quaternion.LookRotation(Avatar.ProjectedCameraForward, avatarUp))); + } + }, + finished => + { + _isValidTeleport = false; + IsTeleporting = false; + ControllerStart = RawControllerStart; + ControllerForward = RawControllerForward; + }); } NotifyTeleportSpawnCollider(null); @@ -896,7 +885,7 @@ private void Rotate(float degrees) { if (_lastSpawnCollider != null) { - _lastSpawnCollider.RaiseTeleported(new UxrAvatarMoveEventArgs(Avatar, avatarPos, avatarRot, TeleportPosition, Quaternion.LookRotation(TeleportDirection, avatarUp))); + _lastSpawnCollider.RaiseTeleported(new UxrAvatarMoveEventArgs(Avatar, avatarPos, avatarRot, Avatar.CameraFloorPosition, Quaternion.LookRotation(Avatar.ProjectedCameraForward, avatarUp))); } }, finished => @@ -951,7 +940,7 @@ protected bool IsAllowedToTeleport Vector3 cameraPos = UxrAvatar.LocalAvatar.CameraPosition; Vector3 cameraToController = ControllerStart - cameraPos; - return !HasBlockingRaycastHit(Physics.RaycastAll(cameraPos, cameraToController.normalized, cameraToController.magnitude, LayerMaskRaycast, TriggerCollidersInteraction), out RaycastHit hit); + return !HasBlockingRaycastHit(cameraPos, cameraToController.normalized, cameraToController.magnitude, out RaycastHit hit); } } @@ -960,6 +949,17 @@ protected bool IsAllowedToTeleport /// protected LayerMask LayerMaskRaycast => _layerMaskRaycast; + /// + /// Gets or sets whether to parent the avatar to the destination object () after + /// teleporting. + /// This can also be overriden using a component. + /// + protected bool ParentToDestination + { + get => _parentToDestination; + set => _parentToDestination = value; + } + /// /// Gets or sets whether the component is currently teleporting the avatar. /// @@ -976,36 +976,49 @@ protected bool IsAllowedToTeleport protected Vector3 ControllerForward { get; private set; } /// - /// Gets or sets the current teleport destination. + /// Gets or sets the transform that will be used as reference for and + /// to keep the relative positioning/orientation to while performing potential + /// transitions, such as fades, before the actual teleporting. It is usually assigned the transform of the object that + /// was hit with the destination raycast. + /// The reference transform is used to make teleport transitions work correctly when the avatar is on a moving object. + /// Without it, using absolute position and rotation only, would spawn the avatar with an incorrect offset due to the + /// delay the transition introduces before the teleport. + /// + protected Transform TeleportReference { get; private set; } + + /// + /// Gets or sets the current teleport destination in space. If + /// is null, it will be considered as world-space position. /// - protected Vector3 TeleportPosition + protected Vector3 TeleportLocalPosition { - get => _teleportPosition; + get => _teleportLocalPosition; set { - _teleportPosition = value; + _teleportLocalPosition = value; if (_teleportTarget != null) { - _teleportTarget.transform.position = value + Vector3.up * TargetPlacementAboveHit; - _teleportTarget.OrientArrow(Quaternion.LookRotation(TeleportDirection)); + _teleportTarget.transform.position = TransformExt.GetWorldPosition(TeleportReference, value) + Vector3.up * TargetPlacementAboveHit; + _teleportTarget.OrientArrow(Quaternion.LookRotation(TransformExt.GetWorldDirection(TeleportReference, TeleportLocalDirection))); } } } /// - /// Gets or sets the current teleport direction. + /// Gets or sets the current teleport direction in in space. If + /// is null, it will be considered as world-space rotation. /// - protected Vector3 TeleportDirection + protected Vector3 TeleportLocalDirection { - get => _teleportDirection; + get => _teleportLocalDirection; set { - _teleportDirection = value; + _teleportLocalDirection = value; if (_teleportTarget != null) { - _teleportTarget.OrientArrow(Quaternion.LookRotation(value)); + _teleportTarget.OrientArrow(Quaternion.LookRotation(TeleportReference != null ? TeleportReference.rotation * value : value)); } } } @@ -1080,6 +1093,8 @@ private float RotationSeconds } } + private const float RaycastAboveGround = 0.05f; + private const float RaycastLongDistance = 1000.0f; private const float HeadRadius = 0.2f; private const float DeltaTimeMultiplierFilterMin = 25.0f; private const float DeltaTimeMultiplierFilterMax = 5.0f; @@ -1091,8 +1106,8 @@ private float RotationSeconds private bool _isBackStepAvailable; private bool _isValidTeleport; - private Vector3 _teleportPosition; - private Vector3 _teleportDirection; + private Vector3 _teleportLocalPosition; + private Vector3 _teleportLocalDirection; private LayerMask _layerMaskRaycast = 0; private UxrTeleportTarget _teleportTarget; private UxrTeleportSpawnCollider _lastSpawnCollider; diff --git a/Scripts/Manipulation/Editor/UxrGrabbableObjectEditor.cs b/Scripts/Manipulation/Editor/UxrGrabbableObjectEditor.cs index 7376cba4..07889efe 100644 --- a/Scripts/Manipulation/Editor/UxrGrabbableObjectEditor.cs +++ b/Scripts/Manipulation/Editor/UxrGrabbableObjectEditor.cs @@ -10,8 +10,10 @@ using UltimateXR.Avatar.Controllers; using UltimateXR.Avatar.Editor; using UltimateXR.Core; +using UltimateXR.Core.Math; using UltimateXR.Editor; using UltimateXR.Extensions.Unity; +using UltimateXR.Extensions.Unity.Math; using UltimateXR.Manipulation.HandPoses; using UltimateXR.Manipulation.HandPoses.Editor; using UnityEditor; @@ -111,6 +113,43 @@ public static void ReplacePreviewMesh(UxrGrabbableObject grabbableObject, Mesh o #endregion + #region Internal Methods + + /// + /// Gets the disk radius required to draw a given grabbable object axis rotational constraints using its pre-computed + /// local bounds. + /// + /// Grabbable object + /// Rotation axis + /// Default disk radius if it could not be computed + /// Disk radius + internal static float GetHandlesDiskRadius(UxrGrabbableObject grabbableObject, UxrAxis axis, float defaultDiskRadius) + { + foreach (KeyValuePair openEditor in s_openEditors) + { + foreach (Object targetObject in openEditor.Key.targetObjects) + { + UxrGrabbableObject grabbableObjectTarget = targetObject as UxrGrabbableObject; + + if (grabbableObjectTarget == grabbableObject) + { + if (openEditor.Value._grabbableBounds.TryGetValue(grabbableObject, out Bounds localBounds)) + { + float halfSize1 = Mathf.Max(Mathf.Abs(localBounds.max[axis.Perpendicular]), Mathf.Abs(localBounds.min[axis.Perpendicular])) * grabbableObject.transform.lossyScale[axis.Perpendicular] * 0.5f; + float halfSize2 = Mathf.Max(Mathf.Abs(localBounds.max[axis.OtherPerpendicular]), Mathf.Abs(localBounds.min[axis.OtherPerpendicular])) * grabbableObject.transform.lossyScale[axis.OtherPerpendicular] * 0.5f; + float halfSizeLargest = Mathf.Max(Mathf.Abs(halfSize1), Mathf.Abs(halfSize2)); + float boundsRadius = halfSizeLargest; //Mathf.Sqrt(halfSizeLargest * halfSizeLargest * 2.0f); + return Mathf.Min(boundsRadius * DiskHandleRadiusExpand, MaxDiskHandleRadius); + } + } + } + } + + return defaultDiskRadius; + } + + #endregion + #region Unity /// @@ -120,46 +159,48 @@ private void OnEnable() { Undo.undoRedoPerformed += OnUndoRedo; - _propManipulationMode = serializedObject.FindProperty("_manipulationMode"); - _propIgnoreGrabbableParentDependency = serializedObject.FindProperty("_ignoreGrabbableParentDependency"); - _propControlParentDirection = serializedObject.FindProperty("_controlParentDirection"); - _propPriority = serializedObject.FindProperty("_priority"); - _propRigidBodySource = serializedObject.FindProperty("_rigidBodySource"); - _propRigidBodyDynamicOnRelease = serializedObject.FindProperty("_rigidBodyDynamicOnRelease"); - _propVerticalReleaseMultiplier = serializedObject.FindProperty("_verticalReleaseMultiplier"); - _propHorizontalReleaseMultiplier = serializedObject.FindProperty("_horizontalReleaseMultiplier"); - _propNeedsTwoHandsToRotate = serializedObject.FindProperty("_needsTwoHandsToRotate"); - _propAllowMultiGrab = serializedObject.FindProperty("_allowMultiGrab"); - _propPreviewGrabPosesMode = serializedObject.FindProperty("_previewGrabPosesMode"); - _propPreviewPosesRegenerationType = serializedObject.FindProperty("_previewPosesRegenerationType"); - _propPreviewPosesRegenerationIndex = serializedObject.FindProperty("_previewPosesRegenerationIndex"); - _propFirstGrabPointIsMain = serializedObject.FindProperty("_firstGrabPointIsMain"); - _propGrabPoint = serializedObject.FindProperty(PropertyGrabPoint); - _propAdditionalGrabPoints = serializedObject.FindProperty(PropertyAdditionalGrabPoints); - _propUseParenting = serializedObject.FindProperty("_useParenting"); - _propAutoCreateStartAnchor = serializedObject.FindProperty("_autoCreateStartAnchor"); - _propStartAnchor = serializedObject.FindProperty("_startAnchor"); - _propTag = serializedObject.FindProperty("_tag"); - _propDropAlignTransformUseSelf = serializedObject.FindProperty("_dropAlignTransformUseSelf"); - _propDropAlignTransform = serializedObject.FindProperty("_dropAlignTransform"); - _propDropSnapMode = serializedObject.FindProperty("_dropSnapMode"); - _propDropProximityTransformUseSelf = serializedObject.FindProperty("_dropProximityTransformUseSelf"); - _propDropProximityTransform = serializedObject.FindProperty("_dropProximityTransform"); - _propTranslationConstraintMode = serializedObject.FindProperty("_translationConstraintMode"); - _propRestrictToBox = serializedObject.FindProperty("_restrictToBox"); - _propRestrictToSphere = serializedObject.FindProperty("_restrictToSphere"); - _propTranslationLimitsMin = serializedObject.FindProperty("_translationLimitsMin"); - _propTranslationLimitsMax = serializedObject.FindProperty("_translationLimitsMax"); - _propTranslationLimitsReferenceIsParent = serializedObject.FindProperty("_translationLimitsReferenceIsParent"); - _propTranslationLimitsParent = serializedObject.FindProperty("_translationLimitsParent"); - _propRotationConstraintMode = serializedObject.FindProperty("_rotationConstraintMode"); - _propRotationAngleLimitsMin = serializedObject.FindProperty("_rotationAngleLimitsMin"); - _propRotationAngleLimitsMax = serializedObject.FindProperty("_rotationAngleLimitsMax"); - _propRotationLimitsReferenceIsParent = serializedObject.FindProperty("_rotationLimitsReferenceIsParent"); - _propRotationLimitsParent = serializedObject.FindProperty("_rotationLimitsParent"); - _propLockedGrabReleaseDistance = serializedObject.FindProperty("_lockedGrabReleaseDistance"); - _propTranslationResistance = serializedObject.FindProperty("_translationResistance"); - _propRotationResistance = serializedObject.FindProperty("_rotationResistance"); + _propIgnoreGrabbableParentDependency = serializedObject.FindProperty("_ignoreGrabbableParentDependency"); + _propControlParentDirection = serializedObject.FindProperty("_controlParentDirection"); + + _propPriority = serializedObject.FindProperty("_priority"); + _propAllowMultiGrab = serializedObject.FindProperty("_allowMultiGrab"); + + _propTranslationConstraintMode = serializedObject.FindProperty("_translationConstraintMode"); + _propRestrictToBox = serializedObject.FindProperty("_restrictToBox"); + _propRestrictToSphere = serializedObject.FindProperty("_restrictToSphere"); + _propTranslationLimitsMin = serializedObject.FindProperty("_translationLimitsMin"); + _propTranslationLimitsMax = serializedObject.FindProperty("_translationLimitsMax"); + _propRotationConstraintMode = serializedObject.FindProperty("_rotationConstraintMode"); + _propRotationAngleLimitsMin = serializedObject.FindProperty("_rotationAngleLimitsMin"); + _propRotationAngleLimitsMax = serializedObject.FindProperty("_rotationAngleLimitsMax"); + _propAutoRotationProvider = serializedObject.FindProperty("_autoRotationProvider"); + _propRotationProvider = serializedObject.FindProperty("_rotationProvider"); + _propRotationLongitudinalAxis = serializedObject.FindProperty("_rotationLongitudinalAxis"); + _propNeedsTwoHandsToRotate = serializedObject.FindProperty("_needsTwoHandsToRotate"); + _propLockedGrabReleaseDistance = serializedObject.FindProperty("_lockedGrabReleaseDistance"); + _propTranslationResistance = serializedObject.FindProperty("_translationResistance"); + _propRotationResistance = serializedObject.FindProperty("_rotationResistance"); + + _propRigidBodySource = serializedObject.FindProperty("_rigidBodySource"); + _propRigidBodyDynamicOnRelease = serializedObject.FindProperty("_rigidBodyDynamicOnRelease"); + _propVerticalReleaseMultiplier = serializedObject.FindProperty("_verticalReleaseMultiplier"); + _propHorizontalReleaseMultiplier = serializedObject.FindProperty("_horizontalReleaseMultiplier"); + + _propPreviewGrabPosesMode = serializedObject.FindProperty("_previewGrabPosesMode"); + _propPreviewPosesRegenerationType = serializedObject.FindProperty("_previewPosesRegenerationType"); + _propPreviewPosesRegenerationIndex = serializedObject.FindProperty("_previewPosesRegenerationIndex"); + _propFirstGrabPointIsMain = serializedObject.FindProperty("_firstGrabPointIsMain"); + _propGrabPoint = serializedObject.FindProperty(PropertyGrabPoint); + _propAdditionalGrabPoints = serializedObject.FindProperty(PropertyAdditionalGrabPoints); + _propUseParenting = serializedObject.FindProperty("_useParenting"); + _propAutoCreateStartAnchor = serializedObject.FindProperty("_autoCreateStartAnchor"); + _propStartAnchor = serializedObject.FindProperty("_startAnchor"); + _propTag = serializedObject.FindProperty("_tag"); + _propDropAlignTransformUseSelf = serializedObject.FindProperty("_dropAlignTransformUseSelf"); + _propDropAlignTransform = serializedObject.FindProperty("_dropAlignTransform"); + _propDropSnapMode = serializedObject.FindProperty("_dropSnapMode"); + _propDropProximityTransformUseSelf = serializedObject.FindProperty("_dropProximityTransformUseSelf"); + _propDropProximityTransform = serializedObject.FindProperty("_dropProximityTransform"); // Try to remember the grip selection we have if possible, considering we may start the inspector in multi-editing mode @@ -206,6 +247,22 @@ private void OnEnable() ComputeGrabPoseMeshes(SelectedAvatarForGrips, _propPreviewGrabPosesMode); + // Precompute local grabbable bounds + + _grabbableBounds = new Dictionary(); + + foreach (Object selectedObject in targets) + { + UxrGrabbableObject grabbableObject = selectedObject as UxrGrabbableObject; + + if (grabbableObject == null) + { + continue; + } + + _grabbableBounds.Add(grabbableObject, grabbableObject.gameObject.GetLocalBounds(true)); + } + // Add a static reference so that we can update the grip preview meshes from the Hand Pose Editor if (s_openEditors.ContainsKey(serializedObject) == false) @@ -258,22 +315,20 @@ public override void OnInspectorGUI() } } - // General parameters - UxrGrabbableObject grabbableObject = (UxrGrabbableObject)serializedObject.targetObject; UxrGrabPointShape grabPointShape = grabbableObject.GetComponent(); Transform grabbableParentDependency = grabbableObject.GrabbableParentDependency; + // Grabbable dependency options + if (grabbableParentDependency != null) { EditorGUILayout.Space(); if (_propTranslationConstraintMode.enumValueIndex != (int)UxrTranslationConstraintMode.Free && _propIgnoreGrabbableParentDependency.boolValue == false) { - EditorGUILayout.HelpBox("Object will be constrained by " + grabbableParentDependency.name + " because this object has translation constraints applied. You may even use it to control its direction with the parameter below", - MessageType.Info); - EditorGUILayout.PropertyField(_propControlParentDirection, - new GUIContent("Control " + grabbableParentDependency.name + " Direction", "Allows to control the direction of the parent grabbable object when this object is grabbed")); + EditorGUILayout.HelpBox($"Object will be constrained by {grabbableParentDependency.name} because this object has translation constraints applied. You may even use it to control its direction with the parameter below", MessageType.Info); + EditorGUILayout.PropertyField(_propControlParentDirection, new GUIContent($"Control {grabbableParentDependency.name} Direction", "Allows to control the direction of the parent grabbable object when this object is grabbed")); } if (_propTranslationConstraintMode.enumValueIndex != (int)UxrTranslationConstraintMode.Free) @@ -282,64 +337,144 @@ public override void OnInspectorGUI() } } - EditorGUILayout.Space(); + // General parameters - _foldoutManipulation = UxrEditorUtils.FoldoutStylish("Manipulation", _foldoutManipulation); + EditorGUILayout.Space(); + _foldoutGeneralParameters = UxrEditorUtils.FoldoutStylish("General parameters", _foldoutGeneralParameters); - if (_foldoutManipulation) + if (_foldoutGeneralParameters) { - EditorGUILayout.PropertyField(_propManipulationMode, ContentManipulationMode); + EditorGUILayout.PropertyField(_propPriority, ContentPriority); + + if (_propAdditionalGrabPoints.arraySize > 0 || grabPointShape != null) + { + EditorGUILayout.PropertyField(_propAllowMultiGrab, ContentAllowMultiGrab); + } } + + // Constraints parameters - bool usesGeneralParametersFoldout = false; + EditorGUILayout.Space(); + _foldoutConstraints = UxrEditorUtils.FoldoutStylish("Constraints", _foldoutConstraints); - if (!grabbableObject.UsesGrabbableParentDependency && _propManipulationMode.enumValueIndex == (int)UxrManipulationMode.GrabAndMove) + if (_foldoutConstraints) { - EditorGUILayout.Space(); - _foldoutGeneralParameters = UxrEditorUtils.FoldoutStylish("General parameters", _foldoutGeneralParameters); + EditorGUILayout.PropertyField(_propTranslationConstraintMode, ContentTranslationConstraintMode); - usesGeneralParametersFoldout = true; + if (_propTranslationConstraintMode.enumValueIndex == (int)UxrTranslationConstraintMode.RestrictLocalOffset) + { + EditorGUILayout.PropertyField(_propTranslationLimitsMin, ContentTranslationLimitsMin); + EditorGUILayout.PropertyField(_propTranslationLimitsMax, ContentTranslationLimitsMax); + } + else if (_propTranslationConstraintMode.enumValueIndex == (int)UxrTranslationConstraintMode.RestrictToBox) + { + EditorGUILayout.PropertyField(_propRestrictToBox, ContentRestrictToBox); + } + else if (_propTranslationConstraintMode.enumValueIndex == (int)UxrTranslationConstraintMode.RestrictToSphere) + { + EditorGUILayout.PropertyField(_propRestrictToSphere, ContentRestrictToSphere); + } - if (_foldoutGeneralParameters) + EditorGUILayout.PropertyField(_propRotationConstraintMode, ContentRotationConstraintMode); + + if (_propRotationConstraintMode.enumValueIndex == (int)UxrRotationConstraintMode.RestrictLocalRotation) { - EditorGUILayout.PropertyField(_propPriority, ContentPriority); - EditorGUILayout.PropertyField(_propRigidBodySource, ContentRigidBodySource); + EditorGUILayout.PropertyField(_propRotationAngleLimitsMin, ContentRotationAngleLimitsMin); + EditorGUILayout.PropertyField(_propRotationAngleLimitsMax, ContentRotationAngleLimitsMax); + } - if (_propRigidBodySource.objectReferenceValue != null) + if (_propTranslationConstraintMode.enumValueIndex != (int)UxrTranslationConstraintMode.Free && + _propRotationConstraintMode.enumValueIndex != (int)UxrRotationConstraintMode.Locked && + _propRotationConstraintMode.enumValueIndex != (int)UxrRotationConstraintMode.Free) + { + Vector3 minEuler = _propRotationAngleLimitsMin.vector3Value; + Vector3 maxEuler = _propRotationAngleLimitsMax.vector3Value; + + int rangeOfMotionAxisCount = Vector3Ext.DifferentComponentCount(minEuler, maxEuler); + + if (rangeOfMotionAxisCount > 0) { - EditorGUILayout.PropertyField(_propRigidBodyDynamicOnRelease, ContentRigidBodyDynamicOnRelease); + // Longitudinal axis + + if (rangeOfMotionAxisCount > 1) + { + EditorGUILayout.PropertyField(_propRotationLongitudinalAxis, ContentRotationLongitudinalAxis); + } + + // Rotation provider - if (_propRigidBodyDynamicOnRelease.boolValue) + GameObject selectedAvatar = grabbableObject.Editor_GetSelectedAvatarPrefabForGrips(); + UxrAvatar selectedAvatarComponent = selectedAvatar != null ? selectedAvatar.GetComponent() : null; + + List rotationProviderOptions = new List(); + string autoOption = "Auto"; + Transform rightHandSnapTransform = selectedAvatarComponent != null ? grabbableObject.Editor_GetGrabPointGrabAlignTransform(selectedAvatarComponent, 0, UxrHandSide.Right) : grabbableObject.GetGrabPoint(0).GetGripPoseInfo(0).GripAlignTransformHandRight; + UxrRotationProvider autoRotationProvider = grabbableObject.Editor_GetAutoRotationProvider(rightHandSnapTransform != null ? rightHandSnapTransform.position : grabbableObject.transform.position); + + if (serializedObject.isEditingMultipleObjects) { - EditorGUILayout.PropertyField(_propVerticalReleaseMultiplier, ContentVerticalReleaseMultiplier); - EditorGUILayout.PropertyField(_propHorizontalReleaseMultiplier, ContentHorizontalReleaseMultiplier); + rotationProviderOptions.Add(autoOption); + } + else + { + rotationProviderOptions.Add($"{autoOption} ({autoRotationProvider})"); + } + + foreach (UxrRotationProvider rotationProvider in Enum.GetValues(typeof(UxrRotationProvider))) + { + rotationProviderOptions.Add(rotationProvider.ToString()); + } + + int popupIndex = _propAutoRotationProvider.boolValue ? 0 : _propRotationProvider.enumValueIndex + 1; + + EditorGUI.BeginChangeCheck(); + popupIndex = EditorGUILayout.Popup(ContentRotationProvider, popupIndex, UxrEditorUtils.ToGUIContentArray(rotationProviderOptions)); + if (EditorGUI.EndChangeCheck()) + { + _propAutoRotationProvider.boolValue = popupIndex == 0; } + + _propRotationProvider.enumValueIndex = popupIndex != 0 ? popupIndex - 1 : (int)autoRotationProvider; } } - } - else - { - if (_foldoutManipulation) + + bool rotatesUsingHandPositionAroundPivot = _propTranslationConstraintMode.enumValueIndex != (int)UxrTranslationConstraintMode.Free && _propRotationProvider.enumValueIndex == (int)UxrRotationProvider.HandPositionAroundPivot; + + if (!grabbableObject.UsesGrabbableParentDependency && rotatesUsingHandPositionAroundPivot && _propAdditionalGrabPoints.arraySize > 0) { - EditorGUILayout.PropertyField(_propPriority, ContentPriority); + EditorGUILayout.PropertyField(_propNeedsTwoHandsToRotate, ContentNeedsTwoHandsToRotate); } - } - if (!grabbableObject.UsesGrabbableParentDependency && _propManipulationMode.enumValueIndex == (int)UxrManipulationMode.RotateAroundAxis && _propAdditionalGrabPoints.arraySize > 0) - { - if (_foldoutManipulation) + if ((!rotatesUsingHandPositionAroundPivot && _propAdditionalGrabPoints.arraySize > 0) || !grabbableObject.UsesGrabbableParentDependency) { - EditorGUILayout.PropertyField(_propNeedsTwoHandsToRotate, ContentNeedsTwoHandsToRotate); + EditorGUILayout.PropertyField(_propLockedGrabReleaseDistance, ContentLockedGrabReleaseDistance); } + + EditorGUILayout.Slider(_propTranslationResistance, 0.0f, 1.0f, ContentTranslationResistance); + EditorGUILayout.Slider(_propRotationResistance, 0.0f, 1.0f, ContentRotationResistance); } - if (_propAdditionalGrabPoints.arraySize > 0 || grabPointShape != null) + // Physics parameters + + if (!grabbableObject.UsesGrabbableParentDependency && _propTranslationConstraintMode.enumValueIndex == (int)UxrTranslationConstraintMode.Free) { - bool show = usesGeneralParametersFoldout ? _foldoutGeneralParameters : _foldoutManipulation; + EditorGUILayout.Space(); + _foldoutPhysics = UxrEditorUtils.FoldoutStylish("Physics", _foldoutPhysics); - if (show) + if (_foldoutPhysics) { - EditorGUILayout.PropertyField(_propAllowMultiGrab, ContentAllowMultiGrab); + EditorGUILayout.PropertyField(_propRigidBodySource, ContentRigidBodySource); + + if (_propRigidBodySource.objectReferenceValue != null) + { + EditorGUILayout.PropertyField(_propRigidBodyDynamicOnRelease, ContentRigidBodyDynamicOnRelease); + + if (_propRigidBodyDynamicOnRelease.boolValue) + { + EditorGUILayout.PropertyField(_propVerticalReleaseMultiplier, ContentVerticalReleaseMultiplier); + EditorGUILayout.PropertyField(_propHorizontalReleaseMultiplier, ContentHorizontalReleaseMultiplier); + } + } } } @@ -485,7 +620,7 @@ public override void OnInspectorGUI() // Object placement parameters - if (!grabbableObject.UsesGrabbableParentDependency && _propManipulationMode.enumValueIndex == (int)UxrManipulationMode.GrabAndMove) + if (!grabbableObject.UsesGrabbableParentDependency && _propTranslationConstraintMode.enumValueIndex != (int)UxrTranslationConstraintMode.Locked) { EditorGUILayout.Space(); _foldoutObjectPlacing = UxrEditorUtils.FoldoutStylish("Object placement", _foldoutObjectPlacing); @@ -530,66 +665,6 @@ public override void OnInspectorGUI() } } - // Constraints - - EditorGUILayout.Space(); - _foldoutConstraints = UxrEditorUtils.FoldoutStylish("Constraints", _foldoutConstraints); - - if (_foldoutConstraints) - { - if (_propManipulationMode.enumValueIndex == (int)UxrManipulationMode.GrabAndMove) - { - EditorGUILayout.PropertyField(_propTranslationConstraintMode, ContentTranslationConstraintMode); - } - - if (_propManipulationMode.enumValueIndex == (int)UxrManipulationMode.RotateAroundAxis || - _propTranslationConstraintMode.enumValueIndex == (int)UxrTranslationConstraintMode.RestrictLocalOffset) - { - EditorGUILayout.PropertyField(_propTranslationLimitsMin, ContentTranslationLimitsMin); - EditorGUILayout.PropertyField(_propTranslationLimitsMax, ContentTranslationLimitsMax); - EditorGUILayout.PropertyField(_propTranslationLimitsReferenceIsParent, ContentTranslationLimitsReferenceIsParent); - - if (_propTranslationLimitsReferenceIsParent.boolValue == false) - { - EditorGUILayout.PropertyField(_propTranslationLimitsParent, ContentTranslationLimitsParent); - } - } - else if (_propTranslationConstraintMode.enumValueIndex == (int)UxrTranslationConstraintMode.RestrictToBox) - { - EditorGUILayout.PropertyField(_propRestrictToBox, ContentRestrictToBox); - } - else if (_propTranslationConstraintMode.enumValueIndex == (int)UxrTranslationConstraintMode.RestrictToSphere) - { - EditorGUILayout.PropertyField(_propRestrictToSphere, ContentRestrictToSphere); - } - - if (_propManipulationMode.enumValueIndex == (int)UxrManipulationMode.GrabAndMove) - { - EditorGUILayout.PropertyField(_propRotationConstraintMode, ContentRotationConstraintMode); - } - - if (_propManipulationMode.enumValueIndex == (int)UxrManipulationMode.RotateAroundAxis || - _propRotationConstraintMode.enumValueIndex == (int)UxrRotationConstraintMode.RestrictLocalRotation) - { - EditorGUILayout.PropertyField(_propRotationAngleLimitsMin, ContentRotationAngleLimitsMin); - EditorGUILayout.PropertyField(_propRotationAngleLimitsMax, ContentRotationAngleLimitsMax); - EditorGUILayout.PropertyField(_propRotationLimitsReferenceIsParent, ContentRotationLimitsReferenceIsParent); - - if (_propRotationLimitsReferenceIsParent.boolValue == false) - { - EditorGUILayout.PropertyField(_propRotationLimitsParent, ContentRotationLimitsParent); - } - } - - if ((_propManipulationMode.enumValueIndex == (int)UxrManipulationMode.GrabAndMove && _propAdditionalGrabPoints.arraySize > 0) || !grabbableObject.UsesGrabbableParentDependency) - { - EditorGUILayout.PropertyField(_propLockedGrabReleaseDistance, ContentLockedGrabReleaseDistance); - } - - EditorGUILayout.Slider(_propTranslationResistance, 0.0f, 1.0f, ContentTranslationResistance); - EditorGUILayout.Slider(_propRotationResistance, 0.0f, 1.0f, ContentRotationResistance); - } - // Added new grab points? Set their values from previous grab points to make life easier if (additionalPointsAfter > additionalPointsBefore) @@ -690,6 +765,92 @@ public override void OnInspectorGUI() serializedObject.ApplyModifiedProperties(); } } + + /// + /// Draws the constraints. + /// + private void OnSceneGUI() + { + if (Application.isPlaying) + { + return; + } + + UxrGrabbableObject grabbableObject = target as UxrGrabbableObject; + + // Draw rotation range of motion disks + + foreach(UxrAxis axis in grabbableObject.LimitedRangeOfMotionRotationAxes) + { + Vector3 normal = grabbableObject.transform.TransformDirection(axis); + Handles.color = axis.GetColor(UxrEditorUtils.HandlesAlpha); + + float angleMin = grabbableObject.RotationAngleLimitsMin[axis]; + float angleMax = grabbableObject.RotationAngleLimitsMax[axis]; + + Vector3 perpendicular = axis.Perpendicular; + + // Try to make sure that we orient the min and max the best possible way + + if (axis != grabbableObject.RotationLongitudinalAxis && grabbableObject.RangeOfMotionRotationAxisCount > 1) + { + perpendicular = grabbableObject.RotationLongitudinalAxis; + + if (_grabbableBounds.TryGetValue(grabbableObject, out Bounds localBounds)) + { + if (localBounds.center[grabbableObject.RotationLongitudinalAxis] < 0.0f) + { + perpendicular = -perpendicular; + } + } + } + else + { + if (_grabbableBounds.TryGetValue(grabbableObject, out Bounds localBounds)) + { + if (Mathf.Abs(localBounds.size[axis.Perpendicular]) > Mathf.Abs(localBounds.size[axis.OtherPerpendicular])) + { + perpendicular = localBounds.center[axis.Perpendicular] > 0.0f ? axis.Perpendicular : -axis.Perpendicular; + } + else + { + perpendicular = localBounds.center[axis.OtherPerpendicular] > 0.0f ? axis.OtherPerpendicular : -axis.OtherPerpendicular; + } + } + } + + Handles.DrawSolidArc(grabbableObject.transform.position, + normal, + Quaternion.AngleAxis(angleMin, normal) * grabbableObject.transform.TransformDirection(perpendicular), + angleMax - angleMin, + GetHandlesDiskRadius(grabbableObject, axis, DefaultDiskRadius)); + } + + // Draw translation range of motion lines + + if (grabbableObject.TranslationConstraint == UxrTranslationConstraintMode.RestrictLocalOffset) + { + for (int i = 0; i < 3; ++i) + { + UxrAxis axis = i; + + float min = grabbableObject.TranslationLimitsMin[axis]; + float max = grabbableObject.TranslationLimitsMax[axis]; + + if (!Mathf.Approximately(min, max)) + { + Vector3 pos1 = grabbableObject.transform.TransformPoint((Vector3)axis * min); + Vector3 pos2 = grabbableObject.transform.TransformPoint((Vector3)axis * max); + Vector3 normal = grabbableObject.transform.TransformDirection((Vector3)axis); + + Handles.color = axis.GetColor(1.0f); + Handles.DrawLine(pos1, pos2); + Handles.DrawSolidDisc(pos1, normal, LinearConstraintDiskRadius); + Handles.DrawSolidDisc(pos2, normal, LinearConstraintDiskRadius); + } + } + } + } #endregion @@ -1227,15 +1388,27 @@ private string[] GetPreviewGrabPosesModeStrings() private GUIContent ContentRegisterAvatar { get; } = new GUIContent("Register Avatar For Grips", "Registers an avatar to have the possibility to have different grip parameters for each avatar. This allows to fine-tune how different hand shapes and sizes wrap around the same object. Parameters that can be adjusted for each avatar will be colored to help in the process."); private GUIContent ContentSelectAvatar { get; } = new GUIContent("Selected Avatar Grips:", "Switches the avatar currently selected to edit its grip parameters. Being able to register different avatars allows to fine-tune how different hand shapes and sizes wrap around the same object. Parameters that can be adjusted for each avatar will be colored to help in the process."); - private GUIContent ContentManipulationMode { get; } = new GUIContent("Manipulation Mode", "Use Grab And Move to pick up objects and move/throw/place them. Use Rotate Around Axis for wheels, levers and similar objects."); private GUIContent ContentIgnoreGrabbableParentDependency { get; } = new GUIContent("Ignore Parent Dependency", "Mark this to ignore the parent constraint and tell this object is independent."); private GUIContent ContentPriority { get; } = new GUIContent("Priority", "By default, closer objects will be always grabbed over far objects. Using priority, objects with higher priority will always be grabbed if they are in range."); + private GUIContent ContentTranslationConstraintMode { get; } = new GUIContent("Translation Constraint Mode", "Allows to constrain the translation of this object while being grabbed."); + private GUIContent ContentRestrictToBox { get; } = new GUIContent("Restrict To Box", "Allowed volume where this object's pivot will be allowed to move while being grabbed."); + private GUIContent ContentRestrictToSphere { get; } = new GUIContent("Restrict To Sphere", "Allowed volume where this object's pivot will be allowed to move while being grabbed."); + private GUIContent ContentTranslationLimitsMin { get; } = new GUIContent("Translation Offset Min", "Minimum allowed offset in local coordinates."); + private GUIContent ContentTranslationLimitsMax { get; } = new GUIContent("Translation Offset Max", "Maximum allowed offset in local coordinates."); + private GUIContent ContentRotationConstraintMode { get; } = new GUIContent("Rotation Constraint Mode", "Allows to constrain the rotation of this object while being grabbed."); + private GUIContent ContentRotationAngleLimitsMin { get; } = new GUIContent("Rotation Angles Offset Min", "Minimum allowed rotation offset degrees in local axes."); + private GUIContent ContentRotationAngleLimitsMax { get; } = new GUIContent("Rotation Angles Offset Max", "Maximum allowed rotation offset degrees in local axes."); + private GUIContent ContentRotationProvider { get; } = new GUIContent("Rotation Provider", "Controls how an object with constrained position is rotated. Auto will to try to infer the most appropriate rotation provider automatically based on the object shape and the grip. HandOrientation will rotate the object directly by rotating the hand, useful for knobs or small joysticks where the torque is applied mostly by twisting the wrist. HandPositionAroundPivot will rotate the object using the position of the hand around the pivot instead, useful for levers, bigger joysticks, steering wheels and similar objects where the torque is applied using hand leverage around the rotation axis."); + private GUIContent ContentRotationLongitudinalAxis { get; } = new GUIContent("Longitudinal Axis", "Specifies which axis, in the object coordinate system, is the longitudinal axis. The longitudinal axis is the axis that goes from head to tail along the object."); + private GUIContent ContentNeedsTwoHandsToRotate { get; } = new GUIContent("Needs 2 Hands To Rotate", "When the Rotation Provider is set to HandPositionAroundPivot this will tell if the user will be able to rotate the object using one hand only or the object needs to be grabbed with two hands in order to rotate it."); + private GUIContent ContentLockedGrabReleaseDistance { get; } = new GUIContent("Constrained Grab Release Distance", "Maximum allowed distance of a constrained grab to drift away from the grab point before the avatar releases it automatically."); + private GUIContent ContentTranslationResistance { get; } = new GUIContent("Translation Resistance", "Resistance of the object to being moved. Values higher than zero may be used to simulate heavy objects."); + private GUIContent ContentRotationResistance { get; } = new GUIContent("Rotation Resistance", "Resistance of the object to being rotated. Values higher than zero may be used to simulate heavy objects."); private GUIContent ContentRigidBodySource { get; } = new GUIContent("Rigidbody", "References the object's rigidbody when physics are required. The object will be made kinematic when grabbed and optionally dynamic when released."); private GUIContent ContentRigidBodyDynamicOnRelease { get; } = new GUIContent("Rigidbody Dynamic On Release", "When a rigidbody is specified it controls whether it will be marked as dynamic after being grabbed and released. Otherwise it will continue to be kinematic after being released."); private GUIContent ContentVerticalReleaseMultiplier { get; } = new GUIContent("Vertical Release Multiplier", "When throwing a rigidbody this parameter will enable increasing or decreasing the actual release velocity (vertical component)."); private GUIContent ContentHorizontalReleaseMultiplier { get; } = new GUIContent("Horizontal Release Multiplier", "When throwing a rigidbody this parameter will enable increasing or decreasing the actual release velocity (horizontal component)."); - private GUIContent ContentNeedsTwoHandsToRotate { get; } = new GUIContent("Needs 2 Hands To Rotate", "When manipulation mode is set to Rotate Around Axis this will tell if the user will be able to rotate the object using one hand only or the object needs to be grabbed with two hands in order to rotate it."); - private GUIContent ContentAllowMultiGrab { get; } = new GUIContent("Allow 2 Hands Grab", "When more than one grab point has been specified, this parameter will tell if the object can be grabbed with two hands at the same time."); + private GUIContent ContentAllowMultiGrab { get; } = new GUIContent("Allow Two Handed Grab", "When more than one grab point has been specified, this parameter will tell if the object can be grabbed with two hands at the same time."); private GUIContent ContentPreviewGrabPosesMode { get; } = new GUIContent("Preview Grip Pose Meshes", "Will show/hide the preview grip pose meshes in the Scene Window."); private GUIContent ContentFirstGrabPointIsMain { get; } = new GUIContent("First Grab Point Is Main", "Whether the first grab point in the list is the main grab in objects with more than one grab point. When an object is grabbed with both hands, the main grab controls the actual position while the secondary grab controls the direction. Set it to true in objects like a rifle, where the trigger hand should be the first grab in order to keep the object in place, and the front grab will control the aiming direction. If false, the grab point order is irrelevant and the hand that grabbed the object first will be considered as the main grab."); private GUIContent ContentGrabPoint { get; } = new GUIContent("Grab Point", "Parameters of the grabbing point."); @@ -1247,27 +1420,17 @@ private string[] GetPreviewGrabPosesModeStrings() private GUIContent ContentDropAlignTransform { get; } = new GUIContent("Anchor Snap", "The transform where objects will be snapped to when being placed."); private GUIContent ContentDropSnapMode { get; } = new GUIContent("Anchor Snap Mode", $"How this object will snap to the {nameof(UxrGrabbableObjectAnchor)} transform after being placing on it."); private GUIContent ContentDropProximityTransform { get; } = new GUIContent("Anchor Proximity Position", $"The reference that will be used to know if this object is close enough to an {nameof(UxrGrabbableObjectAnchor)} to place it there."); - private GUIContent ContentTranslationConstraintMode { get; } = new GUIContent("Anchor Proximity Translation Constraint Mode", "Allows to constrain the translation of this object while being grabbed."); - private GUIContent ContentRestrictToBox { get; } = new GUIContent("Restrict To Box", "Allowed volume where this object's pivot will be allowed to move while being grabbed."); - private GUIContent ContentRestrictToSphere { get; } = new GUIContent("Restrict To Sphere", "Allowed volume where this object's pivot will be allowed to move while being grabbed."); - private GUIContent ContentTranslationLimitsMin { get; } = new GUIContent("Translation Offset Min", "Minimum allowed offset in local coordinates."); - private GUIContent ContentTranslationLimitsMax { get; } = new GUIContent("Translation Offset Max", "Maximum allowed offset in local coordinates."); - private GUIContent ContentTranslationLimitsReferenceIsParent { get; } = new GUIContent("Translation Reference Is Parent", "Tells whether the local translation limits specified are with respect to the parent or another transform."); - private GUIContent ContentTranslationLimitsParent { get; } = new GUIContent("Translation Parent Transform", "Allows to specify a different transform as parent reference for the local translation limits."); - private GUIContent ContentRotationConstraintMode { get; } = new GUIContent("Rotation Constraint Mode", "Allows to constrain the rotation of this object while being grabbed."); - private GUIContent ContentRotationAngleLimitsMin { get; } = new GUIContent("Rotation Angles Offset Min", "Minimum allowed rotation offset degrees in local axes."); - private GUIContent ContentRotationAngleLimitsMax { get; } = new GUIContent("Rotation Angles Offset Max", "Maximum allowed rotation offset degrees in local axes."); - private GUIContent ContentRotationLimitsReferenceIsParent { get; } = new GUIContent("Rotation Reference Is Parent", "Tells whether the local rotation limits specified are with respect to the parent or another transform."); - private GUIContent ContentRotationLimitsParent { get; } = new GUIContent("Rotation Parent Transform", "Allows to specify a different transform as parent reference for the local rotation limits."); - private GUIContent ContentLockedGrabReleaseDistance { get; } = new GUIContent("Locked Grab Release Distance", "Maximum allowed distance of a locked grab to move away from the grab point before being released."); - private GUIContent ContentTranslationResistance { get; } = new GUIContent("Translation Resistance", "Resistance of the object to being moved. Values higher than zero may be used to simulate heavy objects."); - private GUIContent ContentRotationResistance { get; } = new GUIContent("Rotation Resistance", "Resistance of the object to being rotated. Values higher than zero may be used to simulate heavy objects."); + + private const float DefaultDiskRadius = 0.05f; + private const float MaxDiskHandleRadius = 1.5f; + private const float DiskHandleRadiusExpand = 1.1f; + private const float LinearConstraintDiskRadius = 0.01f; private const string DefaultHelp = "The grabbable object is ready-to-use before setting up any additional parameter.\n" + "For advanced manipulation you can get assistance by hovering the mouse over each parameter.\n" + + "The Constraints section lets you set up manipulation constraints.\n" + "Use Grab Points to specify different grabbable parts on the object. Register avatars to adjust grab poses and snapping differently for each avatar prefab. Child avatar prefabs will automatically inherit the parent prefab's parameters but can also be registered to override settings.\n" + - "The Object Placement section lets you control where and how the object can be placed.\n" + - "The Constraints section lets you set up manipulation constraints."; + "The Object Placement section lets you control where and how the object can be placed."; private static readonly string NeedsUxrAvatar = $"The {nameof(UxrGrabbableObject)} component needs an {nameof(UxrAvatar)} in the scene. Add an avatar to the scene unless you know that you are instancing an avatar at runtime. You can find prefabs in UltimateXR/Prefabs"; private static readonly string NeedsUxrAvatarWithGrabbing = $"Could not find {nameof(UxrAvatar)}(s) in the scene with {nameof(UxrGrabber)} components. These components are the ones where grabbable objects are attached to when picking them up." + $"\nCreate two child GameObjects -one on each hand of the avatar- and set up an {nameof(UxrGrabber)} component on each."; @@ -1275,17 +1438,32 @@ private string[] GetPreviewGrabPosesModeStrings() private static readonly Dictionary s_openEditors = new Dictionary(); - private SerializedProperty _propManipulationMode; private SerializedProperty _propIgnoreGrabbableParentDependency; private SerializedProperty _propControlParentDirection; private SerializedProperty _propPriority; + private SerializedProperty _propAllowMultiGrab; + + private SerializedProperty _propTranslationConstraintMode; + private SerializedProperty _propRestrictToBox; + private SerializedProperty _propRestrictToSphere; + private SerializedProperty _propTranslationLimitsMin; + private SerializedProperty _propTranslationLimitsMax; + private SerializedProperty _propRotationConstraintMode; + private SerializedProperty _propRotationAngleLimitsMin; + private SerializedProperty _propRotationAngleLimitsMax; + private SerializedProperty _propRotationLongitudinalAxis; + private SerializedProperty _propAutoRotationProvider; + private SerializedProperty _propRotationProvider; + private SerializedProperty _propNeedsTwoHandsToRotate; + private SerializedProperty _propLockedGrabReleaseDistance; + private SerializedProperty _propTranslationResistance; + private SerializedProperty _propRotationResistance; + private SerializedProperty _propRigidBodySource; private SerializedProperty _propRigidBodyDynamicOnRelease; private SerializedProperty _propVerticalReleaseMultiplier; private SerializedProperty _propHorizontalReleaseMultiplier; - private SerializedProperty _propNeedsTwoHandsToRotate; - private SerializedProperty _propAllowMultiGrab; private SerializedProperty _propPreviewGrabPosesMode; private SerializedProperty _propPreviewPosesRegenerationType; @@ -1304,30 +1482,16 @@ private string[] GetPreviewGrabPosesModeStrings() private SerializedProperty _propDropProximityTransformUseSelf; private SerializedProperty _propDropProximityTransform; - private SerializedProperty _propTranslationConstraintMode; - private SerializedProperty _propRestrictToBox; - private SerializedProperty _propRestrictToSphere; - private SerializedProperty _propTranslationLimitsMin; - private SerializedProperty _propTranslationLimitsMax; - private SerializedProperty _propTranslationLimitsReferenceIsParent; - private SerializedProperty _propTranslationLimitsParent; - private SerializedProperty _propRotationConstraintMode; - private SerializedProperty _propRotationAngleLimitsMin; - private SerializedProperty _propRotationAngleLimitsMax; - private SerializedProperty _propRotationLimitsReferenceIsParent; - private SerializedProperty _propRotationLimitsParent; - private SerializedProperty _propLockedGrabReleaseDistance; - private SerializedProperty _propTranslationResistance; - private SerializedProperty _propRotationResistance; - private int _selectedAvatarGripPoseIndex; - private bool _foldoutManipulation = true; private bool _foldoutGeneralParameters = true; + private bool _foldoutConstraints = true; + private bool _foldoutPhysics = true; private bool _foldoutAvatarGrips = true; private bool _foldoutGrabPoints = true; private bool _foldoutObjectPlacing = true; - private bool _foldoutConstraints = true; + + private Dictionary _grabbableBounds; #endregion } diff --git a/Scripts/Manipulation/HandPoses/Editor/UxrHandPoseEditorWindow.cs b/Scripts/Manipulation/HandPoses/Editor/UxrHandPoseEditorWindow.cs index fdc35da5..6edf71fe 100644 --- a/Scripts/Manipulation/HandPoses/Editor/UxrHandPoseEditorWindow.cs +++ b/Scripts/Manipulation/HandPoses/Editor/UxrHandPoseEditorWindow.cs @@ -1335,9 +1335,7 @@ private void OnGUI() // Force update of skin meshes, sometimes they don't get updated even if the transforms are changed - SkinnedMeshRenderer[] skins = _avatar.GetComponentsInChildren(); - - foreach (SkinnedMeshRenderer skin in skins) + foreach (SkinnedMeshRenderer skin in _avatar.GetAllAvatarRendererComponents()) { bool enabled = skin.enabled; skin.enabled = false; @@ -2316,10 +2314,10 @@ private void RefreshHandPosePresets() /// private void BuildFingerSpinners() { - UxrUniversalLocalAxes leftHandAxes = _avatar.AvatarRigInfo.LeftHandUniversalLocalAxes; - UxrUniversalLocalAxes rightHandAxes = _avatar.AvatarRigInfo.RightHandUniversalLocalAxes; - UxrUniversalLocalAxes leftFingerAxes = _avatar.AvatarRigInfo.LeftFingerUniversalLocalAxes; - UxrUniversalLocalAxes rightFingerAxes = _avatar.AvatarRigInfo.RightFingerUniversalLocalAxes; + UxrUniversalLocalAxes leftHandAxes = _avatar.AvatarRigInfo.GetArmInfo(UxrHandSide.Left).HandUniversalLocalAxes; + UxrUniversalLocalAxes rightHandAxes = _avatar.AvatarRigInfo.GetArmInfo(UxrHandSide.Right).HandUniversalLocalAxes; + UxrUniversalLocalAxes leftFingerAxes = _avatar.AvatarRigInfo.GetArmInfo(UxrHandSide.Left).FingerUniversalLocalAxes; + UxrUniversalLocalAxes rightFingerAxes = _avatar.AvatarRigInfo.GetArmInfo(UxrHandSide.Right).FingerUniversalLocalAxes; UxrAvatarHand leftHand = _avatar.LeftHand; UxrAvatarHand rightHand = _avatar.RightHand; Transform leftHandBone = _avatar.LeftHandBone; diff --git a/Scripts/Manipulation/HandPoses/UxrHandDescriptor.cs b/Scripts/Manipulation/HandPoses/UxrHandDescriptor.cs index 26c061be..010bdfbe 100644 --- a/Scripts/Manipulation/HandPoses/UxrHandDescriptor.cs +++ b/Scripts/Manipulation/HandPoses/UxrHandDescriptor.cs @@ -134,10 +134,7 @@ public UxrFingerDescriptor GetFinger(UxrFingerType fingerType) /// Whether to compute the relative transform to the hand only public void Compute(UxrAvatar avatar, UxrHandSide handSide, bool computeRelativeMatrixOnly = false) { - Compute(handSide == UxrHandSide.Left ? avatar.AvatarRig.LeftArm : avatar.AvatarRig.RightArm, - handSide == UxrHandSide.Left ? avatar.AvatarRigInfo.LeftHandUniversalLocalAxes : avatar.AvatarRigInfo.RightHandUniversalLocalAxes, - handSide == UxrHandSide.Left ? avatar.AvatarRigInfo.LeftFingerUniversalLocalAxes : avatar.AvatarRigInfo.RightFingerUniversalLocalAxes, - computeRelativeMatrixOnly); + Compute(handSide == UxrHandSide.Left ? avatar.AvatarRig.LeftArm : avatar.AvatarRig.RightArm, avatar.AvatarRigInfo.GetArmInfo(handSide).HandUniversalLocalAxes, avatar.AvatarRigInfo.GetArmInfo(handSide).FingerUniversalLocalAxes, computeRelativeMatrixOnly); } /// diff --git a/Scripts/Manipulation/UxrGrabManager.HandTransitionInfo.cs b/Scripts/Manipulation/UxrGrabManager.HandTransitionInfo.cs index 3ad3868c..efe818de 100644 --- a/Scripts/Manipulation/UxrGrabManager.HandTransitionInfo.cs +++ b/Scripts/Manipulation/UxrGrabManager.HandTransitionInfo.cs @@ -20,14 +20,14 @@ private class HandTransitionInfo #region Public Types & Data /// - /// Hand bone position when the grip was released. + /// Hand bone position in local avatar coordinates when the grip was released. /// - public Vector3 StartPosition { get; } + public Vector3 StartLocalAvatarPosition { get; } /// - /// Hand bone rotation when the grip was released. + /// Hand bone rotation in local avatar coordinates when the grip was released. /// - public Quaternion StartRotation { get; } + public Quaternion StartLocalAvatarRotation { get; } /// /// Timer value to control the interpolation. @@ -48,9 +48,9 @@ public HandTransitionInfo(UxrGrabber grabber, Vector3 grabberPosition, Quaternio { Matrix4x4 grabberMtx = Matrix4x4.TRS(grabberPosition, grabberRotation, Vector3.one); - Timer = UxrGrabbableObject.HandLockSeconds; - StartPosition = grabberMtx.MultiplyPoint(grabber.HandBoneRelativePos); - StartRotation = grabberMtx.rotation * grabber.HandBoneRelativeRot; + Timer = UxrGrabbableObject.HandLockSeconds; + StartLocalAvatarPosition = grabber.Avatar.transform.InverseTransformPoint(grabberMtx.MultiplyPoint(grabber.HandBoneRelativePos)); + StartLocalAvatarRotation = Quaternion.Inverse(grabber.Avatar.transform.rotation) * grabberMtx.rotation * grabber.HandBoneRelativeRot; } #endregion diff --git a/Scripts/Manipulation/UxrGrabManager.RuntimeGrabInfo.cs b/Scripts/Manipulation/UxrGrabManager.RuntimeGrabInfo.cs index 4d8e40b6..626dca4a 100644 --- a/Scripts/Manipulation/UxrGrabManager.RuntimeGrabInfo.cs +++ b/Scripts/Manipulation/UxrGrabManager.RuntimeGrabInfo.cs @@ -77,14 +77,14 @@ public float LookAtT public UxrGrabbableObjectAnchor AnchorFrom { get; } /// - /// Gets 's position before being updated by the grab manager. + /// Gets 's local position before being updated by the grab manager. /// - public Vector3 PositionBeforeUpdate { get; set; } + public Vector3 LocalPositionBeforeUpdate { get; set; } /// - /// Gets 's rotation before being updated by the grab manager. + /// Gets 's local rotation before being updated by the grab manager. /// - public Quaternion RotationBeforeUpdate { get; set; } + public Quaternion LocalRotationBeforeUpdate { get; set; } /// /// Gets the timer value that is used to perform smooth "Look At" transitions. @@ -124,8 +124,8 @@ public RuntimeGrabInfo(UxrGrabber grabber, int grabPoint, UxrGrabbableObjectAnch Grabbers.Add(grabber); GrabbedPoints.Add(grabPoint); - PositionBeforeUpdate = grabber.GrabbedObject.transform.position; - RotationBeforeUpdate = grabber.GrabbedObject.transform.rotation; + LocalPositionBeforeUpdate = grabber.GrabbedObject.transform.localPosition; + LocalRotationBeforeUpdate = grabber.GrabbedObject.transform.localRotation; LookAtTimer = -1.0f; diff --git a/Scripts/Manipulation/UxrGrabManager.cs b/Scripts/Manipulation/UxrGrabManager.cs index ba5806fd..04ec5fc2 100644 --- a/Scripts/Manipulation/UxrGrabManager.cs +++ b/Scripts/Manipulation/UxrGrabManager.cs @@ -30,7 +30,7 @@ namespace UltimateXR.Manipulation /// : Anchors where grabbable objects can be placed /// /// - public partial class UxrGrabManager : UxrSingleton, IUxrStateSync + public partial class UxrGrabManager : UxrSingleton, IUxrStateSync, IUxrLogger { #region Public Types & Data @@ -265,11 +265,6 @@ public partial class UxrGrabManager : UxrSingleton, IUxrStateSyn /// public IEnumerable CurrentGrabbedObjects => _currentGrabs.Keys; - /// - /// Gets or sets the current log level. - /// - public UxrLogLevel LogLevel { get; set; } = UxrLogLevel.Relevant; - /// /// Gets or sets whether grabbing is allowed. /// @@ -277,6 +272,13 @@ public partial class UxrGrabManager : UxrSingleton, IUxrStateSyn #endregion + #region Implicit IUxrLogger + + /// + public UxrLogLevel LogLevel { get; set; } = UxrLogLevel.Relevant; + + #endregion + #region Implicit IUxrStateSync /// @@ -534,9 +536,10 @@ public bool PlaceObject(UxrGrabbableObject grabbableObject, UxrGrabbableObjectAn return false; } - UxrGrabber grabber = null; - UxrGrabbableObjectAnchor oldAnchor = grabbableObject.CurrentAnchor; - bool releaseGrip = true; + UxrGrabber grabber = null; + int grabbedPoint = -1; + UxrGrabbableObjectAnchor oldAnchor = grabbableObject.CurrentAnchor; + bool releaseGrip = true; if (!grabbableObject.IsConstrained) { @@ -545,7 +548,8 @@ public bool PlaceObject(UxrGrabbableObject grabbableObject, UxrGrabbableObjectAn if (_currentGrabs.TryGetValue(grabbableObject, out RuntimeGrabInfo grabInfo)) { // TODO: Ideally we would send events from all grabbers later, not just the first - grabber = grabInfo.Grabbers[0]; + grabber = grabInfo.Grabbers[0]; + grabbedPoint = grabInfo.GrabbedPoints[0]; // Don't propagate Release events, because Place and Release are mutually exclusive List grabbersToRelease = new List(grabInfo.Grabbers); @@ -563,8 +567,6 @@ public bool PlaceObject(UxrGrabbableObject grabbableObject, UxrGrabbableObjectAn return true; } - int grabbedPoint = grabber != null ? GetGrabbedPoint(grabber) : 0; - // Remove and raise events if (oldAnchor != null) @@ -904,9 +906,6 @@ public void GrabObject(UxrGrabber grabber, UxrGrabbableObject grabbableObject, i } } - grabber.GrabbedObject.NotifyBeginGrab(grabber, grabPoint); - StopSmoothHandTransition(grabber); - if (handSwapSamePoint) { grabInfo.SwapGrabber(grabInfo.Grabbers[grabInfo.GrabbedPoints.IndexOf(grabPoint)], grabber); @@ -942,6 +941,9 @@ public void GrabObject(UxrGrabber grabber, UxrGrabbableObject grabbableObject, i _currentGrabs.Add(grabbableObject, new RuntimeGrabInfo(grabber, grabPoint, anchorFrom)); } + grabber.GrabbedObject.NotifyBeginGrab(grabber, grabPoint); + StopSmoothHandTransition(grabber); + // Raise events if (manipulationReleaseEventArgs != null) @@ -974,7 +976,7 @@ public void GrabObject(UxrGrabber grabber, UxrGrabbableObject grabbableObject, i } } } - + /// /// Releases an object from a hand. /// @@ -1006,7 +1008,7 @@ public void ReleaseObject(UxrGrabber grabber, UxrGrabbableObject grabbableObject { Debug.LogError($"{UxrConstants.ManipulationModule}: GrabInfo not found for object {grabbableObject.name}. This should not be happening."); } - + return; } @@ -1024,23 +1026,38 @@ public void ReleaseObject(UxrGrabber grabber, UxrGrabbableObject grabbableObject // multiplier is applied using a gradient measured in units/second. float multiplierVelocityGradient = 2.0f; - // we will apply the multiplier gradually depending on the release velocity starting from multiplierVelocityThreshold. - // This is measured in units/sec so objects going below multiplierVelocityThreshold units/sec will have the normal - // release velocity and objects above or equal to (multiplierVelocityThreshold + multiplierVelocityGradient) units/sec will - // get the maximum multiplier. Velocities in between will have a smooth transition. - if (horizontal.magnitude > multiplierVelocityThreshold) + if (grabbableObject.HorizontalReleaseMultiplier > 1.0f) + { + // we will apply the multiplier gradually depending on the release velocity starting from multiplierVelocityThreshold. + // This is measured in units/sec so objects going below multiplierVelocityThreshold units/sec will have the normal + // release velocity and objects above or equal to (multiplierVelocityThreshold + multiplierVelocityGradient) units/sec will + // get the maximum multiplier. Velocities in between will have a smooth transition. + if (horizontal.magnitude > multiplierVelocityThreshold) + { + float lerp = Mathf.Clamp01((horizontal.magnitude - multiplierVelocityThreshold) * (1.0f / multiplierVelocityGradient)); + horizontal = Vector3.Lerp(horizontal, horizontal * grabbableObject.HorizontalReleaseMultiplier, lerp); + } + } + else { - float lerp = Mathf.Clamp01((horizontal.magnitude - multiplierVelocityThreshold) * (1.0f / multiplierVelocityGradient)); - horizontal = Vector3.Lerp(horizontal, horizontal * grabbableObject.HorizontalReleaseMultiplier, lerp); + horizontal *= grabbableObject.HorizontalReleaseMultiplier; } releaseVelocity.x = horizontal.x; releaseVelocity.z = horizontal.y; - if (Mathf.Abs(releaseVelocity.y) > multiplierVelocityThreshold) + if (grabbableObject.VerticalReleaseMultiplier > 1.0f) + { + // Apply multiplier in the same gradual way as the horizontal component. + if (Mathf.Abs(releaseVelocity.y) > multiplierVelocityThreshold) + { + float lerp = Mathf.Clamp01((Mathf.Abs(releaseVelocity.y) - multiplierVelocityThreshold) * (1.0f / multiplierVelocityGradient)); + releaseVelocity.y = Mathf.Lerp(releaseVelocity.y, releaseVelocity.y * grabbableObject.VerticalReleaseMultiplier, lerp); + } + } + else { - float lerp = Mathf.Clamp01((Mathf.Abs(releaseVelocity.y) - multiplierVelocityThreshold) * (1.0f / multiplierVelocityGradient)); - releaseVelocity.y = Mathf.Lerp(releaseVelocity.y, releaseVelocity.y * grabbableObject.VerticalReleaseMultiplier, lerp); + releaseVelocity.y *= grabbableObject.VerticalReleaseMultiplier; } // Raise event(s) @@ -1071,7 +1088,7 @@ public void ReleaseObject(UxrGrabber grabber, UxrGrabbableObject grabbableObject // Start a smooth transition to the actual position. StartSmoothHandTransition(grabber, grabbableObject, grabbedPoint); - if (grabbableObject.Manipulation == UxrManipulationMode.GrabAndMove) + if (grabbableObject.RotationProvider != UxrRotationProvider.HandPositionAroundPivot) { // We released one hand but we keep grabbing with the other. Unless the grabber that keeps grabbing has a snap to // pivot and align rotation point currently grabbed, we change the grabbing parameters so that it keeps @@ -1202,11 +1219,6 @@ public void RemoveObjectFromAnchor(UxrGrabbableObject grabbableObject, bool prop // Perform removal - if (grabbableObject.IsConstrained) - { - grabbableObject.StartSmoothConstrainExit(); - } - if (grabbableObject.RigidBodySource != null && !IsBeingGrabbed(grabbableObject)) { grabbableObject.RigidBodySource.isKinematic = !grabbableObject.RigidBodyDynamicOnRelease; @@ -1217,6 +1229,11 @@ public void RemoveObjectFromAnchor(UxrGrabbableObject grabbableObject, bool prop grabbableObject.transform.SetParent(null, true); } + if (grabbableObject.IsConstrained) + { + grabbableObject.StartSmoothConstrainExit(); + } + if (anchorFrom != null) { anchorFrom.CurrentPlacedObject = null; @@ -1228,7 +1245,7 @@ public void RemoveObjectFromAnchor(UxrGrabbableObject grabbableObject, bool prop { if (grabber.GrabbedObject == grabbableObject) { - grabbableObject.CheckAndApplyConstraints(grabber, GetGrabbedPoint(grabber), grabbableObject.transform.position, grabbableObject.transform.rotation, propagateEvents); + grabbableObject.CheckAndApplyConstraints(grabber, GetGrabbedPoint(grabber), grabbableObject.transform.localPosition, grabbableObject.transform.localRotation, propagateEvents); grabbableObject.CheckAndApplyLockHand(grabber, GetGrabbedPoint(grabber)); } } @@ -1385,12 +1402,39 @@ public bool IsBeingGrabbed(UxrGrabbableObject grabbableObject, int point) return false; } - if (_currentGrabs.TryGetValue(grabbableObject, out RuntimeGrabInfo grabInfo)) + return _currentGrabs.TryGetValue(grabbableObject, out RuntimeGrabInfo grabInfo) && grabInfo.GrabbedPoints.Contains(point); + } + + /// + /// Checks whether the given grabbable object is being grabbed by an avatar. + /// + /// The grabbable object + /// The avatar to check + /// Whether it is being grabbed by the avatar + public bool IsBeingGrabbedBy(UxrGrabbableObject grabbableObject, UxrAvatar avatar) + { + if (_currentGrabs == null) { - return grabInfo.GrabbedPoints.Contains(point); + return false; } - return false; + return _currentGrabs.TryGetValue(grabbableObject, out RuntimeGrabInfo grabInfo) && grabInfo.Grabbers.Any(grabber => grabber.Avatar == avatar); + } + + /// + /// Checks whether the given grabbable object is being grabbed by a specific grabber. + /// + /// The grabbable object + /// The grabber to check + /// Whether it is being grabbed by the given grabber + public bool IsBeingGrabbedBy(UxrGrabbableObject grabbableObject, UxrGrabber grabber) + { + if (_currentGrabs == null) + { + return false; + } + + return _currentGrabs.TryGetValue(grabbableObject, out RuntimeGrabInfo grabInfo) && grabInfo.Grabbers.Any(grb => grabber == grb); } /// @@ -1406,18 +1450,7 @@ public bool IsBeingGrabbedByOtherThan(UxrGrabbableObject grabbableObject, int po return false; } - if (_currentGrabs.TryGetValue(grabbableObject, out RuntimeGrabInfo grabInfo)) - { - foreach (int grabPoint in grabInfo.GrabbedPoints) - { - if (point != grabPoint) - { - return true; - } - } - } - - return false; + return _currentGrabs.TryGetValue(grabbableObject, out RuntimeGrabInfo grabInfo) && grabInfo.GrabbedPoints.Any(grabPoint => point != grabPoint); } /// @@ -1686,8 +1719,8 @@ internal void UpdateManager() grabInfoPair.Value.LookAtTimer -= Time.deltaTime; } - grabInfoPair.Value.PositionBeforeUpdate = grabInfoPair.Key.transform.position; - grabInfoPair.Value.RotationBeforeUpdate = grabInfoPair.Key.transform.rotation; + grabInfoPair.Value.LocalPositionBeforeUpdate = grabInfoPair.Key.transform.localPosition; + grabInfoPair.Value.LocalRotationBeforeUpdate = grabInfoPair.Key.transform.localRotation; } // Initialize some variables for later @@ -2157,18 +2190,34 @@ private void UxrManager_AvatarMoved(object sender, UxrAvatarMoveEventArgs e) // Move grabbed objects without being parented to avatar to new position/orientation to avoid rubber-band effects if (!grabbableObject.transform.HasParent(e.Avatar.transform)) { - Vector3 oldPosition = grabbableObject.transform.position; - Quaternion oldRotation = grabbableObject.transform.rotation; + Vector3 oldLocalPosition = grabbableObject.transform.localPosition; + Quaternion oldLocalRotation = grabbableObject.transform.localRotation; // Use this handy method to make the grabbable object keep the relative positioning to the avatar e.ReorientRelativeToAvatar(grabbableObject.transform); - // Apply constraints + float translationResistance = grabbableObject.TranslationResistance; + float rotationResistance = grabbableObject.RotationResistance; + grabbableObject.TranslationResistance = 0.0f; + grabbableObject.RotationResistance = 0.0f; + + // Apply constraints making sure the resistance doesn't get in the way of resistance interpolations foreach (UxrGrabber grabber in dependency.Grabbers) { - grabbableObject.CheckAndApplyConstraints(grabber, GetGrabbedPoint(grabber), oldPosition, oldRotation, true); + // Also make sure align to controller doesn't get in the way + int grabbedPoint = GetGrabbedPoint(grabber); + UxrGrabPointInfo grabPointInfo = grabbableObject.GetGrabPoint(grabbedPoint); + bool alignToController = grabPointInfo.AlignToController; + grabPointInfo.AlignToController = false; + + grabbableObject.CheckAndApplyConstraints(grabber, GetGrabbedPoint(grabber), oldLocalPosition, oldLocalRotation, true); grabbableObject.CheckAndApplyLockHand(grabber, GetGrabbedPoint(grabber)); + + grabPointInfo.AlignToController = alignToController; } + + grabbableObject.TranslationResistance = translationResistance; + grabbableObject.RotationResistance = rotationResistance; } } } @@ -2531,7 +2580,7 @@ private void NotifyReleaseGrab(UxrGrabber grabber) /// private void ProcessGrab(UxrGrabbableObject grabbableObject, RuntimeGrabInfo grabInfo, ref List listToRelease) { - if (grabbableObject.Manipulation == UxrManipulationMode.GrabAndMove && grabInfo.GrabbedPoints.Count > 1) + if (grabbableObject.RotationProvider != UxrRotationProvider.HandPositionAroundPivot && grabInfo.GrabbedPoints.Count > 1) { // Position multi-grab object HandleMultiGrab(grabbableObject, ref listToRelease, grabInfo.LookAtT); @@ -2555,7 +2604,7 @@ private void ProcessGrab(UxrGrabbableObject grabbableObject, RuntimeGrabInfo gra { // Revert grabber processed in previous pass RevertToUnprocessedGrabberTransform(mainGrabInfo.Grabbers[0]); - + // Do look-at PerformPrimaryLookAtSecondaryGrab(mainGrabInfo, mainGrabInfo.Grabbers[0], grabInfo.GrabbableParentBeingGrabbed, mainGrabInfo.GrabbedPoints[0], grabber, grabbableObject, pointIndex, mainGrabInfo.LookAtT); performedLookAt = true; @@ -2568,7 +2617,7 @@ private void ProcessGrab(UxrGrabbableObject grabbableObject, RuntimeGrabInfo gra } } - if (grabbableObject.Manipulation == UxrManipulationMode.GrabAndMove) + if (grabbableObject.RotationProvider != UxrRotationProvider.HandPositionAroundPivot) { // Place Vector3 finalPosition = grabber.transform.position; @@ -2582,12 +2631,12 @@ private void ProcessGrab(UxrGrabbableObject grabbableObject, RuntimeGrabInfo gra } // Restrict movement? - if (point == 0 && !(grabbableObject.Manipulation == UxrManipulationMode.RotateAroundAxis && grabbableObject.NeedsTwoHandsToRotate && grabbableObject.GrabPointCount > 1 && grabInfo.GrabbedPoints.Count < 2)) + if (point == 0 && !(grabbableObject.RotationProvider == UxrRotationProvider.HandPositionAroundPivot && grabbableObject.NeedsTwoHandsToRotate && grabbableObject.GrabPointCount > 1 && grabInfo.GrabbedPoints.Count < 2)) { - grabbableObject.CheckAndApplyConstraints(grabber, pointIndex, grabInfo.PositionBeforeUpdate, grabInfo.RotationBeforeUpdate, triggerApplyConstraintsEvents); + grabbableObject.CheckAndApplyConstraints(grabber, pointIndex, grabInfo.LocalPositionBeforeUpdate, grabInfo.LocalRotationBeforeUpdate, triggerApplyConstraintsEvents); } - // Check if the user separated its hands too much to drop it from the hand that went too far + // Check if the user separated the hands too much to drop it from the hand that went beyond the distance threshold if (Vector3.Distance(grabbableObject.GetGrabbedPointGrabProximityPosition(grabber, pointIndex), grabber.transform.position) > grabbableObject.LockedGrabReleaseDistance) { if (grabbableObject.IsConstrained) @@ -2655,7 +2704,7 @@ private void PerformPrimaryLookAtSecondaryGrab(RuntimeGrabInfo mainGrabInfo, float lookAtT) { // First place in main hand - + Vector3 finalPosition = grabberMain.transform.position; Quaternion finalRotation = grabberMain.transform.rotation; @@ -2673,7 +2722,10 @@ private void PerformPrimaryLookAtSecondaryGrab(RuntimeGrabInfo mainGrabInfo, grabbableObjectMain.transform.rotation = finalRotation; // Constrain using main hand - grabbableObjectMain.CheckAndApplyConstraints(grabberMain, grabPointMain, mainGrabInfo.PositionBeforeUpdate, mainGrabInfo.RotationBeforeUpdate, false); + + bool raiseApplyConstraintsEvents = mainGrabInfo.ChildDependentGrabCount == 0 || mainGrabInfo.ChildDependentGrabProcessed == mainGrabInfo.ChildDependentGrabCount - 1; + + grabbableObjectMain.CheckAndApplyConstraints(grabberMain, grabPointMain, mainGrabInfo.LocalPositionBeforeUpdate, mainGrabInfo.LocalRotationBeforeUpdate, raiseApplyConstraintsEvents && grabbableObjectMain.RotationConstraint != UxrRotationConstraintMode.RestrictLocalRotation); // Now just rotate towards the second hand Vector3 otherHandPoint = grabbableObjectSecondary.GetGrabPointSnapModeAffectsPosition(grabPointSecondary, UxrHandSnapDirection.ObjectToHand) @@ -2687,12 +2739,10 @@ private void PerformPrimaryLookAtSecondaryGrab(RuntimeGrabInfo mainGrabInfo, grabbableObjectMain.transform.RotateAround(grabberMain.transform.position, rotationAxis, Vector3.SignedAngle(currentVectorToSecondHand, desiredVectorToSecondHand, rotationAxis) * lookAtT); - bool raiseApplyConstraintsEvents = mainGrabInfo.ChildDependentGrabCount == 0 || mainGrabInfo.ChildDependentGrabProcessed == mainGrabInfo.ChildDependentGrabCount - 1; - // Second pass to handle possible rotation constraints due to the look-at if (grabbableObjectMain.RotationConstraint == UxrRotationConstraintMode.RestrictLocalRotation) { - grabbableObjectMain.CheckAndApplyConstraints(grabberMain, grabPointMain, mainGrabInfo.PositionBeforeUpdate, mainGrabInfo.RotationBeforeUpdate, raiseApplyConstraintsEvents); + grabbableObjectMain.CheckAndApplyConstraints(grabberMain, grabPointMain, mainGrabInfo.LocalPositionBeforeUpdate, mainGrabInfo.LocalRotationBeforeUpdate, raiseApplyConstraintsEvents); } if (grabbableObjectMain.GetGrabPointSnapModeAffectsRotation(grabPointMain, UxrHandSnapDirection.ObjectToHand)) @@ -2724,7 +2774,7 @@ private void HandleMultiGrab(UxrGrabbableObject grabbableObject, ref List grabbableObject.LockedGrabReleaseDistance) { StartSmoothHandTransition(grabberSecondary, grabbableObject, pointIndexSecondary); @@ -2788,8 +2838,8 @@ private void UpdateSmoothHandTransitions(float deltaTime) float t = 1.0f - Mathf.Clamp01(handTransitionPair.Value.Timer / UxrGrabbableObject.HandLockSeconds); - handTransitionPair.Key.HandBone.position = Vector3.Lerp(handTransitionPair.Value.StartPosition, handTransitionPair.Key.transform.TransformPoint(handTransitionPair.Key.HandBoneRelativePos), t); - handTransitionPair.Key.HandBone.rotation = Quaternion.Slerp(handTransitionPair.Value.StartRotation, handTransitionPair.Key.transform.rotation * handTransitionPair.Key.HandBoneRelativeRot, t); + handTransitionPair.Key.HandBone.position = Vector3.Lerp(handTransitionPair.Key.Avatar.transform.TransformPoint(handTransitionPair.Value.StartLocalAvatarPosition), handTransitionPair.Key.transform.TransformPoint(handTransitionPair.Key.HandBoneRelativePos), t); + handTransitionPair.Key.HandBone.rotation = Quaternion.Slerp(handTransitionPair.Key.Avatar.transform.rotation * handTransitionPair.Value.StartLocalAvatarRotation, handTransitionPair.Key.transform.rotation * handTransitionPair.Key.HandBoneRelativeRot, t); if (deltaTime > 0.0f && handTransitionPair.Value.Timer < 0.0f && keysToRemove == null) { diff --git a/Scripts/Manipulation/UxrGrabbableObject.cs b/Scripts/Manipulation/UxrGrabbableObject.cs index dff1c0f4..0fe161b9 100644 --- a/Scripts/Manipulation/UxrGrabbableObject.cs +++ b/Scripts/Manipulation/UxrGrabbableObject.cs @@ -5,11 +5,14 @@ // -------------------------------------------------------------------------------------------------------------------- using System; using System.Collections.Generic; +using System.Linq; using UltimateXR.Animation.Interpolation; using UltimateXR.Avatar; using UltimateXR.Core; using UltimateXR.Core.Components; +using UltimateXR.Core.Math; using UltimateXR.Devices.Visualization; +using UltimateXR.Extensions.System.Math; using UltimateXR.Extensions.Unity; using UltimateXR.Extensions.Unity.Math; using UnityEngine; @@ -62,26 +65,56 @@ public class UxrGrabbableObject : UxrComponent, IUxrGrabbabl { #region Inspector Properties/Serialized Fields - [SerializeField] private UxrManipulationMode _manipulationMode = UxrManipulationMode.GrabAndMove; - [SerializeField] private bool _controlParentDirection = true; - [SerializeField] private bool _ignoreGrabbableParentDependency; + // Grabbable dependency + + [SerializeField] private bool _controlParentDirection = true; + [SerializeField] private bool _ignoreGrabbableParentDependency; + + // General parameters + + [SerializeField] private int _priority; + [SerializeField] private bool _allowMultiGrab = true; + + // Constraints + + [SerializeField] private UxrTranslationConstraintMode _translationConstraintMode = UxrTranslationConstraintMode.Free; + [SerializeField] private BoxCollider _restrictToBox; + [SerializeField] private SphereCollider _restrictToSphere; + [SerializeField] private Vector3 _translationLimitsMin = Vector3.zero; + [SerializeField] private Vector3 _translationLimitsMax = Vector3.zero; + [SerializeField] private UxrRotationConstraintMode _rotationConstraintMode = UxrRotationConstraintMode.Free; + [SerializeField] private Vector3 _rotationAngleLimitsMin = Vector3.zero; + [SerializeField] private Vector3 _rotationAngleLimitsMax = Vector3.zero; + [SerializeField] private bool _autoRotationProvider = true; + [SerializeField] private UxrRotationProvider _rotationProvider = UxrRotationProvider.HandOrientation; + [SerializeField] private UxrAxis _rotationLongitudinalAxis = UxrAxis.Z; + [SerializeField] private bool _needsTwoHandsToRotate; + [SerializeField] private float _lockedGrabReleaseDistance = 0.4f; + [SerializeField] private float _translationResistance; + [SerializeField] private float _rotationResistance; + + // Physics - [SerializeField] private int _priority; [SerializeField] private Rigidbody _rigidBodySource; [SerializeField] private bool _rigidBodyDynamicOnRelease = true; [SerializeField] private float _verticalReleaseMultiplier = 1.0f; [SerializeField] private float _horizontalReleaseMultiplier = 1.0f; - [SerializeField] private bool _needsTwoHandsToRotate; - [SerializeField] private bool _allowMultiGrab = true; - [SerializeField] private UxrPreviewGrabPoses _previewGrabPosesMode = UxrPreviewGrabPoses.ShowBothHands; - [SerializeField] private int _previewPosesRegenerationType; - [SerializeField] private int _previewPosesRegenerationIndex = -1; - [SerializeField] private GameObject _selectedAvatarForGrips; + // Avatar grips + + [SerializeField] private UxrPreviewGrabPoses _previewGrabPosesMode = UxrPreviewGrabPoses.ShowBothHands; + [SerializeField] private int _previewPosesRegenerationType; + [SerializeField] private int _previewPosesRegenerationIndex = -1; + [SerializeField] private GameObject _selectedAvatarForGrips; + + // Grab points + [SerializeField] private bool _firstGrabPointIsMain = true; [SerializeField] private UxrGrabPointInfo _grabPoint; [SerializeField] private List _additionalGrabPoints; + // Placement + [SerializeField] private bool _useParenting; [SerializeField] private bool _autoCreateStartAnchor; [SerializeField] private UxrGrabbableObjectAnchor _startAnchor; @@ -92,22 +125,6 @@ public class UxrGrabbableObject : UxrComponent, IUxrGrabbabl [SerializeField] private bool _dropProximityTransformUseSelf = true; [SerializeField] private Transform _dropProximityTransform; - [SerializeField] private UxrTranslationConstraintMode _translationConstraintMode = UxrTranslationConstraintMode.Free; - [SerializeField] private BoxCollider _restrictToBox; - [SerializeField] private SphereCollider _restrictToSphere; - [SerializeField] private Vector3 _translationLimitsMin = Vector3.zero; - [SerializeField] private Vector3 _translationLimitsMax = Vector3.zero; - [SerializeField] private bool _translationLimitsReferenceIsParent = true; - [SerializeField] private Transform _translationLimitsParent; - [SerializeField] private UxrRotationConstraintMode _rotationConstraintMode = UxrRotationConstraintMode.Free; - [SerializeField] private Vector3 _rotationAngleLimitsMin = Vector3.zero; - [SerializeField] private Vector3 _rotationAngleLimitsMax = Vector3.zero; - [SerializeField] private bool _rotationLimitsReferenceIsParent = true; - [SerializeField] private Transform _rotationLimitsParent; - [SerializeField] private float _lockedGrabReleaseDistance = 0.4f; - [SerializeField] private float _translationResistance; - [SerializeField] private float _rotationResistance; - #endregion #region Public Types & Data @@ -198,13 +215,120 @@ public class UxrGrabbableObject : UxrComponent, IUxrGrabbabl public Transform GrabbableParentDependency => GetGrabbableParentDependency(transform); /// - /// Gets whether the object has position/rotation constraints. + /// Gets whether the object has translation/rotation constraints. + /// + public bool IsConstrained => HasTranslationConstraint || HasRotationConstraint || IsLockedInPlace || _constraintExitTimer > 0.0f; + + /// + /// Gets whether the object has a translation constraint. + /// + public bool HasTranslationConstraint => TranslationConstraint != UxrTranslationConstraintMode.Free; + + /// + /// Gets whether the object has a rotation constraint. /// - public bool IsConstrained => TranslationConstraint != UxrTranslationConstraintMode.Free || - RotationConstraint != UxrRotationConstraintMode.Free || - Manipulation == UxrManipulationMode.RotateAroundAxis || - IsLockedInPlace || - _constraintExitTimer > 0.0f; + public bool HasRotationConstraint => RotationConstraint != UxrRotationConstraintMode.Free; + + /// + /// Gets the number of axes that the object can rotate around. + /// + public int RangeOfMotionRotationAxisCount + { + get + { + if (RotationConstraint == UxrRotationConstraintMode.Free) + { + return 3; + } + + if (RotationConstraint == UxrRotationConstraintMode.Locked) + { + return 0; + } + + return Vector3Ext.DifferentComponentCount(_rotationAngleLimitsMin, _rotationAngleLimitsMax); + } + } + + /// + /// Gets the local axes that the object can rotate around. + /// + public IEnumerable RangeOfMotionRotationAxes + { + get + { + if (RotationConstraint == UxrRotationConstraintMode.Free) + { + yield return UxrAxis.X; + yield return UxrAxis.Y; + yield return UxrAxis.Z; + } + + if (RotationConstraint == UxrRotationConstraintMode.Locked) + { + yield break; + } + + for (int axisIndex = 0; axisIndex < 3; ++axisIndex) + { + if (!Mathf.Approximately(_rotationAngleLimitsMin[axisIndex], _rotationAngleLimitsMax[axisIndex])) + { + yield return axisIndex; + } + } + } + } + + /// + /// Gets the local axes that the object can rotate around with limited range of motion (not freely, nor locked). + /// + public IEnumerable LimitedRangeOfMotionRotationAxes + { + get + { + if (RotationConstraint == UxrRotationConstraintMode.Free || RotationConstraint == UxrRotationConstraintMode.Locked) + { + yield break; + } + + for (int axisIndex = 0; axisIndex < 3; ++axisIndex) + { + if (!Mathf.Approximately(_rotationAngleLimitsMin[axisIndex], _rotationAngleLimitsMax[axisIndex])) + { + yield return axisIndex; + } + } + } + } + + /// + /// Gets the index of the rotation axis if the object can only rotate around that single axis. + /// Will return any of these values: (x = 0, y = 1, z = 2, none or more than one = -1). + /// + public int SingleRotationAxisIndex + { + get + { + if (RotationConstraint == UxrRotationConstraintMode.Free || RotationConstraint == UxrRotationConstraintMode.Locked) + { + return -1; + } + + int constrainedAxisIndex = 0; + int constrainedAxisCount = 0; + + for (int axisIndex = 0; axisIndex < 3; ++axisIndex) + { + if (!Mathf.Approximately(_rotationAngleLimitsMin[axisIndex], _rotationAngleLimitsMax[axisIndex])) + { + constrainedAxisIndex = axisIndex; + constrainedAxisCount++; + } + } + + return constrainedAxisCount == 1 ? constrainedAxisIndex : -1; + } + } /// /// Gets the total number of grab points. @@ -265,6 +389,39 @@ public class UxrGrabbableObject : UxrComponent, IUxrGrabbabl /// public UxrGrabbableObjectAnchor StartAnchor => _startAnchor; + /// + /// Gets or sets the rotation angle in degrees for objects that have a single rotational degree of freedom. + /// + public float SingleRotationAxisDegrees + { + get + { + int singleRotationAxisIndex = SingleRotationAxisIndex; + + if (singleRotationAxisIndex == -1) + { + return 0.0f; + } + + return (_singleRotationAngleCumulative + _singleRotationAngleGrab).Clamped(_rotationAngleLimitsMin[singleRotationAxisIndex], _rotationAngleLimitsMax[singleRotationAxisIndex]); + } + set + { + if (!IsBeingGrabbed) + { + int singleRotationAxisIndex = SingleRotationAxisIndex; + + if (singleRotationAxisIndex == -1) + { + return; + } + + _singleRotationAngleCumulative = value.Clamped(_rotationAngleLimitsMin[singleRotationAxisIndex], _rotationAngleLimitsMax[singleRotationAxisIndex]); + transform.localRotation = InitialLocalRotation * Quaternion.AngleAxis(_singleRotationAngleCumulative, (UxrAxis)singleRotationAxisIndex); + } + } + } + /// /// Gets the where the object is actually placed or null if it's not placed on /// any. @@ -281,15 +438,6 @@ public class UxrGrabbableObject : UxrComponent, IUxrGrabbabl /// public bool IsLockedInPlace { get; set; } = false; - /// - /// Gets or sets the manipulation mode used by the grabbable object. - /// - public UxrManipulationMode Manipulation - { - get => _manipulationMode; - set => _manipulationMode = value; - } - /// /// Gets or sets whether a dependent object can control the grabbable parent's direction when both are being grabbed at /// the same time. @@ -425,32 +573,6 @@ public Vector3 TranslationLimitsMax set => _translationLimitsMax = value; } - /// - /// Gets or sets whether the reference for the and - /// is the object's parent. A different object can be specified using - /// . - /// - public bool IsParentTranslationLimitsReference - { - get => _translationLimitsReferenceIsParent; - set => _translationLimitsReferenceIsParent = value; - } - - /// - /// Gets or sets the reference for and when - /// is used. - /// - public Transform TranslationLimitsParent - { - get => _translationLimitsParent; - set => _translationLimitsParent = value; - } - - /// - /// Gets or sets the initial local position with respect to the translation constraint reference. - /// - public Vector3 InitialLocalPositionToReference { get; set; } = Vector3.zero; - /// /// Gets or sets the rotation constraint type. /// @@ -481,31 +603,24 @@ public Vector3 RotationAngleLimitsMax } /// - /// Gets or sets whether the reference for the and - /// is the object's parent. A different object can be specified using - /// . + /// Gets or sets the rotation provider. The rotation provider is used in objects with constrained position to know + /// which element drives the rotation. /// - public bool IsParentRotationLimitsReference + public UxrRotationProvider RotationProvider { - get => _rotationLimitsReferenceIsParent; - set => _rotationLimitsReferenceIsParent = value; + get => HasTranslationConstraint && LimitedRangeOfMotionRotationAxes.Any() ? _rotationProvider : UxrRotationProvider.HandOrientation; + set => _rotationProvider = value; } /// - /// Gets or sets the reference for and when - /// is used. + /// Gets or sets which one is the longitudinal axis (x, y or z) in a rotation with constraints on two or more axes. /// - public Transform RotationLimitsParent + public UxrAxis RotationLongitudinalAxis { - get => _rotationLimitsParent; - set => _rotationLimitsParent = value; + get => _rotationLongitudinalAxis; + set => _rotationLongitudinalAxis = value; } - /// - /// Gets or sets the initial local rotation angles with respect to the rotation constraint reference. - /// - public Vector3 InitialLocalEulerAnglesToReference { get; set; } = Vector3.zero; - /// /// Gets or sets the resistance to the object being moved around. This can be used to smooth out the position but also /// to simulate heavy objects. @@ -812,8 +927,8 @@ public bool CanBeGrabbedByGrabber(UxrGrabber grabber, int grabPoint) /// Whether the returned data is meaningful public bool ComputeRequiredGrabberTransform(UxrGrabber grabber, int grabPoint, out Vector3 grabberPosition, out Quaternion grabberRotation) { - grabberPosition = Vector3.zero; - grabberRotation = Quaternion.identity; + grabberPosition = grabber.transform.position; + grabberRotation = grabber.transform.rotation; UxrGrabPointInfo grabPointInfo = GetGrabPoint(grabPoint); Transform snapTransform = GetGrabPointGrabAlignTransform(grabber.Avatar, grabPoint, grabber.Side); @@ -823,8 +938,15 @@ public bool ComputeRequiredGrabberTransform(UxrGrabber grabber, int grabPoint, o return false; } - grabberPosition = snapTransform.position; - grabberRotation = snapTransform.rotation; + if (GetSnapModeAffectsPosition(grabPointInfo.SnapMode)) + { + grabberPosition = snapTransform.position; + } + + if (GetSnapModeAffectsRotation(grabPointInfo.SnapMode)) + { + grabberRotation = snapTransform.rotation; + } UxrGrabPointShape grabPointShape = GetGrabPointShape(grabPoint); @@ -977,11 +1099,11 @@ public void CheckAndApplyLockHand(UxrGrabber grabber, int grabPoint) { if (GetGrabPointSnapModeAffectsPosition(grabPoint)) { - grabber.HandBone.position = Vector3.Lerp(grabInfo.HandBonePositionOnGrab, grabber.HandBone.position, t); + grabber.HandBone.position = Vector3.Lerp(grabber.Avatar.transform.TransformPoint(grabInfo.HandBoneLocalAvatarPositionOnGrab), grabber.HandBone.position, t); } if (GetGrabPointSnapModeAffectsRotation(grabPoint)) { - grabber.HandBone.rotation = Quaternion.Slerp(grabInfo.HandBoneRotationOnGrab, grabber.HandBone.rotation, t); + grabber.HandBone.rotation = Quaternion.Slerp(grabber.Avatar.transform.rotation * grabInfo.HandBoneLocalAvatarRotationOnGrab, grabber.HandBone.rotation, t); } } } @@ -1006,10 +1128,10 @@ internal void StartSmoothConstrain() /// internal void StartSmoothConstrainExit() { - _constraintTimer = -1.0f; - _constraintExitTimer = ConstrainSeconds; - _constraintExitPos = transform.position; - _constraintExitRot = transform.rotation; + _constraintTimer = -1.0f; + _constraintExitTimer = ConstrainSeconds; + _constraintLocalExitPos = transform.localPosition; + _constraintLocalExitRot = transform.localRotation; } /// @@ -1271,13 +1393,13 @@ internal void ComputeGrabTransforms(UxrGrabber grabber, int grabPoint) /// /// Grabber that is currently grabbing the object /// Point that is being grabbed - /// Object position before being updated - /// Object rotation before being updated + /// Object local position before being updated + /// Object local rotation before being updated /// /// Whether to propagate and /// events /// - internal void CheckAndApplyConstraints(UxrGrabber grabber, int grabPoint, Vector3 positionBeforeUpdate, Quaternion rotationBeforeUpdate, bool propagateEvents) + internal void CheckAndApplyConstraints(UxrGrabber grabber, int grabPoint, Vector3 localPositionBeforeUpdate, Quaternion localRotationBeforeUpdate, bool propagateEvents) { if (propagateEvents) { @@ -1286,20 +1408,116 @@ internal void CheckAndApplyConstraints(UxrGrabber grabber, int grabPoint, Vector if (IsLockedInPlace) { - transform.position = positionBeforeUpdate; - transform.rotation = rotationBeforeUpdate; + transform.localPosition = localPositionBeforeUpdate; + transform.localRotation = localRotationBeforeUpdate; + + if (propagateEvents) + { + OnConstraintsApplied(new UxrApplyConstraintsEventArgs(this)); + } + return; } - if (_manipulationMode == UxrManipulationMode.GrabAndMove) + UxrGrabPointInfo grabPointInfo = GetGrabPoint(grabPoint); + + if (grabPointInfo == null) { - // Alignment + if (propagateEvents) + { + OnConstraintsApplied(new UxrApplyConstraintsEventArgs(this)); + } - if (grabPoint >= 0 && UsesGrabbableParentDependency == false) + return; + } + + UxrRuntimeGripInfo gripInfo = grabPointInfo.RuntimeGrabs[grabber]; + + if (RotationProvider == UxrRotationProvider.HandPositionAroundPivot) + { + // Position + + transform.position = grabber.transform.TransformPoint(GetGrabPointRelativeGrabPosition(grabber, grabPoint)); + ConstrainTransform(true, false, localPositionBeforeUpdate, localRotationBeforeUpdate); + + int rangeOfMotionAxisCount = RangeOfMotionRotationAxisCount; + + // Rotation: We use the angle between V1(pivot, initial grab position) and V2 (pivot, current grab position). + // This method works better for levers, steering wheels, etc. It won't work well with elements + // like knobs or similar because the point where the torque is applied lies in the rotation axis itself. + // In this cases we recommend using ManipulationMode.GrabAndMove instead. + + Vector3 grabDirection = grabber.transform.TransformPoint(gripInfo.GrabberLocalLeverageSource) - transform.position; + Vector3 initialGrabDirection = grabber.Avatar.transform.TransformPoint(gripInfo.GrabberLocalAvatarLeverageSourceOnGrab) - transform.position; + + if (rangeOfMotionAxisCount == 1) { - UxrGrabPointInfo grabPointInfo = GetGrabPoint(grabPoint); - UxrRuntimeGripInfo gripInfo = grabPointInfo.RuntimeGrabs[grabber]; + // When there's a single axis with range of motion, we use additional computations to be able to specify ranges below/above -180/180 degrees + + Quaternion initialLocalRotation = Quaternion.Euler(InitialLocalEulerAngles); + UxrAxis rotationAxis = SingleRotationAxisIndex; + Vector3 projectedGrabDirection = Vector3.ProjectOnPlane(grabDirection, localRotationBeforeUpdate * rotationAxis); + Vector3 projectedInitialGrabDirection = Vector3.ProjectOnPlane(initialGrabDirection, localRotationBeforeUpdate * rotationAxis); + + float angle = Vector3.SignedAngle(projectedInitialGrabDirection, projectedGrabDirection, localRotationBeforeUpdate * rotationAxis); + float angleDelta = angle - _singleRotationAngleGrab.ToEuler180(); + // Keep track of turns below/above -360/360 degrees. + + if (angleDelta > 180.0f) + { + _singleRotationAngleGrab -= 360.0f - angleDelta; + } + else if (angleDelta < -180.0f) + { + _singleRotationAngleGrab += 360.0f + angleDelta; + } + else + { + _singleRotationAngleGrab += angleDelta; + } + + // Clamp inside valid range + + float rotationAngle = (_singleRotationAngleCumulative + _singleRotationAngleGrab).Clamped(_rotationAngleLimitsMin[rotationAxis], _rotationAngleLimitsMax[rotationAxis]); + _singleRotationAngleGrab = rotationAngle - _singleRotationAngleCumulative; + + // Rotate using absolute current rotation to preserve precision + + transform.localRotation = initialLocalRotation * Quaternion.AngleAxis(rotationAngle, rotationAxis); + } + else + { + // Here we can potentially have up to 3 rotational ranges of motion but we use the hand position around the pivot to rotate the object, so we need to be + // extra careful with not losing any information when computing the rotation and clamping. + + // First compute the rotation of the grabbed object if it were to be controlled by the hand orientation + Quaternion rotUsingHandOrientation = grabber.transform.rotation * GetGrabPointRelativeGrabRotation(grabber, grabPoint); + + // Now compute the rotation of the grabbed object if we were to use the hand position around the axis. But we do not use this directly because we + // potentially lose the rotation around the longitudinal axis if there is one. We use it instead to know where the longitudinal axis will point, + // and correct rotUsingHandOrientation. + Quaternion rotationOnGrab = transform.GetParentRotation() * gripInfo.LocalRotationOnGrab; + Quaternion rotUsingHandPosAroundAxis = Quaternion.FromToRotation(initialGrabDirection.normalized, grabDirection.normalized) * rotationOnGrab; + Quaternion rotCorrection = Quaternion.FromToRotation(rotUsingHandOrientation * _rotationLongitudinalAxis, rotUsingHandPosAroundAxis * _rotationLongitudinalAxis); + Quaternion localRotation = Quaternion.Inverse(transform.GetParentRotation()) * rotCorrection * rotUsingHandOrientation; + + if (RotationConstraint == UxrRotationConstraintMode.Free) + { + transform.localRotation = localRotation; + } + else + { + transform.localRotation = ClampRotation(localRotation, localRotationBeforeUpdate, InitialLocalEulerAngles, _rotationAngleLimitsMin, _rotationAngleLimitsMax, false, ref _singleRotationAngleCumulative); + } + } + } + else + { + // Alignment + + if (UsesGrabbableParentDependency == false) + { Quaternion sourceRotation = transform.rotation * gripInfo.RelativeGrabAlignRotation; Quaternion targetRotation = grabber.transform.rotation; @@ -1317,19 +1535,12 @@ internal void CheckAndApplyConstraints(UxrGrabber grabber, int grabPoint, Vector } } - transform.ApplyAlignment(transform.TransformPoint(gripInfo.RelativeGrabAlignPosition), - sourceRotation, - grabber.transform.position, - targetRotation, - GetGrabPointSnapModeAffectsRotation(grabPoint, UxrHandSnapDirection.ObjectToHand), - false); - ConstrainTransform(false, true, positionBeforeUpdate, rotationBeforeUpdate); // Constrain rotation before snapping pivot - transform.ApplyAlignment(transform.TransformPoint(gripInfo.RelativeGrabAlignPosition), - sourceRotation, - grabber.transform.position, - targetRotation, - false, - GetGrabPointSnapModeAffectsPosition(grabPoint, UxrHandSnapDirection.ObjectToHand)); + transform.ApplyAlignment(transform.TransformPoint(gripInfo.RelativeGrabAlignPosition), sourceRotation, grabber.transform.position, targetRotation, GetGrabPointSnapModeAffectsRotation(grabPoint, UxrHandSnapDirection.ObjectToHand), false); + + // Constrain rotation before snapping pivot + ConstrainTransform(false, true, localPositionBeforeUpdate, localRotationBeforeUpdate); + + transform.ApplyAlignment(transform.TransformPoint(gripInfo.RelativeGrabAlignPosition), sourceRotation, grabber.transform.position, targetRotation, false, GetGrabPointSnapModeAffectsPosition(grabPoint, UxrHandSnapDirection.ObjectToHand)); if (gripInfo.GrabTimer > 0.0f) { @@ -1342,48 +1553,23 @@ internal void CheckAndApplyConstraints(UxrGrabber grabber, int grabPoint, Vector // Translation & Rotation - ConstrainTransform(true, true, positionBeforeUpdate, rotationBeforeUpdate); + ConstrainTransform(true, true, localPositionBeforeUpdate, localRotationBeforeUpdate); // Smoothly exit from a constraint if we had manually created the transition using StartSmoothConstraintExit() if (_constraintExitTimer > 0.0f) { float constraintExitT = 1.0f - _constraintExitTimer / ConstrainSeconds; - transform.position = Vector3.Lerp(_constraintExitPos, transform.position, constraintExitT); - transform.rotation = Quaternion.Lerp(_constraintExitRot, transform.rotation, constraintExitT); + transform.localPosition = Vector3.Lerp(_constraintLocalExitPos, transform.localPosition, constraintExitT); + transform.localRotation = Quaternion.Lerp(_constraintLocalExitRot, transform.localRotation, constraintExitT); } } - else if (_manipulationMode == UxrManipulationMode.RotateAroundAxis && grabPoint >= 0) - { - UxrGrabPointInfo grabPointInfo = GetGrabPoint(grabPoint); - UxrRuntimeGripInfo gripInfo = grabPointInfo.RuntimeGrabs[grabber]; - - // Position - - transform.position = grabber.transform.TransformPoint(GetGrabPointRelativeGrabPosition(grabber, grabPoint)); - ConstrainTransform(true, false, positionBeforeUpdate, rotationBeforeUpdate); - - // Rotation: We use the angle between V1(pivot, initial grab position) and V2 (pivot, current grab position). - // This method works better for levers, steering wheels, etc. It won't work well with elements - // like knobs or similar because the point where the torque is applied lies in the rotation axis itself. - // In this cases we recommend using ManipulationMode.GrabAndMove instead. - - Vector3 grabDirection = grabber.transform.position - transform.position; - Vector3 initialGrabDirection = grabber.Avatar.transform.TransformPoint(gripInfo.GrabberLocalAvatarPositionOnGrab) - transform.position; - Quaternion rotationOnGrab = transform.parent != null ? transform.parent.rotation * gripInfo.LocalRotationOnGrab : gripInfo.LocalRotationOnGrab; - - Quaternion rotation = Quaternion.FromToRotation(initialGrabDirection.normalized, grabDirection.normalized) * rotationOnGrab; - Quaternion inverseParentRotation = Quaternion.Inverse(RotationLimitsParentRotation); - Quaternion localRotation = inverseParentRotation * rotation; - Quaternion localRotationPrevious = inverseParentRotation * rotationBeforeUpdate; - Quaternion clampedLocalRotation = ClampRotation(localRotation, localRotationPrevious, Quaternion.Euler(InitialLocalEulerAnglesToReference), _rotationAngleLimitsMin, _rotationAngleLimitsMax); - transform.rotation = RotationLimitsParentRotation * clampedLocalRotation; - } - if (UxrGrabManager.Instance.GetHandsGrabbingCount(this) == 1) + if (UxrGrabManager.Instance.GetHandsGrabbingCount(this) == 1 && _constraintTimer <= 0.0f && _constraintExitTimer <= 0.0f && _placementTimer <= 0.0f && gripInfo.HandLockTimer <= 0.0f && gripInfo.GrabTimer <= 0.0f) { - transform.position = UxrInterpolator.SmoothDampPosition(positionBeforeUpdate, transform.position, _translationResistance); - transform.rotation = UxrInterpolator.SmoothDampRotation(rotationBeforeUpdate, transform.rotation, _rotationResistance); + // Only apply smoothing when grabbing with a single hand and no transitions are being executed + transform.localPosition = UxrInterpolator.SmoothDampPosition(localPositionBeforeUpdate, transform.localPosition, _translationResistance); + transform.localRotation = UxrInterpolator.SmoothDampRotation(localRotationBeforeUpdate, transform.localRotation, _rotationResistance); } if (propagateEvents) @@ -1428,19 +1614,42 @@ internal void NotifyBeginGrab(UxrGrabber grabber, int grabPoint) ComputeGrabTransforms(grabber, grabPoint); - grabInfo.RelativeGrabAlignRotation = Quaternion.Inverse(transform.rotation) * snapRotation; - grabInfo.RelativeGrabAlignPosition = transform.InverseTransformPoint(snapPosition); - grabInfo.RelativeProximityPosition = transform.InverseTransformPoint(snapMatrix.MultiplyPoint(localProximity)); + grabInfo.RelativeGrabAlignRotation = Quaternion.Inverse(transform.rotation) * snapRotation; + grabInfo.RelativeGrabAlignPosition = transform.InverseTransformPoint(snapPosition); + grabInfo.RelativeProximityPosition = transform.InverseTransformPoint(snapMatrix.MultiplyPoint(localProximity)); + grabInfo.GrabberLocalLeverageSource = Vector3.zero; + + if (_autoRotationProvider) + { + _rotationProvider = Editor_GetAutoRotationProvider(snapPosition); + } + + if (RotationProvider == UxrRotationProvider.HandPositionAroundPivot && GetGrabPointSnapModeAffectsRotation(grabPoint)) + { + // Check if the leverage is provided by the inner side of the palm (where the thumb is) or the outer side. + // We do that by checking the difference in distance of both to the rotation pivot. If it is above a threshold, it is provided by either one of the two. + // If it is below a threshold it is provide by the grabber itself. + + float separation = UxrConstants.Hand.HandWidth; + float distanceInner = Vector3.Distance(transform.position, snapPosition + snapRotation * grabber.LocalPalmThumbDirection * (separation * 0.5f)); + float distanceOuter = Vector3.Distance(transform.position, snapPosition - snapRotation * grabber.LocalPalmThumbDirection * (separation * 0.5f)); + + if (Mathf.Abs(distanceInner - distanceOuter) > separation * 0.5f) + { + grabInfo.GrabberLocalLeverageSource = grabber.LocalPalmThumbDirection * separation * 0.5f * (distanceInner > distanceOuter ? 1.0f : -1.0f); + } + } - grabInfo.LocalPositionOnGrab = transform.localPosition; - grabInfo.LocalRotationOnGrab = transform.localRotation; - grabInfo.AlignPositionOnGrab = snapPosition; - grabInfo.AlignRotationOnGrab = snapRotation; - grabInfo.GrabberLocalAvatarPositionOnGrab = grabber.Avatar.transform.InverseTransformPoint(grabber.transform.position); - grabInfo.GrabberLocalAvatarRotationOnGrab = Quaternion.Inverse(grabber.Avatar.transform.rotation) * grabber.transform.rotation; - grabInfo.HandBonePositionOnGrab = grabber.HandBone.position; - grabInfo.HandBoneRotationOnGrab = grabber.HandBone.rotation; - grabInfo.GrabTimer = ObjectAlignmentSeconds; + grabInfo.LocalPositionOnGrab = transform.localPosition; + grabInfo.LocalRotationOnGrab = transform.localRotation; + grabInfo.AlignPositionOnGrab = snapPosition; + grabInfo.AlignRotationOnGrab = snapRotation; + grabInfo.GrabberLocalAvatarPositionOnGrab = grabber.Avatar.transform.InverseTransformPoint(grabber.transform.position); + grabInfo.GrabberLocalAvatarRotationOnGrab = Quaternion.Inverse(grabber.Avatar.transform.rotation) * grabber.transform.rotation; + grabInfo.GrabberLocalAvatarLeverageSourceOnGrab = grabber.Avatar.transform.InverseTransformPoint(grabber.transform.TransformPoint(grabInfo.GrabberLocalLeverageSource)); + grabInfo.HandBoneLocalAvatarPositionOnGrab = grabber.Avatar.transform.InverseTransformPoint(grabber.HandBone.position); + grabInfo.HandBoneLocalAvatarRotationOnGrab = Quaternion.Inverse(grabber.Avatar.transform.rotation) * grabber.HandBone.rotation; + grabInfo.GrabTimer = ObjectAlignmentSeconds; if (IsConstrained || UxrGrabManager.Instance.IsBeingGrabbedByOtherThan(this, grabPoint, grabber)) { @@ -1453,6 +1662,11 @@ internal void NotifyBeginGrab(UxrGrabber grabber, int grabPoint) // Do not lock the hand to the grab point while in transition, only when the object is already in the hand grabInfo.LockHandInTransition = false; } + + if (!UxrGrabManager.Instance.IsBeingGrabbedByOtherThan(this, grabPoint, grabber)) + { + _singleRotationAngleGrab = 0.0f; + } } /// @@ -1462,12 +1676,15 @@ internal void NotifyBeginGrab(UxrGrabber grabber, int grabPoint) /// Point that was grabbed internal void NotifyEndGrab(UxrGrabber grabber, int grabPoint) { - // Don't remove from grabPointInfo.RuntimeGrabs because we may have transitions that need the GrabInfo - - /* UxrGrabPointInfo grabPointInfo = GetGrabPoint(grabPoint); - grabPointInfo.RuntimeGrabs.Remove(grabber); - */ + + if (grabPointInfo != null && grabPointInfo.RuntimeGrabs != null && grabPointInfo.RuntimeGrabs.ContainsKey(grabber)) + { + grabPointInfo.RuntimeGrabs.Remove(grabber); + + _singleRotationAngleCumulative += _singleRotationAngleGrab; + _singleRotationAngleGrab = 0.0f; + } } #endregion @@ -1482,8 +1699,19 @@ protected override void Awake() { base.Awake(); + // Make sure singleton is created so that grabbable objects get registered UxrGrabManager.Instance.Poke(); + // Fix some common mistakes just in case + Vector3 fixedMin = Vector3.Min(_translationLimitsMin, _translationLimitsMax); + Vector3 fixedMax = Vector3.Max(_translationLimitsMin, _translationLimitsMax); + _translationLimitsMin = fixedMin; + _translationLimitsMax = fixedMax; + fixedMin = Vector3.Min(_rotationAngleLimitsMin, _rotationAngleLimitsMax); + fixedMax = Vector3.Max(_rotationAngleLimitsMin, _rotationAngleLimitsMax); + _rotationAngleLimitsMin = fixedMin; + _rotationAngleLimitsMax = fixedMax; + if (_autoCreateStartAnchor) { UxrGrabbableObjectAnchor newAnchor = new GameObject($"{name} Auto Anchor", typeof(UxrGrabbableObjectAnchor)).GetComponent(); @@ -1511,9 +1739,7 @@ protected override void Awake() } } - InitialLocalPositionToReference = TranslationLimitsParentMatrix.inverse.MultiplyPoint(transform.position); - InitialLocalEulerAnglesToReference = (Quaternion.Inverse(RotationLimitsParentRotation) * transform.rotation).eulerAngles; - _initialIsKinematic = IsKinematic; + _initialIsKinematic = IsKinematic; _grabPointShapes = new Dictionary(); UxrGrabPointShape[] grabPointShapes = GetComponents(); @@ -1531,12 +1757,32 @@ protected override void Awake() } } + /// + /// Subscribes to events. + /// + protected override void OnEnable() + { + base.OnEnable(); + UxrManager.StageUpdated += UxrManager_StageUpdated; + } + + /// + /// Unsubscribes from events. + /// + protected override void OnDisable() + { + base.OnDisable(); + + UxrManager.StageUpdated -= UxrManager_StageUpdated; + } + /// /// Resets the component. /// private void Reset() { - _grabPoint = new UxrGrabPointInfo(); + _grabPoint = new UxrGrabPointInfo(); + _rotationLongitudinalAxis = Editor_GetMostProbableLongitudinalRotationAxis(); } /// @@ -1564,64 +1810,72 @@ protected override void Start() _constraintExitTimer = -1.0f; } + #endregion + + #region Event Handling Methods + /// - /// Updates some of the transitions made to have smooth object interaction. + /// Called when the finished updating a stage. /// - private void Update() + /// Stage that finished updating + private void UxrManager_StageUpdated(UxrUpdateStage stage) { - for (int i = 0; i < GrabPointCount; ++i) + if (stage == UxrUpdateStage.Manipulation) { - UxrGrabPointInfo grabPointInfo = GetGrabPoint(i); - - if (grabPointInfo.RuntimeGrabs != null) + for (int i = 0; i < GrabPointCount; ++i) { - foreach (KeyValuePair grabPairInfo in grabPointInfo.RuntimeGrabs) + UxrGrabPointInfo grabPointInfo = GetGrabPoint(i); + + if (grabPointInfo.RuntimeGrabs != null) { - if (grabPairInfo.Value.GrabTimer > 0.0f) + foreach (KeyValuePair grabPairInfo in grabPointInfo.RuntimeGrabs) { - grabPairInfo.Value.GrabTimer -= Time.unscaledDeltaTime; - } + if (grabPairInfo.Value.GrabTimer > 0.0f) + { + grabPairInfo.Value.GrabTimer -= Time.unscaledDeltaTime; + } - if (grabPairInfo.Value.HandLockTimer > 0.0f) - { - grabPairInfo.Value.HandLockTimer -= Time.unscaledDeltaTime; + if (grabPairInfo.Value.HandLockTimer > 0.0f) + { + grabPairInfo.Value.HandLockTimer -= Time.unscaledDeltaTime; + } } } } - } - if (_placementTimer > 0.0f && CurrentAnchor) - { - _placementTimer -= Time.unscaledDeltaTime; + if (_placementTimer > 0.0f && CurrentAnchor) + { + _placementTimer -= Time.unscaledDeltaTime; - transform.ApplyAlignment(DropAlignTransform.position, - DropAlignTransform.rotation, - CurrentAnchor.AlignTransform.position, - CurrentAnchor.AlignTransform.rotation, - GetSnapModeAffectsRotation(_dropSnapMode), - GetSnapModeAffectsPosition(_dropSnapMode), - 1.0f - Mathf.Clamp01(_placementTimer / ObjectAlignmentSeconds)); + transform.ApplyAlignment(DropAlignTransform.position, + DropAlignTransform.rotation, + CurrentAnchor.AlignTransform.position, + CurrentAnchor.AlignTransform.rotation, + GetSnapModeAffectsRotation(_dropSnapMode), + GetSnapModeAffectsPosition(_dropSnapMode), + 1.0f - Mathf.Clamp01(_placementTimer / ObjectAlignmentSeconds)); - if (_placementTimer <= 0.0f) - { - CurrentAnchor.RaiseSmoothTransitionPlaceEnded(); + if (_placementTimer <= 0.0f) + { + CurrentAnchor.RaiseSmoothTransitionPlaceEnded(); + } } - } - - if (_constraintTimer > 0.0f) - { - _constraintTimer -= Time.unscaledDeltaTime; - if (UxrGrabManager.Instance.IsBeingGrabbed(this) == false) + if (_constraintTimer > 0.0f) { - // This object was released into the constrained zone before the full transition finished. Finish manually. - ConstrainTransform(true, true, transform.position, transform.rotation); + _constraintTimer -= Time.unscaledDeltaTime; + + if (UxrGrabManager.Instance.IsBeingGrabbed(this) == false) + { + // This object was released into the constrained zone before the full transition finished. Finish manually. + ConstrainTransform(true, true, transform.position, transform.rotation); + } } - } - if (_constraintExitTimer > 0.0f) - { - _constraintExitTimer -= Time.unscaledDeltaTime; + if (_constraintExitTimer > 0.0f) + { + _constraintExitTimer -= Time.unscaledDeltaTime; + } } } @@ -1775,43 +2029,56 @@ private Transform GetGrabbableParentDependency(Transform grabbableTransform) /// /// Whether to apply the position constraints if any /// Whether to apply the rotation constraints if any - /// The object position on the last frame - /// The object rotation on the last frame - private void ConstrainTransform(bool processPosition, bool processRotation, Vector3 lastPosition, Quaternion lastRotation) + /// The object local position before the manipulation update during this frame + /// The object local rotation before the manipulation update during this frame + private void ConstrainTransform(bool processPosition, bool processRotation, Vector3 unprocessedLocalPosition, Quaternion unprocessedLocalRotation) { // Rotation - if (_rotationConstraintMode == UxrRotationConstraintMode.RestrictLocalRotation && processRotation) + if (processRotation && _rotationConstraintMode != UxrRotationConstraintMode.Free) { - Quaternion inverseParentRot = Quaternion.Inverse(RotationLimitsParentRotation); - Quaternion localQuat = inverseParentRot * transform.rotation; - Quaternion localQuatPrevious = inverseParentRot * lastRotation; - Quaternion clampedQuat = ClampRotation(localQuat, localQuatPrevious, Quaternion.Euler(InitialLocalEulerAnglesToReference), _rotationAngleLimitsMin, _rotationAngleLimitsMax); - Quaternion targetQuat = RotationLimitsParentRotation * clampedQuat; + Quaternion targetLocalRotation = InitialLocalRotation; + + if (_rotationConstraintMode == UxrRotationConstraintMode.RestrictLocalRotation) + { + targetLocalRotation = ClampRotation(transform.localRotation, unprocessedLocalRotation, InitialLocalEulerAngles, _rotationAngleLimitsMin, _rotationAngleLimitsMax, false, ref _singleRotationAngleCumulative); + } - transform.rotation = _constraintTimer < 0.0f ? targetQuat : Quaternion.Slerp(transform.rotation, targetQuat, 1.0f - _constraintTimer / ConstrainSeconds); + transform.localRotation = _constraintTimer < 0.0f ? targetLocalRotation : Quaternion.Slerp(unprocessedLocalRotation, targetLocalRotation, 1.0f - _constraintTimer / ConstrainSeconds); } // Translation - if (processPosition) + if (processPosition && _translationConstraintMode != UxrTranslationConstraintMode.Free) { + Vector3 localPos = unprocessedLocalPosition; + Vector3 targetLocalPos = InitialLocalPosition; + if (_translationConstraintMode == UxrTranslationConstraintMode.RestrictToBox && _restrictToBox != null) { - Vector3 targetPos = transform.position.ClampToBox(_restrictToBox); - transform.position = _constraintTimer < 0.0f ? targetPos : Vector3.Lerp(transform.position, targetPos, 1.0f - _constraintTimer / ConstrainSeconds); + targetLocalPos = transform.GetParentWorldMatrix().inverse.MultiplyPoint(transform.position.ClampToBox(_restrictToBox)); } else if (_translationConstraintMode == UxrTranslationConstraintMode.RestrictToSphere && _restrictToSphere != null) { - Vector3 targetPos = transform.position.ClampToSphere(_restrictToSphere); - transform.position = _constraintTimer < 0.0f ? targetPos : Vector3.Lerp(transform.position, targetPos, 1.0f - _constraintTimer / ConstrainSeconds); + targetLocalPos = transform.GetParentWorldMatrix().inverse.MultiplyPoint(transform.position.ClampToSphere(_restrictToSphere)); } - else if (_translationConstraintMode == UxrTranslationConstraintMode.RestrictLocalOffset || Manipulation == UxrManipulationMode.RotateAroundAxis) + else if (_translationConstraintMode == UxrTranslationConstraintMode.RestrictLocalOffset) { - Vector3 localPos = TranslationLimitsParentMatrix.inverse.MultiplyPoint(transform.position); - Vector3 targetPos = TranslationLimitsParentMatrix.MultiplyPoint(localPos.Clamp(InitialLocalPositionToReference + _translationLimitsMin, InitialLocalPositionToReference + _translationLimitsMax)); - transform.position = _constraintTimer < 0.0f ? targetPos : Vector3.Lerp(transform.position, targetPos, 1.0f - _constraintTimer / ConstrainSeconds); + if (transform.parent != null || InitialParent == null) + { + // Current local space -> Initial local space using the matrix at Awake() + Vector3 localPosOffset = InitialRelativeMatrix.inverse.MultiplyPoint3x4(transform.localPosition); + + // Clamp in initial local space, transform to current local space + targetLocalPos = InitialRelativeMatrix.MultiplyPoint(localPosOffset.Clamp(_translationLimitsMin, _translationLimitsMax)); + } + else + { + targetLocalPos = transform.localPosition; + } } + + transform.localPosition = _constraintTimer < 0.0f ? targetLocalPos : Vector3.Lerp(localPos, targetLocalPos, 1.0f - _constraintTimer / ConstrainSeconds); } } @@ -1819,220 +2086,186 @@ private void ConstrainTransform(bool processPosition, bool processRotation, Vect /// Tries to clamp a rotation. /// /// Rotation to clamp - /// Rotation the previous frame - /// Initial rotation + /// Rotation before the manipulation update this frame + /// Initial euler angles /// Minimum euler values /// Maximum euler values - /// - private Quaternion ClampRotation(Quaternion rot, Quaternion previousRot, Quaternion initialRot, Vector3 eulerMin, Vector3 eulerMax) + /// Whether to invert the rotation angles + /// + /// The rotation angle if rotation is being constrained to a single axis. This improves + /// constraining by allowing ranges over +-360 degrees. + /// + /// Clamped rotation + private Quaternion ClampRotation(Quaternion rot, Quaternion rotBeforeUpdate, Vector3 initialEuler, Vector3 eulerMin, Vector3 eulerMax, bool invertRotation, ref float singleRotationAngle) { - Vector3 axis; - Vector3 currentAxis; - Quaternion quatMin; - Quaternion quatMax; - float angleMin; - float angleMax; - - int axisIndex = 0; - int axisCount = 0; - - // First pass: Check for components that have a fixed value. Sometimes x, y or z needs to be -90 for instance because of the object axis system + int rangeOfMotionAxisCount = RangeOfMotionRotationAxisCount; + Quaternion initialRot = Quaternion.Euler(initialEuler); - for (axisIndex = 0; axisIndex < 3; ++axisIndex) + if (RangeOfMotionRotationAxisCount == 0) { - if (Mathf.Approximately(eulerMin[axisIndex], eulerMax[axisIndex]) && !Mathf.Approximately(eulerMin[axisIndex], 0.0f)) - { - Vector3 fixedEuler = rot.eulerAngles; - fixedEuler[axisIndex] = eulerMin[axisIndex]; - rot = Quaternion.Euler(fixedEuler); - } - else if(!Mathf.Approximately(eulerMin[axisIndex], eulerMax[axisIndex])) - { - axisCount++; - } + return initialRot; } - // If we have more than one axis constraint we probably have something like a socket-ball joint. We solve this easier. - - if (axisCount > 1) + if (rangeOfMotionAxisCount > 1) { - Vector3 initialEuler = initialRot.eulerAngles; - return Quaternion.Euler(rot.eulerAngles.ToEuler180().Clamp(initialEuler + eulerMin, initialEuler + eulerMax)); - } + bool invertPitchYaw = false; + int ignorePitchYaw = -1; + bool clampLongitudinal = false; - // Second pass: Try to fix around axis that has a range of degrees + // Use classic yaw/pitch clamping when more than one axis has constrained range of motion. - if ((!Mathf.Approximately(eulerMin.x, 0.0f) || !Mathf.Approximately(eulerMax.x, 0.0f)) && !Mathf.Approximately(eulerMin.x, eulerMax.x)) - { - axisIndex = 0; - axis = initialRot * Vector3.right; - currentAxis = rot * Vector3.right; - angleMin = eulerMin.x; - angleMax = eulerMax.x; - } - else if ((!Mathf.Approximately(eulerMin.y, 0.0f) || !Mathf.Approximately(eulerMax.y, 0.0f)) && !Mathf.Approximately(eulerMin.y, eulerMax.y)) - { - axisIndex = 1; - axis = initialRot * Vector3.up; - currentAxis = rot * Vector3.up; - angleMin = eulerMin.y; - angleMax = eulerMax.y; - } - else if ((!Mathf.Approximately(eulerMin.z, 0.0f) || !Mathf.Approximately(eulerMax.z, 0.0f)) && !Mathf.Approximately(eulerMin.z, eulerMax.z)) - { - axisIndex = 2; - axis = initialRot * Vector3.forward; - currentAxis = rot * Vector3.forward; - angleMin = eulerMin.z; - angleMax = eulerMax.z; - } - else - { - return initialRot; - } + UxrAxis axis1 = RangeOfMotionRotationAxes.First(); + UxrAxis axis2 = RangeOfMotionRotationAxes.Last(); + UxrAxis longitudinalAxis = UxrAxis.OtherThan(axis1, axis2); - quatMin = Quaternion.AngleAxis(angleMin, axis) * initialRot; - quatMax = Quaternion.AngleAxis(angleMax, axis) * initialRot; - - // First fix rotation so that it is constrained to the correct plane - - Quaternion fixedQuat = Quaternion.FromToRotation(currentAxis, axis) * rot; + if (rangeOfMotionAxisCount == 3) + { + // Pitch/yaw clamping will be on the other-than-longitudinal axes, when all 3 axes have constrained range of motion. - // Now make sure it is clamped between the given angles + axis1 = RangeOfMotionRotationAxes.First(a => a != _rotationLongitudinalAxis); + axis2 = RangeOfMotionRotationAxes.Last(a => a != _rotationLongitudinalAxis); + longitudinalAxis = _rotationLongitudinalAxis; + clampLongitudinal = true; + } + else + { + // If there are only two rotation axes constrained, check if one of the constrained axes is actually the longitudinal axis. + // In this case, we will zero either the pitch or yaw and perform longitudinal clamping later. + if (!longitudinalAxis.Equals(_rotationLongitudinalAxis)) + { + // Ignore the incorrectly computed longitudinal axis, which in reality is either the pitch or yaw + ignorePitchYaw = longitudinalAxis; - float currentAngle = 0.0f; + // Assign the longitudinal axis correctly based on what the user selected + longitudinalAxis = _rotationLongitudinalAxis; - if (axisIndex == 0) - { - currentAngle = Vector3.SignedAngle(initialRot * Vector3.forward, fixedQuat * Vector3.forward, axis); - } - else if (axisIndex == 1) - { - currentAngle = Vector3.SignedAngle(initialRot * Vector3.right, fixedQuat * Vector3.right, axis); - } - else if (axisIndex == 2) - { - currentAngle = Vector3.SignedAngle(initialRot * Vector3.up, fixedQuat * Vector3.up, axis); - } + if (axis1 == longitudinalAxis) + { + axis1 = UxrAxis.OtherThan(longitudinalAxis, axis2); + } + else if (axis2 == longitudinalAxis) + { + axis2 = UxrAxis.OtherThan(longitudinalAxis, axis1); + } - bool clampAngle = false; + // Clamp the rotation around longitudinal axis later + clampLongitudinal = true; - if (angleMin < -180.0f) - { - if (angleMax - angleMin < 360.0f) - { - if (currentAngle > angleMax && currentAngle < angleMin + 360.0f) - { - clampAngle = true; - } - } - } - else if (angleMax > 180.0f) - { - if (angleMax - angleMin < 360.0f) - { - if (currentAngle < angleMin && currentAngle > angleMax - 360.0f) - { - clampAngle = true; + // Check need to invert + invertPitchYaw = UxrAxis.OtherThan(ignorePitchYaw, longitudinalAxis) == UxrAxis.Y; } } - } - else if (currentAngle < angleMin) - { - if (angleMax - angleMin > 240.0f) - { - // This way of clamping will behave nicer if the valid arc is big - clampAngle = true; - } - else + + Quaternion relativeRot = Quaternion.Inverse(initialRot) * rot; + Vector3 targetDirection = relativeRot * longitudinalAxis; + + float pitch = -Mathf.Asin(targetDirection[axis2]) * Mathf.Rad2Deg; + float yaw = Mathf.Atan2(targetDirection[axis1], targetDirection[longitudinalAxis]) * Mathf.Rad2Deg; + + pitch = longitudinalAxis == UxrAxis.Y ? Mathf.Clamp(pitch, -eulerMax[axis1], -eulerMin[axis1]) : Mathf.Clamp(pitch, eulerMin[axis1], eulerMax[axis1]); + yaw = longitudinalAxis == UxrAxis.Y ? Mathf.Clamp(yaw, -eulerMax[axis2], -eulerMin[axis2]) : Mathf.Clamp(yaw, eulerMin[axis2], eulerMax[axis2]); + + // Detect cases where we need to invert angles due to math + + if (invertPitchYaw || longitudinalAxis == UxrAxis.Y) { - return quatMin; + pitch = -pitch; + yaw = -yaw; } - } - else if (currentAngle > angleMax) - { - if (angleMax - angleMin > 240.0f) + + // Now invert if it was requested + + if (invertRotation) { - // This way of clamping will behave nicer if the valid arc is big - clampAngle = true; + pitch = -pitch; + yaw = -yaw; } - else + + // Create clamped rotation using pitch/yaw + + Vector3 clampedEuler = Vector3.zero; + + clampedEuler[axis1] = pitch; + clampedEuler[axis2] = yaw; + + if (ignorePitchYaw != -1) { - return quatMax; + clampedEuler[ignorePitchYaw] = 0.0f; } - } - if (clampAngle) - { - // The current angle is inside the invalid arc. Instead of clamping the current angle - // between (min, max) we will clamp the last value to the closest between (min, max). - // This should be more correct because the last value should always be a correctly - // clamped angle, and would avoid traversing the invalid arc or switching between - // min and max due to overshooting. + Quaternion clampedRot = Quaternion.Euler(clampedEuler); + + // Clamp rotation around the longitudinal axis if necessary - float angleFromPreviousToMin = Quaternion.Angle(previousRot, quatMin); - float angleFromPreviousToMax = Quaternion.Angle(previousRot, quatMax); + if (clampLongitudinal) + { + Vector3 fixedLongitudinal = clampedRot * longitudinalAxis; + Quaternion correctionRot = Quaternion.FromToRotation(targetDirection, fixedLongitudinal); + Quaternion withRoll = correctionRot * relativeRot; + float roll = Vector3.SignedAngle(clampedRot * axis1, withRoll * axis1, fixedLongitudinal) * (invertRotation ? -1.0f : 1.0f); + float clampedRoll = roll.Clamped(eulerMin[longitudinalAxis], eulerMax[longitudinalAxis]); - return angleFromPreviousToMin < angleFromPreviousToMax ? quatMin : quatMax; + return initialRot * Quaternion.AngleAxis(clampedRoll, fixedLongitudinal) * clampedRot; + } + + return initialRot * clampedRot; } - return fixedQuat; - } + // At this point we have a rotation constrained to a single axis. We will allow limits beyond +- 360 degrees by keeping track of the rotation angle. - #endregion + // Get a perpendicular vector to the rotation axis, compute the projection on the rotation plane and then get the angle increment. - #region Private Types & Data + int singleAxisIndex = SingleRotationAxisIndex; + Vector3 rotationAxis = (UxrAxis)singleAxisIndex; + Vector3 perpendicularAxis = ((UxrAxis)singleAxisIndex).Perpendicular; + Vector3 initialPerpendicularVector = initialRot * perpendicularAxis; + Vector3 currentPerpendicularVector = Vector3.ProjectOnPlane(rot * perpendicularAxis, rotationAxis); + float angle = Vector3.SignedAngle(initialPerpendicularVector, currentPerpendicularVector, rotationAxis) * (invertRotation ? -1.0f : 1.0f); + float angleDelta = angle - singleRotationAngle.ToEuler180(); - // Private properties + // Keep track of turns below/above -360/360 degrees. - /// - /// Gets the parent matrix where the min/max translation constraints are applied. - /// - private Matrix4x4 TranslationLimitsParentMatrix - { - get + if (angleDelta > 180.0f) { - if (!_translationLimitsReferenceIsParent && _translationLimitsParent) - { - return _translationLimitsParent.localToWorldMatrix; - } - - return transform.parent != null ? transform.parent.localToWorldMatrix : Matrix4x4.identity; + singleRotationAngle -= 360.0f - angleDelta; } - } - - /// - /// Gets the parent matrix where the min/max rotation constraints are applied. - /// - private Quaternion RotationLimitsParentRotation - { - get + else if (angleDelta < -180.0f) { - if (!_rotationLimitsReferenceIsParent && _rotationLimitsParent) - { - return _rotationLimitsParent.rotation; - } - - return transform.parent != null ? transform.parent.rotation : Quaternion.identity; + singleRotationAngle += 360.0f + angleDelta; + } + else + { + singleRotationAngle += angleDelta; } + + // Clamp inside valid range + + singleRotationAngle.Clamp(_rotationAngleLimitsMin[singleAxisIndex], _rotationAngleLimitsMax[singleAxisIndex]); + return initialRot * Quaternion.AngleAxis(singleRotationAngle, rotationAxis); } + #endregion + + #region Private Types & Data + /// /// Minimum distance allowed between two grabbable points that can be grabbed at the same time. Avoid hand overlapping. /// - private const float MinHandGrabInterDistance = 0.05f; - - private readonly Dictionary _grabPointEnabledStates = new Dictionary(); + private const float MinHandGrabInterDistance = UxrConstants.Hand.HandWidth * 0.5f + 0.01f; // Private vars - private bool _initialIsKinematic = true; - private float _placementTimer = -1.0f; - private float _constraintTimer = -1.0f; - private float _constraintExitTimer = -1.0f; - private Vector3 _constraintExitPos = Vector3.zero; - private Quaternion _constraintExitRot = Quaternion.identity; - private Dictionary _grabPointShapes = new Dictionary(); + private readonly Dictionary _grabPointEnabledStates = new Dictionary(); + + private bool _initialIsKinematic = true; + private float _singleRotationAngleGrab; + private float _singleRotationAngleCumulative; + private float _placementTimer = -1.0f; + private float _constraintTimer = -1.0f; + private float _constraintExitTimer = -1.0f; + private Vector3 _constraintLocalExitPos = Vector3.zero; + private Quaternion _constraintLocalExitRot = Quaternion.identity; + private Dictionary _grabPointShapes = new Dictionary(); #endregion @@ -2350,6 +2583,95 @@ public bool Editor_HasGrabPointWithGrabAlignTransform(Transform snapTransform, G return false; } + + /// + /// Tries to get the longitudinal rotation axis of the grabbable object. If it hasn't been defined by the user (on + /// objects where is less than 2. + /// + /// Longitudinal rotation axis + public UxrAxis Editor_GetMostProbableLongitudinalRotationAxis() + { + if (RangeOfMotionRotationAxisCount > 1) + { + // Longitudinal axis is user defined for constrained rotation with more than one axis + return _rotationLongitudinalAxis; + } + + // We have an object with a single rotation axis. First compute bounds and see if the rotation pivot is not centered. + + Bounds localBounds = gameObject.GetLocalBounds(true); + int maxUncenteredComponent = -1; + float maxUncenteredDistance = 0.0f; + + for (int i = 0; i < 3; ++i) + { + float centerOffset = Mathf.Abs(localBounds.center[i]); + + if (centerOffset > localBounds.size[i] * 0.25f && centerOffset > maxUncenteredDistance) + { + maxUncenteredComponent = i; + maxUncenteredDistance = centerOffset; + } + } + + // Found an axis that is significantly larger than others? + + if (maxUncenteredComponent != -1) + { + return maxUncenteredComponent; + } + + // At this point the best bet is the single rotation axis + + int singleRotationAxisIndex = SingleRotationAxisIndex; + + if (singleRotationAxisIndex != -1) + { + return singleRotationAxisIndex; + } + + return UxrAxis.Z; + } + + /// + /// Tries to infer the most appropriate to rotate the object based on the shape and + /// size of the object, and the grip. + /// + /// The grip snap position + /// Most appropriate + public UxrRotationProvider Editor_GetAutoRotationProvider(Vector3 gripPos) + { + if (!(HasTranslationConstraint && LimitedRangeOfMotionRotationAxes.Any())) + { + // No constraint + return UxrRotationProvider.HandOrientation; + } + + UxrAxis longitudinalAxis = Editor_GetMostProbableLongitudinalRotationAxis(); + int singleRotationAxisIndex = SingleRotationAxisIndex; + float leverageDistance = 0.0f; + + if (singleRotationAxisIndex != -1) + { + // Object with a single rotation axis + + if (longitudinalAxis != singleRotationAxisIndex) + { + // Lever action + return UxrRotationProvider.HandPositionAroundPivot; + } + + // Lever action will depend on grabber distance to rotation axis. Smaller than a hand distance will use rotation while larger will use leverage. + leverageDistance = Vector3.ProjectOnPlane(gripPos - transform.position, transform.TransformDirection(longitudinalAxis)).magnitude; + return leverageDistance > UxrConstants.Hand.HandWidth ? UxrRotationProvider.HandPositionAroundPivot : UxrRotationProvider.HandOrientation; + } + + // Object with more than one rotation axis + + leverageDistance = Mathf.Abs(gripPos.DistanceToPlane(transform.position, transform.TransformDirection(longitudinalAxis))); + return leverageDistance > UxrConstants.Hand.HandWidth ? UxrRotationProvider.HandPositionAroundPivot : UxrRotationProvider.HandOrientation; + } + #endif } } diff --git a/Scripts/Manipulation/UxrGrabber.cs b/Scripts/Manipulation/UxrGrabber.cs index 1ed762dc..44212910 100644 --- a/Scripts/Manipulation/UxrGrabber.cs +++ b/Scripts/Manipulation/UxrGrabber.cs @@ -9,7 +9,6 @@ using UltimateXR.Avatar.Rig; using UltimateXR.Core; using UltimateXR.Core.Components.Composite; -using UltimateXR.Core.Math; using UltimateXR.Extensions.Unity; using UltimateXR.Extensions.Unity.Math; using UnityEngine; @@ -43,7 +42,7 @@ public partial class UxrGrabber : UxrAvatarComponent /// /// Gets from all the positive and negative axes in the grabber's transform, the axis in local-space that is pointing - /// to the fingers. + /// to the fingers, excluding the thumb. /// public Vector3 LocalFingerDirection { @@ -54,15 +53,13 @@ public Vector3 LocalFingerDirection return transform.forward; } - UxrUniversalLocalAxes handUniversalAxes = Side == UxrHandSide.Left ? Avatar.AvatarRigInfo.LeftHandUniversalLocalAxes : Avatar.AvatarRigInfo.RightHandUniversalLocalAxes; - - return transform.GetClosestLocalAxis(handUniversalAxes.WorldForward); + return transform.GetClosestLocalAxis(Avatar.AvatarRigInfo.GetArmInfo(Side).HandUniversalLocalAxes.WorldForward); } } /// /// Gets from all the positive and negative axes in the grabber's transform, the axis in world-space that is pointing - /// to the fingers. + /// to the fingers, excluding the thumb. /// public Vector3 FingerDirection => transform.TransformDirection(LocalFingerDirection); @@ -76,12 +73,10 @@ public Vector3 LocalPalmOutDirection { if (Avatar == null || Avatar.AvatarRigInfo == null) { - return transform.forward; + return -transform.up; } - UxrUniversalLocalAxes handUniversalAxes = Side == UxrHandSide.Left ? Avatar.AvatarRigInfo.LeftHandUniversalLocalAxes : Avatar.AvatarRigInfo.RightHandUniversalLocalAxes; - - return transform.GetClosestLocalAxis(-handUniversalAxes.WorldUp); + return transform.GetClosestLocalAxis(-Avatar.AvatarRigInfo.GetArmInfo(Side).HandUniversalLocalAxes.WorldUp); } } @@ -91,6 +86,31 @@ public Vector3 LocalPalmOutDirection /// public Vector3 PalmOutDirection => transform.TransformDirection(LocalPalmOutDirection); + /// + /// Gets from all the positive and negative axes in the grabber's transform, the axis in local-space that is pointing + /// towards the thumb. + /// + public Vector3 LocalPalmThumbDirection + { + get + { + Vector3 direction = transform.right; + + if (Avatar != null && Avatar.AvatarRigInfo != null) + { + direction = transform.GetClosestLocalAxis(Avatar.AvatarRigInfo.GetArmInfo(Side).HandUniversalLocalAxes.WorldRight); + } + + return Side == UxrHandSide.Left ? direction : -direction; + } + } + + /// + /// Gets from all the positive and negative axes in the grabber's transform, the axis in world-space that is pointing + /// towards the thumb. + /// + public Vector3 PalmThumbDirection => transform.TransformDirection(LocalPalmThumbDirection); + /// /// /// Gets, based on and , which mirroring snap diff --git a/Scripts/Manipulation/UxrManipulationMode.cs b/Scripts/Manipulation/UxrManipulationMode.cs deleted file mode 100644 index e641ff66..00000000 --- a/Scripts/Manipulation/UxrManipulationMode.cs +++ /dev/null @@ -1,23 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) VRMADA, All rights reserved. -// -// -------------------------------------------------------------------------------------------------------------------- -namespace UltimateXR.Manipulation -{ - /// - /// Enumerates the different ways a can be manipulated. - /// - public enum UxrManipulationMode - { - /// - /// Object can be moved around. - /// - GrabAndMove, - - /// - /// Object can only be rotated around an axis. - /// - RotateAroundAxis - } -} \ No newline at end of file diff --git a/Scripts/Manipulation/UxrManipulationMode.cs.meta b/Scripts/Manipulation/UxrManipulationMode.cs.meta deleted file mode 100644 index 9d49b4b4..00000000 --- a/Scripts/Manipulation/UxrManipulationMode.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: aeef6c1f206a473cbc4ca572cc574d15 -timeCreated: 1643287345 \ No newline at end of file diff --git a/Scripts/Manipulation/UxrRotationConstraintMode.cs b/Scripts/Manipulation/UxrRotationConstraintMode.cs index 61a429d7..9431b21f 100644 --- a/Scripts/Manipulation/UxrRotationConstraintMode.cs +++ b/Scripts/Manipulation/UxrRotationConstraintMode.cs @@ -18,6 +18,11 @@ public enum UxrRotationConstraintMode /// /// Local rotation constraint. /// - RestrictLocalRotation + RestrictLocalRotation, + + /// + /// The cannot rotate. + /// + Locked } } \ No newline at end of file diff --git a/Scripts/Manipulation/UxrRotationProvider.cs b/Scripts/Manipulation/UxrRotationProvider.cs new file mode 100644 index 00000000..cf0401f4 --- /dev/null +++ b/Scripts/Manipulation/UxrRotationProvider.cs @@ -0,0 +1,24 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) VRMADA, All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- +namespace UltimateXR.Manipulation +{ + /// + /// Enumerates the different ways a with rotation constraints can be + /// rotated while being manipulated. + /// + public enum UxrRotationProvider + { + /// + /// Grabbed object will rotate as the hand rotates. + /// + HandOrientation, + + /// + /// Grabbed object rotate based on the hand position around the object's pivot. Useful for levers, steering wheels... + /// + HandPositionAroundPivot, + } +} \ No newline at end of file diff --git a/Scripts/Manipulation/UxrRotationProvider.cs.meta b/Scripts/Manipulation/UxrRotationProvider.cs.meta new file mode 100644 index 00000000..d8e4cc46 --- /dev/null +++ b/Scripts/Manipulation/UxrRotationProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d759530f0ab8884f92ae39e777871bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Manipulation/UxrRuntimeGripInfo.cs b/Scripts/Manipulation/UxrRuntimeGripInfo.cs index 6a3fd8ad..e6006e25 100644 --- a/Scripts/Manipulation/UxrRuntimeGripInfo.cs +++ b/Scripts/Manipulation/UxrRuntimeGripInfo.cs @@ -77,6 +77,14 @@ internal class UxrRuntimeGripInfo /// public Quaternion AlignRotationOnGrab { get; set; } + /// + /// Gets or sets the source in local coordinates where the source of leverage will be + /// computed for manipulation. This will improve rotation + /// behaviour when the hands are rotated because otherwise the source of leverage is the grabber itself and rotating + /// the hand will keep the grabber more or less stationary. + /// + public Vector3 GrabberLocalLeverageSource { get; set; } + /// /// Gets or sets the position in local avatar coordinates at the moment the /// was grabbed. @@ -90,14 +98,21 @@ internal class UxrRuntimeGripInfo public Quaternion GrabberLocalAvatarRotationOnGrab { get; set; } /// - /// Gets or sets the world-space hand bone position at the moment the was grabbed. + /// Gets or sets the leverage source in local avatar coordinates at the moment the was grabbed. + /// + public Vector3 GrabberLocalAvatarLeverageSourceOnGrab { get; set; } + + /// + /// Gets or sets the hand bone position in local avatar coordinates at the moment the + /// was grabbed. /// - public Vector3 HandBonePositionOnGrab { get; set; } + public Vector3 HandBoneLocalAvatarPositionOnGrab { get; set; } /// - /// Gets or sets the world-space hand bone rotation at the moment the was grabbed. + /// Gets or sets the hand bone rotation in local avatar coordinates at the moment the + /// was grabbed. /// - public Quaternion HandBoneRotationOnGrab { get; set; } + public Quaternion HandBoneLocalAvatarRotationOnGrab { get; set; } /// /// Gets or sets the decreasing timer that is initialized at diff --git a/Scripts/Manipulation/UxrTranslationConstraintMode.cs b/Scripts/Manipulation/UxrTranslationConstraintMode.cs index ff82e2cf..f0484186 100644 --- a/Scripts/Manipulation/UxrTranslationConstraintMode.cs +++ b/Scripts/Manipulation/UxrTranslationConstraintMode.cs @@ -23,7 +23,7 @@ public enum UxrTranslationConstraintMode RestrictToBox, /// - /// The local position is constrained between minimum and maximum offsets. + /// The local position is constrained between minimum and maximum offsets pointed by the initial local axes. /// RestrictLocalOffset, @@ -31,6 +31,11 @@ public enum UxrTranslationConstraintMode /// The position is constrained to a sphere defined by a /// . /// - RestrictToSphere + RestrictToSphere, + + /// + /// The cannot move. + /// + Locked } } \ No newline at end of file diff --git a/Scripts/Mechanics/Weapons/UxrActor.cs b/Scripts/Mechanics/Weapons/UxrActor.cs index 4764edfa..f314304e 100644 --- a/Scripts/Mechanics/Weapons/UxrActor.cs +++ b/Scripts/Mechanics/Weapons/UxrActor.cs @@ -121,6 +121,20 @@ public void Die(float delaySeconds) #endregion + #region Unity + + /// + /// Makes sure the singleton instance is available so that actors are registered."/> + /// + protected override void Awake() + { + base.Awake(); + + UxrWeaponManager.Instance.Poke(); + } + + #endregion + #region Event Trigger Methods /// @@ -138,6 +152,8 @@ private void OnReceiveDamage(UxrDamageEventArgs e) if (!e.IsCanceled) { + bool destroy = false; + if (_automaticDamageHandling) { _life -= e.Damage; @@ -149,10 +165,7 @@ private void OnReceiveDamage(UxrDamageEventArgs e) if (_automaticDeadHandling) { - if (this != null && gameObject != null) - { - DieInternal(); - } + destroy = true; } else { @@ -175,6 +188,11 @@ private void OnReceiveDamage(UxrDamageEventArgs e) } DamageReceived?.Invoke(this, e); + + if (destroy) + { + DieInternal(); + } } } diff --git a/Scripts/Mechanics/Weapons/UxrFirearmTrigger.cs b/Scripts/Mechanics/Weapons/UxrFirearmTrigger.cs index 7237ceb4..31753224 100644 --- a/Scripts/Mechanics/Weapons/UxrFirearmTrigger.cs +++ b/Scripts/Mechanics/Weapons/UxrFirearmTrigger.cs @@ -5,6 +5,7 @@ // -------------------------------------------------------------------------------------------------------------------- using System; using UltimateXR.Audio; +using UltimateXR.Core.Math; using UltimateXR.Haptics; using UltimateXR.Manipulation; using UnityEngine; @@ -31,7 +32,8 @@ internal class UxrFirearmTrigger [SerializeField] private UxrGrabbableObject _triggerGrabbable; [SerializeField] private int _grabbableGrabPointIndex; [SerializeField] private Transform _triggerTransform; - [SerializeField] private Vector3 _triggerRotationOffset; + [SerializeField] private UxrAxis _triggerRotationAxis = UxrAxis.X; + [SerializeField] private float _triggerRotationDegrees = 40.0f; [SerializeField] private UxrGrabbableObjectAnchor _ammunitionMagAnchor; [SerializeField] private float _recoilAngleOneHand = 0.5f; [SerializeField] private float _recoilAngleTwoHands = 2.0f; @@ -90,9 +92,14 @@ internal class UxrFirearmTrigger public Transform TriggerTransform => _triggerTransform; /// - /// Gets the local euler angles that the trigger will use to rotate when it is fully pressed. + /// Gets the trigger rotation axis. /// - public Vector3 TriggerRotationOffset => _triggerRotationOffset; + public UxrAxis TriggerRotationAxis => _triggerRotationAxis; + + /// + /// Gets the amount of degrees that the trigger will rotate when it is fully pressed. + /// + public float TriggerRotationDegrees => _triggerRotationDegrees; /// /// Gets the anchor where mags for ammo that will be shot using the trigger will be attached to. @@ -139,9 +146,9 @@ internal class UxrFirearmTrigger internal bool HasReloaded { get; set; } /// - /// Gets or sets the trigger's initial local euler angles. + /// Gets or sets the trigger's initial local rotation. /// - internal Vector3 TriggerInitialLocalEuler { get; set; } + internal Quaternion TriggerInitialLocalRotation { get; set; } /// /// Gets or sets the decreasing timer in seconds that will reach zero when the recoil animation finished. diff --git a/Scripts/Mechanics/Weapons/UxrFirearmWeapon.cs b/Scripts/Mechanics/Weapons/UxrFirearmWeapon.cs index dcd0a24c..434e4bb4 100644 --- a/Scripts/Mechanics/Weapons/UxrFirearmWeapon.cs +++ b/Scripts/Mechanics/Weapons/UxrFirearmWeapon.cs @@ -238,7 +238,7 @@ protected override void Start() if (trigger.TriggerTransform) { - trigger.TriggerInitialLocalEuler = trigger.TriggerTransform.localEulerAngles; + trigger.TriggerInitialLocalRotation = trigger.TriggerTransform.localRotation; } if (trigger.AmmunitionMagAnchor != null) @@ -281,7 +281,7 @@ private void UxrManager_AvatarsUpdated() if (trigger.TriggerTransform != null) { - trigger.TriggerTransform.localEulerAngles = trigger.TriggerInitialLocalEuler + trigger.TriggerRotationOffset * triggerPressAmount; + trigger.TriggerTransform.localRotation = trigger.TriggerInitialLocalRotation * Quaternion.AngleAxis(trigger.TriggerRotationDegrees * triggerPressAmount, trigger.TriggerRotationAxis); } // Now depending on the weapon type check if we need to shoot @@ -420,7 +420,7 @@ private void RootGrabbable_ConstraintsApplied(object sender, UxrApplyConstraints float amplitude = 1.0f - recoilT; Vector3 recoilOffset = grabbingHandCount == 1 ? amplitude * trigger.RecoilOffsetOneHand : amplitude * trigger.RecoilOffsetTwoHands; - + grabbableTransform.position += recoilRight * recoilOffset.x + recoilUp * recoilOffset.y + recoilForward * recoilOffset.z; grabbableTransform.RotateAround(recoilPosition, -recoilRight, grabbingHandCount == 1 ? amplitude * trigger.RecoilAngleOneHand : amplitude * trigger.RecoilAngleTwoHands); } @@ -539,7 +539,7 @@ private static void SetAmmoLeft(UxrFirearmTrigger trigger, int ammo) /// Pressed value between range [0.0, 1.0] private static void SetTriggerPressed(UxrFirearmTrigger trigger, float triggerValue) { - trigger.TriggerTransform.localEulerAngles = trigger.TriggerInitialLocalEuler + trigger.TriggerRotationOffset * triggerValue; + trigger.TriggerTransform.localRotation = trigger.TriggerInitialLocalRotation * Quaternion.AngleAxis(trigger.TriggerRotationDegrees * triggerValue, trigger.TriggerRotationAxis); } /// diff --git a/Scripts/Mechanics/Weapons/UxrWeaponManager.cs b/Scripts/Mechanics/Weapons/UxrWeaponManager.cs index 68818776..d07bc809 100644 --- a/Scripts/Mechanics/Weapons/UxrWeaponManager.cs +++ b/Scripts/Mechanics/Weapons/UxrWeaponManager.cs @@ -5,6 +5,7 @@ // -------------------------------------------------------------------------------------------------------------------- using System; using System.Collections.Generic; +using UltimateXR.Core; using UltimateXR.Core.Components.Singleton; using UnityEngine; @@ -14,7 +15,7 @@ namespace UltimateXR.Mechanics.Weapons /// Singleton manager in charge of updating projectiles, computing hits against entities and damage done on /// components. /// - public partial class UxrWeaponManager : UxrSingleton + public partial class UxrWeaponManager : UxrSingleton, IUxrLogger { #region Public Types & Data @@ -38,6 +39,13 @@ public partial class UxrWeaponManager : UxrSingleton #endregion + #region Implicit IUxrLogger + + /// + public UxrLogLevel LogLevel { get; set; } = UxrLogLevel.Relevant; + + #endregion + #region Public Methods /// @@ -103,6 +111,8 @@ protected override void Awake() /// protected override void OnDestroy() { + base.OnDestroy(); + UxrActor.GlobalEnabled -= Actor_Enabled; UxrActor.GlobalDisabled -= Actor_Disabled; } @@ -180,6 +190,12 @@ private void OnDamageReceiving(UxrDamageEventArgs e) /// Event parameters private void OnDamageReceived(UxrDamageEventArgs e) { + if (LogLevel >= UxrLogLevel.Relevant) + { + string sourceInfo = e.ActorSource != null ? $" from actor {e.ActorSource.name}." : string.Empty; + Debug.Log($"{UxrConstants.WeaponsModule}: Actor {e.ActorTarget.name} received {e.Damage} damage of type {e.DamageType}{sourceInfo}. Did die? {e.Dies}."); + } + DamageReceived?.Invoke(this, e); } diff --git a/Scripts/Rendering/FX/UxrMagnifyingGlassUrp.cs b/Scripts/Rendering/FX/UxrMagnifyingGlassUrp.cs index fa65dfdd..b13df98d 100644 --- a/Scripts/Rendering/FX/UxrMagnifyingGlassUrp.cs +++ b/Scripts/Rendering/FX/UxrMagnifyingGlassUrp.cs @@ -265,7 +265,9 @@ private void RenderRefraction(ScriptableRenderContext context, Camera renderCame refractionCamera.projectionMatrix = projection; refractionCamera.cullingMatrix = refractionCamera.projectionMatrix * refractionCamera.worldToCameraMatrix; +#if ULTIMATEXR_UNITY_URP UniversalRenderPipeline.RenderSingleCamera(context, refractionCamera); +#endif } /// diff --git a/Scripts/Rendering/FX/UxrPlanarReflectionUrp.cs b/Scripts/Rendering/FX/UxrPlanarReflectionUrp.cs index aa79bf4c..02d905f5 100644 --- a/Scripts/Rendering/FX/UxrPlanarReflectionUrp.cs +++ b/Scripts/Rendering/FX/UxrPlanarReflectionUrp.cs @@ -75,6 +75,8 @@ protected override void Awake() /// protected override void OnDestroy() { + base.OnDestroy(); + foreach (KeyValuePair camPair in _reflectionCameras) { if (camPair.Value != null) diff --git a/Shaders/FX/LaserDot.shader b/Shaders/FX/LaserDot.shader index 96fad134..1d99a205 100644 --- a/Shaders/FX/LaserDot.shader +++ b/Shaders/FX/LaserDot.shader @@ -27,12 +27,16 @@ { float4 vertex : POSITION; float2 uv : TEXCOORD0; + + UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : POSITION; float2 uv : TEXCOORD0; + + UNITY_VERTEX_OUTPUT_STEREO }; sampler2D _MainTex; @@ -42,10 +46,10 @@ v2f vert (appdata v) { v2f o; -/* + UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_OUTPUT(v2f, o); - UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);*/ + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex);