Files
VIVE-OpenXR-Unity/com.htc.upm.vive.openxr/Runtime/Toolkits/RealisticHandInteraction/Scripts/GrabColliderManager.cs
Sean Lu(呂祥榮) 7f2a459592 version 2.3.0
2024-05-15 14:09:18 +08:00

487 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// "Wave SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the Wave SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public class GrabColliderManager : MonoBehaviour
{
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.GrabColliderManager";
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
/// <summary>
/// The struct is designed to record movable colliders,
/// including which grabbable they belong to, which joints collisioned with, and whether they have been grabbed.
/// </summary>
private struct MovableHitInfo
{
public struct JointHitInfo
{
public JointType joint;
public Vector3 hitOffset;
public int hitTime { get; private set; }
public JointHitInfo(JointType in_JointType, Vector3 in_HitOffset)
{
joint = in_JointType;
hitOffset = in_HitOffset;
hitTime = Time.frameCount;
}
public void Update()
{
hitTime = Time.frameCount;
}
}
public HandGrabInteractable grabbable;
public List<JointHitInfo> jointHitInfos;
public bool grabbed;
public bool stopMove;
public MovableHitInfo(HandGrabInteractable in_Grabbable, JointType in_Joint, Vector3 in_Offset)
{
grabbable = in_Grabbable;
jointHitInfos = new List<JointHitInfo>()
{
new JointHitInfo(in_Joint, in_Offset)
};
grabbed = false;
stopMove = false;
}
public void Update(bool in_Grabbed, bool in_StopMove)
{
grabbed = in_Grabbed;
stopMove = in_StopMove;
}
public void Reset()
{
grabbed = false;
stopMove = false;
}
/// <summary>
/// Add a JointType. If it's already in the dictionary, update the time.
/// </summary>
/// <param name="joint">The joint which needs to be added.</param>
public void AddJoint(JointType joint, Vector3 offset)
{
int hitId = jointHitInfos.FindIndex(x => x.joint == joint);
if (hitId == -1)
{
jointHitInfos.Add(new JointHitInfo(joint, offset));
}
else
{
JointHitInfo jointHitInfo = jointHitInfos[hitId];
jointHitInfo.Update();
jointHitInfos[hitId] = jointHitInfo;
}
}
/// <summary>
/// Remove a JointType and check if it has been grabbed.
/// </summary>
/// <param name="joint">The joint which needs to be removed.</param>
public void RemoveJoint(JointType joint)
{
int hitId = jointHitInfos.FindIndex(x => x.joint == joint);
if (hitId != -1)
{
jointHitInfos.RemoveAt(hitId);
}
}
}
[SerializeField]
private HandMeshManager jointManager;
private GrabCollider[] jointsCollider = new GrabCollider[(int)JointType.Count];
private Pose[] jointsPrevFramePose = new Pose[(int)JointType.Count];
private bool isImmovableCollision = false;
private bool isGrabbing = false;
private List<MovableHitInfo> movableHits = new List<MovableHitInfo>();
private Dictionary<GrabCollider, Collider> immovableHits = new Dictionary<GrabCollider, Collider>();
public delegate void OnImmovableCollision(bool enable);
private OnImmovableCollision immovableCollisionHandler;
private void Awake()
{
if (jointManager == null)
{
jointManager = transform.GetComponent<HandMeshManager>();
if (jointManager == null)
{
ERROR("Failed to find HandJointManager.");
}
}
}
private void OnEnable()
{
if (jointManager != null)
{
jointManager.HandGrabber.AddBeginGrabListener(OnGrabberBeginGrab);
jointManager.HandGrabber.AddEndGrabListener(OnGrabberEndGrab);
}
CreateJointsCollider();
}
private void OnDisable()
{
if (jointManager != null)
{
jointManager.HandGrabber.RemoveBeginGrabListener(OnGrabberBeginGrab);
jointManager.HandGrabber.RemoveEndGrabListener(OnGrabberEndGrab);
}
foreach (var collider in jointsCollider)
{
collider.RemoveListener(CollisionEvent);
Destroy(collider);
}
Array.Clear(jointsCollider, 0, jointsCollider.Length);
}
private void Update()
{
if (jointManager == null) { return; }
UpdateColliderPose();
if (!isGrabbing)
{
UpdateImmovable();
UpdateMovable();
}
for (int i = 0; i < jointsCollider.Length; i++)
{
jointsPrevFramePose[i] = jointsCollider[i] == null ? Pose.identity : new Pose(jointsCollider[i].transform.position, jointsCollider[i].transform.rotation);
}
}
/// <summary>
/// Create colliders for each joint and set them do not collide with each other.
/// </summary>
private void CreateJointsCollider()
{
if (jointManager != null)
{
var cloneRoot = Instantiate(jointManager.HandRootJoint, jointManager.HandRootJoint.parent);
cloneRoot.name = jointManager.HandRootJoint.name;
List<GameObject> children = new List<GameObject>() { cloneRoot.gameObject };
GetChildren(cloneRoot, children);
foreach (var child in children)
{
Transform target = jointManager.HandJoints.FirstOrDefault(x => x.name == child.name);
if (target != null)
{
int index = Array.IndexOf(jointManager.HandJoints, target);
GrabCollider grabCollider = child.AddComponent<GrabCollider>();
grabCollider.AddListener(CollisionEvent);
grabCollider.SetJointId(index);
jointsCollider[index] = grabCollider;
}
}
}
for (int i = 0; i < jointsCollider.Length; i++)
{
if (jointsCollider[i] == null)
{
continue;
}
for (int j = i + 1; j < jointsCollider.Length; j++)
{
if (jointsCollider[j] != null)
{
Physics.IgnoreCollision(jointsCollider[i].Collider, jointsCollider[j].Collider, true);
}
}
}
}
private void GetChildren(Transform parent, List<GameObject> children)
{
foreach (Transform child in parent)
{
children.Add(child.gameObject);
GetChildren(child, children);
}
}
/// <summary>
/// Update the position of the collider using the position of the joint.
/// </summary>
private void UpdateColliderPose()
{
HandData hand = CachedHand.Get(jointManager.IsLeft);
bool isTracked = hand.isTracked;
if (!isTracked)
{
return;
}
var parentTransform = jointManager.HandRootJoint.parent;
var parentRotation = Matrix4x4.Rotate(parentTransform.rotation);
Vector3 jointPosition = Vector3.zero;
Quaternion jointRotation = Quaternion.identity;
for (int i = 0; i < jointsCollider.Length; i++)
{
if (jointsCollider[i] == null) { continue; }
hand.GetJointPosition((JointType)i, ref jointPosition);
hand.GetJointRotation((JointType)i, ref jointRotation);
if ((JointType)i == JointType.Wrist)
{
jointsCollider[i].transform.localPosition = jointPosition;
jointsCollider[i].transform.localRotation = jointRotation;
}
jointsCollider[i].transform.rotation = (parentRotation * Matrix4x4.Rotate(jointRotation)).rotation;
}
}
/// <summary>
/// Save the hand pose if a collision has already occurred with a joint.
/// </summary>
private void UpdateImmovable()
{
bool isCollision = jointsCollider.Any(x => x != null && x.IsCollision);
foreach (var jointCollider in jointsCollider)
{
jointCollider.Collider.enabled = isCollision ? jointCollider.IsCollision : true;
}
if (isImmovableCollision != isCollision)
{
isImmovableCollision = isCollision;
immovableCollisionHandler?.Invoke(isImmovableCollision);
}
}
/// <summary>
/// Check all movableHits and move the object relative to the movement of the collisioned joint.
/// </summary>
private void UpdateMovable()
{
if (isImmovableCollision) { return; }
const int k_MinCollisionTimeDiff = 5;
const int k_MaxCollisionTimeDiff = 50;
for (int i = movableHits.Count - 1; i >= 0; i--)
{
MovableHitInfo hit = movableHits[i];
if (hit.stopMove) { continue; }
Vector3 totalPosition = Vector3.zero;
Vector3 totalOffset = Vector3.zero;
int validCount = 0;
for (int j = hit.jointHitInfos.Count - 1; j >= 0; j--)
{
MovableHitInfo.JointHitInfo jointHit = hit.jointHitInfos[j];
int frameCountDiff = Time.frameCount - jointHit.hitTime;
if (frameCountDiff > k_MinCollisionTimeDiff)
{
if (frameCountDiff > k_MaxCollisionTimeDiff)
{
hit.RemoveJoint(jointHit.joint);
}
continue;
}
int jointId = (int)jointHit.joint;
Vector3 currentPose = jointsCollider[jointId].transform.position;
Vector3 prevPose = jointsPrevFramePose[jointId].position;
// Condition 1: Calculate the displacement between consecutive frames of joints, it should greater than 1E-6f as significant.
// Condition 2: Calculate distance score relative to grabbable; the score of current pose should be greater than the previous pose.
// Condition 3: The dot product of the vector between the current pose and the grabbable object,
// and the vector representing finger movement direction should be less than 0.
if (Vector3.Distance(prevPose, currentPose) > 1E-6f &&
movableHits[i].grabbable.CalculateDistanceScore(currentPose) >= movableHits[i].grabbable.CalculateDistanceScore(prevPose) &&
Vector3.Dot((currentPose - prevPose).normalized, (movableHits[i].grabbable.transform.position - prevPose).normalized) > 0)
{
validCount++;
totalPosition += currentPose;
totalOffset += jointHit.hitOffset;
}
}
if (validCount > 0)
{
movableHits[i].grabbable.transform.position = (totalPosition - totalOffset) / validCount;
}
if (hit.jointHitInfos.Count == 0)
{
movableHits.RemoveAt(i);
}
else
{
movableHits[i] = hit;
}
}
}
/// <summary>
/// Enable or disable the collider of joints.
/// </summary>
/// <param name="enable">Enable (true) or disable (false) the colliders.</param>
public void EnableCollider(bool enable)
{
for (int i = 0; i < jointsCollider.Length; i++)
{
if (jointsCollider[i] != null)
{
jointsCollider[i].gameObject.SetActive(enable);
}
}
}
#region Collision Event
/// <summary>
/// Adds a listener for immovable collision events.
/// </summary>
/// <param name="handler">The method to be called when an immovable collision occurs.</param>
public void AddImmovableCollisionListener(OnImmovableCollision handler)
{
immovableCollisionHandler += handler;
}
/// <summary>
/// Removes a listener for immovable collision events.
/// </summary>
/// <param name="handler">The method to be removed from the immovable collision event listeners.</param>
public void RemoveImmovableCollisionListener(OnImmovableCollision handler)
{
immovableCollisionHandler -= handler;
}
/// <summary>
/// Event handler for when the grabber begins grabbing.
/// </summary>
/// <param name="grabber">The grabber of IGrabber.</param>
private void OnGrabberBeginGrab(IGrabber grabber)
{
isGrabbing = true;
for (int i = 0; i < movableHits.Count; i++)
{
if (grabber.grabbable is HandGrabInteractable &&
(HandGrabInteractable)grabber.grabbable == movableHits[i].grabbable)
{
MovableHitInfo movableHit = movableHits[i];
movableHit.Update(true, true);
movableHits[i] = movableHit;
}
}
}
private void OnGrabberEndGrab(IGrabber grabber)
{
isGrabbing = false;
}
/// <summary>
/// Filter all collision events, check for grabbables, and update collision data.
/// </summary>
/// <param name="joint">The joint which has been collision.</param>
/// <param name="collision">The data of Collision.</param>
/// <param name="isColliding">True when the collision event is OnCollisionEnter or OnCollisionStay.</param>
private void CollisionEvent(JointType joint, Collision collision, GrabCollider.CollisionState state)
{
bool isCollision = state != GrabCollider.CollisionState.end;
Rigidbody rigidbody = collision.rigidbody;
GrabManager.GetFirstHandGrabbableFromParent(collision.collider.gameObject, out HandGrabInteractable grabbable);
if (collision.rigidbody == null && (grabbable == null || grabbable != null && !grabbable.enabled)) { return; }
if ((rigidbody == null || rigidbody.isKinematic) && grabbable != null && grabbable.forceMovable)
{
if (isCollision)
{
UpdateMovableHits(joint, grabbable);
}
else
{
RemoveMovableHits(joint, grabbable);
}
}
else if ((rigidbody != null && rigidbody.isKinematic) || (grabbable != null && !grabbable.forceMovable))
{
UpdateImmovableHIts(joint, collision.collider, isCollision);
}
}
private void UpdateMovableHits(JointType joint, HandGrabInteractable grabbable)
{
int index = movableHits.FindIndex(x => x.grabbable == grabbable);
if (index != -1)
{
MovableHitInfo moveable = movableHits[index];
moveable.AddJoint(joint, jointsCollider[(int)joint].transform.position - grabbable.transform.position);
movableHits[index] = moveable;
}
else
{
MovableHitInfo moveable = new MovableHitInfo(grabbable, joint, jointsCollider[(int)joint].transform.position - grabbable.transform.position);
movableHits.Add(moveable);
}
}
private void RemoveMovableHits(JointType joint, HandGrabInteractable grabbable)
{
int index = movableHits.FindIndex(x => x.grabbable == grabbable);
if (index != -1)
{
MovableHitInfo movable = movableHits[index];
movable.RemoveJoint(joint);
if (movable.jointHitInfos.Count == 0)
{
movableHits.Remove(movable);
}
else
{
movableHits[index] = movable;
}
}
}
private void UpdateImmovableHIts(JointType joint, Collider collider, bool isCollision)
{
GrabCollider grabCollider = jointsCollider[(int)joint];
grabCollider.IsCollision = isCollision;
if (isCollision && !immovableHits.ContainsKey(grabCollider))
{
immovableHits.Add(grabCollider, collider);
}
else if (!isCollision && immovableHits.ContainsKey(grabCollider))
{
immovableHits.Remove(grabCollider);
}
}
#endregion
}
}