namespace VRTK.Prefabs.Interactions.Controllables
{
using UnityEngine;
using Malimbe.BehaviourStateRequirementMethod;
using Zinnia.Extension;
using Zinnia.Data.Type;
///
/// The basis to drive a control in a rotational angle.
///
public abstract class RotationalDrive : Drive
{
///
/// The previous rotation angle of the control.
///
protected float PreviousActualAngle => previousActualRotation[(int)Facade.DriveAxis];
///
/// The current rotation angle of the control.
///
protected float CurrentActualAngle => currentActualRotation[(int)Facade.DriveAxis];
///
/// The target angle for the control to reach.
///
protected float ActualTargetAngle => Mathf.Lerp(DriveLimits.minimum, DriveLimits.maximum, Facade.TargetValue);
///
/// The total number of degrees in a circle.
///
protected const float circleDegrees = 360f;
///
/// The angle range that defines the upper right quadrant of a circle.
///
protected static readonly FloatRange circleUpperRightQuadrant = new FloatRange(-1f, 90f);
///
/// The angle range that defines the upper left quadrant of a circle.
///
protected static readonly FloatRange circleUpperLeftQuadrant = new FloatRange(270f, 360f);
///
/// The representation of the previous frame rotation in meaningful values and not limited to 0f to 360f.
///
protected float previousPseudoRotation;
///
/// The representation of the current frame rotation in meaningful values and not limited to 0f to 360f.
///
protected float currentPseudoRotation;
///
/// The representation of the rotational velocity.
///
protected float pseudoAngularVelocity;
///
/// The multiplier used to determine how many complete revolutions the drive has performed.
///
protected float rotationMultiplier;
///
/// The previous actual rotational value of the drive.
///
protected Vector3 previousActualRotation;
///
/// The current actual rotational value of the drive.
///
protected Vector3 currentActualRotation;
///
/// Calculates the location of the rotational hinge for the drive.
///
/// The new local space for the hinge point.
public abstract void CalculateHingeLocation(Vector3 newHingeLocation);
///
/// Attempts to apply the existing angular velocity back on to the rotation of the drive.
///
/// The amount to multiply the angular velocity to be applied by.
public abstract void ApplyExistingAngularVelocity(float multiplier = 1f);
///
[RequiresBehaviourState]
public override void Process()
{
base.Process();
previousActualRotation = currentActualRotation;
currentActualRotation = GetSimpleEulerAngles();
CalculateRotationMultiplier();
AttemptApplyLimits();
currentPseudoRotation = CurrentActualAngle + (circleDegrees * rotationMultiplier);
pseudoAngularVelocity = !currentPseudoRotation.ApproxEquals(previousPseudoRotation) ? previousPseudoRotation - currentPseudoRotation : pseudoAngularVelocity;
previousPseudoRotation = currentPseudoRotation;
float autoDriveTargetVelocity = CalculateAutoDriveVelocity();
ProcessAutoDrive(autoDriveTargetVelocity);
MatchActualTargetAngle(autoDriveTargetVelocity);
}
///
/// Automatically controls the drive to the target rotation at the given speed.
///
/// The speed to automatically rotate the drive.
protected abstract void ProcessAutoDrive(float driveSpeed);
///
protected override void SetUpInternals()
{
ConfigureAutoDrive(Facade.MoveToTargetValue);
CalculateHingeLocation(Facade.HingeLocation);
}
///
protected override FloatRange CalculateDriveLimits(RotationalDriveFacade facade)
{
return facade.DriveLimit;
}
///
[RequiresBehaviourState]
protected override float CalculateValue(DriveAxis.Axis driveAxis, FloatRange limits)
{
return Mathf.Clamp(currentPseudoRotation, limits.minimum, limits.maximum);
}
///
/// Attempts to retrieve a simple x, y or z euler angle from the utilizing any other axis rotation.
///
/// The actual axis angle from 0f to 360f.
protected virtual Vector3 GetSimpleEulerAngles()
{
Vector3 currentEulerAngle = GetDriveTransform().localEulerAngles;
if (Facade.DriveAxis == DriveAxis.Axis.XAxis && !currentEulerAngle.y.ApproxEquals(0f, 1f))
{
currentEulerAngle.x = currentEulerAngle.y - (currentEulerAngle.x > (circleDegrees * 0.5f) ? currentEulerAngle.x - circleDegrees : currentEulerAngle.x);
}
return currentEulerAngle;
}
///
/// Calculates a multiplier based on the direction the rotation is traveling.
///
/// The multiplier that represents the direction.
protected virtual float CalculateDirectionMultiplier()
{
float actualAngle = ActualTargetAngle;
if (actualAngle.ApproxEquals(currentPseudoRotation, TargetValueReachedThreshold))
{
return 0f;
}
return actualAngle > currentPseudoRotation ? 1f : -1f;
}
///
/// Attempts to apply the limits on the drive.
///
protected virtual void AttemptApplyLimits()
{
ApplyLimits();
}
///
/// Applies the limits on the drive rotation.
///
/// Whether the limits have been applied.
protected virtual bool ApplyLimits()
{
if (currentPseudoRotation < DriveLimits.minimum - TargetValueReachedThreshold)
{
GetDriveTransform().localRotation = Quaternion.Euler(-AxisDirection * DriveLimits.minimum);
return true;
}
else if (currentPseudoRotation > DriveLimits.maximum + TargetValueReachedThreshold)
{
GetDriveTransform().localRotation = Quaternion.Euler(-AxisDirection * DriveLimits.maximum);
return true;
}
return false;
}
///
/// Calculates the velocity that the drive should automatically rotate the control with.
///
/// The velocity to drive the control automatically with.
protected virtual float CalculateAutoDriveVelocity()
{
return (Facade.MoveToTargetValue && !currentPseudoRotation.ApproxEquals(ActualTargetAngle, TargetValueReachedThreshold) ? Facade.DriveSpeed : 0f) * CalculateDirectionMultiplier();
}
///
/// Calculates the current rotation the control is at.
///
protected virtual void CalculateRotationMultiplier()
{
if (circleUpperLeftQuadrant.Contains(PreviousActualAngle) && circleUpperRightQuadrant.Contains(CurrentActualAngle))
{
rotationMultiplier++;
}
else if (circleUpperRightQuadrant.Contains(PreviousActualAngle) && circleUpperLeftQuadrant.Contains(CurrentActualAngle))
{
rotationMultiplier--;
}
}
///
/// Attempts to match the target angle to set the control at the correct angle if the drive is no longer moving.
///
/// The speed the drive is automatically rotating at.
protected virtual void MatchActualTargetAngle(float driveSpeed)
{
if (Facade.MoveToTargetValue && driveSpeed.ApproxEquals(0f))
{
GetDriveTransform().localRotation = Quaternion.Euler(-AxisDirection * ActualTargetAngle);
}
}
}
}