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

217 lines
8.6 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabPointShapeAxisAngle.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Math;
using UltimateXR.Extensions.Unity.Math;
using UnityEngine;
#pragma warning disable 414 // Disable warnings due to unused values
namespace UltimateXR.Manipulation
{
/// <summary>
/// Grab shape used to grab cylindrical objects. The cylinder is described by an axis and a length. It is possible to
/// specify if the object can be grabbed in both directions or a direction only.
/// </summary>
public class UxrGrabPointShapeAxisAngle : UxrGrabPointShape
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Transform _center;
[SerializeField] private UxrAxis _centerAxis = UxrAxis.Z;
[SerializeField] private bool _bidirectional;
[SerializeField] private float _angleMin = -180.0f;
[SerializeField] private float _angleMax = 180.0f;
[SerializeField] private float _angleInterval = 0.01f;
[SerializeField] private float _offsetMin = -0.1f;
[SerializeField] private float _offsetMax = 0.1f;
[SerializeField] private float _offsetInterval = 0.001f;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the axis center.
/// </summary>
public Transform Center => _center != null ? _center : transform;
/// <summary>
/// Gets or sets the axis around which the grab can rotate.
/// </summary>
public UxrAxis CenterAxis
{
get => _centerAxis;
set => _centerAxis = value;
}
/// <summary>
/// Gets or sets the minimum angle the grip, defined by the <see cref="UxrGrabbableObject" />, can rotate around
/// <see cref="CenterAxis" />.
/// </summary>
public float AngleMin
{
get => _angleMin;
set => _angleMin = value;
}
/// <summary>
/// Gets or sets the maximum angle the grip, defined by the <see cref="UxrGrabbableObject" />, can rotate around
/// <see cref="CenterAxis" />.
/// </summary>
public float AngleMax
{
get => _angleMax;
set => _angleMax = value;
}
/// <summary>
/// Gets or sets the discrete angle interval steps the grip can rotate around <see cref="CenterAxis" />.
/// </summary>
public float AngleInterval
{
get => _angleInterval;
set => _angleInterval = value;
}
/// <summary>
/// Gets or sets the minimum offset from the center, and along <see cref="CenterAxis" />, the grip can move.
/// </summary>
public float OffsetMin
{
get => _offsetMin;
set => _offsetMin = value;
}
/// <summary>
/// Gets or sets the maximum offset from the center, and along <see cref="CenterAxis" />, the grip can move.
/// </summary>
public float OffsetMax
{
get => _offsetMax;
set => _offsetMax = value;
}
/// <summary>
/// Gets or sets the discrete offset steps along along <see cref="CenterAxis" /> the grip can move.
/// </summary>
public float OffsetInterval
{
get => _offsetInterval;
set => _offsetInterval = value;
}
#endregion
#region Public Overrides UxrGrabPointShape
/// <inheritdoc />
public override float GetDistanceFromGrabber(UxrGrabber grabber, Transform snapTransform, Transform objectDistanceTransform, Transform grabberDistanceTransform)
{
// TODO: Consider rotation difference
return grabberDistanceTransform.position.DistanceToSegment(GetSegmentA(objectDistanceTransform.position), GetSegmentB(objectDistanceTransform.position));
}
/// <inheritdoc />
public override void GetClosestSnap(UxrGrabber grabber, Transform snapTransform, Transform distanceTransform, Transform grabberDistanceTransform, out Vector3 position, out Quaternion rotation)
{
// Compute best fitting rotation
Vector3 worldAxis = Center.TransformDirection(CenterAxis);
Vector3 localSnapAxis = snapTransform.InverseTransformDirection(worldAxis);
Vector3 worldGrabberAxis = grabber.transform.TransformDirection(localSnapAxis);
bool reverseGrip = _bidirectional && Vector3.Angle(worldGrabberAxis, -worldAxis) < Vector3.Angle(worldGrabberAxis, worldAxis);
// worldGrabberAxis contains the axis in world coordinates if it was being grabbed with the current grabber orientation
// projection contains the rotation that the grabber would need to rotate to align to the axis using the closest angle
Quaternion projection = Quaternion.FromToRotation(worldGrabberAxis, reverseGrip ? -worldAxis : worldAxis);
/*
if (reverseGrip)
{
Vector3 right = projection * Vector3.right;
Vector3 up = projection * Vector3.up;
Vector3 forward = projection * Vector3.forward;
}*/
// Compute the rotation required to rotate the grabber to the best suited grip on the axis with the given properties
rotation = projection * grabber.transform.rotation;
// Compute perpendicular vectors to the axis to get the angle from snap rotation to projected snap rotation.
Vector3 worldPerpendicular = Center.TransformDirection(CenterAxis.Perpendicular);
Vector3 localPerpendicular = snapTransform.InverseTransformDirection(worldPerpendicular);
Quaternion grabberRotation = grabber.transform.rotation;
grabber.transform.rotation = rotation;
// Compute angle and clamp it.
float angle = Vector3.SignedAngle(worldPerpendicular, grabber.transform.TransformDirection(localPerpendicular), worldAxis);
float clampedAngle = Mathf.Clamp(angle, AngleMin, AngleMax);
rotation = Quaternion.AngleAxis(clampedAngle - angle, worldAxis) * rotation;
// TODO: use _angleInterval
grabber.transform.rotation = grabberRotation;
// Compute grabber position by rotating the snap position around the axis
Vector3 projectedSnap = snapTransform.position.ProjectOnLine(Center.position, worldAxis);
Vector3 fromAxisToSnap = snapTransform.position - projectedSnap;
Vector3 grabberPos = grabber.transform.position;
if (reverseGrip)
{
fromAxisToSnap = Quaternion.AngleAxis(180.0f, worldPerpendicular) * fromAxisToSnap;
}
position = grabberPos.ProjectOnSegment(GetSegmentA(projectedSnap), GetSegmentB(projectedSnap)) + fromAxisToSnap.GetRotationAround(worldAxis, clampedAngle);
}
#endregion
#region Unity
/// <summary>
/// Called when the object is selected, to draw the gizmos in the scene window.
/// </summary>
private void OnDrawGizmosSelected()
{
UxrGrabbableObject grabbableObject = GetComponent<UxrGrabbableObject>();
if (_grabPointIndex >= 0 && _grabPointIndex < grabbableObject.GrabPointCount)
{
Gizmos.DrawLine(GetSegmentA(transform.position), GetSegmentB(transform.position));
}
}
#endregion
#region Private Methods
/// <summary>
/// Gets one side of the grabbable segment in world space if it started in <paramref name="center" />.
/// </summary>
/// <param name="center">Center in world space to consider</param>
private Vector3 GetSegmentA(Vector3 center)
{
return center + Center.TransformDirection(CenterAxis) * OffsetMin;
}
/// <summary>
/// Gets the other side of the grabbable segment in world space if it started in <paramref name="center" />.
/// </summary>
/// <param name="center">Center in world space to consider</param>
private Vector3 GetSegmentB(Vector3 center)
{
return center + Center.TransformDirection(CenterAxis) * OffsetMax;
}
#endregion
}
}
#pragma warning restore 414