namespace VRTK.Prefabs.Locomotion.Movement.SpatialManipulation
{
using UnityEngine;
using Malimbe.XmlDocumentationAttribute;
using Malimbe.PropertySerializationAttribute;
///
/// Manipulates the rotation of the given Target based on the forward/backward motion between the two sources.
///
public class RotationManipulator : SpatialManipulator
{
#region Rotation Settings
///
/// Whether to rotate around the offset point.
///
[Serialized]
[field: Header("Rotation Settings"), DocumentedByXml]
public bool RotateAroundOffset { get; set; } = true;
#endregion
///
/// The previous frame rotation angle.
///
protected Vector2 previousRotationAngle = Vector2.zero;
///
/// The previous frame rotation data.
///
protected Quaternion previousRotation;
///
/// Processes the rotation manipulation.
///
public override void Process()
{
if (ActivationAction == null || !ActivationAction.Value)
{
wasActivated = false;
return;
}
bool primaryValid = IsObjectValid(PrimarySource);
bool secondaryValid = IsObjectValid(SecondarySource);
GameObject singleSource = primaryValid && !secondaryValid ? PrimarySource : !primaryValid && secondaryValid ? SecondarySource : null;
if (!wasActivated)
{
wasActivated = true;
previousRotationAngle = GetRotationDelta(PrimarySource, SecondarySource);
previousRotation = singleSource != null ? singleSource.transform.localRotation : Quaternion.identity;
}
if (singleSource == null)
{
Vector2 currentRotationAngle = GetRotationDelta(PrimarySource, SecondarySource);
float newAngle = Vector2.Angle(currentRotationAngle, previousRotationAngle) * Mathf.Sign(Vector3.Cross(currentRotationAngle, previousRotationAngle).z);
RotateByAngle(newAngle);
previousRotationAngle = currentRotationAngle;
}
else
{
Quaternion currentRotationAngle = singleSource.transform.localRotation;
Quaternion rotationDelta = currentRotationAngle * Quaternion.Inverse(previousRotation);
float theta = 2.0f * Mathf.Acos(Mathf.Clamp(rotationDelta.w, -1.0f, 1.0f));
if (theta > Mathf.PI)
{
theta -= 2.0f * Mathf.PI;
}
Vector3 angularVelocity = new Vector3(rotationDelta.x, rotationDelta.y, rotationDelta.z);
if (angularVelocity.sqrMagnitude > 0.0f)
{
angularVelocity = theta * (1.0f / Time.deltaTime) * angularVelocity.normalized;
}
RotateByAngle(angularVelocity.y);
previousRotation = currentRotationAngle;
}
}
///
/// Gets the delta of the rotation between the given sources from the previous frame to the current frame.
///
/// The primary source.
/// The secondary source.
/// The rotation delta.
protected virtual Vector2 GetRotationDelta(GameObject primary, GameObject secondary)
{
return new Vector2((GetLocalPosition(primary) - GetLocalPosition(secondary)).x, (GetLocalPosition(primary) - GetLocalPosition(secondary)).z);
}
///
/// Rotates the by the given angle.
///
/// The angle to rotate by.
protected virtual void RotateByAngle(float angle)
{
ApplyRotation(Target, RotateAroundOffset ? Offset : null, angle * Multiplier);
}
///
/// Applies a rotation to the given target.
///
/// The target to rotate.
/// The optional offset to rotate around.
/// The angle to rotate by.
protected virtual void ApplyRotation(GameObject rotationTarget, GameObject rotationOffset, float rotationAngle)
{
if (Mathf.Abs(rotationAngle) >= ActivationThreshold)
{
if (IsObjectValid(rotationOffset))
{
rotationTarget.transform.RotateAround(rotationOffset.transform.position, Vector3.up, rotationAngle);
}
else
{
rotationTarget.transform.Rotate(Vector3.up * rotationAngle);
}
}
}
}
}