rabidus-test/Assets/BNG Framework/Scripts/Helpers/HandController.cs

642 lines
24 KiB
C#
Raw Permalink Normal View History

2023-07-24 16:38:13 +03:00
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace BNG {
/// <summary>
/// An example hand controller that sets animation values depending on Grabber state
/// </summary>
public class HandController : MonoBehaviour {
[Tooltip("HandController parent will be set to this on Start if specified")]
public Transform HandAnchor;
[Tooltip("If true, this transform will be parented to HandAnchor and it's position / rotation set to 0,0,0.")]
public bool ResetHandAnchorPosition = true;
public Animator HandAnimator;
[Tooltip("(Optional) If specified, this HandPoser can be used when setting poses retrieved from a grabbed Grabbable.")]
public HandPoser handPoser;
[Tooltip("(Optional) If specified, this AutoPoser component can be used when if set on the Grabbable, or if AutoPose is set to true")]
public AutoPoser autoPoser;
// We can use the HandPoseBlender to blend between an open and closed hand pose, using controller inputs such as grip and trigger as the blend values
HandPoseBlender poseBlender;
[Tooltip("How to handle the hand when nothing is being grabbed / idle. Ex : Can use an Animator to control the hand via blending, a HandPoser to control via blend states, AutoPoser to continually auto pose while nothing is being held, or 'None' if you want to handle the idle state yourself.")]
public HandPoserType IdlePoseType = HandPoserType.HandPoser;
[Tooltip("If true, the idle hand pose will be determined by the connected Valve Index Controller's finger tracking. Requires the SteamVR SDK. Make sure IdlePoseType is set to 'HandPoser'")]
public bool UseIndexFingerTracking = true;
/// <summary>
/// How fast to Lerp the Layer Animations
/// </summary>
[Tooltip("How fast to Lerp the Layer Animations")]
public float HandAnimationSpeed = 20f;
[Tooltip("Check the state of this grabber to determine animation state. If null, a child Grabber component will be used.")]
public Grabber grabber;
[Header("Shown for Debug : ")]
/// <summary>
/// 0 = Open Hand, 1 = Full Grip
/// </summary>
public float GripAmount;
private float _prevGrip;
/// <summary>
/// 0 = Index Curled in, 1 = Pointing Finger
/// </summary>
public float PointAmount;
private float _prevPoint;
/// <summary>
/// 0 = Thumb Down, 1 = Thumbs Up
/// </summary>
public float ThumbAmount;
private float _prevThumb;
// Raw input values
private bool _thumbIsNear = false;
private bool _indexIsNear = false;
private float _triggerValue = 0f;
private float _gripValue = 0f;
public int PoseId;
ControllerOffsetHelper offset;
InputBridge input;
Rigidbody rigid;
Transform offsetTransform;
Vector3 offsetPosition {
get {
if(offset) {
return offset.OffsetPosition;
}
return Vector3.zero;
}
}
Vector3 offsetRotation {
get {
if (offset) {
return offset.OffsetRotation;
}
return Vector3.zero;
}
}
void Start() {
rigid = GetComponent<Rigidbody>();
offset = GetComponent<ControllerOffsetHelper>();
offsetTransform = new GameObject("OffsetHelper").transform;
offsetTransform.parent = transform;
if (HandAnchor) {
transform.parent = HandAnchor;
offsetTransform.parent = HandAnchor;
if (ResetHandAnchorPosition) {
transform.localPosition = offsetPosition;
transform.localEulerAngles = offsetRotation;
}
}
if(grabber == null) {
grabber = GetComponentInChildren<Grabber>();
}
// Subscribe to grab / release events
if(grabber != null) {
grabber.onAfterGrabEvent.AddListener(OnGrabberGrabbed);
grabber.onReleaseEvent.AddListener(OnGrabberReleased);
}
// Try getting child animator
2023-09-11 15:44:17 +03:00
//SetHandAnimator();
2023-07-24 16:38:13 +03:00
input = InputBridge.Instance;
}
public void Update() {
CheckForGrabChange();
// Set Hand state according to InputBridge
UpdateFromInputs();
// Holding something - update the appropriate component
if(HoldingObject()) {
UpdateHeldObjectState();
}
else {
UpdateIdleState();
}
}
public virtual void UpdateHeldObjectState() {
// Holding Animator Grabbable
if (IsAnimatorGrabbable()) {
UpdateAnimimationStates();
}
// Holding Hand Poser Grabbable
else if (IsHandPoserGrabbable()) {
UpdateHandPoser();
}
// Holding Auto Poser Grabbable
else if (IsAutoPoserGrabbable()) {
//EnableAutoPoser();
}
}
public virtual void UpdateIdleState() {
// Not holding something - update the idle state
if (IdlePoseType == HandPoserType.Animator) {
UpdateAnimimationStates();
}
else if (IdlePoseType == HandPoserType.HandPoser) {
//UpdateHandPoser();
UpdateHandPoserIdleState();
}
else if (IdlePoseType == HandPoserType.AutoPoser) {
EnableAutoPoser(true);
}
}
public GameObject PreviousHeldObject;
public virtual bool HoldingObject() {
if(grabber != null && grabber.HeldGrabbable != null) {
return true;
}
return false;
}
public virtual void CheckForGrabChange() {
if(grabber != null) {
// Check for null object but no animator enabled
if(grabber.HeldGrabbable == null && PreviousHeldObject != null) {
OnGrabDrop();
}
else if(grabber.HeldGrabbable != null && !GameObject.ReferenceEquals(grabber.HeldGrabbable.gameObject, PreviousHeldObject)) {
OnGrabChange(grabber.HeldGrabbable.gameObject);
}
}
}
public virtual void OnGrabChange(GameObject newlyHeldObject) {
// Update Component state if the held object has changed
if(HoldingObject()) {
// Switch components based on held object properties
// Animator
if (grabber.HeldGrabbable.handPoseType == HandPoseType.AnimatorID) {
EnableHandAnimator();
}
// Auto Poser - Once
else if (grabber.HeldGrabbable.handPoseType == HandPoseType.AutoPoseOnce) {
EnableAutoPoser(false);
}
// Auto Poser - Continuous
else if (grabber.HeldGrabbable.handPoseType == HandPoseType.AutoPoseContinuous) {
EnableAutoPoser(true);
}
// Hand Poser
else if (grabber.HeldGrabbable.handPoseType == HandPoseType.HandPose) {
// If we have a valid hand pose use it, otherwise fall back to a default closed pose
if (grabber.HeldGrabbable.SelectedHandPose != null) {
EnableHandPoser();
// Make sure blender isn't active
if(poseBlender != null) {
poseBlender.UpdatePose = false;
}
if(handPoser != null) {
handPoser.CurrentPose = grabber.HeldGrabbable.SelectedHandPose;
}
}
else {
// Debug.Log("No Selected Hand Pose was found.");
}
}
}
PreviousHeldObject = newlyHeldObject;
}
/// <summary>
/// Dropped our held item - nothing currently in our hands
/// </summary>
public virtual void OnGrabDrop() {
// Should we use auto pose when nothing in the hand?
if (IdlePoseType == HandPoserType.AutoPoser) {
EnableAutoPoser(true);
}
else if (IdlePoseType == HandPoserType.HandPoser) {
DisableAutoPoser();
}
else if (IdlePoseType == HandPoserType.Animator) {
DisablePoseBlender();
EnableHandAnimator();
DisableAutoPoser();
}
PreviousHeldObject = null;
}
public virtual void SetHandAnimator() {
if (HandAnimator == null || !HandAnimator.gameObject.activeInHierarchy) {
HandAnimator = GetComponentInChildren<Animator>();
}
}
/// <summary>
/// Update GripAmount, PointAmount, and ThumbAmount based raw input from InputBridge
/// </summary>
public virtual void UpdateFromInputs() {
// Grabber may have been deactivated
if (grabber == null || !grabber.isActiveAndEnabled) {
grabber = GetComponentInChildren<Grabber>();
GripAmount = 0;
PointAmount = 0;
ThumbAmount = 0;
return;
}
// Update raw values based on hand side
if (grabber.HandSide == ControllerHand.Left) {
_indexIsNear = input.LeftTriggerNear;
_thumbIsNear = input.LeftThumbNear;
_triggerValue = input.LeftTrigger;
_gripValue = input.LeftGrip;
}
else if (grabber.HandSide == ControllerHand.Right) {
_indexIsNear = input.RightTriggerNear;
_thumbIsNear = input.RightThumbNear;
_triggerValue = input.RightTrigger;
_gripValue = input.RightGrip;
}
// Massage raw values to get a better value set the animator can use
GripAmount = _gripValue;
ThumbAmount = _thumbIsNear ? 0 : 1;
// Point Amount can vary depending on if touching or our input source
PointAmount = 1 - _triggerValue; // Range between 0 and 1. 1 == Finger all the way out
PointAmount *= InputBridge.Instance.InputSource == XRInputSource.SteamVR ? 0.25F : 0.5F; // Reduce the amount our finger points out if Oculus or XRInput
// If not near the trigger, point finger all the way out
if (input.SupportsIndexTouch && _indexIsNear == false && PointAmount != 0) {
PointAmount = 1f;
}
// Does not support touch, stick finger out as if pointing if no trigger found
else if (!input.SupportsIndexTouch && _triggerValue == 0) {
PointAmount = 1;
}
}
public bool DoUpdateAnimationStates = true;
public bool DoUpdateHandPoser = true;
public virtual void UpdateAnimimationStates()
{
if(DoUpdateAnimationStates == false) {
return;
}
// Enable Animator if it was disabled by the hand poser
if(IsAnimatorGrabbable() && !HandAnimator.isActiveAndEnabled) {
EnableHandAnimator();
}
// Update Hand Animator info
if (HandAnimator != null && HandAnimator.isActiveAndEnabled && HandAnimator.runtimeAnimatorController != null) {
_prevGrip = Mathf.Lerp(_prevGrip, GripAmount, Time.deltaTime * HandAnimationSpeed);
_prevThumb = Mathf.Lerp(_prevThumb, ThumbAmount, Time.deltaTime * HandAnimationSpeed);
_prevPoint = Mathf.Lerp(_prevPoint, PointAmount, Time.deltaTime * HandAnimationSpeed);
// 0 = Hands Open, 1 = Grip closes
HandAnimator.SetFloat("Flex", _prevGrip);
HandAnimator.SetLayerWeight(1, _prevThumb);
//// 0 = pointer finger inwards, 1 = pointing out
//// Point is played as a blend
//// Near trigger? Push finger down a bit
HandAnimator.SetLayerWeight(2, _prevPoint);
// Should we use a custom hand pose?
if (grabber != null && grabber.HeldGrabbable != null) {
HandAnimator.SetLayerWeight(0, 0);
HandAnimator.SetLayerWeight(1, 0);
HandAnimator.SetLayerWeight(2, 0);
PoseId = (int)grabber.HeldGrabbable.CustomHandPose;
if (grabber.HeldGrabbable.ActiveGrabPoint != null) {
// Default Grip to 1 when holding an item
HandAnimator.SetLayerWeight(0, 1);
HandAnimator.SetFloat("Flex", 1);
// Get the Min / Max of our finger blends if set by the user
// This allows a pose to blend between states
// Index Finger
setAnimatorBlend(grabber.HeldGrabbable.ActiveGrabPoint.IndexBlendMin, grabber.HeldGrabbable.ActiveGrabPoint.IndexBlendMax, PointAmount, 2);
// Thumb
setAnimatorBlend(grabber.HeldGrabbable.ActiveGrabPoint.ThumbBlendMin, grabber.HeldGrabbable.ActiveGrabPoint.ThumbBlendMax, ThumbAmount, 1);
}
else {
// Force everything to grab if we're holding something
if (grabber.HoldingItem) {
GripAmount = 1;
PointAmount = 0;
ThumbAmount = 0;
}
}
HandAnimator.SetInteger("Pose", PoseId);
}
else {
HandAnimator.SetInteger("Pose", 0);
}
}
}
void setAnimatorBlend(float min, float max, float input, int animationLayer) {
HandAnimator.SetLayerWeight(animationLayer, min + (input) * max - min);
}
/// <summary>
/// Returns true if there is a valid animator and the held grabbable is set to use an Animation ID
/// </summary>
/// <returns></returns>
public virtual bool IsAnimatorGrabbable() {
return HandAnimator != null && grabber != null && grabber.HeldGrabbable != null && grabber.HeldGrabbable.handPoseType == HandPoseType.AnimatorID;
}
public virtual void UpdateHandPoser() {
if (DoUpdateHandPoser == false) {
return;
}
// HandPoser may have changed - check for new component
if (handPoser == null || !handPoser.isActiveAndEnabled) {
handPoser = GetComponentInChildren<HandPoser>();
}
// Bail early if missing any info
if(handPoser == null || grabber == null || grabber.HeldGrabbable == null || grabber.HeldGrabbable.handPoseType != HandPoseType.HandPose) {
return;
}
// Make sure blending isn't active
if(poseBlender != null && poseBlender.UpdatePose) {
poseBlender.UpdatePose = false;
}
// Update hand pose if changed
if (handPoser.CurrentPose == null || handPoser.CurrentPose != grabber.HeldGrabbable.SelectedHandPose) {
UpdateCurrentHandPose();
}
}
public virtual bool IsHandPoserGrabbable() {
return handPoser != null && grabber != null && grabber.HeldGrabbable != null && grabber.HeldGrabbable.handPoseType == HandPoseType.HandPose;
}
public virtual void UpdateHandPoserIdleState() {
// Makde sure animator isn't firing while we do our idle state
DisableHandAnimator();
// Check if we need to set up the pose blender
if(!SetupPoseBlender()) {
// If Pose Blender couldn't be setup we should just exit
return;
}
// Make sure poseBlender updates the pose
poseBlender.UpdatePose = true;
// Check for Valve Index Knuckles finger tracking
if (UseIndexFingerTracking && InputBridge.Instance.IsValveIndexController) {
UpdateIndexFingerBlending();
return;
}
// Update pose blender depending on inputs from controller
// Thumb near can be counted as 'thumbTouch', primaryTouch, secondaryTouch, or primary2DAxisTouch (such as on knuckles controller)
poseBlender.ThumbValue = Mathf.Lerp(poseBlender.ThumbValue, _thumbIsNear ? 1 : 0, Time.deltaTime * handPoser.AnimationSpeed);
// Use Trigger for Index Finger
float targetIndexValue = _triggerValue;
// If the index finger is on the trigger we can bring the finger in a bit
if (targetIndexValue < 0.1f && _indexIsNear) {
targetIndexValue = 0.1f;
}
poseBlender.IndexValue = Mathf.Lerp(poseBlender.IndexValue, targetIndexValue, Time.deltaTime * handPoser.AnimationSpeed);
// Grip
poseBlender.GripValue = _gripValue;
}
public virtual void UpdateIndexFingerBlending() {
#if STEAM_VR_SDK
if (grabber.HandSide == ControllerHand.Left) {
poseBlender.IndexValue = InputBridge.Instance.LeftIndexCurl;
poseBlender.ThumbValue = InputBridge.Instance.LeftThumbCurl;
poseBlender.MiddleValue = InputBridge.Instance.LeftMiddleCurl;
poseBlender.RingValue = InputBridge.Instance.LeftRingCurl;
poseBlender.PinkyValue = InputBridge.Instance.LeftPinkyCurl;
}
else if (grabber.HandSide == ControllerHand.Right) {
poseBlender.IndexValue = InputBridge.Instance.RightIndexCurl;
poseBlender.ThumbValue = InputBridge.Instance.RightThumbCurl;
poseBlender.MiddleValue = InputBridge.Instance.RightMiddleCurl;
poseBlender.RingValue = InputBridge.Instance.RightRingCurl;
poseBlender.PinkyValue = InputBridge.Instance.RightPinkyCurl;
}
#endif
}
public virtual bool SetupPoseBlender() {
// Make sure we have a valid handPoser to work with
if(handPoser == null || !handPoser.isActiveAndEnabled) {
handPoser = GetComponentInChildren<HandPoser>(false);
}
// No HandPoser is found, we should just exit
if (handPoser == null) {
return false;
// Debug.Log("Adding Hand Poser to " + transform.name);
// handPoser = this.gameObject.AddComponent<HandPoser>();
}
// If no pose blender is found, add it and set it up so we can use it in Update()
if (poseBlender == null || !poseBlender.isActiveAndEnabled) {
poseBlender = GetComponentInChildren<HandPoseBlender>();
}
// If no pose blender is found, add it and set it up so we can use it in Update()
if (poseBlender == null) {
if(handPoser != null) {
poseBlender = handPoser.gameObject.AddComponent<HandPoseBlender>();
}
else {
poseBlender = this.gameObject.AddComponent<HandPoseBlender>();
}
// Don't update pose in Update since we will be controlling this ourselves
poseBlender.UpdatePose = false;
// Set up the blend to use some default poses
poseBlender.Pose1 = GetDefaultOpenPose();
poseBlender.Pose2 = GetDefaultClosedPose();
}
return true;
}
public virtual HandPose GetDefaultOpenPose() {
return Resources.Load<HandPose>("Open");
}
public virtual HandPose GetDefaultClosedPose() {
return Resources.Load<HandPose>("Closed");
}
public virtual void EnableHandPoser() {
// Disable the hand animator if we have a valid hand pose to use
if(handPoser != null) {
// Just need to make sure animator isn't enabled
DisableHandAnimator();
}
}
public virtual void EnableAutoPoser(bool continuous) {
// Check if AutoPoser was set
if (autoPoser == null || !autoPoser.gameObject.activeInHierarchy) {
if(handPoser != null) {
autoPoser = handPoser.GetComponent<AutoPoser>();
}
// Check for active children
else {
autoPoser = GetComponentInChildren<AutoPoser>(false);
}
}
// Do the auto pose
if (autoPoser != null) {
autoPoser.UpdateContinuously = continuous;
if(!continuous) {
autoPoser.UpdateAutoPoseOnce();
}
DisableHandAnimator();
// Disable pose blending updates
DisablePoseBlender();
}
}
public virtual void DisablePoseBlender() {
if (poseBlender != null) {
poseBlender.UpdatePose = false;
}
}
public virtual void DisableAutoPoser() {
if (autoPoser != null) {
autoPoser.UpdateContinuously = false;
}
}
public virtual bool IsAutoPoserGrabbable() {
return autoPoser != null && grabber != null && grabber.HeldGrabbable != null && (grabber.HeldGrabbable.handPoseType == HandPoseType.AutoPoseOnce || grabber.HeldGrabbable.handPoseType == HandPoseType.AutoPoseContinuous);
}
public virtual void EnableHandAnimator() {
if (HandAnimator != null && HandAnimator.enabled == false) {
HandAnimator.enabled = true;
}
// If using a hand poser reset the currennt pose so it can be set again later
if(handPoser != null) {
handPoser.CurrentPose = null;
}
}
public virtual void DisableHandAnimator() {
if (HandAnimator != null && HandAnimator.enabled) {
HandAnimator.enabled = false;
}
}
public virtual void OnGrabberGrabbed(Grabbable grabbed) {
// Set the Hand Pose on our component
if (grabbed.SelectedHandPose != null) {
UpdateCurrentHandPose();
}
else if(grabbed.handPoseType == HandPoseType.HandPose && grabbed.SelectedHandPose == null) {
// Debug.Log("No HandPose selected for object '" + grabbed.transform.name + "'. Falling back to default hand pose.");
// Fall back to the closed pose if no selected hand pose was found
grabbed.SelectedHandPose = GetDefaultClosedPose();
UpdateCurrentHandPose();
}
}
public virtual void UpdateCurrentHandPose() {
2023-10-23 12:40:36 +03:00
if(handPoser != null && grabber != null && grabber.HeldGrabbable != null) {
2023-07-24 16:38:13 +03:00
// Update the pose
2023-10-23 12:40:36 +03:00
try
{
handPoser.CurrentPose = grabber.HeldGrabbable.SelectedHandPose;
handPoser.OnPoseChanged();
}
catch (System.Exception ex)
{
Debug.LogError(ex.ToString());
}
2023-07-24 16:38:13 +03:00
}
}
public virtual void OnGrabberReleased(Grabbable released) {
OnGrabDrop();
}
}
public enum HandPoserType {
HandPoser,
Animator,
AutoPoser,
None
}
}