// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using UltimateXR.Core.Math; using UltimateXR.Extensions.Unity.Math; using UnityEngine; #pragma warning disable 414 // Disable warnings due to unused values namespace UltimateXR.Manipulation { /// /// 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. /// 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 /// /// Gets the axis center. /// public Transform Center => _center != null ? _center : transform; /// /// Gets or sets the axis around which the grab can rotate. /// public UxrAxis CenterAxis { get => _centerAxis; set => _centerAxis = value; } /// /// Gets or sets the minimum angle the grip, defined by the , can rotate around /// . /// public float AngleMin { get => _angleMin; set => _angleMin = value; } /// /// Gets or sets the maximum angle the grip, defined by the , can rotate around /// . /// public float AngleMax { get => _angleMax; set => _angleMax = value; } /// /// Gets or sets the discrete angle interval steps the grip can rotate around . /// public float AngleInterval { get => _angleInterval; set => _angleInterval = value; } /// /// Gets or sets the minimum offset from the center, and along , the grip can move. /// public float OffsetMin { get => _offsetMin; set => _offsetMin = value; } /// /// Gets or sets the maximum offset from the center, and along , the grip can move. /// public float OffsetMax { get => _offsetMax; set => _offsetMax = value; } /// /// Gets or sets the discrete offset steps along along the grip can move. /// public float OffsetInterval { get => _offsetInterval; set => _offsetInterval = value; } #endregion #region Public Overrides UxrGrabPointShape /// 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)); } /// 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 /// /// Called when the object is selected, to draw the gizmos in the scene window. /// private void OnDrawGizmosSelected() { UxrGrabbableObject grabbableObject = GetComponent(); if (_grabPointIndex >= 0 && _grabPointIndex < grabbableObject.GrabPointCount) { Gizmos.DrawLine(GetSegmentA(transform.position), GetSegmentB(transform.position)); } } #endregion #region Private Methods /// /// Gets one side of the grabbable segment in world space if it started in . /// /// Center in world space to consider private Vector3 GetSegmentA(Vector3 center) { return center + Center.TransformDirection(CenterAxis) * OffsetMin; } /// /// Gets the other side of the grabbable segment in world space if it started in . /// /// Center in world space to consider private Vector3 GetSegmentB(Vector3 center) { return center + Center.TransformDirection(CenterAxis) * OffsetMax; } #endregion } } #pragma warning restore 414