using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace BNG {
public class HandPhysics : MonoBehaviour {
///
/// This is the object our physical hand should try to follow / match
///
[Tooltip("This is the object our physical hand should try to follow / match. Should typically be an object on the controller Transform")]
public Transform AttachTo;
[Tooltip("Amount of Velocity to apply to hands when trying to reach anchor point")]
public float HandVelocity = 1500f;
[Tooltip("If true, Hand COlliders will be disabled while grabbing an object")]
public bool DisableHandCollidersOnGrab = true;
[Tooltip("If the hand exceeds this distance from it's origin it will snap back to the original position. Specified in meters.")]
public float SnapBackDistance = 1f;
[Tooltip("This is the Grabber to use when this hand is active.")]
public Grabber ThisGrabber;
[Tooltip("Disable this Grabber when this hand is active. (Optional)")]
public Grabber DisableGrabber;
[Tooltip("This is the RemoteGrabber to use when this hand is active.")]
public RemoteGrabber ThisRemoteGrabber;
[Tooltip("Disable this RemoteGrabber when this hand is active. (Optional)")]
public RemoteGrabber DisableRemoteGrabber;
[Tooltip("Assign Hand Colliders this material if provided")]
public PhysicMaterial ColliderMaterial;
public Transform HandModel;
public Transform HandModelOffset;
public bool HoldingObject {
get {
return ThisGrabber != null && ThisGrabber.HeldGrabbable != null;
}
}
// Colliders that live in the hand model
List handColliders;
Rigidbody rigid;
ConfigurableJoint configJoint;
Grabbable heldGrabbable;
List collisions = new List();
LineRenderer line;
Vector3 localHandOffset;
Vector3 localHandOffsetRotation;
bool wasHoldingObject = false;
void Start() {
rigid = GetComponent();
configJoint = GetComponent();
line = GetComponent();
// Create Attach Point based on current position and rotation
if(AttachTo == null) {
AttachTo = new GameObject("AttachToTransform").transform;
}
AttachTo.parent = transform.parent;
AttachTo.SetPositionAndRotation(transform.position, transform.rotation);
// Connect config joint to our AttachPoint's Rigidbody
Rigidbody attachRB = AttachTo.gameObject.AddComponent();
attachRB.useGravity = false;
attachRB.isKinematic = true;
attachRB.constraints = RigidbodyConstraints.FreezeAll;
// configJoint.connectedBody = attachRB;
Destroy(configJoint);
localHandOffset = HandModel.localPosition;
localHandOffsetRotation = HandModel.localEulerAngles;
initHandColliders();
_priorParent = transform.parent;
// Physics Hands typically want to have no parent at all
transform.parent = null;
}
void Update() {
updateHandGraphics();
// Line indicating our object is far away
drawDistanceLine();
// Check if we should ignore collision with an object that is being remotely pulled towards us
checkRemoteCollision();
// Check if hand has gotten too far away
checkBreakDistance();
// Our root object is disabled
if (!AttachTo.gameObject.activeSelf) {
transform.parent = AttachTo;
transform.localPosition = Vector3.zero;
transform.localRotation = Quaternion.identity;
return;
}
// If we are holding something, move the hands in Update, ignoring physics.
if (HoldingObject) {
// Call On Grabbed Event if our first grab
if (!wasHoldingObject) {
OnGrabbedObject(ThisGrabber.HeldGrabbable);
}
// If we are holding something, move the hands in Update, ignoring physics.
//transform.position = AttachTo.position;
//transform.rotation = AttachTo.rotation;
}
else {
if (wasHoldingObject) {
OnReleasedObject(heldGrabbable);
}
}
wasHoldingObject = HoldingObject;
}
void FixedUpdate() {
// Move object directly to our hand since the hand joint is controlling movement now
if (HoldingObject && ThisGrabber.HeldGrabbable.DidParentHands) {
rigid.MovePosition(AttachTo.position);
rigid.MoveRotation(AttachTo.rotation);
}
else {
// Move using Velocity
Vector3 positionDelta = AttachTo.position - transform.position;
rigid.velocity = Vector3.MoveTowards(rigid.velocity, (positionDelta * HandVelocity) * Time.fixedDeltaTime, 5f);
// Rotate using angular velocity
float angle;
Vector3 axis;
Quaternion rotationDelta = AttachTo.rotation * Quaternion.Inverse(transform.rotation);
rotationDelta.ToAngleAxis(out angle, out axis);
// Fix rotation angle
if (angle > 180) {
angle -= 360;
}
if (angle != 0) {
Vector3 angularTarget = angle * axis;
angularTarget = (angularTarget * 60f) * Time.fixedDeltaTime;
rigid.angularVelocity = Vector3.MoveTowards(rigid.angularVelocity, angularTarget, 20f);
}
}
// Reset Collisions every physics update
collisions = new List();
}
void initHandColliders() {
handColliders = new List();
// Only accept non-trigger colliders.
var tempColliders = GetComponentsInChildren(false);
for (int x = 0; x < tempColliders.Length; x++) {
Collider c = tempColliders[x];
if (!c.isTrigger && c.enabled) {
if (ColliderMaterial) {
c.material = ColliderMaterial;
}
handColliders.Add(c);
}
}
// Ignore all other hand collider
for (int x = 0; x < handColliders.Count; x++) {
Collider thisCollider = handColliders[x];
for (int y = 0; y < handColliders.Count; y++) {
Physics.IgnoreCollision(thisCollider, handColliders[y], true);
}
}
}
Grabbable remoteIgnoredGrabbable;
void checkRemoteCollision() {
// Should we unignore this object if we are no longer pulling it towards us?
if(remoteIgnoredGrabbable != null && ThisGrabber.RemoteGrabbingGrabbable != remoteIgnoredGrabbable) {
// If we are holding this object then let the settings take care of it
if(ThisGrabber.HeldGrabbable == remoteIgnoredGrabbable) {
remoteIgnoredGrabbable = null;
}
// Otherwise we dropped it mid flight and should unignore it
else {
IgnoreGrabbableCollisions(remoteIgnoredGrabbable, false);
remoteIgnoredGrabbable = null;
}
}
// Ignore collision with object we started pulling towards us
if(ThisGrabber.RemoteGrabbingGrabbable != null && ThisGrabber.RemoteGrabbingGrabbable != remoteIgnoredGrabbable) {
remoteIgnoredGrabbable = ThisGrabber.RemoteGrabbingGrabbable;
IgnoreGrabbableCollisions(remoteIgnoredGrabbable, true);
}
}
// Line indicating our object is far away
void drawDistanceLine() {
if (line) {
if (Vector3.Distance(transform.position, AttachTo.position) > 0.05f) {
line.enabled = true;
line.SetPosition(0, transform.position);
line.SetPosition(1, AttachTo.position);
}
else {
line.enabled = false;
}
}
}
void checkBreakDistance() {
if (SnapBackDistance > 0 && Vector3.Distance(transform.position, AttachTo.position) > SnapBackDistance) {
transform.position = AttachTo.position;
}
}
void updateHandGraphics() {
bool holdingObject = ThisGrabber.HeldGrabbable != null;
if (!holdingObject) {
if (HandModelOffset) {
HandModelOffset.parent = HandModel;
HandModelOffset.localPosition = Vector3.zero;
HandModelOffset.localEulerAngles = Vector3.zero;
}
return;
}
// Position Hand Model
if (HandModelOffset && ThisGrabber.HandsGraphics) {
HandModelOffset.parent = ThisGrabber.HandsGraphics;
HandModelOffset.localPosition = localHandOffset;
HandModelOffset.localEulerAngles = localHandOffsetRotation;
}
}
IEnumerator UnignoreAllCollisions() {
var thisGrabbable = heldGrabbable;
heldGrabbable = null;
// Delay briefly so any held objects don't automatically clip
yield return new WaitForSeconds(0.1f);
IgnoreGrabbableCollisions(thisGrabbable, false);
}
public void IgnoreGrabbableCollisions(Grabbable grab, bool ignorePhysics) {
var grabColliders = grab.GetComponentsInChildren();
// Ignore all other hand collider
for (int x = 0; x < grabColliders.Length; x++) {
Collider thisGrabCollider = grabColliders[x];
for (int y = 0; y < handColliders.Count; y++) {
Physics.IgnoreCollision(thisGrabCollider, handColliders[y], ignorePhysics);
}
}
}
public void DisableHandColliders() {
for (int x = 0; x < handColliders.Count; x++) {
if(handColliders[x] != null && handColliders[x].enabled) {
handColliders[x].enabled = false;
}
}
}
public void EnableHandColliders() {
for (int x = 0; x < handColliders.Count; x++) {
if (handColliders[x] != null && handColliders[x].enabled == false) {
handColliders[x].enabled = true;
}
}
}
public virtual void OnGrabbedObject(Grabbable grabbedObject) {
heldGrabbable = grabbedObject;
if (DisableHandCollidersOnGrab) {
DisableHandColliders();
}
// Make the hand ignore the grabbable's colliders
else {
IgnoreGrabbableCollisions(heldGrabbable, true);
}
}
Transform _priorParent;
public virtual void LockLocalPosition() {
_priorParent = transform.parent;
transform.parent = AttachTo;
}
public virtual void UnlockLocalPosition() {
transform.parent = _priorParent;
}
public virtual void OnReleasedObject(Grabbable grabbedObject) {
if (heldGrabbable != null) {
// Make sure hand colliders come back
if (DisableHandCollidersOnGrab) {
EnableHandColliders();
}
// Unignore the grabbable's colliders
else {
StartCoroutine(UnignoreAllCollisions());
}
}
heldGrabbable = null;
}
void OnEnable() {
if(DisableGrabber) {
DisableGrabber.enabled = false;
}
if (ThisGrabber) {
ThisGrabber.enabled = true;
}
if (ThisRemoteGrabber) {
ThisRemoteGrabber.enabled = true;
DisableRemoteGrabber.enabled = false;
}
// Move events
PlayerTeleport.OnBeforeTeleport += LockLocalPosition;
PlayerTeleport.OnAfterTeleport += UnlockLocalPosition;
PlayerRotation.OnBeforeRotate += LockLocalPosition;
PlayerRotation.OnAfterRotate += UnlockLocalPosition;
SmoothLocomotion.OnBeforeMove += LockOffset;
SmoothLocomotion.OnAfterMove += UnlockOffset;
}
Vector3 _priorLocalOffsetPosition;
public virtual void LockOffset() {
_priorLocalOffsetPosition = AttachTo.InverseTransformPoint(transform.position);
}
public virtual void UnlockOffset() {
Vector3 dest = AttachTo.TransformPoint(_priorLocalOffsetPosition);
float dist = Vector3.Distance(transform.position, dest);
// Only move if gone far enough
if (dist > 0.0005f) {
transform.position = dest;
}
}
void OnDisable() {
if (ThisGrabber) {
ThisGrabber.enabled = false;
}
if (DisableGrabber) {
DisableGrabber.enabled = true;
}
if (ThisRemoteGrabber) {
ThisRemoteGrabber.enabled = false;
}
if (DisableRemoteGrabber) {
DisableRemoteGrabber.enabled = true;
}
// Move events
PlayerTeleport.OnBeforeTeleport -= LockLocalPosition;
PlayerTeleport.OnAfterTeleport -= UnlockLocalPosition;
PlayerRotation.OnBeforeRotate -= LockLocalPosition;
PlayerRotation.OnAfterRotate -= UnlockLocalPosition;
SmoothLocomotion.OnBeforeMove -= LockOffset;
SmoothLocomotion.OnAfterMove -= UnlockOffset;
}
void OnCollisionStay(Collision collision) {
for (int x = 0; x < collision.contacts.Length; x++) {
ContactPoint contact = collision.contacts[x];
// Keep track of how many objects we are colliding with
if (IsValidCollision(contact.otherCollider) && !collisions.Contains(contact.otherCollider)) {
collisions.Add(contact.otherCollider);
}
}
}
public bool IsValidCollision(Collider col) {
return true;
}
}
}