mirror of
https://udrimavric.com/MAVRIC/Stratasys-450mc-VR.git
synced 2025-01-22 23:28:43 -05:00
324 lines
11 KiB
C#
324 lines
11 KiB
C#
|
using System;
|
||
|
using UnityEngine.Events;
|
||
|
using UnityEngine.XR.Interaction.Toolkit;
|
||
|
|
||
|
namespace UnityEngine.XR.Content.Interaction
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// An interactable joystick that can move side to side, and forward and back by a direct interactor
|
||
|
/// </summary>
|
||
|
public class XRJoystick : XRBaseInteractable
|
||
|
{
|
||
|
const float k_MaxDeadZonePercent = 0.9f;
|
||
|
|
||
|
public enum JoystickType
|
||
|
{
|
||
|
BothCircle,
|
||
|
BothSquare,
|
||
|
FrontBack,
|
||
|
LeftRight,
|
||
|
}
|
||
|
|
||
|
[Serializable]
|
||
|
public class ValueChangeEvent : UnityEvent<float> { }
|
||
|
|
||
|
[Tooltip("Controls how the joystick moves")]
|
||
|
[SerializeField]
|
||
|
JoystickType m_JoystickMotion = JoystickType.BothCircle;
|
||
|
|
||
|
[SerializeField]
|
||
|
[Tooltip("The object that is visually grabbed and manipulated")]
|
||
|
Transform m_Handle = null;
|
||
|
|
||
|
[SerializeField]
|
||
|
[Tooltip("The value of the joystick")]
|
||
|
Vector2 m_Value = Vector2.zero;
|
||
|
|
||
|
[SerializeField]
|
||
|
[Tooltip("If true, the joystick will return to center on release")]
|
||
|
bool m_RecenterOnRelease = true;
|
||
|
|
||
|
[SerializeField]
|
||
|
[Tooltip("Maximum angle the joystick can move")]
|
||
|
[Range(1.0f, 90.0f)]
|
||
|
float m_MaxAngle = 60.0f;
|
||
|
|
||
|
[SerializeField]
|
||
|
[Tooltip("Minimum amount the joystick must move off the center to register changes")]
|
||
|
[Range(1.0f, 90.0f)]
|
||
|
float m_DeadZoneAngle = 10.0f;
|
||
|
|
||
|
[SerializeField]
|
||
|
[Tooltip("Events to trigger when the joystick's x value changes")]
|
||
|
ValueChangeEvent m_OnValueChangeX = new ValueChangeEvent();
|
||
|
|
||
|
[SerializeField]
|
||
|
[Tooltip("Events to trigger when the joystick's y value changes")]
|
||
|
ValueChangeEvent m_OnValueChangeY = new ValueChangeEvent();
|
||
|
|
||
|
IXRSelectInteractor m_Interactor;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Controls how the joystick moves
|
||
|
/// </summary>
|
||
|
public JoystickType joystickMotion
|
||
|
{
|
||
|
get => m_JoystickMotion;
|
||
|
set => m_JoystickMotion = value;
|
||
|
}
|
||
|
|
||
|
/// <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 joystick
|
||
|
/// </summary>
|
||
|
public Vector2 value
|
||
|
{
|
||
|
get => m_Value;
|
||
|
set
|
||
|
{
|
||
|
if (!m_RecenterOnRelease)
|
||
|
{
|
||
|
SetValue(value);
|
||
|
SetHandleAngle(value * m_MaxAngle);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// If true, the joystick will return to center on release
|
||
|
/// </summary>
|
||
|
public bool recenterOnRelease
|
||
|
{
|
||
|
get => m_RecenterOnRelease;
|
||
|
set => m_RecenterOnRelease = value;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Maximum angle the joystick can move
|
||
|
/// </summary>
|
||
|
public float maxAngle
|
||
|
{
|
||
|
get => m_MaxAngle;
|
||
|
set => m_MaxAngle = value;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Minimum amount the joystick must move off the center to register changes
|
||
|
/// </summary>
|
||
|
public float deadZoneAngle
|
||
|
{
|
||
|
get => m_DeadZoneAngle;
|
||
|
set => m_DeadZoneAngle = value;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Events to trigger when the joystick's x value changes
|
||
|
/// </summary>
|
||
|
public ValueChangeEvent onValueChangeX => m_OnValueChangeX;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Events to trigger when the joystick's y value changes
|
||
|
/// </summary>
|
||
|
public ValueChangeEvent onValueChangeY => m_OnValueChangeY;
|
||
|
|
||
|
void Start()
|
||
|
{
|
||
|
if (m_RecenterOnRelease)
|
||
|
SetHandleAngle(Vector2.zero);
|
||
|
}
|
||
|
|
||
|
protected override void OnEnable()
|
||
|
{
|
||
|
base.OnEnable();
|
||
|
selectEntered.AddListener(StartGrab);
|
||
|
selectExited.AddListener(EndGrab);
|
||
|
}
|
||
|
|
||
|
protected override void OnDisable()
|
||
|
{
|
||
|
selectEntered.RemoveListener(StartGrab);
|
||
|
selectExited.RemoveListener(EndGrab);
|
||
|
base.OnDisable();
|
||
|
}
|
||
|
|
||
|
private void StartGrab(SelectEnterEventArgs args)
|
||
|
{
|
||
|
m_Interactor = args.interactorObject;
|
||
|
}
|
||
|
|
||
|
private void EndGrab(SelectExitEventArgs arts)
|
||
|
{
|
||
|
UpdateValue();
|
||
|
|
||
|
if (m_RecenterOnRelease)
|
||
|
{
|
||
|
SetHandleAngle(Vector2.zero);
|
||
|
SetValue(Vector2.zero);
|
||
|
}
|
||
|
|
||
|
m_Interactor = null;
|
||
|
}
|
||
|
|
||
|
public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
|
||
|
{
|
||
|
base.ProcessInteractable(updatePhase);
|
||
|
|
||
|
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
|
||
|
{
|
||
|
if (isSelected)
|
||
|
{
|
||
|
UpdateValue();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Vector3 GetLookDirection()
|
||
|
{
|
||
|
Vector3 direction = m_Interactor.GetAttachTransform(this).position - m_Handle.position;
|
||
|
direction = transform.InverseTransformDirection(direction);
|
||
|
switch (m_JoystickMotion)
|
||
|
{
|
||
|
case JoystickType.FrontBack:
|
||
|
direction.x = 0;
|
||
|
break;
|
||
|
case JoystickType.LeftRight:
|
||
|
direction.z = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
direction.y = Mathf.Clamp(direction.y, 0.01f, 1.0f);
|
||
|
return direction.normalized;
|
||
|
}
|
||
|
|
||
|
void UpdateValue()
|
||
|
{
|
||
|
var lookDirection = GetLookDirection();
|
||
|
|
||
|
// Get up/down angle and left/right angle
|
||
|
var upDownAngle = Mathf.Atan2(lookDirection.z, lookDirection.y) * Mathf.Rad2Deg;
|
||
|
var leftRightAngle = Mathf.Atan2(lookDirection.x, lookDirection.y) * Mathf.Rad2Deg;
|
||
|
|
||
|
// Extract signs
|
||
|
var signX = Mathf.Sign(leftRightAngle);
|
||
|
var signY = Mathf.Sign(upDownAngle);
|
||
|
|
||
|
upDownAngle = Mathf.Abs(upDownAngle);
|
||
|
leftRightAngle = Mathf.Abs(leftRightAngle);
|
||
|
|
||
|
var stickValue = new Vector2(leftRightAngle, upDownAngle) * (1.0f / m_MaxAngle);
|
||
|
|
||
|
// Clamp the stick value between 0 and 1 when doing everything but circular stick motion
|
||
|
if (m_JoystickMotion != JoystickType.BothCircle)
|
||
|
{
|
||
|
stickValue.x = Mathf.Clamp01(stickValue.x);
|
||
|
stickValue.y = Mathf.Clamp01(stickValue.y);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// With circular motion, if the stick value is greater than 1, we normalize
|
||
|
// This way, an extremely strong value in one direction will influence the overall stick direction
|
||
|
if (stickValue.magnitude > 1.0f)
|
||
|
{
|
||
|
stickValue.Normalize();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Rebuild the angle values for visuals
|
||
|
leftRightAngle = stickValue.x * signX * m_MaxAngle;
|
||
|
upDownAngle = stickValue.y * signY * m_MaxAngle;
|
||
|
|
||
|
// Apply deadzone and sign back to the logical stick value
|
||
|
var deadZone = m_DeadZoneAngle / m_MaxAngle;
|
||
|
var aliveZone = (1.0f - deadZone);
|
||
|
stickValue.x = Mathf.Clamp01((stickValue.x - deadZone)) / aliveZone;
|
||
|
stickValue.y = Mathf.Clamp01((stickValue.y - deadZone)) / aliveZone;
|
||
|
|
||
|
// Re-apply signs
|
||
|
stickValue.x *= signX;
|
||
|
stickValue.y *= signY;
|
||
|
|
||
|
SetHandleAngle(new Vector2(leftRightAngle, upDownAngle));
|
||
|
SetValue(stickValue);
|
||
|
}
|
||
|
|
||
|
void SetValue(Vector2 value)
|
||
|
{
|
||
|
m_Value = value;
|
||
|
m_OnValueChangeX.Invoke(m_Value.x);
|
||
|
m_OnValueChangeY.Invoke(m_Value.y);
|
||
|
}
|
||
|
|
||
|
void SetHandleAngle(Vector2 angles)
|
||
|
{
|
||
|
if (m_Handle == null)
|
||
|
return;
|
||
|
|
||
|
var xComp = Mathf.Tan(angles.x * Mathf.Deg2Rad);
|
||
|
var zComp = Mathf.Tan(angles.y * Mathf.Deg2Rad);
|
||
|
var largerComp = Mathf.Max(Mathf.Abs(xComp), Mathf.Abs(zComp));
|
||
|
var yComp = Mathf.Sqrt(1.0f - largerComp * largerComp);
|
||
|
|
||
|
m_Handle.up = (transform.up * yComp) + (transform.right * xComp) + (transform.forward * zComp);
|
||
|
}
|
||
|
|
||
|
void OnDrawGizmosSelected()
|
||
|
{
|
||
|
var angleStartPoint = transform.position;
|
||
|
|
||
|
if (m_Handle != null)
|
||
|
angleStartPoint = m_Handle.position;
|
||
|
|
||
|
const float k_AngleLength = 0.25f;
|
||
|
|
||
|
if (m_JoystickMotion != JoystickType.LeftRight)
|
||
|
{
|
||
|
Gizmos.color = Color.green;
|
||
|
var axisPoint1 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(m_MaxAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
|
||
|
var axisPoint2 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(-m_MaxAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
|
||
|
Gizmos.DrawLine(angleStartPoint, axisPoint1);
|
||
|
Gizmos.DrawLine(angleStartPoint, axisPoint2);
|
||
|
|
||
|
if (m_DeadZoneAngle > 0.0f)
|
||
|
{
|
||
|
Gizmos.color = Color.red;
|
||
|
axisPoint1 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(m_DeadZoneAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
|
||
|
axisPoint2 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(-m_DeadZoneAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
|
||
|
Gizmos.DrawLine(angleStartPoint, axisPoint1);
|
||
|
Gizmos.DrawLine(angleStartPoint, axisPoint2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (m_JoystickMotion != JoystickType.FrontBack)
|
||
|
{
|
||
|
Gizmos.color = Color.green;
|
||
|
var axisPoint1 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(0.0f, 0.0f, m_MaxAngle) * Vector3.up) * k_AngleLength;
|
||
|
var axisPoint2 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(0.0f, 0.0f, -m_MaxAngle) * Vector3.up) * k_AngleLength;
|
||
|
Gizmos.DrawLine(angleStartPoint, axisPoint1);
|
||
|
Gizmos.DrawLine(angleStartPoint, axisPoint2);
|
||
|
|
||
|
if (m_DeadZoneAngle > 0.0f)
|
||
|
{
|
||
|
Gizmos.color = Color.red;
|
||
|
axisPoint1 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(0.0f, 0.0f, m_DeadZoneAngle) * Vector3.up) * k_AngleLength;
|
||
|
axisPoint2 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(0.0f, 0.0f, -m_DeadZoneAngle) * Vector3.up) * k_AngleLength;
|
||
|
Gizmos.DrawLine(angleStartPoint, axisPoint1);
|
||
|
Gizmos.DrawLine(angleStartPoint, axisPoint2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnValidate()
|
||
|
{
|
||
|
m_DeadZoneAngle = Mathf.Min(m_DeadZoneAngle, m_MaxAngle * k_MaxDeadZonePercent);
|
||
|
}
|
||
|
}
|
||
|
}
|