Files
dungeons/Assets/UltimateXR/Runtime/Scripts/Devices/Visualization/UxrController3DModel.cs
2024-08-06 21:58:35 +02:00

650 lines
30 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrController3DModel.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Avatar.Rig;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Devices.Visualization
{
/// <summary>
/// Represents the 3D model of a VR controller. It allows to graphically render the current position/orientation and
/// input state of the device.
/// </summary>
public class UxrController3DModel : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _needsBothHands;
[SerializeField] private UxrHandSide _handSide;
[SerializeField] private UxrControllerHand _controllerHand;
[SerializeField] private UxrControllerHand _controllerHandLeft;
[SerializeField] private UxrControllerHand _controllerHandRight;
[SerializeField] private Transform _forward;
[SerializeField] private List<UxrElement> _controllerElements = new List<UxrElement>();
#endregion
#region Public Types & Data
/// <summary>
/// Gets whether the controller requires two hands to hold it.
/// </summary>
public bool NeedsBothHands => _needsBothHands;
/// <summary>
/// Gets the hand required to hold the controller, if <see cref="NeedsBothHands" /> is false.
/// </summary>
public UxrHandSide HandSide => _handSide;
/// <summary>
/// Gets the forward transform as it is currently in the scene. It can be different than the actual forward tracking
/// when we use grab mechanics because the hand transform can be modified by the grab manager and the controller
/// usually hangs from the hand hierarchy.
/// If you need to know the forward controller transform using the information of tracking sensors without any
/// intervention by external elements like the grabbing mechanics use <see cref="ForwardTrackingRotation" />.
/// </summary>
public Transform Forward => _forward;
/// <summary>
/// Gets the rotation that represents the controller's forward orientation. We use this mainly to be able to align
/// certain mechanics no matter the controller that is currently active. A gun in a game needs to be aligned to the
/// controller, teleport mechanics, etc.
/// </summary>
public Quaternion ForwardTrackingRotation
{
get
{
IUxrControllerTracking controllerTracking = _avatar != null ? _avatar.FirstControllerTracking : null;
if (controllerTracking == null)
{
return _forward.rotation;
}
Quaternion relativeRotation = Quaternion.Inverse(transform.rotation) * _forward.transform.rotation;
Quaternion sensorRotation = _handSide == UxrHandSide.Left ? controllerTracking.SensorLeftRot : controllerTracking.SensorRightRot;
return sensorRotation * relativeRotation;
}
}
/// <summary>
/// Gets or sets the hand that is interacting with the controller, when the controller is used with only one hand.
/// </summary>
public UxrControllerHand ControllerHand
{
get => _controllerHand;
set => _controllerHand = value;
}
/// <summary>
/// Gets or sets the left hand that is interacting with the controller, when the controller can be held using both
/// hands.
/// </summary>
public UxrControllerHand ControllerHandLeft
{
get => _controllerHandLeft;
set => _controllerHandLeft = value;
}
/// <summary>
/// Gets or sets the right hand that is interacting with the controller, when the controller can be held using both
/// hands.
/// </summary>
public UxrControllerHand ControllerHandRight
{
get => _controllerHandRight;
set => _controllerHandRight = value;
}
/// <summary>
/// Gets or sets whether the controller is visible.
/// </summary>
public bool IsControllerVisible
{
get => _isControllerVisible;
set
{
_isControllerVisible = value;
gameObject.SetActive(_isControllerVisible);
}
}
/// <summary>
/// Gets or sets whether the hand, if present, is visible. In setups where both hands are used, it targets visibility
/// of both hands.
/// </summary>
public bool IsHandVisible
{
get => _isHandVisible;
set
{
_isHandVisible = value;
if (_controllerHand != null)
{
_controllerHand.gameObject.SetActive(_isHandVisible);
}
if (_controllerHandLeft != null)
{
_controllerHandLeft.gameObject.SetActive(_isHandVisible);
}
if (_controllerHandRight != null)
{
_controllerHandRight.gameObject.SetActive(_isHandVisible);
}
}
}
#endregion
#region Public Methods
/// <summary>
/// Updates the current visual state using the given input.
/// </summary>
/// <param name="controllerInput">The input device to update the controller with</param>
/// <param name="onlyIfControllerHand">Whether to update the visual state only if a controller hand is currently assigned</param>
public void UpdateFromInput(UxrControllerInput controllerInput, bool onlyIfControllerHand = false)
{
if (controllerInput == null)
{
return;
}
foreach (UxrFingerType fingerType in Enum.GetValues(typeof(UxrFingerType)))
{
if (fingerType != UxrFingerType.None)
{
_fingerContacts[fingerType].Transform = null;
_fingerContactsLeft[fingerType].Transform = null;
_fingerContactsRight[fingerType].Transform = null;
}
}
foreach (UxrElement element in _controllerElements)
{
if (element.ElementObject == null)
{
continue;
}
// Update controller element
bool contact = false;
UxrInputButtons controllerButton = UxrControllerInput.ControllerElementToButton(element.Element);
switch (element.ElementType)
{
case UxrElementType.Button:
if (onlyIfControllerHand && !IsControllerHandPresent(element.HandSide))
{
element.ElementObject.transform.localPosition = element.InitialLocalPos;
}
else
{
if (controllerInput.GetButtonsPress(element.HandSide, controllerButton, true))
{
element.ElementObject.transform.localPosition = element.InitialLocalPos +
element.LocalOffsetX * element.ButtonPressedOffset.x +
element.LocalOffsetY * element.ButtonPressedOffset.y +
element.LocalOffsetZ * element.ButtonPressedOffset.z;
}
else
{
element.ElementObject.transform.localPosition = element.InitialLocalPos;
}
}
break;
case UxrElementType.Input1DRotate:
float inputRotateValue = 0.0f;
if (onlyIfControllerHand && !IsControllerHandPresent(element.HandSide))
{
}
else
{
inputRotateValue = controllerInput.GetInput1D(element.HandSide, UxrControllerInput.ControllerElementToInput1D(element.Element), true);
}
Vector3 euler = element.Input1DPressedOffsetAngle * inputRotateValue;
element.ElementObject.transform.localRotation = element.InitialLocalRot * Quaternion.Euler(euler);
contact = contact || inputRotateValue > 0.01f;
break;
case UxrElementType.Input1DPush:
float inputPushValue = 0.0f;
if (onlyIfControllerHand && !IsControllerHandPresent(element.HandSide))
{
}
else
{
inputPushValue = controllerInput.GetInput1D(element.HandSide, UxrControllerInput.ControllerElementToInput1D(element.Element), true);
}
Vector3 offset = element.Input1DPressedOffset * inputPushValue;
element.ElementObject.transform.localPosition = element.InitialLocalPos + element.LocalOffsetX * offset.x + element.LocalOffsetY * offset.y + element.LocalOffsetZ * offset.z;
contact = contact || inputPushValue > 0.01f;
break;
case UxrElementType.Input2DJoystick:
Vector2 inputValueJoystick = Vector2.zero;
if (onlyIfControllerHand && !IsControllerHandPresent(element.HandSide))
{
}
else
{
inputValueJoystick = controllerInput.GetInput2D(element.HandSide, UxrControllerInput.ControllerElementToInput2D(element.Element), true);
}
Vector3 euler1 = Vector3.Lerp(-element.Input2DFirstAxisOffsetAngle, element.Input2DFirstAxisOffsetAngle, (inputValueJoystick.x + 1.0f) * 0.5f);
Vector3 euler2 = Vector3.Lerp(-element.Input2DSecondAxisOffsetAngle, element.Input2DSecondAxisOffsetAngle, (inputValueJoystick.y + 1.0f) * 0.5f);
element.ElementObject.transform.localRotation = Quaternion.Euler(euler2) * Quaternion.Euler(euler1) * element.InitialLocalRot;
contact = contact || inputValueJoystick != Vector2.zero;
break;
case UxrElementType.Input2DTouch:
Vector2 inputValueTouch = controllerInput.GetInput2D(element.HandSide, UxrControllerInput.ControllerElementToInput2D(element.Element), true);
if (onlyIfControllerHand && !IsControllerHandPresent(element.HandSide))
{
}
else
{
inputValueTouch = controllerInput.GetInput2D(element.HandSide, UxrControllerInput.ControllerElementToInput2D(element.Element), true);
}
Vector3 offset1 = Vector3.Lerp(-element.Input2DFirstAxisOffset, element.Input2DFirstAxisOffset, (inputValueTouch.x + 1.0f) * 0.5f);
Vector3 offset2 = Vector3.Lerp(-element.Input2DSecondAxisOffset, element.Input2DSecondAxisOffset, (inputValueTouch.y + 1.0f) * 0.5f);
element.FingerContactPoint.transform.localPosition = element.FingerContactInitialLocalPos +
element.LocalFingerPosOffsetX * offset1.x + element.LocalFingerPosOffsetY * offset1.y + element.LocalFingerPosOffsetZ * offset1.z +
element.LocalFingerPosOffsetX * offset2.x + element.LocalFingerPosOffsetY * offset2.y + element.LocalFingerPosOffsetZ * offset2.z;
contact = contact || inputValueTouch != Vector2.zero;
break;
case UxrElementType.DPad:
bool dpadLeft = false;
bool dpadRight = false;
bool dpadUp = false;
bool dpadDown = false;
if (onlyIfControllerHand && !IsControllerHandPresent(element.HandSide))
{
}
else
{
dpadLeft = controllerInput.GetButtonsPress(element.HandSide, UxrInputButtons.DPadLeft, true);
dpadRight = controllerInput.GetButtonsPress(element.HandSide, UxrInputButtons.DPadRight, true);
dpadUp = controllerInput.GetButtonsPress(element.HandSide, UxrInputButtons.DPadUp, true);
dpadDown = controllerInput.GetButtonsPress(element.HandSide, UxrInputButtons.DPadDown, true);
}
Vector3 dpadEuler1 = dpadLeft ? -element.DpadFirstAxisOffsetAngle :
dpadRight ? element.DpadFirstAxisOffsetAngle : Vector3.zero;
Vector3 dpadEuler2 = dpadUp ? -element.DpadSecondAxisOffsetAngle :
dpadDown ? element.DpadSecondAxisOffsetAngle : Vector3.zero;
Vector3 dpadOffset1 = dpadLeft ? -element.DpadFirstAxisOffset :
dpadRight ? element.DpadFirstAxisOffset : Vector3.zero;
Vector3 dpadOffset2 = dpadUp ? -element.DpadSecondAxisOffset :
dpadDown ? element.DpadSecondAxisOffset : Vector3.zero;
element.ElementObject.transform.localRotation = Quaternion.Euler(dpadEuler2) * Quaternion.Euler(dpadEuler1) * element.InitialLocalRot;
element.FingerContactPoint.transform.localPosition = element.FingerContactInitialLocalPos +
element.LocalFingerPosOffsetX * dpadOffset1.x +
element.LocalFingerPosOffsetY * dpadOffset1.y +
element.LocalFingerPosOffsetZ * dpadOffset1.z +
element.LocalFingerPosOffsetX * dpadOffset2.x +
element.LocalFingerPosOffsetY * dpadOffset2.y +
element.LocalFingerPosOffsetZ * dpadOffset2.z;
contact = contact || dpadLeft || dpadRight || dpadUp || dpadDown;
break;
case UxrElementType.NotSet: break;
}
// Update finger contact?
contact = contact || (controllerButton != UxrInputButtons.None && (controllerInput.GetButtonsTouch(element.HandSide, controllerButton, true) || controllerInput.GetButtonsPress(element.HandSide, controllerButton, true)));
if (onlyIfControllerHand && !IsControllerHandPresent(element.HandSide))
{
contact = false;
}
if (element.FingerContactPoint == null)
{
continue;
}
if (element.FingerContactPoint != element.ElementObject)
{
bool handVisible = _controllerHand && _controllerHand.gameObject.activeSelf;
if (_needsBothHands)
{
handVisible = (element.HandSide == UxrHandSide.Left && _controllerHandLeft != null && _controllerHandLeft.gameObject.activeSelf) ||
(element.HandSide == UxrHandSide.Right && _controllerHandRight != null && _controllerHandRight.gameObject.activeSelf);
}
element.FingerContactPoint.SetActive(contact && !handVisible);
}
if (!contact || element.Finger == UxrFingerType.None)
{
continue;
}
if (_needsBothHands == false)
{
_fingerContacts[element.Finger].Transform = element.FingerContactPoint.transform;
}
else
{
switch (element.HandSide)
{
case UxrHandSide.Left:
_fingerContactsLeft[element.Finger].Transform = element.FingerContactPoint.transform;
break;
case UxrHandSide.Right:
_fingerContactsRight[element.Finger].Transform = element.FingerContactPoint.transform;
break;
default: throw new ArgumentOutOfRangeException();
}
}
}
// Update fingers
if (_needsBothHands == false)
{
if (_controllerHand != null && _fingerContacts != null)
{
foreach (KeyValuePair<UxrFingerType, UxrFingerContactInfo> fingerTransformPair in _fingerContacts)
{
_controllerHand.UpdateFinger(fingerTransformPair.Key, fingerTransformPair.Value);
}
}
}
else
{
if (_controllerHandLeft != null && _fingerContactsLeft != null)
{
foreach (KeyValuePair<UxrFingerType, UxrFingerContactInfo> fingerTransformPair in _fingerContactsLeft)
{
_controllerHandLeft.UpdateFinger(fingerTransformPair.Key, fingerTransformPair.Value);
}
}
if (_controllerHandRight != null && _fingerContactsRight != null)
{
foreach (KeyValuePair<UxrFingerType, UxrFingerContactInfo> fingerTransformPair in _fingerContactsRight)
{
_controllerHandRight.UpdateFinger(fingerTransformPair.Key, fingerTransformPair.Value);
}
}
}
}
/// <summary>
/// Gets the list of GameObjects that represent the given different controller input elements.
/// </summary>
/// <param name="elements">Flags representing the input elements to get the objects of</param>
/// <returns>List of GameObjects representing the given controller input elements</returns>
public IEnumerable<GameObject> GetElements(UxrControllerElements elements)
{
foreach (UxrControllerElements element in ControllerElements)
{
if (elements.HasFlag(element) && _hashedElements.TryGetValue(element, out GameObject elementGameObject))
{
yield return elementGameObject;
}
}
}
/// <summary>
/// Gets the list of materials of all objects that represent the given different controller input elements.
/// </summary>
/// <param name="elements">Flags representing the input elements to get the materials from</param>
/// <returns>List of materials used by the objects representing the given controller input elements</returns>
public IEnumerable<Material> GetElementsMaterials(UxrControllerElements elements)
{
foreach (UxrControllerElements element in ControllerElements)
{
if (elements.HasFlag(element) && _hashedElements.TryGetValue(element, out GameObject elementGameObject))
{
Renderer elementRenderer = elementGameObject.GetComponent<Renderer>();
if (elementRenderer != null && elementRenderer.material != null)
{
yield return elementRenderer.material;
}
}
}
}
/// <summary>
/// Gets the list of original shared materials of all objects that represent the given different controller input
/// elements. The original materials are the shared materials that the input elements had at the beginning, before any
/// modifications.
/// </summary>
/// <param name="elements">Flags representing the input elements to get the original shared materials from</param>
/// <returns>List of original shared materials used by the objects representing the given controller input elements</returns>
public IEnumerable<Material> GetElementsOriginalMaterials(UxrControllerElements elements)
{
foreach (UxrControllerElements element in ControllerElements)
{
if (elements.HasFlag(element) && _hashedElementsOriginalMaterial.TryGetValue(element, out Material elementMaterial))
{
yield return elementMaterial;
}
}
}
/// <summary>
/// Changes the material of the objects that represent the given different controller input elements.
/// </summary>
/// <param name="elements">Flags representing the input elements whose materials will be changed</param>
/// <param name="material">New material to assign</param>
public void SetElementsMaterial(UxrControllerElements elements, Material material)
{
foreach (UxrControllerElements element in ControllerElements)
{
if (elements.HasFlag(element) && _hashedElements.TryGetValue(element, out GameObject elementGameObject)
&& elementGameObject.TryGetComponent<Renderer>(out var elementRenderer))
{
elementRenderer.material = material;
}
}
}
/// <summary>
/// Restores the materials of the objects that represent the given different controller input elements.
/// </summary>
/// <param name="elements">Flags representing the input elements whose materials to restore</param>
public void RestoreElementsMaterials(UxrControllerElements elements)
{
foreach (UxrControllerElements element in ControllerElements)
{
if (elements.HasFlag(element) && _hashedElements.TryGetValue(element, out GameObject elementGameObject)
&& elementGameObject.TryGetComponent<Renderer>(out var elementRenderer))
{
elementRenderer.sharedMaterial = _hashedElementsOriginalMaterial[element];
}
}
}
/// <summary>
/// Changes the current hand to use the controller to the opposite side.
/// </summary>
public void SwitchHandedness()
{
if (_needsBothHands)
{
return;
}
_handSide = _handSide == UxrHandSide.Left ? UxrHandSide.Right : UxrHandSide.Left;
foreach (UxrElement element in _controllerElements)
{
element.HandSide = element.HandSide == UxrHandSide.Left ? UxrHandSide.Right : UxrHandSide.Left;
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
// Initialize data
_avatar = GetComponentInParent<UxrAvatar>();
foreach (UxrFingerType fingerType in Enum.GetValues(typeof(UxrFingerType)))
{
if (fingerType != UxrFingerType.None)
{
_fingerContacts.Add(fingerType, new UxrFingerContactInfo(null));
_fingerContactsLeft.Add(fingerType, new UxrFingerContactInfo(null));
_fingerContactsRight.Add(fingerType, new UxrFingerContactInfo(null));
}
}
if (_controllerElements != null)
{
foreach (UxrElement element in _controllerElements)
{
if (element.ElementObject != null)
{
// Initialize initial pos/rot
element.InitialLocalPos = element.ElementObject.transform.localPosition;
element.InitialLocalRot = element.ElementObject.transform.localRotation;
// Initialize original materials and hashed elements
if (_hashedElements.ContainsKey(element.Element))
{
//Debug.LogWarning($"Element {element.Element} was already found in the {nameof(UxrController3DModel)} list of {name}. Ignoring.");
}
else
{
// Element
_hashedElements.Add(element.Element, element.ElementObject);
// Original materials
Renderer renderer = element.ElementObject.GetComponent<Renderer>();
_hashedElementsOriginalMaterial.Add(element.Element, renderer != null ? renderer.sharedMaterial : null);
}
element.LocalOffsetX = element.ElementObject.transform.parent.InverseTransformDirection(element.ElementObject.transform.right);
element.LocalOffsetY = element.ElementObject.transform.parent.InverseTransformDirection(element.ElementObject.transform.up);
element.LocalOffsetZ = element.ElementObject.transform.parent.InverseTransformDirection(element.ElementObject.transform.forward);
if (element.FingerContactPoint != null)
{
element.LocalFingerPosOffsetX = element.FingerContactPoint.transform.parent.InverseTransformDirection(element.ElementObject.transform.right);
element.LocalFingerPosOffsetY = element.FingerContactPoint.transform.parent.InverseTransformDirection(element.ElementObject.transform.up);
element.LocalFingerPosOffsetZ = element.FingerContactPoint.transform.parent.InverseTransformDirection(element.ElementObject.transform.forward);
}
}
if (element.ElementObject != null && element.FingerContactPoint != null)
{
element.FingerContactInitialLocalPos = element.FingerContactPoint.transform.localPosition;
if (element.FingerContactPoint != element.ElementObject)
{
element.FingerContactPoint.SetActive(false);
}
}
}
}
}
#endregion
#region Private Methods
/// <summary>
/// Gets whether the component has a visual hand available for visualization.
/// </summary>
/// <param name="handSide">Hand to check for</param>
/// <returns>Whether there is a visual hand available</returns>
private bool IsControllerHandPresent(UxrHandSide handSide)
{
if (_needsBothHands)
{
return handSide == UxrHandSide.Left ? _controllerHandLeft != null : _controllerHandRight != null;
}
return _controllerHand != null;
}
#endregion
#region Private Types & Data
/// <summary>
/// Gets all the possible elements in a controller.
/// </summary>
private IEnumerable<UxrControllerElements> ControllerElements
{
get
{
foreach (var value in Enum.GetValues(typeof(UxrControllerElements)))
{
UxrControllerElements element = (UxrControllerElements)value;
if (element != UxrControllerElements.None && element != UxrControllerElements.Everything)
{
yield return element;
}
}
}
}
private readonly Dictionary<UxrControllerElements, GameObject> _hashedElements = new Dictionary<UxrControllerElements, GameObject>();
private readonly Dictionary<UxrControllerElements, Material> _hashedElementsOriginalMaterial = new Dictionary<UxrControllerElements, Material>();
private readonly Dictionary<UxrFingerType, UxrFingerContactInfo> _fingerContacts = new Dictionary<UxrFingerType, UxrFingerContactInfo>();
private readonly Dictionary<UxrFingerType, UxrFingerContactInfo> _fingerContactsLeft = new Dictionary<UxrFingerType, UxrFingerContactInfo>();
private readonly Dictionary<UxrFingerType, UxrFingerContactInfo> _fingerContactsRight = new Dictionary<UxrFingerType, UxrFingerContactInfo>();
private UxrAvatar _avatar;
private bool _isControllerVisible = true;
private bool _isHandVisible = true;
#endregion
}
}