//======= Copyright (c) Valve Corporation, All rights reserved. =============== // // Purpose: handles the physics of hands colliding with the world // //============================================================================= using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Valve.VR.InteractionSystem { public class HandPhysics : MonoBehaviour { [Tooltip("Hand collider prefab to instantiate")] public HandCollider handColliderPrefab; [HideInInspector] public HandCollider handCollider; [Tooltip("Layers to consider when checking if an area is clear")] public LayerMask clearanceCheckMask; [HideInInspector] public Hand hand; // distance at which hand will teleport back to controller const float handResetDistance = 0.6f; const float collisionReenableClearanceRadius = 0.1f; private bool initialized = false; private bool collisionsEnabled = true; private void Start() { hand = GetComponent(); //spawn hand collider and link it to us handCollider = ((GameObject)Instantiate(handColliderPrefab.gameObject)).GetComponent(); Vector3 localPosition = handCollider.transform.localPosition; Quaternion localRotation = handCollider.transform.localRotation; handCollider.transform.parent = Player.instance.transform; handCollider.transform.localPosition = localPosition; handCollider.transform.localRotation = localRotation; handCollider.hand = this; GetComponent().onTransformUpdated.AddListener(UpdateHand); } // cached transformations Matrix4x4 wristToRoot; Matrix4x4 rootToArmature; Matrix4x4 wristToArmature; Vector3 targetPosition = Vector3.zero; Quaternion targetRotation = Quaternion.identity; //bones const int wristBone = SteamVR_Skeleton_JointIndexes.wrist; const int rootBone = SteamVR_Skeleton_JointIndexes.root; private void FixedUpdate() { if (hand.skeleton == null) return; initialized = true; UpdateCenterPoint(); handCollider.MoveTo(targetPosition, targetRotation); if ((handCollider.transform.position - targetPosition).sqrMagnitude > handResetDistance * handResetDistance) handCollider.TeleportTo(targetPosition, targetRotation); UpdateFingertips(); } private void UpdateCenterPoint() { Vector3 offset = hand.skeleton.GetBonePosition(SteamVR_Skeleton_JointIndexes.middleProximal) - hand.skeleton.GetBonePosition(SteamVR_Skeleton_JointIndexes.root); if (hand.HasSkeleton()) { handCollider.SetCenterPoint(hand.skeleton.transform.position + offset); } } Collider[] clearanceBuffer = new Collider[1]; private void UpdatePositions() { // disable collisions when holding something if (hand.currentAttachedObject != null) { collisionsEnabled = false; } else { // wait for area to become clear before reenabling collisions if (!collisionsEnabled) { clearanceBuffer[0] = null; Physics.OverlapSphereNonAlloc(hand.objectAttachmentPoint.position, collisionReenableClearanceRadius, clearanceBuffer); // if we don't find anything in the vicinity, reenable collisions! if (clearanceBuffer[0] == null) { collisionsEnabled = true; } } } handCollider.SetCollisionDetectionEnabled(collisionsEnabled); if (hand.skeleton == null) return; initialized = true; // get the desired pose of the wrist in world space. Can't get the wrist bone transform, as this is affected by the resulting physics. wristToRoot = Matrix4x4.TRS(ProcessPos(wristBone, hand.skeleton.GetBone(wristBone).localPosition), ProcessRot(wristBone, hand.skeleton.GetBone(wristBone).localRotation), Vector3.one).inverse; rootToArmature = Matrix4x4.TRS(ProcessPos(rootBone, hand.skeleton.GetBone(rootBone).localPosition), ProcessRot(rootBone, hand.skeleton.GetBone(rootBone).localRotation), Vector3.one).inverse; wristToArmature = (wristToRoot * rootToArmature).inverse; // step up through virtual transform hierarchy and into world space targetPosition = transform.TransformPoint(wristToArmature.MultiplyPoint3x4(Vector3.zero)); targetRotation = transform.rotation * wristToArmature.GetRotation(); //bypass physics when game paused if (Time.timeScale == 0) { handCollider.TeleportTo(targetPosition, targetRotation); } } Transform wrist; const int thumbBone = SteamVR_Skeleton_JointIndexes.thumbDistal; const int indexBone = SteamVR_Skeleton_JointIndexes.indexDistal; const int middleBone = SteamVR_Skeleton_JointIndexes.middleDistal; const int ringBone = SteamVR_Skeleton_JointIndexes.ringDistal; const int pinkyBone = SteamVR_Skeleton_JointIndexes.pinkyDistal; void UpdateFingertips() { wrist = hand.skeleton.GetBone(SteamVR_Skeleton_JointIndexes.wrist); // set finger tip positions in wrist space for(int finger = 0; finger < 5; finger++) { int tip = SteamVR_Skeleton_JointIndexes.GetBoneForFingerTip(finger); int bone = tip; for(int i = 0; i < handCollider.fingerColliders[finger].Length; i++) { bone = tip - 1 - i; // start at distal and go down if (handCollider.fingerColliders[finger][i] != null) handCollider.fingerColliders[finger][i].localPosition = wrist.InverseTransformPoint(hand.skeleton.GetBone(bone).position); } } /* if(handCollider.tip_thumb != null) handCollider.tip_thumb.localPosition = wrist.InverseTransformPoint(hand.skeleton.GetBone(thumbBone).position); if(handCollider.tip_index != null) handCollider.tip_index.localPosition = wrist.InverseTransformPoint(hand.skeleton.GetBone(indexBone).position); if(handCollider.tip_middle != null) handCollider.tip_middle.localPosition = wrist.InverseTransformPoint(hand.skeleton.GetBone(middleBone).position); if(handCollider.tip_ring != null) handCollider.tip_ring.localPosition = wrist.InverseTransformPoint(hand.skeleton.GetBone(ringBone).position); if (handCollider.tip_pinky != null) handCollider.tip_pinky.localPosition = wrist.InverseTransformPoint(hand.skeleton.GetBone(pinkyBone).position); */ } void UpdateHand(SteamVR_Behaviour_Pose pose, SteamVR_Input_Sources inputSource) { if (!initialized) return; UpdateCenterPoint(); UpdatePositions(); Quaternion offsetRotation = handCollider.transform.rotation * wristToArmature.inverse.GetRotation(); hand.mainRenderModel.transform.rotation = offsetRotation; Vector3 offsetPosition = handCollider.transform.TransformPoint(wristToArmature.inverse.MultiplyPoint3x4(Vector3.zero)); hand.mainRenderModel.transform.position = offsetPosition; /* Vector3 wristPointInArmatureSpace = transform.InverseTransformPoint(handCollider.transform.position); Vector3 handTargetPosition = hand.mainRenderModel.transform.position = handTargetPosition; //Quaternion handTargetRotation = transform.rotation * (wristToArmature.inverse.rotation * (Quaternion.Inverse(transform.rotation) * handCollider.transform.rotation)); //hand.mainRenderModel.transform.rotation = handTargetRotation; */ } Vector3 ProcessPos(int boneIndex, Vector3 pos) { if(hand.skeleton.mirroring != SteamVR_Behaviour_Skeleton.MirrorType.None) { return SteamVR_Behaviour_Skeleton.MirrorPosition(boneIndex, pos); } return pos; } Quaternion ProcessRot(int boneIndex, Quaternion rot) { if (hand.skeleton.mirroring != SteamVR_Behaviour_Skeleton.MirrorType.None) { return SteamVR_Behaviour_Skeleton.MirrorRotation(boneIndex, rot); } return rot; } } }