mirror of
https://udrimavric.com/MAVRIC/Stratasys-450mc-VR.git
synced 2025-01-23 15:48:33 -05:00
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;
|
|
}
|
|
}
|
|
}
|