You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
493 lines
18 KiB
C#
493 lines
18 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.SceneManagement;
|
|
using NaughtyAttributes;
|
|
using UnityEditor;
|
|
using UnityEngine.Serialization;
|
|
|
|
namespace Autohand {
|
|
|
|
[DefaultExecutionOrder(-100)]
|
|
public class GrabbableBase : MonoBehaviour {
|
|
|
|
[AutoHeader("Grabbable")]
|
|
public bool ignoreMe;
|
|
|
|
[Tooltip("The physics body to connect this colliders grab to - if left empty will default to local body")]
|
|
public Rigidbody body;
|
|
|
|
[Tooltip("A copy of the mesh will be created and slighly scaled and this material will be applied to create a highlight effect with options")]
|
|
public Material hightlightMaterial;
|
|
|
|
[HideInInspector]
|
|
public bool isGrabbable = true;
|
|
|
|
private PlacePoint _placePoint = null;
|
|
public PlacePoint placePoint { get { return _placePoint; } protected set { _placePoint = value; } }
|
|
|
|
|
|
internal List<Collider> _grabColliders = new List<Collider>();
|
|
public List<Collider> grabColliders { get { return _grabColliders; } }
|
|
|
|
|
|
protected List<PlacePoint> _childPlacePoints = new List<PlacePoint>();
|
|
public List<PlacePoint> childPlacePoints { get { return _childPlacePoints; } }
|
|
|
|
internal Grabbable rootGrabbable;
|
|
internal List<Grabbable> grabbableChildren = new List<Grabbable>();
|
|
internal List<Grabbable> grabbableParents = new List<Grabbable>();
|
|
internal List<Grabbable> jointedGrabbables = new List<Grabbable>();
|
|
internal List<GrabbableChild> grabChildren = new List<GrabbableChild>();
|
|
|
|
internal float preheldDrag;
|
|
internal float preheldAngularDrag;
|
|
|
|
protected Dictionary<Collider, PhysicMaterial> grabColliderMaterials = new Dictionary<Collider, PhysicMaterial>();
|
|
protected Dictionary<Transform, int> originalLayers = new Dictionary<Transform, int>();
|
|
|
|
protected List<Hand> heldBy = new List<Hand>();
|
|
protected List<Hand> beingGrabbedBy = new List<Hand>();
|
|
protected List<Hand> waitingToGrabHands = new List<Hand>();
|
|
protected bool hightlighting;
|
|
protected GameObject highlightObj;
|
|
protected PlacePoint lastPlacePoint = null;
|
|
|
|
public Transform originalParent { get; set; }
|
|
protected Vector3 lastCenterOfMassPos;
|
|
protected Quaternion lastCenterOfMassRot;
|
|
protected CollisionDetectionMode detectionMode;
|
|
protected RigidbodyInterpolation startInterpolation;
|
|
|
|
protected internal bool beingGrabbed = false;
|
|
protected internal bool beforeGrabFrame = false;
|
|
protected bool wasIsGrabbable = false;
|
|
protected bool beingDestroyed = false;
|
|
protected Dictionary<Hand, Coroutine> resetLayerRoutine = new Dictionary<Hand, Coroutine>();
|
|
protected Dictionary<Hand, Coroutine> ignoreWhileGrabbingRoutine = new Dictionary<Hand, Coroutine>();
|
|
protected List<Transform> jointedParents = new List<Transform>();
|
|
protected Dictionary<Material, List<GameObject>> highlightObjs = new Dictionary<Material, List<GameObject>>();
|
|
|
|
protected GrabbablePoseCombiner poseCombiner;
|
|
protected float lastUpdateTime;
|
|
|
|
protected bool rigidbodyDeactivated = false;
|
|
protected SaveRigidbodyData rigidbodyData;
|
|
|
|
/// <summary>This transform represents the root rigidbody gameobject. This is used in place a rigidbody call just in case the rigidbody is disabled</summary>
|
|
public Transform rootTransform {
|
|
get {
|
|
if(body != null)
|
|
return body.transform;
|
|
else if(rigidbodyData.IsSet())
|
|
return rigidbodyData.GetOrigin();
|
|
else if(gameObject.CanGetComponent<Rigidbody>(out var rigidbody))
|
|
return rigidbody.transform;
|
|
else if(gameObject.GetComponentInParent<Rigidbody>() != null)
|
|
return gameObject.GetComponentInParent<Rigidbody>().transform;
|
|
else
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
private CollisionTracker _collisionTracker;
|
|
public CollisionTracker collisionTracker {
|
|
get {
|
|
if(_collisionTracker == null) {
|
|
if(!(_collisionTracker = GetComponent<CollisionTracker>())) {
|
|
_collisionTracker = gameObject.AddComponent<CollisionTracker>();
|
|
_collisionTracker.disableTriggersTracking = true;
|
|
}
|
|
}
|
|
return _collisionTracker;
|
|
}
|
|
protected set {
|
|
if(_collisionTracker != null)
|
|
Destroy(_collisionTracker);
|
|
|
|
_collisionTracker = value;
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
protected bool editorSelected = false;
|
|
#endif
|
|
|
|
public virtual void Awake() {
|
|
if(!gameObject.CanGetComponent(out poseCombiner))
|
|
poseCombiner = gameObject.AddComponent<GrabbablePoseCombiner>();
|
|
|
|
GetPoseSaves(transform);
|
|
|
|
//body.maxDepenetrationVelocity = 1f;
|
|
|
|
void GetPoseSaves(Transform obj) {
|
|
//Stop if you get to another grabbable
|
|
if(obj.CanGetComponent(out Grabbable grab) && grab != this)
|
|
return;
|
|
|
|
var poses = obj.GetComponents<GrabbablePose>();
|
|
for(int i = 0; i < poses.Length; i++)
|
|
poseCombiner.AddPose(poses[i]);
|
|
|
|
for(int i = 0; i < obj.childCount; i++)
|
|
GetPoseSaves(obj.GetChild(i));
|
|
}
|
|
|
|
|
|
|
|
if(body == null){
|
|
if(GetComponent<Rigidbody>())
|
|
body = GetComponent<Rigidbody>();
|
|
else
|
|
Debug.LogError("RIGIDBODY MISSING FROM GRABBABLE: " + transform.name + " \nPlease add/attach a rigidbody", this);
|
|
}
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
if (Selection.activeGameObject == gameObject){
|
|
Selection.activeGameObject = null;
|
|
Debug.Log("Auto Hand (EDITOR ONLY): Selecting the grabbable in the inspector can cause lag and quality reduction at runtime. (Automatically deselecting at runtime) Remove this code at any time.", this);
|
|
editorSelected = true;
|
|
}
|
|
|
|
Application.quitting += () => { if (editorSelected) Selection.activeGameObject = gameObject; };
|
|
#endif
|
|
|
|
originalParent = body.transform.parent;
|
|
detectionMode = body.collisionDetectionMode;
|
|
startInterpolation = body.interpolation;
|
|
|
|
|
|
grabColliders.Clear();
|
|
grabColliderMaterials.Clear();
|
|
SetCollidersRecursive(body.transform);
|
|
}
|
|
|
|
|
|
private void OnDestroy() {
|
|
beingDestroyed = true;
|
|
}
|
|
|
|
public virtual void HeldFixedUpdate() {
|
|
if(heldBy.Count > 0) {
|
|
lastCenterOfMassRot = body.transform.rotation;
|
|
lastCenterOfMassPos = body.transform.position;
|
|
}
|
|
|
|
}
|
|
|
|
protected virtual void OnDisable(){
|
|
foreach(var routine in resetLayerRoutine) {
|
|
IgnoreHand(routine.Key, false);
|
|
if(routine.Value != null)
|
|
StopCoroutine(routine.Value);
|
|
}
|
|
resetLayerRoutine.Clear();
|
|
|
|
foreach(var routine in ignoreGrabbableCollisions) {
|
|
if(routine.Value != null)
|
|
StopCoroutine(routine.Value);
|
|
}
|
|
ignoreGrabbableCollisions.Clear();
|
|
|
|
foreach(var routine in ignoreHandCollisions) {
|
|
if(routine.Value != null)
|
|
StopCoroutine(routine.Value);
|
|
}
|
|
ignoreHandCollisions.Clear();
|
|
|
|
}
|
|
|
|
|
|
internal void SetPlacePoint(PlacePoint point) {
|
|
this.placePoint = point;
|
|
|
|
foreach(var grabbable in grabbableChildren) {
|
|
grabbable.placePoint = point;
|
|
}
|
|
}
|
|
|
|
internal void SetGrabbableChild(GrabbableChild child) {
|
|
child.grabParent = this as Grabbable;
|
|
if(!grabChildren.Contains(child))
|
|
grabChildren.Add(child);
|
|
}
|
|
|
|
|
|
public void DeactivateRigidbody()
|
|
{
|
|
if (body != null){
|
|
if(body != null)
|
|
rigidbodyData = new SaveRigidbodyData(body);
|
|
|
|
body = null;
|
|
rigidbodyDeactivated = true;
|
|
}
|
|
|
|
foreach(var grabbable in grabbableChildren) {
|
|
if(grabbable.body != null) {
|
|
grabbable.body = null;
|
|
grabbable.rigidbodyData = new SaveRigidbodyData(rigidbodyData);
|
|
grabbable.rigidbodyDeactivated = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void ActivateRigidbody()
|
|
{
|
|
if (rigidbodyDeactivated && !beingDestroyed){
|
|
rigidbodyDeactivated = false;
|
|
body = rigidbodyData.ReloadRigidbody();
|
|
|
|
foreach(var grabbable in grabbableChildren) {
|
|
grabbable.rigidbodyDeactivated = false;
|
|
if(grabbable.body == null)
|
|
grabbable.body = body;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
internal void SetLayerRecursive(int newLayer) {
|
|
foreach(var transform in originalLayers) {
|
|
transform.Key.gameObject.layer = newLayer;
|
|
}
|
|
}
|
|
|
|
/// <summary>Sets the grabbable and children to the physics layers it had on Start()</summary>
|
|
internal void ResetOriginalLayers() {
|
|
foreach(var transform in originalLayers) {
|
|
transform.Key.gameObject.layer = transform.Value;
|
|
}
|
|
}
|
|
|
|
|
|
Dictionary<Grabbable, Coroutine> ignoreGrabbableCollisions = new Dictionary<Grabbable, Coroutine>();
|
|
public void IgnoreGrabbableCollisionUntilNone(Grabbable other) {
|
|
if(!beingDestroyed && !ignoreGrabbableCollisions.ContainsKey(other))
|
|
ignoreGrabbableCollisions.Add(other, StartCoroutine(IgnoreGrabbableCollisionUntilNoneRoutine(other)));
|
|
}
|
|
|
|
protected IEnumerator IgnoreGrabbableCollisionUntilNoneRoutine(Grabbable other) {
|
|
IgnoreGrabbableColliders(other, true);
|
|
|
|
yield return new WaitForSeconds(0.05f);
|
|
while(IsGrabbableOverlapping(other))
|
|
yield return new WaitForSeconds(0.1f);
|
|
|
|
IgnoreGrabbableColliders(other, false);
|
|
ignoreGrabbableCollisions.Remove(other);
|
|
|
|
if(ignoreGrabbableCollisions.ContainsKey(other))
|
|
ignoreGrabbableCollisions.Remove(other);
|
|
|
|
}
|
|
|
|
public bool IsGrabbableOverlapping(Grabbable other) {
|
|
foreach(var col1 in grabColliders) {
|
|
foreach(var col2 in other.grabColliders) {
|
|
if(col1.enabled && !col1.isTrigger && !col1.isTrigger && col2.enabled && !col2.isTrigger && !col2.isTrigger &&
|
|
Physics.ComputePenetration(col1, col1.transform.position, col1.transform.rotation, col2, col2.transform.position, col2.transform.rotation, out _, out _)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void IgnoreGrabbableColliders(Grabbable other, bool ignore) {
|
|
foreach(var col1 in grabColliders) {
|
|
foreach(var col2 in other.grabColliders) {
|
|
Physics.IgnoreCollision(col1, col2, ignore);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
Dictionary<Hand, Coroutine> ignoreHandCollisions = new Dictionary<Hand, Coroutine>();
|
|
public void IgnoreHandCollisionUntilNone(Hand hand, float minIgnoreTime = 1) {
|
|
if(gameObject.activeInHierarchy && !beingDestroyed && !ignoreHandCollisions.ContainsKey(hand))
|
|
ignoreHandCollisions.Add(hand, StartCoroutine(IgnoreHandCollisionUntilNoneRoutine(hand, minIgnoreTime)));
|
|
}
|
|
|
|
protected IEnumerator IgnoreHandCollisionUntilNoneRoutine(Hand hand, float minIgnoreTime) {
|
|
if(!ignoringHand.ContainsKey(hand) || !ignoringHand[hand]) {
|
|
IgnoreHand(hand, true);
|
|
|
|
yield return new WaitForSeconds(minIgnoreTime);
|
|
if(minIgnoreTime != 0)
|
|
while(IsHandOverlapping(hand))
|
|
yield return new WaitForSeconds(0.1f);
|
|
|
|
IgnoreHand(hand, false);
|
|
if(resetLayerRoutine.ContainsKey(hand))
|
|
resetLayerRoutine.Remove(hand);
|
|
if(ignoreHandCollisions.ContainsKey(hand))
|
|
ignoreHandCollisions.Remove(hand);
|
|
}
|
|
}
|
|
|
|
|
|
protected IEnumerator IgnoreHandCollision(Hand hand, float time) {
|
|
if(!ignoringHand.ContainsKey(hand) || !ignoringHand[hand]) {
|
|
IgnoreHand(hand, true);
|
|
|
|
yield return new WaitForSeconds(time);
|
|
|
|
IgnoreHand(hand, false);
|
|
resetLayerRoutine.Remove(hand);
|
|
}
|
|
}
|
|
|
|
protected Dictionary<Hand, bool> ignoringHand = new Dictionary<Hand, bool>();
|
|
public void IgnoreHand(Hand hand, bool ignore, bool overrideIgnoreRoutines = false)
|
|
{
|
|
if(overrideIgnoreRoutines && resetLayerRoutine.ContainsKey(hand) && resetLayerRoutine[hand] != null) {
|
|
StopCoroutine(resetLayerRoutine[hand]);
|
|
resetLayerRoutine[hand] = null;
|
|
}
|
|
|
|
foreach (var col in grabColliders)
|
|
hand.HandIgnoreCollider(col, ignore);
|
|
|
|
foreach(var grab in grabbableChildren)
|
|
foreach(var col in grab.grabColliders)
|
|
hand.HandIgnoreCollider(col, ignore);
|
|
|
|
foreach(var grab in grabbableParents)
|
|
foreach(var col in grab.grabColliders)
|
|
hand.HandIgnoreCollider(col, ignore);
|
|
|
|
if(!ignoringHand.ContainsKey(hand))
|
|
ignoringHand.Add(hand, ignore);
|
|
else
|
|
ignoringHand[hand] = ignore;
|
|
}
|
|
|
|
|
|
public bool IsHandOverlapping(Hand hand) {
|
|
float dist;
|
|
Vector3 dir;
|
|
foreach(var col2 in grabColliders) {
|
|
foreach(var col1 in hand.handColliders) {
|
|
if(col1.enabled && !col1.isTrigger && !col1.isTrigger && col2.enabled && !col2.isTrigger && !col2.isTrigger &&
|
|
Physics.ComputePenetration(col1, col1.transform.position, col1.transform.rotation, col2, col2.transform.position, col2.transform.rotation, out dir, out dist)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public bool GetSavedPose(out GrabbablePoseCombiner pose) {
|
|
if(poseCombiner != null && poseCombiner.PoseCount() > 0) {
|
|
pose = poseCombiner;
|
|
return true;
|
|
}
|
|
else {
|
|
pose = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool HasCustomPose() {
|
|
return poseCombiner.PoseCount() > 0;
|
|
}
|
|
|
|
|
|
/// <summary>Resets the physics materials on all the colliders to how it was during Start()</summary>
|
|
public void SetPhysicsMateiral(PhysicMaterial physMat) {
|
|
foreach(var collider in grabColliders) {
|
|
collider.material = physMat;
|
|
}
|
|
}
|
|
|
|
/// <summary>Resets the physics materials on all the colliders to how it was during Start()</summary>
|
|
public void ResetPhysicsMateiral() {
|
|
foreach(var col in grabColliderMaterials) {
|
|
col.Key.sharedMaterial = col.Value;
|
|
}
|
|
}
|
|
|
|
|
|
public void SetCollidersRecursive(Transform obj) {
|
|
|
|
foreach(var col in obj.GetComponents<Collider>()) {
|
|
if(col.isTrigger)
|
|
continue;
|
|
|
|
if(!grabColliders.Contains(col))
|
|
grabColliders.Add(col);
|
|
|
|
if(col.sharedMaterial == null)
|
|
grabColliderMaterials.Add(col, null);
|
|
else
|
|
grabColliderMaterials.Add(col, col.sharedMaterial);
|
|
|
|
if(!originalLayers.ContainsKey(col.transform)) {
|
|
if(col.gameObject.layer == LayerMask.NameToLayer("Default") || LayerMask.LayerToName(col.gameObject.layer) == "")
|
|
col.gameObject.layer = LayerMask.NameToLayer(Hand.grabbableLayerNameDefault);
|
|
originalLayers.Add(col.transform, col.gameObject.layer);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < obj.childCount; i++)
|
|
SetCollidersRecursive(obj.GetChild(i));
|
|
}
|
|
|
|
/// <summary>Adds a grabbables collider to this list of colliders on this grabbable</summary>
|
|
public void AddGrabbableColliders(Grabbable other) {
|
|
var ignoreHandKeys = new List<Hand>(ignoreHandCollisions.Keys);
|
|
foreach(var col in other.grabColliders) {
|
|
if(!grabColliders.Contains(col)) {
|
|
grabColliders.Add(col);
|
|
for(int i = 0; i < ignoreHandKeys.Count; i++)
|
|
ignoreHandKeys[i].HandIgnoreCollider(col, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RemoveGrabbableColliders(Grabbable other) {
|
|
var ignoreHandKeys = new List<Hand>(ignoreHandCollisions.Keys);
|
|
foreach(var col in other.grabColliders) {
|
|
if(grabColliders.Contains(col)) {
|
|
grabColliders.Remove(col);
|
|
for(int i = 0; i < ignoreHandKeys.Count; i++)
|
|
ignoreHandKeys[i].HandIgnoreCollider(col, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool BeingDestroyed() {
|
|
return beingDestroyed;
|
|
}
|
|
|
|
public void DebugBreak() {
|
|
#if UNITY_EDITOR
|
|
Debug.Break();
|
|
#endif
|
|
}
|
|
|
|
|
|
}
|
|
} |