// Slider|Controls3D|100090
|
|
namespace VRTK
|
|
{
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </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 selection of sliders at various angles with different step values to demonstrate their usage.
|
|
/// </example>
|
|
[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<Rigidbody>();
|
|
}
|
|
}
|
|
|
|
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<Rigidbody>();
|
|
if (sliderRigidbody == null)
|
|
{
|
|
sliderRigidbody = gameObject.AddComponent<Rigidbody>();
|
|
}
|
|
sliderRigidbody.isKinematic = false;
|
|
sliderRigidbody.useGravity = false;
|
|
sliderRigidbody.constraints = RigidbodyConstraints.FreezeRotation;
|
|
sliderRigidbody.drag = releasedFriction;
|
|
|
|
if (connectedTo != null)
|
|
{
|
|
Rigidbody connectedToRigidbody = connectedTo.GetComponent<Rigidbody>();
|
|
if (connectedToRigidbody == null)
|
|
{
|
|
connectedToRigidbody = connectedTo.AddComponent<Rigidbody>();
|
|
connectedToRigidbody.useGravity = false;
|
|
connectedToRigidbody.isKinematic = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void InitInteractableObject()
|
|
{
|
|
VRTK_InteractableObject sliderInteractableObject = GetComponent<VRTK_InteractableObject>();
|
|
if (sliderInteractableObject == null)
|
|
{
|
|
sliderInteractableObject = gameObject.AddComponent<VRTK_InteractableObject>();
|
|
}
|
|
sliderInteractableObject.isGrabbable = true;
|
|
sliderInteractableObject.grabAttachMechanicScript = gameObject.AddComponent<GrabAttachMechanics.VRTK_TrackObjectGrabAttach>();
|
|
sliderInteractableObject.secondaryGrabActionScript = gameObject.AddComponent<SecondaryControllerGrabActions.VRTK_SwapControllerGrabAction>();
|
|
sliderInteractableObject.grabAttachMechanicScript.precisionGrab = true;
|
|
sliderInteractableObject.stayGrabbedOnTeleport = false;
|
|
}
|
|
|
|
protected virtual void InitJoint()
|
|
{
|
|
sliderJoint = GetComponent<ConfigurableJoint>();
|
|
if (sliderJoint == null)
|
|
{
|
|
sliderJoint = gameObject.AddComponent<ConfigurableJoint>();
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|