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 _grabColliders = new List(); public List grabColliders { get { return _grabColliders; } } protected List _childPlacePoints = new List(); public List childPlacePoints { get { return _childPlacePoints; } } internal Grabbable rootGrabbable; internal List grabbableChildren = new List(); internal List grabbableParents = new List(); internal List jointedGrabbables = new List(); internal List grabChildren = new List(); internal float preheldDrag; internal float preheldAngularDrag; protected Dictionary grabColliderMaterials = new Dictionary(); protected Dictionary originalLayers = new Dictionary(); protected List heldBy = new List(); protected List beingGrabbedBy = new List(); protected List waitingToGrabHands = new List(); 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 resetLayerRoutine = new Dictionary(); protected Dictionary ignoreWhileGrabbingRoutine = new Dictionary(); protected List jointedParents = new List(); protected Dictionary> highlightObjs = new Dictionary>(); protected GrabbablePoseCombiner poseCombiner; protected float lastUpdateTime; protected bool rigidbodyDeactivated = false; protected SaveRigidbodyData rigidbodyData; /// This transform represents the root rigidbody gameobject. This is used in place a rigidbody call just in case the rigidbody is disabled public Transform rootTransform { get { if(body != null) return body.transform; else if(rigidbodyData.IsSet()) return rigidbodyData.GetOrigin(); else if(gameObject.CanGetComponent(out var rigidbody)) return rigidbody.transform; else if(gameObject.GetComponentInParent() != null) return gameObject.GetComponentInParent().transform; else return null; } } private CollisionTracker _collisionTracker; public CollisionTracker collisionTracker { get { if(_collisionTracker == null) { if(!(_collisionTracker = GetComponent())) { _collisionTracker = gameObject.AddComponent(); _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(); 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(); 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()) body = GetComponent(); 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; } } /// Sets the grabbable and children to the physics layers it had on Start() internal void ResetOriginalLayers() { foreach(var transform in originalLayers) { transform.Key.gameObject.layer = transform.Value; } } Dictionary ignoreGrabbableCollisions = new Dictionary(); 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 ignoreHandCollisions = new Dictionary(); 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 ignoringHand = new Dictionary(); 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; } /// Resets the physics materials on all the colliders to how it was during Start() public void SetPhysicsMateiral(PhysicMaterial physMat) { foreach(var collider in grabColliders) { collider.material = physMat; } } /// Resets the physics materials on all the colliders to how it was during Start() public void ResetPhysicsMateiral() { foreach(var col in grabColliderMaterials) { col.Key.sharedMaterial = col.Value; } } public void SetCollidersRecursive(Transform obj) { foreach(var col in obj.GetComponents()) { 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)); } /// Adds a grabbables collider to this list of colliders on this grabbable public void AddGrabbableColliders(Grabbable other) { var ignoreHandKeys = new List(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(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 } } }