// Wheel|Controls3D|100062 namespace VRTK { using UnityEngine; using GrabAttachMechanics; /// /// Attaching the script to a game object will allow the user to interact with it as if it were a spinnable wheel. /// /// /// The script will instantiate the required Rigidbody and Interactable components automatically in case they do not exist yet. /// /// /// `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. /// [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 { /// /// The grab attach mechanic to use. /// public enum GrabTypes { /// /// Utilise the track object grab mechanic. /// TrackObject, /// /// Utilise the rotator track grab mechanic. /// 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(); } } 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(); if (wheelRigidbody == null) { wheelRigidbody = gameObject.AddComponent(); wheelRigidbody.angularDrag = releasedFriction; } wheelRigidbody.isKinematic = false; wheelRigidbody.useGravity = false; if (connectedTo) { Rigidbody connectedToRigidbody = connectedTo.GetComponent(); if (connectedToRigidbody == null) { connectedToRigidbody = connectedTo.AddComponent(); connectedToRigidbody.useGravity = false; connectedToRigidbody.isKinematic = true; } } } protected virtual void SetupHinge() { wheelHinge = GetComponent(); if (wheelHinge == null) { wheelHinge = gameObject.AddComponent(); 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(); if (wheelInteractableObject == null) { wheelInteractableObject = gameObject.AddComponent(); } wheelInteractableObject.isGrabbable = true; VRTK_TrackObjectGrabAttach attachMechanic; if (grabType == GrabTypes.TrackObject) { attachMechanic = gameObject.AddComponent(); if (lockAtLimits) { attachMechanic.velocityLimit = 0f; attachMechanic.angularVelocityLimit = angularVelocityLimit; } } else { attachMechanic = gameObject.AddComponent(); } attachMechanic.precisionGrab = true; attachMechanic.detachDistance = detatchDistance; wheelInteractableObject.grabAttachMechanicScript = attachMechanic; wheelInteractableObject.secondaryGrabActionScript = gameObject.AddComponent(); 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; } } }