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
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
    }
 | 
						|
} |