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#

6 months ago
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
}
}
}