using NaughtyAttributes;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
using UnityEngine.UIElements;
namespace Autohand {
///
///
///
public enum HandMovementType {
/// Movement method for Auto Hand V2 and below
Legacy,
/// Uses physics forces
Forces
}
public enum HandType {
both,
right,
left,
none
}
public enum GrabType {
/// On grab, hand will move to the grabbable, create grab connection, then return to follow position
HandToGrabbable,
/// On grab, grabbable will move to the hand, then create grab connection
GrabbableToHand,
/// On grab, grabbable instantly travel to the hand
InstantGrab
}
[Serializable]
public struct VelocityTimePair {
public float time;
public Vector3 velocity;
}
public delegate void HandGrabEvent(Hand hand, Grabbable grabbable);
public delegate void HandGameObjectEvent(Hand hand, GameObject other);
[Serializable] public class UnityHandGrabEvent : UnityEvent { }
[Serializable] public class UnityHandEvent : UnityEvent { }
[RequireComponent(typeof(Rigidbody)), DefaultExecutionOrder(10)]
/// This is the base of the Auto Hand hand class, used for organizational purposes
public class HandBase : MonoBehaviour {
[AutoHeader("AUTO HAND")]
public bool ignoreMe;
public Finger[] fingers;
[Tooltip("An empty GameObject that should be placed on the surface of the center of the palm")]
public Transform palmTransform;
[FormerlySerializedAs("isLeft")]
[Tooltip("Whether this is the left (on) or right (off) hand")]
public bool left = false;
[Space]
[Tooltip("Maximum distance for pickup"), Min(0.01f)]
public float reachDistance = 0.3f;
[AutoToggleHeader("Enable Movement", 0, 0, tooltip = "Whether or not to enable the hand's Rigidbody Physics movement")]
public bool enableMovement = true;
[EnableIf("enableMovement"), Tooltip("Follow target, the hand will always try to match this transforms position with rigidbody movements")]
public Transform follow;
[EnableIf("enableMovement"), Tooltip("Returns hand to the target after this distance [helps just in case it gets stuck]"), Min(0)]
public float maxFollowDistance = 0.5f;
[EnableIf("enableMovement"), Tooltip("Amplifier for applied velocity on released object"), Min(0)]
public float throwPower = 1f;
[Tooltip("Speed at which the gentle grab returns the grabbable"), Min(0)]
[FormerlySerializedAs("smoothReturnSpeed")]
public float gentleGrabSpeed = 1;
[HideInInspector]
public bool advancedFollowSettings = true;
[AutoToggleHeader("Enable Auto Posing", 0, 0, tooltip = "Auto Posing will override Unity Animations -- This will disable all the Auto Hand IK, including animations from: finger sway, pose areas, finger bender scripts (runtime Auto Posing will still work)")]
[Tooltip("Turn this on when you want to animate the hand or use other IK Drivers")]
public bool enableIK = true;
[EnableIf("enableIK"), Tooltip("How much the fingers sway from the velocity")]
public float swayStrength = 0.7f;
[EnableIf("enableIK"), Tooltip("This will offset each fingers bend (0 is no bend, 1 is full bend)")]
public float gripOffset = 0.1f;
//HIDDEN ADVANCED SETTINGS
[NonSerialized, Tooltip("The maximum allowed velocity of the hand"), Min(0)]
public float maxVelocity = 12f;
[NonSerialized, Tooltip("Follow target speed (Can cause jittering if turned too high - recommend increasing drag with speed)"), Min(0)]
public float followPositionStrength = 60;
[HideInInspector, NonSerialized]
public float startDrag = 12;
[HideInInspector, NonSerialized, Tooltip("Follow target rotation speed (Can cause jittering if turned too high - recommend increasing angular drag with speed)"), Min(0)]
public float followRotationStrength = 150;
[HideInInspector, NonSerialized]
public float startAngularDrag = 60;
[HideInInspector, NonSerialized, Tooltip("After this many seconds velocity data within a 'throw window' will be tossed out. (This allows you to get only use acceeleration data from the last 'x' seconds of the throw.)")]
public float throwVelocityExpireTime = 0.125f;
[HideInInspector, NonSerialized, Tooltip("After this many seconds velocity data within a 'throw window' will be tossed out. (This allows you to get only use acceeleration data from the last 'x' seconds of the throw.)")]
public float throwAngularVelocityExpireTime = 0.25f;
[HideInInspector, NonSerialized, Tooltip("Increase for closer finger tip results / Decrease for less physics checks - The number of steps the fingers take when bending to grab something")]
public int fingerBendSteps = 40;
[HideInInspector]
public bool usingPoseAreas = true;
[HideInInspector]
public QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.Ignore;
Grabbable HoldingObj = null;
public Grabbable holdingObj {
get { return HoldingObj; }
internal set { HoldingObj = value; }
}
Grabbable _lookingAtObj = null;
public Grabbable lookingAtObj {
get { return _lookingAtObj; }
protected set { _lookingAtObj = value; }
}
Transform _moveTo = null;
public Transform moveTo {
get {
if(!gameObject.activeInHierarchy)
return null;
if(_moveTo == null) {
_moveTo = new GameObject().transform;
_moveTo.parent = transform.parent;
_moveTo.name = "HAND FOLLOW POINT";
}
return _moveTo;
}
}
Rigidbody _body;
public Rigidbody body {
get{
if(_body == null)
_body = GetComponent();
return _body;
}
internal set { _body = body; }
}
Vector3 _grabPositionOffset = Vector3.zero;
public Vector3 grabPositionOffset {
get { return _grabPositionOffset; }
internal set { _grabPositionOffset = value; }
}
Quaternion _grabRotationOffset = Quaternion.identity;
public Quaternion grabRotationOffset {
get { return _grabRotationOffset; }
internal set { _grabRotationOffset = value; }
}
public bool disableIK {
get { return !enableIK; }
set { enableIK = !value; }
}
private CollisionTracker _collisionTracker;
public CollisionTracker collisionTracker {
get {
if(_collisionTracker == null)
_collisionTracker = gameObject.AddComponent();
return _collisionTracker;
}
protected set {
if(_collisionTracker != null)
Destroy(_collisionTracker);
_collisionTracker = value;
}
}
protected GrabbablePose _currentHeldPose;
public GrabbablePose currentHeldPose {
get {
return _currentHeldPose;
}
protected set {
if(value == null && _currentHeldPose != null)
_currentHeldPose.CancelHandPose(this as Hand);
_currentHeldPose = value;
}
}
[HideInInspector, NonSerialized]
public ConfigurableJoint heldJoint;
public bool grabbing { get; protected set; }
public bool squeezing { get; protected set; }
public HandPoseArea handPoseArea { get; protected set; }
public Vector3 lastAngularVelocity { get; protected set; }
public Vector3 lastVelocity { get; protected set; }
protected float gripAxis;
protected float squeezeAxis;
protected Coroutine handAnimateRoutine;
protected HandPoseData preHandPoseAreaPose;
internal List handColliders = new List();
Transform _handGrabPoint;
internal Transform handGrabPoint {
get {
if(_handGrabPoint == null && gameObject.scene.isLoaded) {
_handGrabPoint = new GameObject().transform;
_handGrabPoint.name = "grabPoint";
}
return _handGrabPoint;
}
}
Transform _localGrabbablePoint;
internal Transform localGrabbablePoint {
get {
if(!gameObject.activeInHierarchy)
_localGrabbablePoint = null;
else if(gameObject.activeInHierarchy && _localGrabbablePoint == null) {
_localGrabbablePoint = new GameObject().transform;
_localGrabbablePoint.name = "grabPosition";
_localGrabbablePoint.parent = transform;
}
return _localGrabbablePoint;
}
}
BoxCollider _handEncapsulationCollider;
internal BoxCollider handEncapsulationBox {
get {
if(!gameObject.activeInHierarchy)
_handEncapsulationCollider = null;
else if(gameObject.activeInHierarchy && _handEncapsulationCollider == null) {
_handEncapsulationCollider = new GameObject().AddComponent();
_handEncapsulationCollider.name = "handEncapsulationBox";
_handEncapsulationCollider.transform.parent = transform;
_handEncapsulationCollider.transform.localPosition = Vector3.zero;
_handEncapsulationCollider.transform.localRotation = Quaternion.identity;
_handEncapsulationCollider.transform.localScale = Vector3.one;
_handEncapsulationCollider.isTrigger = true;
_handEncapsulationCollider.enabled = false;
}
return _handEncapsulationCollider;
}
}
internal int handLayers;
internal int handIgnoreCollisionLayers;
protected Collider palmCollider;
protected RaycastHit highlightHit;
protected RaycastHit grabbingHit;
protected HandVelocityTracker velocityTracker;
protected Transform palmChild;
protected Vector3 lastFrameFollowPos;
protected Quaternion lastFrameFollowRot;
protected bool ignoreMoveFrame;
protected Vector3 followVel;
protected Vector3 followAngularVel;
internal bool allowUpdateMovement = true;
protected Vector3[] updatePositionTracked = new Vector3[3];
protected List closestHits = new List();
protected List closestGrabs = new List();
protected int tryMaxDistanceCount;
protected Vector3 lastFollowPosition;
protected Vector3 lastFollowRotation;
protected int noCollisionFrames = 0;
protected int collisionFrames = 0;
protected bool prerendered = false;
protected Vector3 preRenderPos;
protected Quaternion preRenderRot;
protected float currGrip = 1f;
protected bool usingDynamicTimestep;
protected virtual void Awake() {
body = GetComponent();
body.interpolation = RigidbodyInterpolation.None;
body.useGravity = false;
body.solverIterations = 100;
body.solverVelocityIterations = 100;
if(palmCollider == null) {
palmCollider = palmTransform.gameObject.AddComponent();
(palmCollider as BoxCollider).size = new Vector3(0.2f, 0.15f, 0.05f);
(palmCollider as BoxCollider).center = new Vector3(0f, 0f, -0.025f);
palmCollider.enabled = false;
}
if(palmChild == null) {
palmChild = new GameObject().transform;
palmChild.parent = palmTransform;
}
var cams = AutoHandExtensions.CanFindObjectsOfType(true);
foreach(var cam in cams) {
if(cam.targetDisplay == 0) {
bool found = false;
var handStabilizers = cam.gameObject.GetComponents();
foreach(var handStabilizer in handStabilizers) {
if(handStabilizer.hand == this)
found = true;
}
if(!found)
cam.gameObject.AddComponent().hand = this;
}
}
if(velocityTracker == null)
velocityTracker = new HandVelocityTracker(this);
usingDynamicTimestep = AutoHandSettings.UsingDynamicTimestep();
if(usingDynamicTimestep) {
if(AutoHandExtensions.CanFindObjectOfType() == null) {
new GameObject() { name = "DynamicFixedTimeSetter" }.AddComponent();
Debug.Log("AUTO HAND: Creating Dynamic Timestepper");
}
}
//Update the hand encapsulation sphere
var bounds = new Bounds(Vector3.zero, Vector3.zero);
foreach(var finger in fingers) {
var fingerJoints = finger.FingerJoints;
for(int i = 0; i < fingerJoints.Length; i++)
bounds.Encapsulate(transform.InverseTransformPoint(fingerJoints[i].position));
bounds.Encapsulate(transform.InverseTransformPoint(finger.tip.position + (finger.tip.position - transform.position)*finger.tipRadius));
}
bounds.Encapsulate(transform.InverseTransformPoint(palmTransform.position + palmTransform.forward*0.01f));
bounds.Encapsulate(transform.InverseTransformPoint(palmTransform.position - palmTransform.forward*0.01f));
handEncapsulationBox.center = bounds.center;
handEncapsulationBox.size = bounds.size;
handEncapsulationBox.gameObject.layer = LayerMask.NameToLayer("Hand");
}
protected virtual void OnEnable() {
SetHandCollidersRecursive(transform);
}
protected virtual void OnDisable() {
handColliders.Clear();
}
protected virtual void OnDestroy() {
if(handGrabPoint != null)
Destroy(handGrabPoint.gameObject);
if(localGrabbablePoint != null)
Destroy(localGrabbablePoint.gameObject);
if(moveTo != null)
Destroy(moveTo.gameObject);
}
protected virtual void FixedUpdate(){
if(follow != null) {
followVel = follow.position - lastFollowPosition;
followAngularVel = follow.rotation.eulerAngles - lastFollowRotation;
lastFollowPosition = follow.position;
lastFollowRotation = follow.rotation.eulerAngles;
}
if (!IsGrabbing() && enableMovement && follow != null && !body.isKinematic) {
MoveTo(Time.fixedDeltaTime);
TorqueTo(Time.fixedDeltaTime);
}
velocityTracker.UpdateThrowing();
if(ignoreMoveFrame){
body.velocity = Vector3.zero;
body.angularVelocity = Vector3.zero;
}
ignoreMoveFrame = false;
if(CollisionCount() > 0) {
noCollisionFrames = 0;
collisionFrames++;
}
else {
noCollisionFrames++;
collisionFrames = 0;
}
if(holdingObj != null)
holdingObj.HeldFixedUpdate();
for(int i = 1; i < updatePositionTracked.Length; i++)
updatePositionTracked[i] = updatePositionTracked[i - 1];
updatePositionTracked[0] = transform.localPosition;
//Update the finger sway
if(!IsGrabbing())
UpdateFingers(Time.fixedDeltaTime);
}
protected virtual void Update(){
SetMoveTo();
}
//This is used to force the hand to always look like its where it should be even when physics is being weird
public virtual void OnPreRender(){
preRenderPos = transform.position;
preRenderRot = transform.rotation;
//Hides fixed joint jitterings
if(!prerendered && holdingObj != null && holdingObj.customGrabJoint == null && !IsGrabbing()) {
transform.position = handGrabPoint.position;
transform.rotation = handGrabPoint.rotation;
prerendered = true;
}
}
//This puts everything where it should be for the physics update
public virtual void OnPostRender(){
//Returns position after hiding for camera
if(prerendered && holdingObj != null && holdingObj.customGrabJoint == null && !IsGrabbing()) {
transform.position = preRenderPos;
transform.rotation = preRenderRot;
}
prerendered = false;
}
/// Creates Joints between hand and grabbable, does not call grab events
protected virtual void CreateJoint(Grabbable grab) {
CreateJoint(grab, grab.jointBreakForce, float.PositiveInfinity);
}
/// Creates Joints between hand and grabbable, does not call grab events
protected virtual void CreateJoint(Grabbable grab, float breakForce, float breakTorque){
if(grab.customGrabJoint == null){
var jointCopy = (Resources.Load("DefaultJoint"));
var newJoint = gameObject.AddComponent().GetCopyOf(jointCopy);
newJoint.anchor = Vector3.zero;
newJoint.breakForce = breakForce;
if(grab.HeldCount() == 1)
newJoint.breakForce += 500;
newJoint.breakTorque = breakTorque;
newJoint.connectedBody = grab.body;
newJoint.enablePreprocessing = jointCopy.enablePreprocessing;
newJoint.autoConfigureConnectedAnchor = false;
newJoint.connectedAnchor = grab.body.transform.InverseTransformPoint(handGrabPoint.position);
newJoint.angularXMotion = jointCopy.angularXMotion;
newJoint.angularYMotion = jointCopy.angularYMotion;
newJoint.angularZMotion = jointCopy.angularZMotion;
heldJoint = newJoint;
}
else {
var newJoint = grab.body.gameObject.AddComponent().GetCopyOf(grab.customGrabJoint);
newJoint.anchor = Vector3.zero;
if(grab.HeldCount() == 1)
newJoint.breakForce += 500;
newJoint.breakForce = breakForce;
newJoint.breakTorque = breakTorque;
newJoint.connectedBody = body;
newJoint.enablePreprocessing = grab.customGrabJoint.enablePreprocessing;
newJoint.autoConfigureConnectedAnchor = false;
newJoint.connectedAnchor = grab.body.transform.InverseTransformPoint(handGrabPoint.position);
newJoint.angularXMotion = grab.customGrabJoint.angularXMotion;
newJoint.angularYMotion = grab.customGrabJoint.angularYMotion;
newJoint.angularZMotion = grab.customGrabJoint.angularZMotion;
heldJoint = newJoint;
}
}
float lastMoveToDistance = float.MaxValue;
//====================== MOVEMENT =======================
//========================================================
//========================================================
/// Moves the hand to the controller rotation using physics movement
protected virtual void MoveTo(float deltaTime) {
SetMoveTo();
if(followPositionStrength <= 0)
return;
var movePos = moveTo.position;
var distance = Vector3.Distance(movePos, transform.position);
if(lastMoveToDistance != distance) {
lastMoveToDistance = distance;
//Returns if out of distance, if you aren't holding anything
if(distance > maxFollowDistance) {
if(holdingObj != null) {
if(holdingObj.parentOnGrab && tryMaxDistanceCount < 1) {
SetHandLocation(movePos, transform.rotation);
tryMaxDistanceCount += 2;
}
else if(!holdingObj.parentOnGrab || tryMaxDistanceCount >= 1) {
holdingObj.ForceHandRelease(this as Hand);
SetHandLocation(movePos, transform.rotation);
}
}
else {
SetHandLocation(movePos, transform.rotation);
}
}
if(tryMaxDistanceCount > 0)
tryMaxDistanceCount--;
distance = Mathf.Clamp(distance, 0, 0.5f);
SetVelocity(0.5f);
void SetVelocity(float minVelocityChange) {
var velocityClamp = holdingObj != null ? holdingObj.maxHeldVelocity : maxVelocity;
Vector3 vel = (movePos - transform.position).normalized * followPositionStrength * distance;
vel.x = Mathf.Clamp(vel.x, -velocityClamp, velocityClamp);
vel.y = Mathf.Clamp(vel.y, -velocityClamp, velocityClamp);
vel.z = Mathf.Clamp(vel.z, -velocityClamp, velocityClamp);
var deltaOffset = Time.fixedDeltaTime / 0.011111f;
var inverseDeltaOffset = 0.011111f / Time.fixedDeltaTime;
var currentVelocity = body.velocity;
body.drag = startDrag * inverseDeltaOffset;
var maxDelta = deltaOffset;
minVelocityChange *= deltaOffset;
var towardsVel = new Vector3(
Mathf.MoveTowards(currentVelocity.x, vel.x, minVelocityChange + Mathf.Abs(currentVelocity.x) * maxDelta),
Mathf.MoveTowards(currentVelocity.y, vel.y, minVelocityChange + Mathf.Abs(currentVelocity.y) * maxDelta),
Mathf.MoveTowards(currentVelocity.z, vel.z, minVelocityChange + Mathf.Abs(currentVelocity.z) * maxDelta)
);
body.velocity = towardsVel;
lastVelocity = body.velocity;
}
}
}
/// Rotates the hand to the controller rotation using physics movement
protected virtual void TorqueTo(float deltaTime) {
var delta = (moveTo.rotation * Quaternion.Inverse(body.rotation));
delta.ToAngleAxis(out float angle, out Vector3 axis);
if (float.IsInfinity(axis.x))
return;
if(angle > 180f)
angle -= 360f;
var multiLinear = Mathf.Deg2Rad * angle * followRotationStrength;
Vector3 angular = multiLinear * axis.normalized;
angle = Mathf.Abs(angle);
var angleStrengthOffset = Mathf.Lerp(1f, 1.5f, angle/16f);
var inverseDeltaOffset = 0.011111f / Time.fixedDeltaTime;
var maxDelta = followRotationStrength * 50f * angleStrengthOffset;
var currentAngleVelocity = body.angularVelocity;
body.angularDrag = Mathf.Lerp((startAngularDrag * 1.2f), startAngularDrag, angle/4f) * inverseDeltaOffset;
body.angularVelocity = new Vector3(
Mathf.MoveTowards(currentAngleVelocity.x, angular.x, maxDelta),
Mathf.MoveTowards(currentAngleVelocity.y, angular.y, maxDelta),
Mathf.MoveTowards(currentAngleVelocity.z, angular.z, maxDelta)
);
lastAngularVelocity = body.angularVelocity;
}
///Moves the hand and whatever it might be holding (if teleport allowed) to given pos/rot
public virtual void SetHandLocation(Vector3 pos, Quaternion rot) {
if(holdingObj && holdingObj.parentOnGrab) {
if (!IsGrabbing()) {
ignoreMoveFrame = true;
if(holdingObj.HeldCount() > 1) {
pos += transform.position - moveTo.position;
rot *= (Quaternion.Inverse(moveTo.rotation)* transform.rotation);
}
var handRuler = AutoHandExtensions.transformRuler;
handRuler.position = transform.position;
handRuler.rotation = transform.rotation;
var grabRuler = AutoHandExtensions.transformRulerChild;
grabRuler.position = holdingObj.body.transform.position;
grabRuler.rotation = holdingObj.body.transform.rotation;
handRuler.position = pos;
handRuler.rotation = rot;
var deltaHandRot = rot * Quaternion.Inverse(transform.rotation);
var deltaGrabPos = grabRuler.position - holdingObj.body.transform.position;
var deltaGrabRot = Quaternion.Inverse(grabRuler.rotation) * holdingObj.body.transform.rotation;
transform.position = handRuler.position;
transform.rotation = handRuler.rotation;
body.position = handRuler.position;
body.rotation = handRuler.rotation;
holdingObj.body.transform.position = grabRuler.position;
holdingObj.body.transform.rotation = grabRuler.rotation;
holdingObj.body.position = grabRuler.position;
holdingObj.body.rotation = grabRuler.rotation;
body.velocity = deltaHandRot * body.velocity;
body.angularVelocity = deltaHandRot * body.angularVelocity;
grabPositionOffset = deltaGrabRot * grabPositionOffset;
foreach(var jointed in holdingObj.jointedBodies)
if(!(jointed.CanGetComponent(out Grabbable grab) && grab.HeldCount() > 0)) {
jointed.position += deltaGrabPos;
jointed.transform.RotateAround(holdingObj.body.transform, deltaGrabRot);
}
velocityTracker.ClearThrow();
}
}
else {
ignoreMoveFrame = true;
transform.position = pos;
transform.rotation = rot;
body.position = pos;
body.rotation = rot;
body.velocity = Vector3.zero;
body.angularVelocity = Vector3.zero;
}
SetMoveTo();
}
///Moves the hand and keeps the local rotation
public virtual void SetHandLocation(Vector3 pos) {
SetMoveTo();
SetHandLocation(pos, transform.rotation);
}
/// Resets the hand location to the follow
public void ResetHandLocation() {
SetHandLocation(moveTo.position, moveTo.rotation);
}
/// Updates the target used to calculate velocity / movements towards follow
public void SetMoveTo(bool ignoreHeld = false) {
if(follow == null)
return;
//Sets [Move To] Object
moveTo.position = follow.position + grabPositionOffset;
moveTo.rotation = follow.rotation * grabRotationOffset;
//Adjust the [Move To] based on offsets
if(holdingObj != null) {
if(left) {
var leftRot = -holdingObj.heldRotationOffset;
leftRot.x *= -1;
moveTo.localRotation *= Quaternion.Euler(leftRot);
var moveLeft = holdingObj.heldPositionOffset;
moveLeft.x *= -1;
moveTo.position += transform.rotation * moveLeft;
}
else {
moveTo.position += transform.rotation * holdingObj.heldPositionOffset;
moveTo.localRotation *= Quaternion.Euler(holdingObj.heldRotationOffset);
}
}
//This is a forumla to help stabilize an object when held by multiple hands
//It's more art than science, but it works well enough
if(holdingObj != null && holdingObj.HeldCount() > 0 && !ignoreHeld) {
var deltaOffset = 0.011111f/Time.fixedDeltaTime;
var heldBy = holdingObj.rootGrabbable.GetHeldBy(true, true);
for(int i = 0; i < heldBy.Count; i++) {
//If the grabbable is held by another hand, we want to move towards that hand when its being pulled away to reduce jitter
if(heldBy[i] != this && holdingObj.rootGrabbable.moveTos.ContainsKey(heldBy[i])) {
var otherHandOffset = holdingObj.rootGrabbable.moveTos[heldBy[i]];
var targetDistance = Mathf.Lerp(1f + 0.5f * deltaOffset, 1f + 0.05f * deltaOffset, otherHandOffset.magnitude*4);
var massOffset = heldBy[i].body.mass/body.mass;
moveTo.position += (otherHandOffset / targetDistance) * massOffset;
}
}
}
}
/// Whether or not this hand can grab the grabbbale based on hand and grabbable settings
public bool CanGrab(Grabbable grab) {
var cantHandSwap = (grab.IsHeld() && grab.singleHandOnly && !grab.allowHeldSwapping);
return (grab.CanGrab(this) && !IsGrabbing() && !cantHandSwap);
}
public float GetTriggerAxis() {
return gripAxis;
}
Collider[] handHighlightNonAlloc = new Collider[128];
/// Finds the closest raycast from a cone of rays -> Returns average direction of all hits
protected virtual Vector3 HandClosestHit(out RaycastHit closestHit, out IGrabbableEvents grabbable, float dist, int layerMask, Grabbable target = null) {
Grabbable grab;
Vector3 palmForward = palmTransform.forward;
Vector3 palmPosition = palmTransform.position;
GameObject rayHitObject;
Grabbable lastRayHitGrabbable = null;
Ray ray = new Ray();
RaycastHit hit;
Collider col;
closestGrabs.Clear();
closestHits.Clear();
var checkSphereRadius = reachDistance * 1.35f;
int overlapCount = Physics.OverlapSphereNonAlloc(palmPosition + palmForward * (checkSphereRadius * 0.9f), checkSphereRadius, handHighlightNonAlloc, layerMask, QueryTriggerInteraction.Collide);
for(int i = 0; i < overlapCount; i++) {
col = handHighlightNonAlloc[i];
if(!(col is MeshCollider) || (col as MeshCollider).convex == true) {
Vector3 closestPoint = col.ClosestPoint(palmTransform.transform.position);
ray.direction = closestPoint - palmTransform.position;
}
else
ray.direction = palmTransform.forward;
ray.origin = palmTransform.transform.position;
ray.origin = Vector3.MoveTowards(ray.origin, col.bounds.center, 0.001f);
var queryTriggerInteraction = QueryTriggerInteraction.Ignore;
if(col.isTrigger)
queryTriggerInteraction = QueryTriggerInteraction.Collide;
if(ray.direction != Vector3.zero && Vector3.Angle(ray.direction, palmTransform.forward) < 100 && Physics.Raycast(ray, out hit, checkSphereRadius*2, layerMask, queryTriggerInteraction)) {
rayHitObject = hit.collider.gameObject;
if(closestGrabs.Count > 0)
lastRayHitGrabbable = closestGrabs[closestGrabs.Count - 1];
if(closestGrabs.Count > 0 && rayHitObject == lastRayHitGrabbable.gameObject) {
if(target == null) {
closestGrabs.Add(lastRayHitGrabbable);
closestHits.Add(hit);
}
}
else if(rayHitObject.HasGrabbable(out grab) && CanGrab(grab)) {
if(target == null || target == grab) {
closestGrabs.Add(grab);
closestHits.Add(hit);
}
}
}
}
int closestHitCount = closestHits.Count;
if(closestHitCount > 0) {
closestHit = closestHits[0];
grabbable = closestGrabs[0];
Vector3 dir = Vector3.zero;
if(grabbable is Grabbable) {
for(int i = 0; i < closestHitCount; i++) {
if(closestHits[i].distance / closestGrabs[i].grabPriorityWeight < closestHit.distance / (grabbable as Grabbable).grabPriorityWeight) {
closestHit = closestHits[i];
grabbable = closestGrabs[i];
}
dir += closestHits[i].point - palmTransform.position;
}
}
else {
for(int i = 0; i < closestHitCount; i++) {
if(closestHits[i].distance / closestGrabs[i].grabPriorityWeight < closestHit.distance ) {
closestHit = closestHits[i];
grabbable = closestGrabs[i];
}
dir += closestHits[i].point - palmTransform.position;
}
}
if(holdingObj == null && !IsGrabbing()) {
if(handGrabPoint.parent != closestHit.transform)
handGrabPoint.parent = closestHit.collider.transform;
handGrabPoint.position = closestHit.point;
handGrabPoint.up = closestHit.normal;
}
return dir / closestHitCount;
}
closestHit = new RaycastHit();
grabbable = null;
return Vector3.zero;
}
private void OnDrawGizmosSelected() {
var radius = reachDistance;
Gizmos.DrawWireSphere(palmTransform.position + palmTransform.forward * radius, radius);
}
public bool IsPosing() {
return handPoseArea != null || (holdingObj != null && holdingObj.HasCustomPose()) || handAnimateRoutine != null;
}
float fingerSwayVel;
/// Determines how the hand should look/move based on its flags
public virtual void UpdateFingers(float deltaTime) {
var averageVel = Vector3.zero;
for (int i = 1; i < updatePositionTracked.Length; i++)
averageVel += updatePositionTracked[i] - updatePositionTracked[i - 1];
averageVel /= updatePositionTracked.Length;
if(transform.parent != null)
averageVel = (Quaternion.Inverse(palmTransform.rotation)*transform.parent.rotation)*averageVel;
//Responsable for movement finger sway
if (!grabbing && !disableIK && !IsPosing() && !holdingObj)
{
float vel = (averageVel*60).z;
if (CollisionCount() > 0) vel = 0;
fingerSwayVel = Mathf.MoveTowards(fingerSwayVel, vel, deltaTime * (Mathf.Abs((fingerSwayVel-vel) * 30f)));
float grip = gripOffset + swayStrength * fingerSwayVel;
currGrip = grip;
foreach (var finger in fingers)
{
finger.UpdateFinger(grip);
}
}
}
public int CollisionCount() {
if(holdingObj != null)
return collisionTracker.collisionObjects.Count + holdingObj.CollisionCount();
return collisionTracker.collisionObjects.Count;
}
public void HandIgnoreCollider(Collider collider, bool ignore) {
for(int i = 0; i < handColliders.Count; i++)
Physics.IgnoreCollision(handColliders[i], collider, ignore);
}
public void SetLayerRecursive(Transform obj, int newLayer) {
obj.gameObject.layer = newLayer;
for(int i = 0; i < obj.childCount; i++) {
SetLayerRecursive(obj.GetChild(i), newLayer);
}
}
protected void SetHandCollidersRecursive(Transform obj) {
handColliders.Clear();
AddHandCol(obj);
void AddHandCol(Transform obj1) {
foreach(var col in obj1.GetComponents())
handColliders.Add(col);
for(int i = 0; i < obj1.childCount; i++) {
AddHandCol(obj1.GetChild(i));
}
}
}
/// Returns the current throw velocity
public Vector3 ThrowVelocity() { return velocityTracker.ThrowVelocity(); }
/// Returns the current throw angular velocity
public Vector3 ThrowAngularVelocity() { return velocityTracker.ThrowAngularVelocity(); }
/// Returns true during the time between when a grab starts and a hold begins
public bool IsGrabbing() {
return grabbing;
}
public bool IsHolding() {
return holdingObj != null;
}
public static int GetHandsLayerMask() {
return LayerMask.GetMask(Hand.rightHandLayerName, Hand.leftHandLayerName);
}
}
}