namespace VRTK.Prefabs.Interactions.Controllables
{
using UnityEngine;
using Malimbe.PropertySerializationAttribute;
using Malimbe.XmlDocumentationAttribute;
using Malimbe.BehaviourStateRequirementMethod;
using Zinnia.Process;
using Zinnia.Extension;
using Zinnia.Data.Type;
using Zinnia.Data.Attribute;
///
/// The basis for a mechanism to drive motion on a control.
///
/// The to be used with the drive.
/// The actual concrete implementation of the drive being used.
public abstract class Drive : MonoBehaviour, IProcessable where TFacade : DriveFacade where TSelf : Drive
{
#region Facade Settings
///
/// The public interface facade.
///
[Serialized]
[field: Header("Facade Settings"), DocumentedByXml, Restricted]
public TFacade Facade { get; protected set; }
#endregion
#region Threshold Settings
///
/// The threshold that the current normalized value of the control can be within to consider the target value has been reached.
///
[Serialized]
[field: Header("Threshold Settings"), DocumentedByXml]
public float TargetValueReachedThreshold { get; set; } = 0.025f;
#endregion
///
/// The current raw value for the drive control.
///
public float Value => CalculateValue(Facade.DriveAxis, DriveLimits);
///
/// The current normalized value for the drive control between the set limits.
///
public float NormalizedValue => Mathf.InverseLerp(DriveLimits.minimum, DriveLimits.maximum, Value);
///
/// The current step value for the drive control.
///
public float StepValue => CalculateStepValue(Facade);
///
/// The current normalized step value for the drive control between the set step range.
///
public float NormalizedStepValue => Mathf.InverseLerp(Facade.StepRange.minimum, Facade.StepRange.maximum, StepValue);
///
/// The calculated direction for the drive axis.
///
public Vector3 AxisDirection { get; protected set; }
///
/// The calculated limits for the drive.
///
public FloatRange DriveLimits { get; protected set; }
///
/// The previous state of .
///
protected float previousValue = float.MaxValue;
///
/// The previous state of .
///
protected float previousStepValue = float.MaxValue;
///
/// The previous state of whether the target value has been reached.
///
protected bool previousTargetValueReached;
///
/// Whether the control is moving or not.
///
protected bool isMoving;
///
/// Sets up the drive mechanism.
///
public virtual void SetUp()
{
SetUpInternals();
DriveLimits = CalculateDriveLimits(Facade);
AxisDirection = CalculateDriveAxis(Facade.DriveAxis);
ProcessDriveSpeed(Facade.DriveSpeed, Facade.MoveToTargetValue);
SetTargetValue(Facade.TargetValue);
}
///
/// Processes the value changes and emits the appropriate events.
///
[RequiresBehaviourState]
public virtual void Process()
{
if (!Value.ApproxEquals(previousValue))
{
if (!isMoving && previousValue < float.MaxValue)
{
EmitStartedMoving();
isMoving = true;
}
previousValue = Value;
EmitValueChanged();
EmitNormalizedValueChanged();
}
else
{
if (isMoving)
{
EmitStoppedMoving();
isMoving = false;
}
}
if (!StepValue.ApproxEquals(previousStepValue))
{
previousStepValue = StepValue;
EmitStepValueChanged();
}
float targetValue = GetTargetValue();
bool targetValueReached = NormalizedValue.ApproxEquals(targetValue, TargetValueReachedThreshold);
bool shouldEmitEvent = !previousTargetValueReached && targetValueReached;
previousTargetValueReached = targetValueReached;
if (CanMoveToTargetValue() && shouldEmitEvent)
{
EmitTargetValueReached();
}
}
///
/// Processes the speed in which the drive can affect the control.
///
/// The speed to drive the control at.
/// Whether to allow the drive to automatically move the control to the desired target value.
public virtual void ProcessDriveSpeed(float driveSpeed, bool moveToTargetValue) { }
///
/// Sets the target value of the drive to the given normalized value.
///
/// The normalized value to set the Target Value to.
[RequiresBehaviourState]
public virtual void SetTargetValue(float normalizedValue)
{
SetDriveTargetValue(AxisDirection * Mathf.Lerp(DriveLimits.minimum, DriveLimits.maximum, Mathf.Clamp01(normalizedValue)));
}
///
/// Calculates the axis to drive the control on.
///
/// The desired world axis.
/// The direction of the drive axis.
public virtual Vector3 CalculateDriveAxis(DriveAxis.Axis driveAxis)
{
return driveAxis.GetAxisDirection(true);
}
///
/// Configures the ability to automatically drive the control.
///
/// Whether the drive can automatically drive the control.
public virtual void ConfigureAutoDrive(bool autoDrive) { }
///
/// Calculates the current value of the control.
///
/// The axis the drive is operating on.
/// The limits of the drive.
/// The calculated value.
protected abstract float CalculateValue(DriveAxis.Axis axis, FloatRange limits);
///
/// Calculates the limits of the drive.
///
/// The facade containing the data for the calculation.
/// The minimum and maximum local space limit the drive can reach.
protected abstract FloatRange CalculateDriveLimits(TFacade facade);
///
/// Gets the that the drive is operating on.
///
/// The drive .
protected abstract Transform GetDriveTransform();
protected virtual void OnEnable()
{
SetUp();
}
///
/// Performs any required internal setup.
///
protected virtual void SetUpInternals() { }
///
/// Sets the target value of the drive.
///
/// The value to set the drive target to.
protected virtual void SetDriveTargetValue(Vector3 targetValue) { }
///
/// Gets the drive control target value.
///
/// The target value specified in the facade.
protected virtual float GetTargetValue()
{
return Facade.TargetValue;
}
///
/// Determines whether the drive can move the control to the target value.
///
/// Whether the drive can automatically move to the target value specified in the facade.
protected virtual bool CanMoveToTargetValue()
{
return Facade.MoveToTargetValue;
}
///
/// Calculates the current step value of the control.
///
/// The facade containing the data for the calculation.
/// The calculated step value.
protected virtual float CalculateStepValue(TFacade facade)
{
return Mathf.Round(Mathf.Lerp(facade.StepRange.minimum / facade.StepIncrement, facade.StepRange.maximum / facade.StepIncrement, NormalizedValue));
}
///
/// Emits the ValueChanged event.
///
protected virtual void EmitValueChanged()
{
Facade.ValueChanged?.Invoke(Value);
}
///
/// Emits the NormalizedValueChanged event.
///
protected virtual void EmitNormalizedValueChanged()
{
Facade.NormalizedValueChanged?.Invoke(NormalizedValue);
}
///
/// Emits the StepValueChanged event.
///
protected virtual void EmitStepValueChanged()
{
Facade.StepValueChanged?.Invoke(StepValue);
}
///
/// Emits the TargetValueReached event.
///
protected virtual void EmitTargetValueReached()
{
Facade.TargetValueReached?.Invoke(NormalizedValue);
}
///
/// Emits the StartedMoving event.
///
protected virtual void EmitStartedMoving()
{
Facade.StartedMoving?.Invoke(0f);
}
///
/// Emits the StoppedMoving event.
///
protected virtual void EmitStoppedMoving()
{
Facade.StoppedMoving?.Invoke(0f);
}
}
}