// Wheel|Controls3D|100062
|
|
namespace VRTK
|
|
{
|
|
using UnityEngine;
|
|
using GrabAttachMechanics;
|
|
|
|
/// <summary>
|
|
/// Attaching the script to a game object will allow the user to interact with it as if it were a spinnable wheel.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The script will instantiate the required Rigidbody and Interactable components automatically in case they do not exist yet.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// `VRTK/Examples/025_Controls_Overview` has a collection of wheels that can be rotated by grabbing with the controller and then rotating the controller in the desired direction.
|
|
/// </example>
|
|
[AddComponentMenu("VRTK/Scripts/Controls/3D/VRTK_Wheel")]
|
|
[System.Obsolete("`VRTK.VRTK_Wheel` has been deprecated and can be recreated with `VRTK.Controllables.ArtificialBased.VRTK_ArtificialRotator`. This script will be removed in a future version of VRTK.")]
|
|
public class VRTK_Wheel : VRTK_Control
|
|
{
|
|
/// <summary>
|
|
/// The grab attach mechanic to use.
|
|
/// </summary>
|
|
public enum GrabTypes
|
|
{
|
|
/// <summary>
|
|
/// Utilise the track object grab mechanic.
|
|
/// </summary>
|
|
TrackObject,
|
|
/// <summary>
|
|
/// Utilise the rotator track grab mechanic.
|
|
/// </summary>
|
|
RotatorTrack
|
|
}
|
|
|
|
[Tooltip("An optional game object to which the wheel will be connected. If the game object moves the wheel will follow along.")]
|
|
public GameObject connectedTo;
|
|
[Tooltip("The grab attach mechanic to use. Track Object allows for rotations of the controller, Rotator Track allows for grabbing the wheel and spinning it.")]
|
|
public GrabTypes grabType = GrabTypes.TrackObject;
|
|
[Tooltip("The maximum distance the grabbing controller is away from the wheel before it is automatically released.")]
|
|
public float detatchDistance = 0.5f;
|
|
[Tooltip("The minimum value the wheel can be set to.")]
|
|
public float minimumValue = 0f;
|
|
[Tooltip("The maximum value the wheel can be set to.")]
|
|
public float maximumValue = 10f;
|
|
[Tooltip("The increments in which values can change.")]
|
|
public float stepSize = 1f;
|
|
[Tooltip("If this is checked then when the wheel is released, it will snap to the step rotation.")]
|
|
public bool snapToStep = false;
|
|
[Tooltip("The amount of friction the wheel will have when it is grabbed.")]
|
|
public float grabbedFriction = 25f;
|
|
[Tooltip("The amount of friction the wheel will have when it is released.")]
|
|
public float releasedFriction = 10f;
|
|
[Range(0f, 359f)]
|
|
[Tooltip("The maximum angle the wheel has to be turned to reach it's maximum value.")]
|
|
public float maxAngle = 359f;
|
|
[Tooltip("If this is checked then the wheel cannot be turned beyond the minimum and maximum value.")]
|
|
public bool lockAtLimits = false;
|
|
|
|
protected float angularVelocityLimit = 150f;
|
|
protected float springStrengthValue = 150f;
|
|
protected float springDamperValue = 5f;
|
|
protected Quaternion initialLocalRotation;
|
|
protected Rigidbody wheelRigidbody;
|
|
protected HingeJoint wheelHinge;
|
|
protected bool wheelHingeCreated = false;
|
|
protected bool initialValueCalculated = false;
|
|
protected float springAngle;
|
|
|
|
protected override void InitRequiredComponents()
|
|
{
|
|
initialLocalRotation = transform.localRotation;
|
|
InitWheel();
|
|
}
|
|
|
|
protected override bool DetectSetup()
|
|
{
|
|
if (wheelHingeCreated)
|
|
{
|
|
wheelHinge.anchor = Vector3.up;
|
|
wheelHinge.axis = Vector3.up;
|
|
|
|
if (connectedTo)
|
|
{
|
|
wheelHinge.connectedBody = connectedTo.GetComponent<Rigidbody>();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected override ControlValueRange RegisterValueRange()
|
|
{
|
|
return new ControlValueRange()
|
|
{
|
|
controlMin = minimumValue,
|
|
controlMax = maximumValue
|
|
};
|
|
}
|
|
|
|
protected override void HandleUpdate()
|
|
{
|
|
CalculateValue();
|
|
if (lockAtLimits && !initialValueCalculated)
|
|
{
|
|
transform.localRotation = initialLocalRotation;
|
|
initialValueCalculated = true;
|
|
}
|
|
}
|
|
|
|
protected virtual void InitWheel()
|
|
{
|
|
SetupRigidbody();
|
|
SetupHinge();
|
|
SetupInteractableObject();
|
|
}
|
|
|
|
protected virtual void SetupRigidbody()
|
|
{
|
|
wheelRigidbody = GetComponent<Rigidbody>();
|
|
if (wheelRigidbody == null)
|
|
{
|
|
wheelRigidbody = gameObject.AddComponent<Rigidbody>();
|
|
wheelRigidbody.angularDrag = releasedFriction;
|
|
}
|
|
wheelRigidbody.isKinematic = false;
|
|
wheelRigidbody.useGravity = false;
|
|
|
|
if (connectedTo)
|
|
{
|
|
Rigidbody connectedToRigidbody = connectedTo.GetComponent<Rigidbody>();
|
|
if (connectedToRigidbody == null)
|
|
{
|
|
connectedToRigidbody = connectedTo.AddComponent<Rigidbody>();
|
|
connectedToRigidbody.useGravity = false;
|
|
connectedToRigidbody.isKinematic = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void SetupHinge()
|
|
{
|
|
wheelHinge = GetComponent<HingeJoint>();
|
|
if (wheelHinge == null)
|
|
{
|
|
wheelHinge = gameObject.AddComponent<HingeJoint>();
|
|
wheelHingeCreated = true;
|
|
}
|
|
|
|
SetupHingeRestrictions();
|
|
}
|
|
|
|
protected virtual void SetupHingeRestrictions()
|
|
{
|
|
float minJointLimit = 0f;
|
|
float maxJointLimit = maxAngle;
|
|
float limitOffset = maxAngle - 180f;
|
|
|
|
if (limitOffset > 0f)
|
|
{
|
|
minJointLimit -= limitOffset;
|
|
maxJointLimit = 180f;
|
|
}
|
|
|
|
if (lockAtLimits)
|
|
{
|
|
wheelHinge.useLimits = true;
|
|
JointLimits wheelLimits = new JointLimits();
|
|
wheelLimits.min = minJointLimit;
|
|
wheelLimits.max = maxJointLimit;
|
|
wheelHinge.limits = wheelLimits;
|
|
Vector3 adjustedLimitsAngle = transform.localEulerAngles;
|
|
|
|
switch (Mathf.RoundToInt(initialLocalRotation.eulerAngles.z))
|
|
{
|
|
case 0:
|
|
adjustedLimitsAngle = new Vector3(transform.localEulerAngles.x, transform.localEulerAngles.y - minJointLimit, transform.localEulerAngles.z);
|
|
break;
|
|
case 90:
|
|
adjustedLimitsAngle = new Vector3(transform.localEulerAngles.x + minJointLimit, transform.localEulerAngles.y, transform.localEulerAngles.z);
|
|
break;
|
|
case 180:
|
|
adjustedLimitsAngle = new Vector3(transform.localEulerAngles.x, transform.localEulerAngles.y + minJointLimit, transform.localEulerAngles.z);
|
|
break;
|
|
}
|
|
transform.localEulerAngles = adjustedLimitsAngle;
|
|
initialValueCalculated = false;
|
|
}
|
|
}
|
|
|
|
protected virtual void ConfigureHingeSpring()
|
|
{
|
|
JointSpring snapSpring = new JointSpring();
|
|
snapSpring.spring = springStrengthValue;
|
|
snapSpring.damper = springDamperValue;
|
|
snapSpring.targetPosition = springAngle + wheelHinge.limits.min;
|
|
wheelHinge.spring = snapSpring;
|
|
}
|
|
|
|
protected virtual void SetupInteractableObject()
|
|
{
|
|
VRTK_InteractableObject wheelInteractableObject = GetComponent<VRTK_InteractableObject>();
|
|
if (wheelInteractableObject == null)
|
|
{
|
|
wheelInteractableObject = gameObject.AddComponent<VRTK_InteractableObject>();
|
|
}
|
|
wheelInteractableObject.isGrabbable = true;
|
|
|
|
VRTK_TrackObjectGrabAttach attachMechanic;
|
|
|
|
if (grabType == GrabTypes.TrackObject)
|
|
{
|
|
attachMechanic = gameObject.AddComponent<VRTK_TrackObjectGrabAttach>();
|
|
if (lockAtLimits)
|
|
{
|
|
attachMechanic.velocityLimit = 0f;
|
|
attachMechanic.angularVelocityLimit = angularVelocityLimit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
attachMechanic = gameObject.AddComponent<VRTK_RotatorTrackGrabAttach>();
|
|
}
|
|
|
|
attachMechanic.precisionGrab = true;
|
|
attachMechanic.detachDistance = detatchDistance;
|
|
|
|
wheelInteractableObject.grabAttachMechanicScript = attachMechanic;
|
|
|
|
wheelInteractableObject.secondaryGrabActionScript = gameObject.AddComponent<SecondaryControllerGrabActions.VRTK_SwapControllerGrabAction>();
|
|
wheelInteractableObject.stayGrabbedOnTeleport = false;
|
|
|
|
wheelInteractableObject.InteractableObjectGrabbed += WheelInteractableObjectGrabbed;
|
|
wheelInteractableObject.InteractableObjectUngrabbed += WheelInteractableObjectUngrabbed;
|
|
}
|
|
|
|
protected virtual void WheelInteractableObjectGrabbed(object sender, InteractableObjectEventArgs e)
|
|
{
|
|
wheelRigidbody.angularDrag = grabbedFriction;
|
|
wheelHinge.useSpring = false;
|
|
}
|
|
|
|
protected virtual void WheelInteractableObjectUngrabbed(object sender, InteractableObjectEventArgs e)
|
|
{
|
|
wheelRigidbody.angularDrag = releasedFriction;
|
|
if (snapToStep)
|
|
{
|
|
wheelHinge.useSpring = true;
|
|
ConfigureHingeSpring();
|
|
}
|
|
}
|
|
|
|
protected virtual void CalculateValue()
|
|
{
|
|
ControlValueRange controlValueRange = RegisterValueRange();
|
|
float angle;
|
|
Vector3 axis;
|
|
|
|
Quaternion rotationDelta = transform.localRotation * Quaternion.Inverse(initialLocalRotation);
|
|
rotationDelta.ToAngleAxis(out angle, out axis);
|
|
|
|
float calculatedValue = Mathf.Round((controlValueRange.controlMin + Mathf.Clamp01(angle / maxAngle) * (controlValueRange.controlMax - controlValueRange.controlMin)) / stepSize) * stepSize;
|
|
|
|
float flatValue = calculatedValue - controlValueRange.controlMin;
|
|
float controlRange = controlValueRange.controlMax - controlValueRange.controlMin;
|
|
springAngle = (flatValue / controlRange) * maxAngle;
|
|
|
|
value = calculatedValue;
|
|
}
|
|
}
|
|
}
|