forked from cgvr/DeltaVR
		
	
		
			
				
	
	
		
			489 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			489 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
namespace UnityEngine.XR.Content.Interaction
 | 
						|
{
 | 
						|
    /// <summary>
 | 
						|
    /// Joins a rigidbody and transform together in a way that optimizes for transform and rigidbody-based motion automatically when appropriate.
 | 
						|
    /// Exerts an increasing force when the rigidbody is separate from the anchor position, but does not oscillate like a spring.
 | 
						|
    /// </summary>
 | 
						|
    public class TransformJoint : MonoBehaviour, ISerializationCallbackReceiver
 | 
						|
    {
 | 
						|
        const float k_MinMass = 0.01f;
 | 
						|
        const float k_MaxForceDistance = 0.01f;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("A reference to another transform this joint connects to.")]
 | 
						|
        Transform m_ConnectedBody;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("The Position of the anchor around which the joints motion is constrained.")]
 | 
						|
        Vector3 m_Anchor;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("The Rotation of the anchor around which the joints motion is constrained")]
 | 
						|
        Vector3 m_AnchorAngle;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("Should the connectedAnchor be calculated automatically?")]
 | 
						|
        bool m_AutoConfigureConnectedAnchor;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("Position of the anchor relative to the connected transform.")]
 | 
						|
        Vector3 m_ConnectedAnchor;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("The Rotation of the anchor relative to the connected transform.")]
 | 
						|
        Vector3 m_ConnectedAnchorAngle;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("Enable collision between bodies connected with the joint.")]
 | 
						|
        bool m_EnableCollision;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("Baseline force applied when an obstacle is between the joint and the connected transform.")]
 | 
						|
        float m_BaseForce = 0.25f;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("Additional force applied based on the distance between joint and connected transform")]
 | 
						|
        float m_SpringForce = 1f;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("The distance this joint must be from the anchor before teleporting.")]
 | 
						|
        float m_BreakDistance = 1.5f;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("The angular distance this joint must be from the anchor before teleporting.")]
 | 
						|
        float m_BreakAngle = 120f;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("Should the angle be matched?")]
 | 
						|
        bool m_MatchRotation = true;
 | 
						|
 | 
						|
        [SerializeField]
 | 
						|
        [Tooltip("Should the mass of the rigidbody be temporarily adjusted to stabilize very strong motion?")]
 | 
						|
        bool m_AdjustMass = true;
 | 
						|
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// A reference to another transform this joint connects to.
 | 
						|
        /// </summary>
 | 
						|
        public Transform connectedBody
 | 
						|
        {
 | 
						|
            get => m_ConnectedBody;
 | 
						|
            set
 | 
						|
            {
 | 
						|
                if (m_ConnectedBody == value)
 | 
						|
                    return;
 | 
						|
 | 
						|
                m_ConnectedBody = value;
 | 
						|
                SetupConnectedBodies(true);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The Position of the anchor around which the joints motion is constrained.
 | 
						|
        /// </summary>
 | 
						|
        public Vector3 anchor
 | 
						|
        {
 | 
						|
            get => m_Anchor;
 | 
						|
            set => m_Anchor = value;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The Rotation of the anchor around which the joints motion is constrained
 | 
						|
        /// </summary>
 | 
						|
        public Vector3 anchorAngle
 | 
						|
        {
 | 
						|
            get => m_AnchorAngle;
 | 
						|
            set
 | 
						|
            {
 | 
						|
                m_AnchorAngle = value;
 | 
						|
                m_AnchorRotation.eulerAngles = m_AnchorAngle;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Should the connectedAnchor be calculated automatically?
 | 
						|
        /// </summary>
 | 
						|
        public bool autoConfigureConnectedAnchor
 | 
						|
        {
 | 
						|
            get => m_AutoConfigureConnectedAnchor;
 | 
						|
            set
 | 
						|
            {
 | 
						|
                m_AutoConfigureConnectedAnchor = value;
 | 
						|
                SetupConnectedBodies(true);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Position of the anchor relative to the connected transform.
 | 
						|
        /// </summary>
 | 
						|
        public Vector3 connectedAnchor
 | 
						|
        {
 | 
						|
            get => m_ConnectedAnchor;
 | 
						|
            set => m_ConnectedAnchor = value;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The Rotation of the anchor relative to the connected transform.
 | 
						|
        /// </summary>
 | 
						|
        public Vector3 connectedAnchorAngle
 | 
						|
        {
 | 
						|
            get => m_ConnectedAnchorAngle;
 | 
						|
            set
 | 
						|
            {
 | 
						|
                m_ConnectedAnchorAngle = value;
 | 
						|
                m_ConnectedAnchorRotation.eulerAngles = m_ConnectedAnchorAngle;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Enable collision between bodies connected with the joint.
 | 
						|
        /// </summary>
 | 
						|
        public bool enableCollision
 | 
						|
        {
 | 
						|
            get => m_EnableCollision;
 | 
						|
            set
 | 
						|
            {
 | 
						|
                m_EnableCollision = value;
 | 
						|
                SetupConnectedBodies();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Should the mass of the rigidbody be temporarily adjusted to stabilize very strong motion?
 | 
						|
        /// </summary>
 | 
						|
        public bool adjustMass
 | 
						|
        {
 | 
						|
            get => m_AdjustMass;
 | 
						|
            set => m_AdjustMass = value;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Baseline force applied when an obstacle is between the joint and the connected transform.
 | 
						|
        /// </summary>
 | 
						|
        public float baseForce
 | 
						|
        {
 | 
						|
            get => m_BaseForce;
 | 
						|
            set => m_BaseForce = value;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Additional force applied based on the distance between joint and connected transform
 | 
						|
        /// </summary>
 | 
						|
        public float springForce
 | 
						|
        {
 | 
						|
            get => m_SpringForce;
 | 
						|
            set => m_SpringForce = value;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The distance this joint must be from the anchor before teleporting.
 | 
						|
        /// </summary>
 | 
						|
        public float breakDistance
 | 
						|
        {
 | 
						|
            get => m_BreakDistance;
 | 
						|
            set => m_BreakDistance = value;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The angular distance this joint must be from the anchor before teleporting.
 | 
						|
        /// </summary>
 | 
						|
        public float breakAngle
 | 
						|
        {
 | 
						|
            get => m_BreakAngle;
 | 
						|
            set => m_BreakAngle = value;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// The angular distance this joint must be from the anchor before teleporting.
 | 
						|
        /// </summary>
 | 
						|
        public bool matchRotation
 | 
						|
        {
 | 
						|
            get => m_MatchRotation;
 | 
						|
            set => m_MatchRotation = value;
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
        Quaternion m_AnchorRotation;
 | 
						|
        Quaternion m_ConnectedAnchorRotation;
 | 
						|
 | 
						|
        Transform m_Transform;
 | 
						|
        Rigidbody m_Rigidbody;
 | 
						|
 | 
						|
        bool m_FixedSyncFrame;
 | 
						|
        bool m_ActiveCollision;
 | 
						|
        bool m_CollisionFrame;
 | 
						|
        bool m_LastCollisionFrame;
 | 
						|
 | 
						|
        Vector3 m_LastPosition;
 | 
						|
        Vector3 m_LastDirection;
 | 
						|
 | 
						|
        Collider m_SourceCollider;
 | 
						|
        Collider m_ConnectedCollider;
 | 
						|
 | 
						|
        float m_BaseMass = 1f;
 | 
						|
        float m_AppliedForce;
 | 
						|
        float m_OldForce;
 | 
						|
 | 
						|
        void Start()
 | 
						|
        {
 | 
						|
            m_Rigidbody = GetComponent<Rigidbody>();
 | 
						|
            m_SourceCollider = GetComponent<Collider>();
 | 
						|
 | 
						|
            m_Transform = transform;
 | 
						|
 | 
						|
            m_AnchorRotation.eulerAngles = m_AnchorAngle;
 | 
						|
            m_ConnectedAnchorRotation.eulerAngles = m_ConnectedAnchorAngle;
 | 
						|
 | 
						|
            if (m_Rigidbody != null && m_Rigidbody.mass > k_MinMass)
 | 
						|
                m_BaseMass = m_Rigidbody.mass;
 | 
						|
 | 
						|
            // Set up connected anchor if attached
 | 
						|
            SetupConnectedBodies(true);
 | 
						|
        }
 | 
						|
 | 
						|
        void OnDestroy()
 | 
						|
        {
 | 
						|
            if (m_Rigidbody != null)
 | 
						|
                m_Rigidbody.mass = m_BaseMass;
 | 
						|
        }
 | 
						|
 | 
						|
        void SetupConnectedBodies(bool updateAnchor = false)
 | 
						|
        {
 | 
						|
            // Handle undoing old setup
 | 
						|
            // If any properties are pre-existing and have changed, reset the last saved collision ignore pairing
 | 
						|
            if (m_SourceCollider != null && m_ConnectedCollider != null)
 | 
						|
            {
 | 
						|
                Physics.IgnoreCollision(m_SourceCollider, m_ConnectedCollider, false);
 | 
						|
                m_ConnectedCollider = null;
 | 
						|
            }
 | 
						|
 | 
						|
            // Handle current setup
 | 
						|
            if (m_ConnectedBody != null)
 | 
						|
            {
 | 
						|
                if (m_AutoConfigureConnectedAnchor && updateAnchor)
 | 
						|
                {
 | 
						|
                    // Calculate what offsets are currently, set them as anchor
 | 
						|
                    m_ConnectedAnchor = m_ConnectedBody.InverseTransformPoint(m_Rigidbody.position + Vector3.Scale((m_Rigidbody.rotation * m_Anchor), m_Transform.lossyScale));
 | 
						|
                    m_ConnectedAnchorRotation = (m_Rigidbody.rotation * m_AnchorRotation);
 | 
						|
                    m_ConnectedAnchorAngle = m_ConnectedAnchorRotation.eulerAngles;
 | 
						|
                }
 | 
						|
                if (m_EnableCollision)
 | 
						|
                {
 | 
						|
                    // Get collider on connected body
 | 
						|
                    m_ConnectedCollider = m_ConnectedBody.GetComponent<Collider>();
 | 
						|
 | 
						|
                    if (m_SourceCollider != null && m_ConnectedCollider != null)
 | 
						|
                    {
 | 
						|
                        Physics.IgnoreCollision(m_SourceCollider, m_ConnectedCollider, true);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        void LateUpdate()
 | 
						|
        {
 | 
						|
            // Move freely unless collision has occurred - then rely on physics
 | 
						|
            if ((m_CollisionFrame || m_ActiveCollision) && !m_FixedSyncFrame)
 | 
						|
            {
 | 
						|
                m_Transform.position = m_Rigidbody.position;
 | 
						|
 | 
						|
                if (m_MatchRotation)
 | 
						|
                    m_Transform.rotation = m_Rigidbody.rotation;
 | 
						|
            }
 | 
						|
 | 
						|
            m_FixedSyncFrame = false;
 | 
						|
        }
 | 
						|
 | 
						|
        void FixedUpdate()
 | 
						|
        {
 | 
						|
            m_FixedSyncFrame = true;
 | 
						|
            m_OldForce = m_AppliedForce;
 | 
						|
            m_AppliedForce = 0f;
 | 
						|
 | 
						|
            // Zero out any existing velocity, we are going to set force manually if needed
 | 
						|
            m_Rigidbody.velocity = Vector3.zero;
 | 
						|
            m_Rigidbody.angularVelocity = Vector3.zero;
 | 
						|
 | 
						|
            UpdateBufferedCollision();
 | 
						|
            UpdatePosition();
 | 
						|
 | 
						|
            if (m_MatchRotation)
 | 
						|
                UpdateRotation();
 | 
						|
 | 
						|
            if (m_AdjustMass)
 | 
						|
            {
 | 
						|
                var offset = (m_AppliedForce / m_BaseMass) * Time.fixedDeltaTime * Time.fixedDeltaTime * 0.5f;
 | 
						|
                var massScale = Mathf.Max((offset / k_MaxForceDistance), 1f);
 | 
						|
                m_Rigidbody.mass = m_BaseMass * massScale;
 | 
						|
            }
 | 
						|
            // and acc = f/m
 | 
						|
            // offset = acc * fixedTimestep * fixedTimestep * .5
 | 
						|
 | 
						|
            // Is offset over certain desirable distance?  ie. .1m
 | 
						|
            // scale offset down by scaling mass up
 | 
						|
            // offset*scale = acc * ftp^2 * .5
 | 
						|
            // offset = acc *
 | 
						|
            //
 | 
						|
            // Based on total force, scale mass
 | 
						|
        }
 | 
						|
 | 
						|
        void UpdateBufferedCollision()
 | 
						|
        {
 | 
						|
            // We buffer collision over three updates
 | 
						|
            // Once from the actual collision to the first fixed update (m_ActiveCollision)
 | 
						|
            // Once for an entire fixedUpdate-to-fixedUpdate cycle (m_CollisionFrame)
 | 
						|
            // And once when a collision is lost - to correct against potential errors when a moving a parent transform
 | 
						|
            m_LastCollisionFrame = m_CollisionFrame;
 | 
						|
            m_CollisionFrame = m_ActiveCollision;
 | 
						|
            m_ActiveCollision = false;
 | 
						|
        }
 | 
						|
 | 
						|
        void UpdatePosition()
 | 
						|
        {
 | 
						|
            // Assume transform is synced to the rigid body position from late update
 | 
						|
            // Convert anchors to world space
 | 
						|
            var worldSourceAnchor = m_Rigidbody.position + Vector3.Scale((m_Rigidbody.rotation * m_Anchor), m_Transform.lossyScale);
 | 
						|
            var worldDestAnchor = m_ConnectedBody.TransformPoint(m_ConnectedAnchor);
 | 
						|
 | 
						|
            // Get the delta between these two positions
 | 
						|
            // Use this to calculate the target world position for the rigidbody
 | 
						|
            var positionDelta = worldDestAnchor - worldSourceAnchor;
 | 
						|
            var offset = positionDelta.magnitude;
 | 
						|
            var direction = positionDelta.normalized;
 | 
						|
            var targetPos = m_Rigidbody.position + positionDelta;
 | 
						|
 | 
						|
            // Convert the target and actual positions to world space
 | 
						|
            var worldPos = m_Rigidbody.position;
 | 
						|
 | 
						|
            if (offset > Mathf.Epsilon)
 | 
						|
            {
 | 
						|
                // Are we past the break distance?
 | 
						|
                if (offset > m_BreakDistance)
 | 
						|
                {
 | 
						|
                    // Warp back to the target
 | 
						|
                    m_Rigidbody.position = targetPos;
 | 
						|
                    m_Transform.position = targetPos;
 | 
						|
                    m_LastDirection = direction;
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                // Can we move back unobstructed? Do that
 | 
						|
                if (!m_CollisionFrame)
 | 
						|
                {
 | 
						|
                    if (m_Rigidbody.SweepTest(direction, out var hitInfo, offset))
 | 
						|
                    {
 | 
						|
                        targetPos = worldPos + (hitInfo.distance * direction);
 | 
						|
                        m_CollisionFrame = true;
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        // If there was a collision during the previous update, we let one more update cycle pass at the current location
 | 
						|
                        // This helps prevent teleporting through objects during scenarios where many things are playing into the object's position
 | 
						|
                        if (m_LastCollisionFrame)
 | 
						|
                        {
 | 
						|
                            // Compare last direction to this direction
 | 
						|
                            // If they are facing opposite directions, no worry of collision
 | 
						|
                            if (Vector3.Dot(direction, m_LastDirection) > 0)
 | 
						|
                            {
 | 
						|
                                targetPos = worldPos;
 | 
						|
                                m_AppliedForce = m_OldForce;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    m_Rigidbody.position = targetPos;
 | 
						|
                    m_Transform.position = targetPos;
 | 
						|
                }
 | 
						|
 | 
						|
                if (m_CollisionFrame)
 | 
						|
                {
 | 
						|
                    // Apply a constant force based on spring logic
 | 
						|
                    //Debug.Log(m_Rigidbody.velocity);
 | 
						|
                    var force = (m_BaseForce + offset * m_SpringForce);
 | 
						|
                    m_AppliedForce = force;
 | 
						|
                    m_Rigidbody.AddForce(direction * force, ForceMode.Impulse);
 | 
						|
                    m_LastPosition = m_Rigidbody.position;
 | 
						|
                }
 | 
						|
                m_LastDirection = direction;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        void UpdateRotation()
 | 
						|
        {
 | 
						|
            // Assume transform is synced to the rigid body position from late update
 | 
						|
            // Convert anchor rotations to world space
 | 
						|
            var worldSourceAnchor = m_Rigidbody.rotation * m_AnchorRotation;
 | 
						|
            var worldDestAnchor = m_ConnectedBody.rotation * m_ConnectedAnchorRotation;
 | 
						|
 | 
						|
            // Get the delta between these two positions
 | 
						|
            // Use this to calculate the target world position for the rigidbody
 | 
						|
            var rotationDelta = worldDestAnchor * Quaternion.Inverse(worldSourceAnchor);
 | 
						|
            var targetRotation = rotationDelta * m_Rigidbody.rotation;
 | 
						|
 | 
						|
            rotationDelta.ToAngleAxis(out var angleInDegrees, out var rotationAxis);
 | 
						|
            if (angleInDegrees > 180f)
 | 
						|
                angleInDegrees -= 360f;
 | 
						|
 | 
						|
            var angleOffset = Mathf.Abs(angleInDegrees);
 | 
						|
 | 
						|
            if (angleOffset > Mathf.Epsilon)
 | 
						|
            {
 | 
						|
                // Are we past the break distance?
 | 
						|
                if (angleOffset > m_BreakAngle)
 | 
						|
                {
 | 
						|
                    // Warp back to the target
 | 
						|
                    m_Rigidbody.rotation = targetRotation;
 | 
						|
                    m_Transform.rotation = targetRotation;
 | 
						|
                }
 | 
						|
 | 
						|
                // Can we move back unobstructed? Do that
 | 
						|
                if (!m_CollisionFrame)
 | 
						|
                {
 | 
						|
                    m_Rigidbody.rotation = targetRotation;
 | 
						|
                    m_Transform.rotation = targetRotation;
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    var force = ((angleInDegrees / 360f) * (m_BaseForce + m_SpringForce));
 | 
						|
                    m_Rigidbody.AddTorque(rotationAxis * force, ForceMode.Impulse);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        void OnCollisionEnter()
 | 
						|
        {
 | 
						|
            // While in a collision state, we change state so that the regular transform/visual updates are locked to the fixed update rate
 | 
						|
            m_ActiveCollision = true;
 | 
						|
            m_CollisionFrame = true;
 | 
						|
        }
 | 
						|
 | 
						|
        void OnCollisionStay()
 | 
						|
        {
 | 
						|
            m_ActiveCollision = true;
 | 
						|
            m_CollisionFrame = true;
 | 
						|
        }
 | 
						|
 | 
						|
        void OnCollisionExit()
 | 
						|
        {
 | 
						|
            if (!enabled)
 | 
						|
                return;
 | 
						|
 | 
						|
            // When exiting collision, we lock to the last known rigidbody position.
 | 
						|
            // This is because we can end up putting fairly strong forces on this object
 | 
						|
            // If a parent or pure transform change invalidates the collision these forces can cause an object to move through things
 | 
						|
            m_Rigidbody.velocity = Vector3.zero;
 | 
						|
            m_Rigidbody.position = m_LastPosition;
 | 
						|
            transform.position = m_LastPosition;
 | 
						|
        }
 | 
						|
 | 
						|
        public void OnBeforeSerialize()
 | 
						|
        {
 | 
						|
        }
 | 
						|
 | 
						|
        public void OnAfterDeserialize()
 | 
						|
        {
 | 
						|
            m_AnchorRotation.eulerAngles = m_AnchorAngle;
 | 
						|
            m_ConnectedAnchorRotation.eulerAngles = m_ConnectedAnchorAngle;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |