// Rigidbody Follow|Utilities|90120 namespace VRTK { using UnityEngine; /// /// Changes one GameObject's rigidbody to follow another GameObject's rigidbody. /// [AddComponentMenu("VRTK/Scripts/Utilities/Object Follow/VRTK_RigidbodyFollow")] public class VRTK_RigidbodyFollow : VRTK_ObjectFollow { /// /// Specifies how to position and rotate the rigidbody. /// public enum MovementOption { /// /// Use Rigidbody.position and Rigidbody.rotation. /// Set, /// /// Use Rigidbody.MovePosition and Rigidbody.MoveRotation. /// Move, /// /// Use Rigidbody.AddForce(Vector3) and Rigidbody.AddTorque(Vector3). /// Add, /// /// Use velocity and angular velocity with MoveTowards. /// Track } [Header("Follow Settings")] [Tooltip("Specifies how to position and rotate the rigidbody.")] public MovementOption movementOption = MovementOption.Set; [Header("Track Movement Settings")] [Tooltip("The maximum distance the tracked `Game Object To Change` Rigidbody can be from the `Game Object To Follow` Rigidbody before the position is forcibly set to match the position.")] public float trackMaxDistance = 0.25f; protected Rigidbody rigidbodyToFollow; protected Rigidbody rigidbodyToChange; protected float maxDistanceDelta = 10f; /// /// Follow `gameObjectToFollow` using the current settings. /// public override void Follow() { CacheRigidbodies(); base.Follow(); } protected virtual void OnDisable() { rigidbodyToFollow = null; rigidbodyToChange = null; } protected virtual void FixedUpdate() { Follow(); } protected virtual void CacheRigidbodies() { if (gameObjectToFollow == null || gameObjectToChange == null || (rigidbodyToFollow != null && rigidbodyToChange != null)) { return; } rigidbodyToFollow = gameObjectToFollow.GetComponent(); rigidbodyToChange = gameObjectToChange.GetComponent(); } protected override Vector3 GetPositionToFollow() { return (rigidbodyToFollow != null ? rigidbodyToFollow.position : Vector3.zero); } protected override Quaternion GetRotationToFollow() { return (rigidbodyToFollow != null ? rigidbodyToFollow.rotation : Quaternion.identity); } protected override Vector3 GetScaleToFollow() { return (rigidbodyToFollow != null ? rigidbodyToFollow.transform.localScale : Vector3.zero); } protected override void SetPositionOnGameObject(Vector3 newPosition) { switch (movementOption) { case MovementOption.Set: rigidbodyToChange.position = newPosition; break; case MovementOption.Move: rigidbodyToChange.MovePosition(newPosition); break; case MovementOption.Add: // TODO: Test if this is correct rigidbodyToChange.AddForce(newPosition - rigidbodyToChange.position); break; case MovementOption.Track: TrackPosition(newPosition); break; } } protected override void SetRotationOnGameObject(Quaternion newRotation) { switch (movementOption) { case MovementOption.Set: rigidbodyToChange.rotation = newRotation; break; case MovementOption.Move: rigidbodyToChange.MoveRotation(newRotation); break; case MovementOption.Add: // TODO: Test if this is correct rigidbodyToChange.AddTorque(newRotation * Quaternion.Inverse(rigidbodyToChange.rotation).eulerAngles); break; case MovementOption.Track: TrackRotation(newRotation); break; } } protected virtual void TrackPosition(Vector3 newPosition) { if (rigidbodyToFollow == null) { return; } if (Vector3.Distance(rigidbodyToChange.position, rigidbodyToFollow.position) > trackMaxDistance) { rigidbodyToChange.position = rigidbodyToFollow.position; rigidbodyToChange.rotation = rigidbodyToFollow.rotation; } float trackVelocityLimit = float.PositiveInfinity; Vector3 positionDelta = newPosition - rigidbodyToChange.position; Vector3 velocityTarget = positionDelta / Time.fixedDeltaTime; Vector3 calculatedVelocity = Vector3.MoveTowards(rigidbodyToChange.velocity, velocityTarget, maxDistanceDelta); if (trackVelocityLimit == float.PositiveInfinity || calculatedVelocity.sqrMagnitude < trackVelocityLimit) { rigidbodyToChange.velocity = calculatedVelocity; } } protected virtual void TrackRotation(Quaternion newRotation) { if (rigidbodyToFollow == null) { return; } float trackAngularVelocityLimit = float.PositiveInfinity; Quaternion rotationDelta = newRotation * Quaternion.Inverse(rigidbodyToChange.rotation); float angle; Vector3 axis; rotationDelta.ToAngleAxis(out angle, out axis); angle = ((angle > 180) ? angle -= 360 : angle); if (angle != 0) { Vector3 angularTarget = angle * axis; Vector3 calculatedAngularVelocity = Vector3.MoveTowards(rigidbodyToChange.angularVelocity, angularTarget, maxDistanceDelta); if (trackAngularVelocityLimit == float.PositiveInfinity || calculatedAngularVelocity.sqrMagnitude < trackAngularVelocityLimit) { rigidbodyToChange.angularVelocity = calculatedAngularVelocity; } } } } }