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); } } }