Move third party assets to ThirdParty folder
This commit is contained in:
348
Assets/ThirdParty/UltimateXR/Editor/Manipulation/HandPoses/UxrFingerSpinner.cs
vendored
Normal file
348
Assets/ThirdParty/UltimateXR/Editor/Manipulation/HandPoses/UxrFingerSpinner.cs
vendored
Normal file
@@ -0,0 +1,348 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrFingerSpinner.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Math;
|
||||
using UltimateXR.Extensions.Unity.Render;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Editor.Manipulation.HandPoses
|
||||
{
|
||||
/// <summary>
|
||||
/// Finger spinner widget that allows to handle the rotation of finger nodes. It is placed inside a hand image together
|
||||
/// with all other finger spinners.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class UxrFingerSpinner
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private UxrFingerAngleType _angleType;
|
||||
[SerializeField] private Texture2D _texMouse;
|
||||
[SerializeField] private Color32 _color;
|
||||
[SerializeField] private Transform _target;
|
||||
[SerializeField] private Transform _parent;
|
||||
[SerializeField] private UxrUniversalLocalAxes _targetLocalAxes;
|
||||
[SerializeField] private UxrUniversalLocalAxes _parentLocalAxes;
|
||||
[SerializeField] private float _minAngle;
|
||||
[SerializeField] private float _maxAngle;
|
||||
[SerializeField] private UxrHandSide _handSide;
|
||||
[SerializeField] private bool _isThumbProximal;
|
||||
[SerializeField] private Vector2 _quadMax;
|
||||
[SerializeField] private Vector2 _quadMin;
|
||||
[SerializeField] private float _offset;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of angle controlled by the spinner.
|
||||
/// </summary>
|
||||
public UxrFingerAngleType Angle => _angleType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the finger bone transform controlled by the spinner.
|
||||
/// </summary>
|
||||
public Transform Target => _target;
|
||||
|
||||
/// <summary>
|
||||
/// Gets which hand the spinner belongs to.
|
||||
/// </summary>
|
||||
public UxrHandSide HandSide => _handSide;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Rect" /> of the UI control in local hand image coordinates.
|
||||
/// </summary>
|
||||
public Rect MouseRect => new Rect(_quadMin.x, _quadMin.y, _quadMax.x - _quadMin.x, _quadMax.y - _quadMin.y);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the angle value in degrees.
|
||||
/// </summary>
|
||||
public float Value
|
||||
{
|
||||
get => GetValueFromObject();
|
||||
set
|
||||
{
|
||||
float oldValue = GetValueFromObject();
|
||||
float newValue = Mathf.Clamp(value, _minAngle, _maxAngle);
|
||||
|
||||
if (_angleType == UxrFingerAngleType.Spread)
|
||||
{
|
||||
_target.Rotate(_targetLocalAxes.LocalUp, newValue - oldValue, Space.Self);
|
||||
}
|
||||
else if (_angleType == UxrFingerAngleType.Curl)
|
||||
{
|
||||
_target.Rotate(_targetLocalAxes.LocalRight, newValue - oldValue, Space.Self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the offset applied to the spinner. It is used to draw the widget instead of using <see cref="Value" />
|
||||
/// which sometimes propagates slightly from one spinner to another since transforms are related.
|
||||
/// </summary>
|
||||
public float Offset
|
||||
{
|
||||
get => _offset;
|
||||
set
|
||||
{
|
||||
float forgiveness = 0.1f;
|
||||
|
||||
if (Value > _minAngle + forgiveness && Value < _maxAngle - forgiveness)
|
||||
{
|
||||
_offset = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors & Finalizer
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="angleType">The type of angle this control will rotate (spread or curl)</param>
|
||||
/// <param name="texMouse">The image that has the clickable parts encoded in colors</param>
|
||||
/// <param name="color">The color this control has inside the <paramref name="texMouse" /> texture</param>
|
||||
/// <param name="target">The transform that will be rotated</param>
|
||||
/// <param name="parent">The parent transform</param>
|
||||
/// <param name="targetLocalAxes">The universal right/up/forward axes of the target transform</param>
|
||||
/// <param name="parentLocalAxes">The universal right/up/forward axes of the parent transform</param>
|
||||
/// <param name="minAngle">The minimum allowed value</param>
|
||||
/// <param name="maxAngle">The maximum allowed angle</param>
|
||||
/// <param name="handSide">Which hand</param>
|
||||
/// <param name="isThumbProximal">Is target a proximal bone from the thumb?</param>
|
||||
public UxrFingerSpinner(UxrFingerAngleType angleType,
|
||||
Texture2D texMouse,
|
||||
Color32 color,
|
||||
Transform target,
|
||||
Transform parent,
|
||||
UxrUniversalLocalAxes targetLocalAxes,
|
||||
UxrUniversalLocalAxes parentLocalAxes,
|
||||
float minAngle,
|
||||
float maxAngle,
|
||||
UxrHandSide handSide,
|
||||
bool isThumbProximal = false)
|
||||
{
|
||||
_angleType = angleType;
|
||||
_texMouse = texMouse;
|
||||
_color = color;
|
||||
_target = target;
|
||||
_parent = parent;
|
||||
_targetLocalAxes = targetLocalAxes;
|
||||
_parentLocalAxes = parentLocalAxes;
|
||||
_minAngle = minAngle;
|
||||
_maxAngle = maxAngle;
|
||||
_handSide = handSide;
|
||||
_isThumbProximal = isThumbProximal;
|
||||
|
||||
if (handSide == UxrHandSide.Left && s_boundsLeftHand == null)
|
||||
{
|
||||
s_boundsLeftHand = GetMouseTextureBounds(texMouse);
|
||||
}
|
||||
|
||||
if (handSide == UxrHandSide.Right && s_boundsRightHand == null)
|
||||
{
|
||||
s_boundsRightHand = GetMouseTextureBounds(texMouse);
|
||||
}
|
||||
|
||||
_quadMin = GetBoundsMin(handSide == UxrHandSide.Left ? s_boundsLeftHand : s_boundsRightHand, color);
|
||||
_quadMax = GetBoundsMax(handSide == UxrHandSide.Left ? s_boundsLeftHand : s_boundsRightHand, color);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the mouse position lies within the control bounds.
|
||||
/// </summary>
|
||||
/// <param name="mousePos">Mouse position in local hand image coordinates</param>
|
||||
/// <returns>Whether the mouse position lies within the control bounds</returns>
|
||||
public bool ContainsMousePos(Vector2 mousePos)
|
||||
{
|
||||
return mousePos.x >= _quadMin.x && mousePos.x <= _quadMax.x && mousePos.y >= _quadMin.y && mousePos.y <= _quadMax.y;
|
||||
|
||||
/*
|
||||
* Alternative: Get from mouse texture.
|
||||
*
|
||||
if (mousePos.x >= 0 && mousePos.x < _texMouse.width && mousePos.y >= 0 && mousePos.y < _texMouse.height)
|
||||
{
|
||||
Color32 texColor = _texMouse.GetPixels32(0)[((_texMouse.height - Mathf.RoundToInt(mousePos.y)) * _texMouse.width) + Mathf.RoundToInt(mousePos.x)];
|
||||
return texColor.r == _color.r && texColor.g == _color.g && texColor.b == _color.b && texColor.a == _color.a;
|
||||
}
|
||||
|
||||
return false;
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the rotation angle from the object's current transform.
|
||||
/// </summary>
|
||||
/// <returns>Rotation value in degrees</returns>
|
||||
public float GetValueFromObject()
|
||||
{
|
||||
return GetValueFromObject(_angleType);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Computes a dictionary where for a given color a minimum and maximum rect value is stored, representing the corners
|
||||
/// where the color appears in the texture.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture</param>
|
||||
/// <returns>Dictionary</returns>
|
||||
private static Dictionary<int, Bounds> GetMouseTextureBounds(Texture2D texture)
|
||||
{
|
||||
Dictionary<int, Bounds> colorBounds = new Dictionary<int, Bounds>();
|
||||
|
||||
Color32[] pixels = texture.GetPixels32(0);
|
||||
|
||||
for (int x = 0; x < texture.width; ++x)
|
||||
{
|
||||
for (int y = 0; y < texture.height; ++y)
|
||||
{
|
||||
int color = pixels[(texture.height - y - 1) * texture.width + x].ToInt();
|
||||
|
||||
if (!colorBounds.ContainsKey(color))
|
||||
{
|
||||
colorBounds.Add(color, new Bounds(new Vector3(x, y, 0.0f), Vector3.zero));
|
||||
}
|
||||
|
||||
Bounds bounds = colorBounds[color];
|
||||
Vector3 min = bounds.min;
|
||||
Vector3 max = bounds.max;
|
||||
|
||||
if (x < min.x)
|
||||
{
|
||||
min.x = x;
|
||||
}
|
||||
if (x > max.x)
|
||||
{
|
||||
max.x = x;
|
||||
}
|
||||
if (y < min.y)
|
||||
{
|
||||
min.y = y;
|
||||
}
|
||||
if (y > max.y)
|
||||
{
|
||||
max.y = y;
|
||||
}
|
||||
|
||||
bounds.min = min;
|
||||
bounds.max = max;
|
||||
|
||||
colorBounds[color] = bounds;
|
||||
}
|
||||
}
|
||||
|
||||
return colorBounds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum value of a rect for a given color.
|
||||
/// </summary>
|
||||
/// <param name="colorBounds">Bounds dictionary</param>
|
||||
/// <param name="color">Color to get the max bounds value of</param>
|
||||
/// <returns>Maximum rect x and y for the given color</returns>
|
||||
private static Vector2 GetBoundsMin(Dictionary<int, Bounds> colorBounds, Color32 color)
|
||||
{
|
||||
if (colorBounds.TryGetValue(color.ToInt(), out Bounds bounds))
|
||||
{
|
||||
return new Vector2(bounds.min.x, bounds.min.y);
|
||||
}
|
||||
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum value of a rect for a given color.
|
||||
/// </summary>
|
||||
/// <param name="colorBounds">Bounds dictionary</param>
|
||||
/// <param name="color">Color to get the max bounds value of</param>
|
||||
/// <returns>Maximum rect x and y for the given color</returns>
|
||||
private static Vector2 GetBoundsMax(Dictionary<int, Bounds> colorBounds, Color32 color)
|
||||
{
|
||||
if (colorBounds.TryGetValue(color.ToInt(), out Bounds bounds))
|
||||
{
|
||||
return new Vector2(bounds.max.x, bounds.max.y);
|
||||
}
|
||||
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a rotation angle from the object's current transform.
|
||||
/// </summary>
|
||||
/// <param name="angleType">Angle to compute (spread or curl)</param>
|
||||
/// <returns>Angle in degrees</returns>
|
||||
private float GetValueFromObject(UxrFingerAngleType angleType)
|
||||
{
|
||||
// Compute roll value by rotating "right" vector from the current forward direction to the initial forward direction. Roll value will be
|
||||
// the angle between initial right and current right.
|
||||
// The parent reference directions are different for the proximal thumb bone which is a special case.
|
||||
|
||||
Vector3 parentRight = _isThumbProximal
|
||||
? _handSide == UxrHandSide.Left ? _parent.TransformDirection(-_parentLocalAxes.LocalForward) : _parent.TransformDirection(_parentLocalAxes.LocalForward)
|
||||
: _parent.TransformDirection(_parentLocalAxes.LocalRight);
|
||||
Vector3 parentUp = _parent.TransformDirection(_parentLocalAxes.LocalUp);
|
||||
Vector3 parentForward = _isThumbProximal
|
||||
? _handSide == UxrHandSide.Left ? _parent.TransformDirection(_parentLocalAxes.LocalRight) : _parent.TransformDirection(-_parentLocalAxes.LocalRight)
|
||||
: _parent.TransformDirection(_parentLocalAxes.LocalForward);
|
||||
|
||||
Vector3 targetRight = _target.TransformDirection(_targetLocalAxes.LocalRight);
|
||||
Vector3 targetUp = _target.TransformDirection(_targetLocalAxes.LocalUp);
|
||||
Vector3 targetForward = _target.TransformDirection(_targetLocalAxes.LocalForward);
|
||||
|
||||
Quaternion toForward = Quaternion.FromToRotation(targetForward, parentForward);
|
||||
Vector3 rightOnlyRoll = toForward * targetRight;
|
||||
float roll = Vector3.SignedAngle(parentRight, rightOnlyRoll, parentForward);
|
||||
|
||||
// Compute rotation to remove roll component
|
||||
|
||||
Quaternion removeRoll = Quaternion.AngleAxis(-roll, targetForward);
|
||||
|
||||
if (angleType == UxrFingerAngleType.Spread)
|
||||
{
|
||||
// Compute correct spread angle
|
||||
|
||||
Vector3 spreadAxis = removeRoll * targetUp;
|
||||
Vector3 planeRightNormal = parentRight;
|
||||
Vector3 spreadVectorOnPlane = Vector3.Cross(planeRightNormal, Vector3.ProjectOnPlane(spreadAxis, planeRightNormal));
|
||||
|
||||
return Vector3.SignedAngle(spreadVectorOnPlane, targetForward, spreadAxis);
|
||||
}
|
||||
if (angleType == UxrFingerAngleType.Curl)
|
||||
{
|
||||
// Compute correct curl angle
|
||||
|
||||
Vector3 curlAxis = removeRoll * targetRight;
|
||||
Vector3 planeUpNormal = parentUp;
|
||||
Vector3 curlVectorOnPlane = Vector3.Cross(Vector3.ProjectOnPlane(curlAxis, planeUpNormal), planeUpNormal);
|
||||
|
||||
return Vector3.SignedAngle(curlVectorOnPlane, targetForward, curlAxis);
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private static Dictionary<int, Bounds> s_boundsLeftHand;
|
||||
private static Dictionary<int, Bounds> s_boundsRightHand;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user