Add ultimate xr
This commit is contained in:
@@ -0,0 +1,650 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2285bc0022316448ac5c40fd488b38a
|
||||
timeCreated: 1504766639
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,81 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrControllerHand.FingerInfo.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using UltimateXR.Animation.IK;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Devices.Visualization
|
||||
{
|
||||
public partial class UxrControllerHand
|
||||
{
|
||||
#region Private Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Describes a finger used by a hand when interacting with a VR controller. It allows to graphically represent fingers
|
||||
/// that interact with VR controllers by using Inverse Kinematics.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
private class FingerIK
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private UxrCcdIKSolver _fingerIKSolver;
|
||||
[SerializeField] private float _fingerToGoalDuration = 0.1f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the finger IK solver.
|
||||
/// </summary>
|
||||
public UxrCcdIKSolver FingerIKSolver => _fingerIKSolver;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the seconds it will take for a finger to reach the input element whenever it is pressed.
|
||||
/// </summary>
|
||||
public float FingerToGoalDuration => _fingerToGoalDuration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the component data has been initialized at runtime using
|
||||
/// <see cref="UxrControllerHand.InitializeFinger" />.
|
||||
/// </summary>
|
||||
public bool Initialized { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the effector initial local position. The effector is the part of the finger in the IK chain that will
|
||||
/// try to reach the goal.
|
||||
/// </summary>
|
||||
public Vector3 LocalEffectorInitialPos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the initial local position of the IK goal in a transition to a pressed state.
|
||||
/// </summary>
|
||||
public Vector3 LocalGoalTransitionStartPos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current goal transform. The goal is the transform that the finger will try to reach whenever the
|
||||
/// input element is pressed.
|
||||
/// </summary>
|
||||
public Transform CurrentFingerGoal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The or sets current timer in the transition to a pressed state, to smoothly transition the finger from its default
|
||||
/// position to the pressed position.
|
||||
/// </summary>
|
||||
public float TimerToGoal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the component is enabled. If it is disabled no IK will take place.
|
||||
/// </summary>
|
||||
public bool ComponentEnabled { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ed85e5d55d14f5dbadfd5dcb07a7e36
|
||||
timeCreated: 1643731645
|
||||
@@ -0,0 +1,52 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrControllerHand.ObjectVariation.MaterialVariation.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Devices.Visualization
|
||||
{
|
||||
public partial class UxrControllerHand
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
public partial class ObjectVariation
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Defines a Material variation in the different materials available for a hand GameObject.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class MaterialVariation
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private string _name;
|
||||
[SerializeField] private Material _material;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the variation name.
|
||||
/// </summary>
|
||||
public string Name => _name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the variation material.
|
||||
/// </summary>
|
||||
public Material Material => _material;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f6ab31a68ba4d94bcbf82c79787c139
|
||||
timeCreated: 1651753651
|
||||
@@ -0,0 +1,52 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrControllerHand.ObjectVariation.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Devices.Visualization
|
||||
{
|
||||
public partial class UxrControllerHand
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Defines a GameObject variation in the different hands that are available in the component.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public partial class ObjectVariation
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private string _name;
|
||||
[SerializeField] private GameObject _gameObject;
|
||||
[SerializeField] private List<MaterialVariation> _materialVariations;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the variation name.
|
||||
/// </summary>
|
||||
public string Name => _name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the variation object.
|
||||
/// </summary>
|
||||
public GameObject GameObject => _gameObject;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the material variations.
|
||||
/// </summary>
|
||||
public IEnumerable<MaterialVariation> MaterialVariations => _materialVariations;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9acb5164948d4a228d66670135a3c308
|
||||
timeCreated: 1651753643
|
||||
@@ -0,0 +1,289 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrControllerHand.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Avatar.Rig;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Components.Composite;
|
||||
using UltimateXR.Manipulation.HandPoses;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Devices.Visualization
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that represents a hand holding a VR controller. It allows to graphically render a hand that
|
||||
/// mimics the interaction that the user performs on a VR controller.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public partial class UxrControllerHand : UxrAvatarComponent<UxrControllerHand>
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private bool _hasAvatarSource;
|
||||
[SerializeField] private UxrAvatar _avatarPrefab;
|
||||
[SerializeField] private UxrHandSide _avatarHandSide;
|
||||
[SerializeField] private UxrHandPoseAsset _handPose;
|
||||
[SerializeField] private List<ObjectVariation> _variations;
|
||||
[SerializeField] private UxrAvatarHand _hand;
|
||||
[SerializeField] private FingerIK _thumb;
|
||||
[SerializeField] private FingerIK _index;
|
||||
[SerializeField] private FingerIK _middle;
|
||||
[SerializeField] private FingerIK _ring;
|
||||
[SerializeField] private FingerIK _little;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hand references.
|
||||
/// </summary>
|
||||
public UxrAvatarHand Hand => _hand;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hand variations, if there are any. Variations allow to provide different visual representations of the
|
||||
/// hand. It can be different objects and each object may have different materials.
|
||||
/// </summary>
|
||||
public IEnumerable<ObjectVariation> Variations => _variations;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the component when the controller hand is dynamic, such as when used through an
|
||||
/// <see cref="UxrAvatar" /> that changes poses.
|
||||
/// </summary>
|
||||
/// <param name="avatar">Avatar the hand belongs to. The current enabled pose will be used to initialize the finger IK.</param>
|
||||
/// <param name="handSide">Which hand side</param>
|
||||
public void InitializeFromCurrentHandPose(UxrAvatar avatar, UxrHandSide handSide)
|
||||
{
|
||||
UxrRuntimeHandPose runtimeHandPose = avatar.GetCurrentRuntimeHandPose(handSide);
|
||||
|
||||
if (runtimeHandPose != null)
|
||||
{
|
||||
// Take snapshot of transforms
|
||||
|
||||
var transforms = UxrAvatarRig.PushHandTransforms(avatar.GetHand(handSide));
|
||||
|
||||
// Briefly switch to pose. This is used because we may be in the middle of a transition and the pose hasn't been acquired yet.
|
||||
|
||||
UxrAvatarRig.UpdateHandUsingRuntimeDescriptor(avatar, handSide, runtimeHandPose.GetHandDescriptor(handSide));
|
||||
|
||||
// Initialize IK
|
||||
|
||||
InitializeFinger(_thumb, true);
|
||||
InitializeFinger(_index, true);
|
||||
InitializeFinger(_middle, true);
|
||||
InitializeFinger(_ring, true);
|
||||
InitializeFinger(_little, true);
|
||||
|
||||
// Restore transforms from snapshot
|
||||
|
||||
UxrAvatarRig.PopHandTransforms(avatar.GetHand(handSide), transforms);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a given finger.
|
||||
/// </summary>
|
||||
/// <param name="finger">Finger to update</param>
|
||||
/// <param name="fingerContactInfo">Finger contact information</param>
|
||||
public void UpdateFinger(UxrFingerType finger, UxrFingerContactInfo fingerContactInfo)
|
||||
{
|
||||
if (_fingers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_fingers.TryGetValue(finger, out FingerIK fingerInfo) && fingerInfo.Initialized && fingerInfo.FingerIKSolver)
|
||||
{
|
||||
Transform fingerIKParent = fingerInfo.FingerIKSolver.Links[0].Bone.parent;
|
||||
|
||||
if (fingerInfo.CurrentFingerGoal != fingerContactInfo.Transform)
|
||||
{
|
||||
fingerInfo.CurrentFingerGoal = fingerContactInfo.Transform;
|
||||
fingerInfo.TimerToGoal = fingerInfo.FingerToGoalDuration;
|
||||
fingerInfo.LocalGoalTransitionStartPos = fingerIKParent.InverseTransformPoint(fingerInfo.FingerIKSolver.Goal.position);
|
||||
}
|
||||
|
||||
if (fingerInfo.TimerToGoal > 0.0f)
|
||||
{
|
||||
fingerInfo.TimerToGoal -= Time.deltaTime;
|
||||
float t = 1.0f - Mathf.Clamp01(fingerInfo.TimerToGoal / fingerInfo.FingerToGoalDuration);
|
||||
fingerInfo.FingerIKSolver.Goal.position = Vector3.Lerp(fingerIKParent.TransformPoint(fingerInfo.LocalGoalTransitionStartPos),
|
||||
fingerContactInfo.Transform != null ? fingerContactInfo.Transform.position : fingerIKParent.TransformPoint(fingerInfo.LocalEffectorInitialPos),
|
||||
t);
|
||||
|
||||
if (fingerContactInfo.Transform != null)
|
||||
{
|
||||
fingerInfo.FingerIKSolver.SolverEnabled = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fingerContactInfo.Transform != null)
|
||||
{
|
||||
fingerInfo.FingerIKSolver.Goal.position = fingerContactInfo.Transform.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
fingerInfo.FingerIKSolver.SolverEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows to manually update the Inverse Kinematics of all fingers.
|
||||
/// </summary>
|
||||
public void UpdateIKManually()
|
||||
{
|
||||
foreach (KeyValuePair<UxrFingerType, FingerIK> fingerPair in _fingers)
|
||||
{
|
||||
if (fingerPair.Value.FingerIKSolver && fingerPair.Value.FingerIKSolver.SolverEnabled)
|
||||
{
|
||||
fingerPair.Value.FingerIKSolver.SolveIK();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Generates the internal list of fingers.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
_fingers = new Dictionary<UxrFingerType, FingerIK>();
|
||||
|
||||
_thumb.ComponentEnabled = _thumb.FingerIKSolver != null && _thumb.FingerIKSolver.enabled;
|
||||
_index.ComponentEnabled = _index.FingerIKSolver != null && _index.FingerIKSolver.enabled;
|
||||
_middle.ComponentEnabled = _middle.FingerIKSolver != null && _middle.FingerIKSolver.enabled;
|
||||
_ring.ComponentEnabled = _ring.FingerIKSolver != null && _ring.FingerIKSolver.enabled;
|
||||
_little.ComponentEnabled = _little.FingerIKSolver != null && _little.FingerIKSolver.enabled;
|
||||
|
||||
_fingers.Add(UxrFingerType.Thumb, _thumb);
|
||||
_fingers.Add(UxrFingerType.Index, _index);
|
||||
_fingers.Add(UxrFingerType.Middle, _middle);
|
||||
_fingers.Add(UxrFingerType.Ring, _ring);
|
||||
_fingers.Add(UxrFingerType.Little, _little);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the finger IK solvers.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
UxrManager.StageUpdating += UxrManager_StageUpdating;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables the finger IK solvers.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
UxrManager.StageUpdating -= UxrManager_StageUpdating;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure the hand rig is allocated when the component is reset.
|
||||
/// </summary>
|
||||
protected override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
_hand = new UxrAvatarHand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the fingers.
|
||||
/// </summary>
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
if (Avatar == null)
|
||||
{
|
||||
UxrManager.LogMissingAvatarInHierarchyError(this);
|
||||
}
|
||||
|
||||
InitializeFinger(_thumb);
|
||||
InitializeFinger(_index);
|
||||
InitializeFinger(_middle);
|
||||
InitializeFinger(_ring);
|
||||
InitializeFinger(_little);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called when the <see cref="UxrManager" /> is about to update a stage.
|
||||
/// </summary>
|
||||
/// <param name="stage"></param>
|
||||
private void UxrManager_StageUpdating(UxrUpdateStage stage)
|
||||
{
|
||||
if (stage == UxrUpdateStage.Animation)
|
||||
{
|
||||
// Check resetting bone transforms?
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a finger.
|
||||
/// </summary>
|
||||
/// <param name="finger">The finger to initialize</param>
|
||||
/// <param name="recomputeLinkData">
|
||||
/// Whether to recompute the link data. This is required if the initial pose for the IK
|
||||
/// changed
|
||||
/// </param>
|
||||
private void InitializeFinger(FingerIK finger, bool recomputeLinkData = false)
|
||||
{
|
||||
if (finger.FingerIKSolver != null)
|
||||
{
|
||||
if (recomputeLinkData)
|
||||
{
|
||||
finger.FingerIKSolver.ComputeLinkData();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Initializing the finger for the first time from the component itself.
|
||||
// This makes sure that the data is computed with the initial finger bone orientations.
|
||||
finger.FingerIKSolver.RestoreInitialRotations();
|
||||
}
|
||||
|
||||
finger.Initialized = true;
|
||||
finger.FingerIKSolver.enabled = finger.ComponentEnabled;
|
||||
finger.LocalEffectorInitialPos = finger.FingerIKSolver.Links[0].Bone.parent.InverseTransformPoint(finger.FingerIKSolver.EndEffector.position);
|
||||
finger.FingerIKSolver.Goal.SetPositionAndRotation(finger.FingerIKSolver.EndEffector.position, finger.FingerIKSolver.EndEffector.rotation);
|
||||
finger.CurrentFingerGoal = null;
|
||||
finger.TimerToGoal = -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private Dictionary<UxrFingerType, FingerIK> _fingers;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e3aec586ed72f74a8f71a9fc93449d4
|
||||
timeCreated: 1505111961
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,192 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrElement.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using UltimateXR.Avatar.Rig;
|
||||
using UltimateXR.Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Devices.Visualization
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the properties of a VR controller input element.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class UxrElement
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private UxrElementType _elementType = UxrElementType.NotSet;
|
||||
[SerializeField] private UxrHandSide _hand;
|
||||
[SerializeField] private UxrControllerElements _element;
|
||||
[SerializeField] private GameObject _gameObject;
|
||||
[SerializeField] private UxrFingerType _finger;
|
||||
[SerializeField] private GameObject _fingerContactPoint;
|
||||
[SerializeField] private Vector3 _buttonPressedOffset;
|
||||
[SerializeField] private Vector3 _input1DPressedOffsetAngle;
|
||||
[SerializeField] private Vector3 _input1DPressedOffset;
|
||||
[SerializeField] private Vector3 _input2DFirstAxisOffsetAngle;
|
||||
[SerializeField] private Vector3 _input2DSecondAxisOffsetAngle;
|
||||
[SerializeField] private Vector3 _input2DFirstAxisOffset;
|
||||
[SerializeField] private Vector3 _input2DSecondAxisOffset;
|
||||
[SerializeField] private Vector3 _dpadFirstAxisOffsetAngle;
|
||||
[SerializeField] private Vector3 _dpadSecondAxisOffsetAngle;
|
||||
[SerializeField] private Vector3 _dpadFirstAxisOffset;
|
||||
[SerializeField] private Vector3 _dpadSecondAxisOffset;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the input element type.
|
||||
/// </summary>
|
||||
public UxrElementType ElementType => _elementType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets which controller element(s) the input element describes.
|
||||
/// </summary>
|
||||
public UxrControllerElements Element => _element;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object that represents the input element.
|
||||
/// </summary>
|
||||
public GameObject ElementObject => _gameObject;
|
||||
|
||||
/// <summary>
|
||||
/// Gets which finger interacts with the input element.
|
||||
/// </summary>
|
||||
public UxrFingerType Finger => _finger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the finger contact point if there is any. If null it will try to contact <see cref="ElementObject" />'s
|
||||
/// transform.
|
||||
/// </summary>
|
||||
public GameObject FingerContactPoint => _fingerContactPoint;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pressed offset for a <see cref="UxrElementType.Button" /> input element.
|
||||
/// </summary>
|
||||
public Vector3 ButtonPressedOffset => _buttonPressedOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pressed offset euler angles for a <see cref="UxrElementType.Input1DRotate" /> input element.
|
||||
/// </summary>
|
||||
public Vector3 Input1DPressedOffsetAngle => _input1DPressedOffsetAngle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pressed offset for a <see cref="UxrElementType.Input1DPush" /> input element.
|
||||
/// </summary>
|
||||
public Vector3 Input1DPressedOffset => _input1DPressedOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum positive angle range for the first axis in a <see cref="UxrElementType.Input2DJoystick" />
|
||||
/// input element. The other side will be the negated angle.
|
||||
/// </summary>
|
||||
public Vector3 Input2DFirstAxisOffsetAngle => _input2DFirstAxisOffsetAngle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum positive angle range for the second axis in a <see cref="UxrElementType.Input2DJoystick" />
|
||||
/// input element. The other side will be the negated angle.
|
||||
/// </summary>
|
||||
public Vector3 Input2DSecondAxisOffsetAngle => _input2DSecondAxisOffsetAngle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum positive offset of the first axis in a <see cref="UxrElementType.Input2DTouch" /> input
|
||||
/// element. The other side will be the negated offset.
|
||||
/// </summary>
|
||||
public Vector3 Input2DFirstAxisOffset => _input2DFirstAxisOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum positive offset of the second axis in a <see cref="UxrElementType.Input2DTouch" /> input
|
||||
/// element. The other side will be the negated offset.
|
||||
/// </summary>
|
||||
public Vector3 Input2DSecondAxisOffset => _input2DSecondAxisOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum positive angle range for the first axis in a <see cref="UxrElementType.DPad" /> input element. The
|
||||
/// other side will be the negated angle.
|
||||
/// </summary>
|
||||
public Vector3 DpadFirstAxisOffsetAngle => _dpadFirstAxisOffsetAngle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum positive angle range for the second axis in a <see cref="UxrElementType.DPad" /> input element.
|
||||
/// The other side will be the negated angle.
|
||||
/// </summary>
|
||||
public Vector3 DpadSecondAxisOffsetAngle => _dpadSecondAxisOffsetAngle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum positive offset for the first axis in a <see cref="UxrElementType.DPad" /> input element. The
|
||||
/// other side will be the negated offset.
|
||||
/// </summary>
|
||||
public Vector3 DpadFirstAxisOffset => _dpadFirstAxisOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum positive offset for the second axis in a <see cref="UxrElementType.DPad" /> input element. The
|
||||
/// other side will be the negated offset.
|
||||
/// </summary>
|
||||
public Vector3 DpadSecondAxisOffset => _dpadSecondAxisOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hand that is used to interact with the input.
|
||||
/// </summary>
|
||||
public UxrHandSide HandSide
|
||||
{
|
||||
get => _hand;
|
||||
internal set => _hand = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the transform's initial local position.
|
||||
/// </summary>
|
||||
internal Vector3 InitialLocalPos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the transform's initial local rotation.
|
||||
/// </summary>
|
||||
internal Quaternion InitialLocalRot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the initial local position of the finger point of contact.
|
||||
/// </summary>
|
||||
internal Vector3 FingerContactInitialLocalPos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local right (X) offset axis.
|
||||
/// </summary>
|
||||
internal Vector3 LocalOffsetX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local up (Y) offset axis.
|
||||
/// </summary>
|
||||
internal Vector3 LocalOffsetY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local forward (Z) offset axis.
|
||||
/// </summary>
|
||||
internal Vector3 LocalOffsetZ { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local right (X) offset of the finger point of contact.
|
||||
/// </summary>
|
||||
internal Vector3 LocalFingerPosOffsetX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local up (Y) offset of the finger point of contact.
|
||||
/// </summary>
|
||||
internal Vector3 LocalFingerPosOffsetY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local forward (Z) offset of the finger point of contact.
|
||||
/// </summary>
|
||||
internal Vector3 LocalFingerPosOffsetZ { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 960381cdea0c4db386cf72679d198700
|
||||
timeCreated: 1643101938
|
||||
@@ -0,0 +1,48 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrElementType.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
namespace UltimateXR.Devices.Visualization
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates the different input element types in a VR controller.
|
||||
/// </summary>
|
||||
public enum UxrElementType
|
||||
{
|
||||
/// <summary>
|
||||
/// Not set.
|
||||
/// </summary>
|
||||
NotSet,
|
||||
|
||||
/// <summary>
|
||||
/// A button.
|
||||
/// </summary>
|
||||
Button,
|
||||
|
||||
/// <summary>
|
||||
/// An analog button that is rotated.
|
||||
/// </summary>
|
||||
Input1DRotate,
|
||||
|
||||
/// <summary>
|
||||
/// An analog button that is pushed.
|
||||
/// </summary>
|
||||
Input1DPush,
|
||||
|
||||
/// <summary>
|
||||
/// An analog joystick.
|
||||
/// </summary>
|
||||
Input2DJoystick,
|
||||
|
||||
/// <summary>
|
||||
/// An analog touch pad.
|
||||
/// </summary>
|
||||
Input2DTouch,
|
||||
|
||||
/// <summary>
|
||||
/// A digital directional pad.
|
||||
/// </summary>
|
||||
DPad
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2464bab5f214391a1671ba46c5dea20
|
||||
timeCreated: 1643101364
|
||||
@@ -0,0 +1,37 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrFingerContactInfo.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Devices.Visualization
|
||||
{
|
||||
/// <summary>
|
||||
/// Finger contact information. Stores information about where a finger touched a controller element.
|
||||
/// </summary>
|
||||
public class UxrFingerContactInfo
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the transform the finger is currently touching.
|
||||
/// </summary>
|
||||
public Transform Transform { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors & Finalizer
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="transform">The transform of where it made contact</param>
|
||||
public UxrFingerContactInfo(Transform transform)
|
||||
{
|
||||
Transform = transform;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa0c647e60bf4a8ebbe73fdc73005d85
|
||||
timeCreated: 1643102273
|
||||
@@ -0,0 +1,255 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrMapControllerToHand.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Components.Composite;
|
||||
using UltimateXR.Manipulation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Devices.Visualization
|
||||
{
|
||||
/// <summary>
|
||||
/// UltimateXR has support for rendering avatars in 'controllers mode' (where the currently
|
||||
/// active controllers are rendered with optional hands on top) or full Avatar mode, where the actual
|
||||
/// visual representation of the user is rendered.
|
||||
/// 'Controllers mode' can be useful during tutorials or menus while the full Avatar mode will be used
|
||||
/// mainly for the core game/simulation.
|
||||
/// When we developed the big example scene we included a small gallery with all supported controllers
|
||||
/// so the user could grab and inspect them. But there was no link between the user input and how the
|
||||
/// hands and the controllers behaved (like the 'controllers mode').
|
||||
/// Since one of the coolest UltimateXR features is mapping user input to controllers and having IK drive
|
||||
/// the hands we also wanted to link the grabbing mechanics to this. In short, what we did was to add
|
||||
/// UxrControllerHand components to the regular avatar hands as well, and then at runtime link them
|
||||
/// to the currently grabbed controller and feed the avatar input. This way we are now able to not
|
||||
/// just grab to controller, but also see how the hand and the controller respond to the user input.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(UxrGrabbableObject))]
|
||||
public class UxrMapControllerToHand : UxrGrabbableObjectComponent<UxrMapControllerToHand>
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private bool _ambidextrous;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Cache components.
|
||||
/// </summary>
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
_controller3DModel = GetComponentInChildren<UxrController3DModel>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coroutines
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the hand:
|
||||
/// <list type="bullet"></list>
|
||||
/// <item>
|
||||
/// Block input mainly to prevent locomotion while pressing the controllers. We want the user to be able to move
|
||||
/// the joysticks and press buttons without triggering unwanted events.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Use the current hand pose, which is used to grab the controller, to initialize the
|
||||
/// <see cref="UxrControllerHand" /> component.
|
||||
/// </item>
|
||||
/// </summary>
|
||||
/// <param name="grabber">Grabber that was used to grab the controller</param>
|
||||
/// <param name="controllerHand">The controller hand component</param>
|
||||
/// <returns>Coroutine IEnumerator</returns>
|
||||
private IEnumerator InitializeHandCoroutine(UxrGrabber grabber, UxrControllerHand controllerHand)
|
||||
{
|
||||
yield return null;
|
||||
|
||||
controllerHand.InitializeFromCurrentHandPose(grabber.Avatar, grabber.Side);
|
||||
|
||||
if (grabber.Side == UxrHandSide.Left)
|
||||
{
|
||||
_ignoreInputLeft = UxrControllerInput.GetIgnoreControllerInput(UxrHandSide.Left);
|
||||
UxrControllerInput.SetIgnoreControllerInput(UxrHandSide.Left, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ignoreInputRight = UxrControllerInput.GetIgnoreControllerInput(UxrHandSide.Right);
|
||||
UxrControllerInput.SetIgnoreControllerInput(UxrHandSide.Right, true);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling Methods
|
||||
|
||||
/// <summary>
|
||||
/// This callback will be executed at the end of all the UltimateXR frame pipeline when the user is grabbing the
|
||||
/// controller. We feed both the controller and the hand's IK the user input.
|
||||
/// </summary>
|
||||
private void UxrManager_AvatarsUpdated()
|
||||
{
|
||||
_controller3DModel.UpdateFromInput(UxrAvatar.LocalAvatarInput);
|
||||
|
||||
if (_controller3DModel.ControllerHand)
|
||||
{
|
||||
_controller3DModel.ControllerHand.UpdateIKManually();
|
||||
}
|
||||
|
||||
if (_controller3DModel.ControllerHandLeft)
|
||||
{
|
||||
_controller3DModel.ControllerHandLeft.UpdateIKManually();
|
||||
}
|
||||
|
||||
if (_controller3DModel.ControllerHandRight)
|
||||
{
|
||||
_controller3DModel.ControllerHandRight.UpdateIKManually();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Trigger Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called when the user grabs the controller. We bind the controller to the
|
||||
/// hand that grabbed it and subscribe to events.
|
||||
/// </summary>
|
||||
/// <param name="e">Grab parameters</param>
|
||||
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
|
||||
{
|
||||
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
|
||||
|
||||
UxrControllerHand controllerHand = GetControllerHand(e.Grabber);
|
||||
|
||||
if (controllerHand)
|
||||
{
|
||||
if (_controller3DModel.NeedsBothHands)
|
||||
{
|
||||
// Controller can be grabbed with both hands simultaneously
|
||||
|
||||
if (e.Grabber.Side == UxrHandSide.Left)
|
||||
{
|
||||
_controller3DModel.ControllerHandLeft = controllerHand;
|
||||
}
|
||||
else
|
||||
{
|
||||
_controller3DModel.ControllerHandRight = controllerHand;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Some controllers are grabbed with one hand only but can be grabbed with both. Switch the component handedness
|
||||
// if necessary to adapt to the hand that is grabbing it
|
||||
|
||||
if (_ambidextrous)
|
||||
{
|
||||
if (e.Grabber.Side != _controller3DModel.HandSide)
|
||||
{
|
||||
_controller3DModel.SwitchHandedness();
|
||||
}
|
||||
}
|
||||
|
||||
// Assign hand
|
||||
|
||||
_controller3DModel.ControllerHand = controllerHand;
|
||||
}
|
||||
|
||||
// Initialize the hand using a coroutine so that it is performed the next frame.
|
||||
StartCoroutine(InitializeHandCoroutine(e.Grabber, controllerHand));
|
||||
|
||||
controllerHand.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnObjectReleased(UxrManipulationEventArgs e)
|
||||
{
|
||||
OnControllerReleaseOrPlace(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnObjectPlaced(UxrManipulationEventArgs e)
|
||||
{
|
||||
OnControllerReleaseOrPlace(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the user releases or places this controller. We remove the connection
|
||||
/// with the controller and the hand that grabbed it, and unsubscribe from events.
|
||||
/// </summary>
|
||||
/// <param name="e">Grab parameters</param>
|
||||
private void OnControllerReleaseOrPlace(UxrManipulationEventArgs e)
|
||||
{
|
||||
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
|
||||
|
||||
UxrControllerHand controllerHand = GetControllerHand(e.Grabber);
|
||||
|
||||
if (controllerHand)
|
||||
{
|
||||
controllerHand.enabled = false;
|
||||
|
||||
if (_controller3DModel.NeedsBothHands)
|
||||
{
|
||||
if (e.Grabber.Side == UxrHandSide.Left)
|
||||
{
|
||||
_controller3DModel.ControllerHandLeft = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_controller3DModel.ControllerHandRight = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_controller3DModel.ControllerHand = null;
|
||||
}
|
||||
|
||||
// Restore blocked input
|
||||
|
||||
UxrControllerInput.SetIgnoreControllerInput(e.Grabber.Side, e.Grabber.Side == UxrHandSide.Left ? _ignoreInputLeft : _ignoreInputRight);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UxrControllerHand component that belongs to the grabber passed. This basically
|
||||
/// just checks if it is the left or right one and looks for the UxrControllerHand component
|
||||
/// in the hand bone object.
|
||||
/// </summary>
|
||||
/// <param name="grabber">The grabber we want to get the UxrControllerHand for</param>
|
||||
/// <returns>UxrControllerHand or null if none was found</returns>
|
||||
private UxrControllerHand GetControllerHand(UxrGrabber grabber)
|
||||
{
|
||||
if (grabber && grabber.Side == UxrHandSide.Left && grabber.Avatar != null)
|
||||
{
|
||||
return grabber.Avatar.LeftHandBone.GetComponent<UxrControllerHand>();
|
||||
}
|
||||
if (grabber && grabber.Side == UxrHandSide.Right && grabber.Avatar != null)
|
||||
{
|
||||
return grabber.Avatar.RightHandBone.GetComponent<UxrControllerHand>();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private UxrController3DModel _controller3DModel;
|
||||
private bool _ignoreInputLeft;
|
||||
private bool _ignoreInputRight;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08075453286e15247a3695522044e2d6
|
||||
timeCreated: 1533914500
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user