// Slider|Controls3D|100090 namespace VRTK { using UnityEngine; /// /// Attaching the script to a game object will allow the user to interact with it as if it were a horizontal or vertical slider. The direction can be freely set and auto-detection is supported. /// /// /// 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 selection of sliders at various angles with different step values to demonstrate their usage. /// [AddComponentMenu("VRTK/Scripts/Controls/3D/VRTK_Slider")] [System.Obsolete("`VRTK.VRTK_Drawer` has been deprecated and can be recreated with `VRTK.Controllables.PhysicsBased.VRTK_PhysicsSlider`. This script will be removed in a future version of VRTK.")] public class VRTK_Slider : VRTK_Control { [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 axis on which the slider should move. All other axis will be frozen.")] public Direction direction = Direction.autodetect; [Tooltip("The collider to specify the minimum limit of the slider.")] public Collider minimumLimit; [Tooltip("The collider to specify the maximum limit of the slider.")] public Collider maximumLimit; [Tooltip("The minimum value of the slider.")] public float minimumValue = 0f; [Tooltip("The maximum value of the slider.")] public float maximumValue = 100f; [Tooltip("The increments in which slider values can change.")] public float stepSize = 0.1f; [Tooltip("If this is checked then when the slider is released, it will snap to the nearest value position.")] public bool snapToStep = false; [Tooltip("The amount of friction the slider will have when it is released.")] public float releasedFriction = 50f; protected Direction finalDirection; protected Rigidbody sliderRigidbody; protected ConfigurableJoint sliderJoint; protected bool sliderJointCreated = false; protected Vector3 minimumLimitDiff; protected Vector3 maximumLimitDiff; protected Vector3 snapPosition; protected override void OnDrawGizmos() { base.OnDrawGizmos(); if (!enabled || !setupSuccessful) { return; } Gizmos.DrawLine(transform.position, minimumLimit.transform.position); Gizmos.DrawLine(transform.position, maximumLimit.transform.position); } protected override void InitRequiredComponents() { DetectSetup(); InitRigidbody(); InitInteractableObject(); InitJoint(); } protected override bool DetectSetup() { if (sliderJointCreated) { if (connectedTo != null) { sliderJoint.connectedBody = connectedTo.GetComponent(); } } finalDirection = direction; if (direction == Direction.autodetect) { RaycastHit hitRight; RaycastHit hitUp; RaycastHit hitForward; bool rightHasHit = Physics.Raycast(transform.position, transform.right, out hitRight); bool upHasHit = Physics.Raycast(transform.position, transform.up, out hitUp); bool forwardHasHit = Physics.Raycast(transform.position, transform.forward, out hitForward); Vector3 sliderDiff = transform.localScale / 2f; //The right ray has found the min on the right, so max is on the left if (rightHasHit && hitRight.collider.gameObject == minimumLimit.gameObject) { finalDirection = Direction.x; minimumLimitDiff = CalculateDiff(minimumLimit.transform.localPosition, Vector3.right, minimumLimit.transform.localScale.x, sliderDiff.x, false); maximumLimitDiff = CalculateDiff(maximumLimit.transform.localPosition, Vector3.right, maximumLimit.transform.localScale.x, sliderDiff.x, true); } //The right ray has found the max on the right, so min is on the left if (rightHasHit && hitRight.collider.gameObject == maximumLimit.gameObject) { finalDirection = Direction.x; minimumLimitDiff = CalculateDiff(minimumLimit.transform.localPosition, Vector3.right, minimumLimit.transform.localScale.x, sliderDiff.x, true); maximumLimitDiff = CalculateDiff(maximumLimit.transform.localPosition, Vector3.right, maximumLimit.transform.localScale.x, sliderDiff.x, false); } // the up ray has found the min above, so max is below if (upHasHit && hitUp.collider.gameObject == minimumLimit.gameObject) { finalDirection = Direction.y; minimumLimitDiff = CalculateDiff(minimumLimit.transform.localPosition, Vector3.up, minimumLimit.transform.localScale.y, sliderDiff.y, false); maximumLimitDiff = CalculateDiff(maximumLimit.transform.localPosition, Vector3.up, maximumLimit.transform.localScale.y, sliderDiff.y, true); } //the up ray has found the max above, so the min ix below if (upHasHit && hitUp.collider.gameObject == maximumLimit.gameObject) { finalDirection = Direction.y; minimumLimitDiff = CalculateDiff(minimumLimit.transform.localPosition, Vector3.up, minimumLimit.transform.localScale.y, sliderDiff.y, true); maximumLimitDiff = CalculateDiff(maximumLimit.transform.localPosition, Vector3.up, maximumLimit.transform.localScale.y, sliderDiff.y, false); } //the forward ray has found the min in front, so the max is behind if (forwardHasHit && hitForward.collider.gameObject == minimumLimit.gameObject) { finalDirection = Direction.z; minimumLimitDiff = CalculateDiff(minimumLimit.transform.localPosition, Vector3.forward, minimumLimit.transform.localScale.y, sliderDiff.y, false); maximumLimitDiff = CalculateDiff(maximumLimit.transform.localPosition, Vector3.forward, maximumLimit.transform.localScale.y, sliderDiff.y, true); } //the forward ray has found the max in front, so the min is behind if (forwardHasHit && hitForward.collider.gameObject == maximumLimit.gameObject) { finalDirection = Direction.z; minimumLimitDiff = CalculateDiff(minimumLimit.transform.localPosition, Vector3.forward, minimumLimit.transform.localScale.z, sliderDiff.z, true); maximumLimitDiff = CalculateDiff(maximumLimit.transform.localPosition, Vector3.forward, maximumLimit.transform.localScale.z, sliderDiff.z, false); } } return true; } protected override ControlValueRange RegisterValueRange() { return new ControlValueRange() { controlMin = minimumValue, controlMax = maximumValue }; } protected override void HandleUpdate() { CalculateValue(); if (snapToStep) { SnapToValue(); } } protected virtual Vector3 CalculateDiff(Vector3 initialPosition, Vector3 givenDirection, float scaleValue, float diffMultiplier, bool addition) { Vector3 additionDiff = givenDirection * diffMultiplier; Vector3 limitDiff = givenDirection * (scaleValue / 2f); if (addition) { limitDiff = initialPosition + limitDiff; } else { limitDiff = initialPosition - limitDiff; } Vector3 answer = initialPosition - limitDiff; if (addition) { answer -= additionDiff; } else { answer += additionDiff; } return answer; } protected virtual void InitRigidbody() { sliderRigidbody = GetComponent(); if (sliderRigidbody == null) { sliderRigidbody = gameObject.AddComponent(); } sliderRigidbody.isKinematic = false; sliderRigidbody.useGravity = false; sliderRigidbody.constraints = RigidbodyConstraints.FreezeRotation; sliderRigidbody.drag = releasedFriction; if (connectedTo != null) { Rigidbody connectedToRigidbody = connectedTo.GetComponent(); if (connectedToRigidbody == null) { connectedToRigidbody = connectedTo.AddComponent(); connectedToRigidbody.useGravity = false; connectedToRigidbody.isKinematic = true; } } } protected virtual void InitInteractableObject() { VRTK_InteractableObject sliderInteractableObject = GetComponent(); if (sliderInteractableObject == null) { sliderInteractableObject = gameObject.AddComponent(); } sliderInteractableObject.isGrabbable = true; sliderInteractableObject.grabAttachMechanicScript = gameObject.AddComponent(); sliderInteractableObject.secondaryGrabActionScript = gameObject.AddComponent(); sliderInteractableObject.grabAttachMechanicScript.precisionGrab = true; sliderInteractableObject.stayGrabbedOnTeleport = false; } protected virtual void InitJoint() { sliderJoint = GetComponent(); if (sliderJoint == null) { sliderJoint = gameObject.AddComponent(); } sliderJoint.xMotion = (finalDirection == Direction.x ? ConfigurableJointMotion.Free : ConfigurableJointMotion.Locked); sliderJoint.yMotion = (finalDirection == Direction.y ? ConfigurableJointMotion.Free : ConfigurableJointMotion.Locked); sliderJoint.zMotion = (finalDirection == Direction.z ? ConfigurableJointMotion.Free : ConfigurableJointMotion.Locked); sliderJoint.angularXMotion = ConfigurableJointMotion.Locked; sliderJoint.angularYMotion = ConfigurableJointMotion.Locked; sliderJoint.angularZMotion = ConfigurableJointMotion.Locked; ToggleSpring(false); sliderJointCreated = true; } protected virtual void CalculateValue() { Vector3 minPoint = minimumLimit.transform.localPosition - minimumLimitDiff; Vector3 maxPoint = maximumLimit.transform.localPosition - maximumLimitDiff; float maxDistance = Vector3.Distance(minPoint, maxPoint); float currentDistance = Vector3.Distance(minPoint, transform.localPosition); float currentValue = Mathf.Round((minimumValue + Mathf.Clamp01(currentDistance / maxDistance) * (maximumValue - minimumValue)) / stepSize) * stepSize; float flatValue = currentValue - minimumValue; float controlRange = maximumValue - minimumValue; float actualPosition = (flatValue / controlRange); snapPosition = minPoint + ((maxPoint - minPoint) * actualPosition); value = currentValue; } protected virtual void ToggleSpring(bool state) { JointDrive snapDriver = new JointDrive(); snapDriver.positionSpring = (state ? 10000f : 0f); snapDriver.positionDamper = (state ? 10f : 0f); snapDriver.maximumForce = (state ? 100f : 0f); sliderJoint.xDrive = snapDriver; sliderJoint.yDrive = snapDriver; sliderJoint.zDrive = snapDriver; } protected virtual void SnapToValue() { ToggleSpring(true); sliderJoint.targetPosition = snapPosition * -1f; sliderJoint.targetVelocity = Vector3.zero; } } }