Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using UnityEngine.Events;
- using UnityEngine.XR.Interaction.Toolkit;
- namespace UnityEngine.XR.Content.Interaction
- {
- /// <summary>
- /// An interactable knob that follows the rotation of the interactor
- /// </summary>
- public class XRKnob : XRBaseInteractable
- {
- const float k_ModeSwitchDeadZone = 0.1f; // Prevents rapid switching between the different rotation tracking modes
- /// <summary>
- /// Helper class used to track rotations that can go beyond 180 degrees while minimizing accumulation error
- /// </summary>
- struct TrackedRotation
- {
- /// <summary>
- /// The anchor rotation we calculate an offset from
- /// </summary>
- float m_BaseAngle;
- /// <summary>
- /// The target rotate we calculate the offset to
- /// </summary>
- float m_CurrentOffset;
- /// <summary>
- /// Any previous offsets we've added in
- /// </summary>
- float m_AccumulatedAngle;
- /// <summary>
- /// The total rotation that occurred from when this rotation started being tracked
- /// </summary>
- public float totalOffset => m_AccumulatedAngle + m_CurrentOffset;
- /// <summary>
- /// Resets the tracked rotation so that total offset returns 0
- /// </summary>
- public void Reset()
- {
- m_BaseAngle = 0.0f;
- m_CurrentOffset = 0.0f;
- m_AccumulatedAngle = 0.0f;
- }
- /// <summary>
- /// Sets a new anchor rotation while maintaining any previously accumulated offset
- /// </summary>
- /// <param name="direction">The XZ vector used to calculate a rotation angle</param>
- public void SetBaseFromVector(Vector3 direction)
- {
- // Update any accumulated angle
- m_AccumulatedAngle += m_CurrentOffset;
- // Now set a new base angle
- m_BaseAngle = Mathf.Atan2(direction.z, direction.x) * Mathf.Rad2Deg;
- m_CurrentOffset = 0.0f;
- }
- public void SetTargetFromVector(Vector3 direction)
- {
- // Set the target angle
- var targetAngle = Mathf.Atan2(direction.z, direction.x) * Mathf.Rad2Deg;
- // Return the offset
- m_CurrentOffset = ShortestAngleDistance(m_BaseAngle, targetAngle, 360.0f);
- // If the offset is greater than 90 degrees, we update the base so we can rotate beyond 180 degrees
- if (Mathf.Abs(m_CurrentOffset) > 90.0f)
- {
- m_BaseAngle = targetAngle;
- m_AccumulatedAngle += m_CurrentOffset;
- m_CurrentOffset = 0.0f;
- }
- }
- }
- // Test
- // Tes 2
- /* More Test
- *
- *
- *
- *
- */
- [Serializable]
- public class ValueChangeEvent : UnityEvent<float> { }
- [SerializeField]
- [Tooltip("The object that is visually grabbed and manipulated")]
- Transform m_Handle = null;
- [SerializeField]
- [Tooltip("The value of the knob")]
- [Range(0.0f, 1.0f)]
- float m_Value = 0.5f;
- [SerializeField]
- [Tooltip("Whether this knob's rotation should be clamped by the angle limits")]
- bool m_ClampedMotion = true;
- [SerializeField]
- [Tooltip("Rotation of the knob at value '1'")]
- float m_MaxAngle = 90.0f;
- [SerializeField]
- [Tooltip("Rotation of the knob at value '0'")]
- float m_MinAngle = -90.0f;
- [SerializeField]
- [Tooltip("Angle increments to support, if greater than '0'")]
- float m_AngleIncrement = 0.0f;
- [SerializeField]
- [Tooltip("The position of the interactor controls rotation when outside this radius")]
- float m_PositionTrackedRadius = 0.1f;
- [SerializeField]
- [Tooltip("How much controller rotation ")]
- float m_TwistSensitivity = 1.5f;
- [SerializeField]
- [Tooltip("Events to trigger when the knob is rotated")]
- ValueChangeEvent m_OnValueChange = new ValueChangeEvent();
- IXRSelectInteractor m_Interactor;
- bool m_PositionDriven = false;
- bool m_UpVectorDriven = false;
- TrackedRotation m_PositionAngles = new TrackedRotation();
- TrackedRotation m_UpVectorAngles = new TrackedRotation();
- TrackedRotation m_ForwardVectorAngles = new TrackedRotation();
- float m_BaseKnobRotation = 0.0f;
- /// <summary>
- /// The object that is visually grabbed and manipulated
- /// </summary>
- public Transform handle
- {
- get => m_Handle;
- set => m_Handle = value;
- }
- /// <summary>
- /// The value of the knob
- /// </summary>
- public float value
- {
- get => m_Value;
- set
- {
- SetValue(value);
- SetKnobRotation(ValueToRotation());
- }
- }
- /// <summary>
- /// Whether this knob's rotation should be clamped by the angle limits
- /// </summary>
- public bool clampedMotion
- {
- get => m_ClampedMotion;
- set => m_ClampedMotion = value;
- }
- /// <summary>
- /// Rotation of the knob at value '1'
- /// </summary>
- public float maxAngle
- {
- get => m_MaxAngle;
- set => m_MaxAngle = value;
- }
- /// <summary>
- /// Rotation of the knob at value '0'
- /// </summary>
- public float minAngle
- {
- get => m_MinAngle;
- set => m_MinAngle = value;
- }
- /// <summary>
- /// The position of the interactor controls rotation when outside this radius
- /// </summary>
- public float positionTrackedRadius
- {
- get => m_PositionTrackedRadius;
- set => m_PositionTrackedRadius = value;
- }
- /// <summary>
- /// Events to trigger when the knob is rotated
- /// </summary>
- public ValueChangeEvent onValueChange => m_OnValueChange;
- void Start()
- {
- SetValue(m_Value);
- SetKnobRotation(ValueToRotation());
- }
- protected override void OnEnable()
- {
- base.OnEnable();
- selectEntered.AddListener(StartGrab);
- selectExited.AddListener(EndGrab);
- }
- protected override void OnDisable()
- {
- selectEntered.RemoveListener(StartGrab);
- selectExited.RemoveListener(EndGrab);
- base.OnDisable();
- }
- void StartGrab(SelectEnterEventArgs args)
- {
- m_Interactor = args.interactorObject;
- m_PositionAngles.Reset();
- m_UpVectorAngles.Reset();
- m_ForwardVectorAngles.Reset();
- UpdateBaseKnobRotation();
- UpdateRotation(true);
- }
- void EndGrab(SelectExitEventArgs args)
- {
- m_Interactor = null;
- }
- public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
- {
- base.ProcessInteractable(updatePhase);
- if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
- {
- if (isSelected)
- {
- UpdateRotation();
- }
- }
- }
- void UpdateRotation(bool freshCheck = false)
- {
- // Are we in position offset or direction rotation mode?
- var interactorTransform = m_Interactor.GetAttachTransform(this);
- // We cache the three potential sources of rotation - the position offset, the forward vector of the controller, and up vector of the controller
- // We store any data used for determining which rotation to use, then flatten the vectors to the local xz plane
- var localOffset = transform.InverseTransformVector(interactorTransform.position - m_Handle.position);
- localOffset.y = 0.0f;
- var radiusOffset = transform.TransformVector(localOffset).magnitude;
- localOffset.Normalize();
- var localForward = transform.InverseTransformDirection(interactorTransform.forward);
- var localY = Math.Abs(localForward.y);
- localForward.y = 0.0f;
- localForward.Normalize();
- var localUp = transform.InverseTransformDirection(interactorTransform.up);
- localUp.y = 0.0f;
- localUp.Normalize();
- if (m_PositionDriven && !freshCheck)
- radiusOffset *= (1.0f + k_ModeSwitchDeadZone);
- // Determine when a certain source of rotation won't contribute - in that case we bake in the offset it has applied
- // and set a new anchor when they can contribute again
- if (radiusOffset >= m_PositionTrackedRadius)
- {
- if (!m_PositionDriven || freshCheck)
- {
- m_PositionAngles.SetBaseFromVector(localOffset);
- m_PositionDriven = true;
- }
- }
- else
- m_PositionDriven = false;
- // If it's not a fresh check, then we weight the local Y up or down to keep it from flickering back and forth at boundaries
- if (!freshCheck)
- {
- if (!m_UpVectorDriven)
- localY *= (1.0f - (k_ModeSwitchDeadZone * 0.5f));
- else
- localY *= (1.0f + (k_ModeSwitchDeadZone * 0.5f));
- }
- if (localY > 0.707f)
- {
- if (!m_UpVectorDriven || freshCheck)
- {
- m_UpVectorAngles.SetBaseFromVector(localUp);
- m_UpVectorDriven = true;
- }
- }
- else
- {
- if (m_UpVectorDriven || freshCheck)
- {
- m_ForwardVectorAngles.SetBaseFromVector(localForward);
- m_UpVectorDriven = false;
- }
- }
- // Get angle from position
- if (m_PositionDriven)
- m_PositionAngles.SetTargetFromVector(localOffset);
- if (m_UpVectorDriven)
- m_UpVectorAngles.SetTargetFromVector(localUp);
- else
- m_ForwardVectorAngles.SetTargetFromVector(localForward);
- // Apply offset to base knob rotation to get new knob rotation
- var knobRotation = m_BaseKnobRotation - ((m_UpVectorAngles.totalOffset + m_ForwardVectorAngles.totalOffset) * m_TwistSensitivity) - m_PositionAngles.totalOffset;
- // Clamp to range
- if (m_ClampedMotion)
- knobRotation = Mathf.Clamp(knobRotation, m_MinAngle, m_MaxAngle);
- SetKnobRotation(knobRotation);
- // Reverse to get value
- var knobValue = (knobRotation - m_MinAngle) / (m_MaxAngle - m_MinAngle);
- SetValue(knobValue);
- }
- void SetKnobRotation(float angle)
- {
- if (m_AngleIncrement > 0)
- {
- var normalizeAngle = angle - m_MinAngle;
- angle = (Mathf.Round(normalizeAngle / m_AngleIncrement) * m_AngleIncrement) + m_MinAngle;
- }
- if (m_Handle != null)
- m_Handle.localEulerAngles = new Vector3(0.0f, angle, 0.0f);
- }
- void SetValue(float value)
- {
- if (m_ClampedMotion)
- value = Mathf.Clamp01(value);
- if (m_AngleIncrement > 0)
- {
- var angleRange = m_MaxAngle - m_MinAngle;
- var angle = Mathf.Lerp(0.0f, angleRange, value);
- angle = Mathf.Round(angle / m_AngleIncrement) * m_AngleIncrement;
- value = Mathf.InverseLerp(0.0f, angleRange, angle);
- }
- m_Value = value;
- m_OnValueChange.Invoke(m_Value);
- }
- float ValueToRotation()
- {
- return m_ClampedMotion ? Mathf.Lerp(m_MinAngle, m_MaxAngle, m_Value) : Mathf.LerpUnclamped(m_MinAngle, m_MaxAngle, m_Value);
- }
- void UpdateBaseKnobRotation()
- {
- m_BaseKnobRotation = Mathf.LerpUnclamped(m_MinAngle, m_MaxAngle, m_Value);
- }
- static float ShortestAngleDistance(float start, float end, float max)
- {
- var angleDelta = end - start;
- var angleSign = Mathf.Sign(angleDelta);
- angleDelta = Math.Abs(angleDelta) % max;
- if (angleDelta > (max * 0.5f))
- angleDelta = -(max - angleDelta);
- return angleDelta * angleSign;
- }
- void OnDrawGizmosSelected()
- {
- const int k_CircleSegments = 16;
- const float k_SegmentRatio = 1.0f / k_CircleSegments;
- // Nothing to do if position radius is too small
- if (m_PositionTrackedRadius <= Mathf.Epsilon)
- return;
- // Draw a circle from the handle point at size of position tracked radius
- var circleCenter = transform.position;
- if (m_Handle != null)
- circleCenter = m_Handle.position;
- var circleX = transform.right;
- var circleY = transform.forward;
- Gizmos.color = Color.green;
- var segmentCounter = 0;
- while (segmentCounter < k_CircleSegments)
- {
- var startAngle = (float)segmentCounter * k_SegmentRatio * 2.0f * Mathf.PI;
- segmentCounter++;
- var endAngle = (float)segmentCounter * k_SegmentRatio * 2.0f * Mathf.PI;
- Gizmos.DrawLine(circleCenter + (Mathf.Cos(startAngle) * circleX + Mathf.Sin(startAngle) * circleY) * m_PositionTrackedRadius,
- circleCenter + (Mathf.Cos(endAngle) * circleX + Mathf.Sin(endAngle) * circleY) * m_PositionTrackedRadius);
- }
- }
- void OnValidate()
- {
- if (m_ClampedMotion)
- m_Value = Mathf.Clamp01(m_Value);
- if (m_MinAngle > m_MaxAngle)
- m_MinAngle = m_MaxAngle;
- SetKnobRotation(ValueToRotation());
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement