123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- // Copyright (c) Microsoft Corporation. All rights reserved.
- // Licensed under the MIT License. See LICENSE in the project root for license information.
- using UnityEngine;
- namespace Microsoft.MixedReality.Toolkit.Utilities.Solvers
- {
- /// <summary>
- /// RadialViewPoser solver locks a tag-along type object within a view cone
- /// </summary>
- [AddComponentMenu("Scripts/MRTK/SDK/RadialView")]
- public class RadialView : Solver
- {
- [SerializeField]
- [Tooltip("Which direction to position the element relative to: HeadOriented rolls with the head, HeadFacingWorldUp view direction but ignores head roll, and HeadMoveDirection uses the direction the head last moved without roll")]
- private RadialViewReferenceDirection referenceDirection = RadialViewReferenceDirection.FacingWorldUp;
- /// <summary>
- /// Which direction to position the element relative to:
- /// HeadOriented rolls with the head,
- /// HeadFacingWorldUp view direction but ignores head roll,
- /// and HeadMoveDirection uses the direction the head last moved without roll.
- /// </summary>
- public RadialViewReferenceDirection ReferenceDirection
- {
- get => referenceDirection;
- set => referenceDirection = value;
- }
- public Vector3 headOffset = Vector3.up * 0.2f;
- [SerializeField]
- [Tooltip("Min distance from eye to position element around, i.e. the sphere radius")]
- private float minDistance = 1f;
- /// <summary>
- /// Min distance from eye to position element around, i.e. the sphere radius.
- /// </summary>
- public float MinDistance
- {
- get => minDistance;
- set => minDistance = value;
- }
- [SerializeField]
- [Tooltip("Max distance from eye to element")]
- private float maxDistance = 2f;
- /// <summary>
- /// Max distance from eye to element.
- /// </summary>
- public float MaxDistance
- {
- get => maxDistance;
- set => maxDistance = value;
- }
- [SerializeField]
- [Tooltip("The element will stay at least this far away from the center of view")]
- private float minViewDegrees = 0f;
- /// <summary>
- /// The element will stay at least this far away from the center of view.
- /// </summary>
- public float MinViewDegrees
- {
- get => minViewDegrees;
- set => minViewDegrees = value;
- }
- [SerializeField]
- [Tooltip("The element will stay at least this close to the center of view")]
- private float maxViewDegrees = 30f;
- /// <summary>
- /// The element will stay at least this close to the center of view.
- /// </summary>
- public float MaxViewDegrees
- {
- get => maxViewDegrees;
- set => maxViewDegrees = value;
- }
- [SerializeField]
- [Tooltip("Apply a different clamp to vertical FOV than horizontal. Vertical = Horizontal * aspectV")]
- private float aspectV = 1f;
- /// <summary>
- /// Apply a different clamp to vertical FOV than horizontal. Vertical = Horizontal * AspectV.
- /// </summary>
- public float AspectV
- {
- get => aspectV;
- set => aspectV = value;
- }
- [SerializeField]
- [Tooltip("Option to ignore angle clamping")]
- private bool ignoreAngleClamp = false;
- /// <summary>
- /// Option to ignore angle clamping.
- /// </summary>
- public bool IgnoreAngleClamp
- {
- get => ignoreAngleClamp;
- set => ignoreAngleClamp = value;
- }
- [SerializeField]
- [Tooltip("Option to ignore distance clamping")]
- private bool ignoreDistanceClamp = false;
- /// <summary>
- /// Option to ignore distance clamping.
- /// </summary>
- public bool IgnoreDistanceClamp
- {
- get => ignoreDistanceClamp;
- set => ignoreDistanceClamp = value;
- }
- [SerializeField]
- [Tooltip("Ignore vertical movement and lock the Y position of the object")]
- private bool useFixedVerticalPosition = false;
- /// <summary>
- /// Ignore vertical movement and lock the Y position of the object.
- /// </summary>
- public bool UseFixedVerticalPosition
- {
- get => useFixedVerticalPosition;
- set => useFixedVerticalPosition = value;
- }
- [SerializeField]
- [Tooltip("Offset amount of the vertical position")]
- private float fixedVerticalPosition = -0.4f;
- /// <summary>
- /// Offset amount of the vertical position.
- /// </summary>
- public float FixedVerticalPosition
- {
- get => fixedVerticalPosition;
- set => fixedVerticalPosition = value;
- }
- [SerializeField]
- [Tooltip("If true, element will orient to ReferenceDirection, otherwise it will orient to ref position.")]
- private bool orientToReferenceDirection = false;
- /// <summary>
- /// If true, element will orient to ReferenceDirection, otherwise it will orient to ref position.
- /// </summary>
- public bool OrientToReferenceDirection
- {
- get => orientToReferenceDirection;
- set => orientToReferenceDirection = value;
- }
- /// <summary>
- /// Position to the view direction, or the movement direction, or the direction of the view cone.
- /// </summary>
- private Vector3 SolverReferenceDirection => SolverHandler.TransformTarget != null ? SolverHandler.TransformTarget.forward : Vector3.forward;
- /// <summary>
- /// The up direction to use for orientation.
- /// </summary>
- /// <remarks>Cone may roll with head, or not.</remarks>
- private Vector3 UpReference
- {
- get
- {
- Vector3 upReference = Vector3.up;
- if (referenceDirection == RadialViewReferenceDirection.ObjectOriented)
- {
- upReference = SolverHandler.TransformTarget != null ? SolverHandler.TransformTarget.up : Vector3.up;
- }
- return upReference;
- }
- }
- private Vector3 ReferencePoint => SolverHandler.TransformTarget != null ? SolverHandler.TransformTarget.position : Vector3.zero;
- /// <inheritdoc />
- public override void SolverUpdate()
- {
- Vector3 goalPosition = WorkingPosition;
- if (ignoreAngleClamp)
- {
- if (ignoreDistanceClamp)
- {
- goalPosition = transform.position;
- }
- else
- {
- GetDesiredOrientation_DistanceOnly(ref goalPosition);
- }
- }
- else
- {
- GetDesiredOrientation(ref goalPosition);
- }
- // Element orientation
- Vector3 refDirUp = UpReference;
- Quaternion goalRotation;
- if (orientToReferenceDirection)
- {
- goalRotation = Quaternion.LookRotation(SolverReferenceDirection, refDirUp);
- }
- else
- {
- goalRotation = Quaternion.LookRotation(goalPosition - ReferencePoint, refDirUp);
- }
- // If gravity aligned then zero out the x and z axes on the rotation
- if (referenceDirection == RadialViewReferenceDirection.GravityAligned)
- {
- goalRotation.x = goalRotation.z = 0f;
- }
- if (UseFixedVerticalPosition)
- {
- goalPosition.y = ReferencePoint.y + FixedVerticalPosition;
- }
- if (goalPosition.y > ReferencePoint.y - headOffset.y)
- {
- goalPosition.y = ReferencePoint.y - headOffset.y;
- }
- GoalPosition = goalPosition;
- GoalRotation = goalRotation;
- }
- /// <summary>
- /// Optimized version of GetDesiredOrientation.
- /// </summary>
- private void GetDesiredOrientation_DistanceOnly(ref Vector3 desiredPos)
- {
- // TODO: There should be a different solver for distance constraint.
- // Determine reference locations and directions
- Vector3 refPoint = ReferencePoint;
- Vector3 elementPoint = transform.position;
- Vector3 elementDelta = elementPoint - refPoint;
- float elementDist = elementDelta.magnitude;
- Vector3 elementDir = elementDist > 0 ? elementDelta / elementDist : Vector3.one;
- // Clamp distance too
- float clampedDistance = Mathf.Clamp(elementDist, minDistance, maxDistance);
- if (!clampedDistance.Equals(elementDist))
- {
- desiredPos = refPoint + clampedDistance * elementDir;
- }
- }
- private void GetDesiredOrientation(ref Vector3 desiredPos)
- {
- // Determine reference locations and directions
- Vector3 direction = SolverReferenceDirection;
- Vector3 upDirection = UpReference;
- Vector3 referencePoint = ReferencePoint;
- Vector3 elementPoint = transform.position;
- Vector3 elementDelta = elementPoint - referencePoint;
- float elementDist = elementDelta.magnitude;
- Vector3 elementDir = elementDist > 0 ? elementDelta / elementDist : Vector3.one;
- // Generate basis: First get axis perpendicular to reference direction pointing toward element
- Vector3 perpendicularDirection = (elementDir - direction);
- perpendicularDirection -= direction * Vector3.Dot(perpendicularDirection, direction);
- perpendicularDirection.Normalize();
- // Calculate the clamping angles, accounting for aspect (need the angle relative to view plane)
- float heightToViewAngle = Vector3.Angle(perpendicularDirection, upDirection);
- float verticalAspectScale = Mathf.Lerp(aspectV, 1f, Mathf.Abs(Mathf.Sin(heightToViewAngle * Mathf.Deg2Rad)));
- // Calculate the current angle
- float currentAngle = Vector3.Angle(elementDir, direction);
- float currentAngleClamped = Mathf.Clamp(currentAngle, minViewDegrees * verticalAspectScale, maxViewDegrees * verticalAspectScale);
- // Clamp distance too, if desired
- float clampedDistance = ignoreDistanceClamp ? elementDist : Mathf.Clamp(elementDist, minDistance, maxDistance);
- // If the angle was clamped, do some special update stuff
- if (currentAngle != currentAngleClamped)
- {
- float angRad = currentAngleClamped * Mathf.Deg2Rad;
- // Calculate new position
- desiredPos = referencePoint + clampedDistance * (direction * Mathf.Cos(angRad) + perpendicularDirection * Mathf.Sin(angRad));
- }
- else if (!clampedDistance.Equals(elementDist))
- {
- // Only need to apply distance
- desiredPos = referencePoint + clampedDistance * elementDir;
- }
- }
- }
- }
|