// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Core.Math;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Animation.Transforms
{
///
/// Component that allows to continuously orientate an object looking at a specified point or along an axis.
///
public sealed class UxrLookAt : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrLookAtMode _mode = UxrLookAtMode.Target;
[SerializeField] private Transform _target;
[SerializeField] private UxrAxis _lookAxis = UxrAxis.Z;
[SerializeField] private UxrAxis _upAxis = UxrAxis.Y;
[SerializeField] private UxrAxis _matchDirection = UxrAxis.Z;
[SerializeField] private bool _allowRotateAroundUp = true;
[SerializeField] private bool _allowRotateAroundRight = true;
[SerializeField] private bool _invertedLookAxis;
[SerializeField] private bool _onlyOnce;
#endregion
#region Public Methods
///
/// Makes an object look at a specific target.
///
/// The object that will look at the target
/// The target
/// The object look axis
/// The object up vector
///
/// Should the lookAt alter the rotation around the vertical axis?
///
///
/// Should the lookAt alter the rotation around the horizontal axis?
///
///
/// If true, the target's look axis will try to point at the opposite direction where the target is. By default this is
/// false, meaning the look vector will try to point at the target
///
public static void MakeLookAt(GameObject gameObject,
Transform target,
UxrAxis lookAxis,
UxrAxis upAxis,
bool allowRotateAroundObjectUp = true,
bool allowRotateAroundObjectRight = true,
bool invertedLookAxis = false,
bool onlyOnce = false)
{
if (gameObject == null)
{
return;
}
UxrLookAt lookAtComponent = gameObject.GetOrAddComponent();
lookAtComponent._mode = UxrLookAtMode.Target;
lookAtComponent._target = target;
lookAtComponent._lookAxis = lookAxis;
lookAtComponent._upAxis = upAxis;
lookAtComponent._allowRotateAroundUp = allowRotateAroundObjectUp;
lookAtComponent._allowRotateAroundRight = allowRotateAroundObjectRight;
lookAtComponent._invertedLookAxis = invertedLookAxis;
lookAtComponent._onlyOnce = onlyOnce;
lookAtComponent.PerformLookAt(false);
}
///
/// Makes an object look along a specific target's direction.
///
/// The object that will look at the target
/// The target
/// The target axis
/// The object look axis
/// The object up vector
///
/// Should the lookAt alter the rotation around the vertical axis?
///
///
/// Should the lookAt alter the rotation around the horizontal axis?
///
///
/// If true, the target's forward axis will try to point at the opposite direction where the target is. By default this
/// is false, meaning the forward vector will try to point at the target
///
/// Whether to perform the look at only once
public static void MatchTargetDirection(GameObject gameObject,
Transform target,
UxrAxis direction,
UxrAxis lookAxis,
UxrAxis upAxis,
bool allowRotateAroundUp = true,
bool allowRotateAroundRight = true,
bool invertedForwardAxis = false,
bool onlyOnce = false)
{
if (gameObject == null)
{
return;
}
UxrLookAt lookAtComponent = gameObject.GetOrAddComponent();
lookAtComponent._mode = UxrLookAtMode.MatchTargetDirection;
lookAtComponent._target = target;
lookAtComponent._matchDirection = direction;
lookAtComponent._lookAxis = lookAxis;
lookAtComponent._upAxis = upAxis;
lookAtComponent._allowRotateAroundUp = allowRotateAroundUp;
lookAtComponent._allowRotateAroundRight = allowRotateAroundRight;
lookAtComponent._invertedLookAxis = invertedForwardAxis;
lookAtComponent._onlyOnce = onlyOnce;
lookAtComponent.PerformLookAt(false);
}
///
/// Makes an object look in a specific direction.
///
/// The object that will look at the given direction
/// The world direction that the object will look at
/// The object look axis
/// The object up vector
///
/// Should the lookAt alter the rotation around the vertical axis?
///
///
/// Should the lookAt alter the rotation around the horizontal axis?
///
///
/// If true, the target's forward axis will try to point at the opposite direction where the target is. By default this
/// is false, meaning the forward vector will try to point at the target
///
/// Whether to perform the look at only once
public static void MatchWorldDirection(GameObject gameObject,
UxrAxis direction,
UxrAxis lookAxis,
UxrAxis upAxis,
bool allowRotateAroundUp = true,
bool allowRotateAroundRight = true,
bool invertedForwardAxis = false,
bool onlyOnce = false)
{
if (gameObject == null)
{
return;
}
UxrLookAt lookAtComponent = gameObject.GetOrAddComponent();
lookAtComponent._mode = UxrLookAtMode.MatchWorldDirection;
lookAtComponent._matchDirection = direction;
lookAtComponent._lookAxis = lookAxis;
lookAtComponent._upAxis = upAxis;
lookAtComponent._allowRotateAroundUp = allowRotateAroundUp;
lookAtComponent._allowRotateAroundRight = allowRotateAroundRight;
lookAtComponent._invertedLookAxis = invertedForwardAxis;
lookAtComponent._onlyOnce = onlyOnce;
lookAtComponent.PerformLookAt(false);
}
///
/// Removes an UxrLookAt component if it exists.
///
/// The GameObject to remove the component from
public static void RemoveLookAt(GameObject gameObject)
{
if (gameObject == null)
{
return;
}
UxrLookAt lookAtComponent = gameObject.GetComponent();
if (lookAtComponent)
{
Destroy(lookAtComponent);
}
}
///
/// Forces to execute an instant LookAt.
///
public void ForceLookAt()
{
PerformLookAt(true);
}
#endregion
#region Unity
///
/// Subscribes to events.
///
protected override void OnEnable()
{
base.OnEnable();
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
_initialLocalRotation = gameObject.transform.localRotation;
}
///
/// Unsubscribes from events.
///
protected override void OnDisable()
{
base.OnDisable();
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
}
#endregion
#region Event Handling Methods
///
/// Called when the avatars finished updating. Performs the look at.
///
private void UxrManager_AvatarsUpdated()
{
PerformLookAt(false);
}
#endregion
#region Private Methods
///
/// Performs look at.
///
private static void PerformLookAt(GameObject gameObject,
Quaternion initialLocalRotation,
UxrLookAtMode mode,
Transform target,
UxrAxis direction,
UxrAxis lookAxis,
UxrAxis upAxis,
bool allowRotateAroundUp = true,
bool allowRotateAroundRight = true,
bool invertedForwardAxis = false)
{
if (gameObject == null)
{
return;
}
if (!allowRotateAroundUp && !allowRotateAroundRight)
{
return;
}
Vector3 worldDir = Vector3.forward;
if (mode == UxrLookAtMode.Target)
{
if (target == null)
{
return;
}
worldDir = target.position - gameObject.transform.position;
}
else if (mode == UxrLookAtMode.MatchTargetDirection)
{
if (target == null)
{
return;
}
worldDir = target.TransformDirection(direction);
}
else if (mode == UxrLookAtMode.MatchWorldDirection)
{
worldDir = direction;
}
if (invertedForwardAxis)
{
worldDir = -worldDir;
}
Quaternion sourceRot = TransformExt.GetWorldRotation(gameObject.transform.parent, initialLocalRotation);
Quaternion rotation = Quaternion.identity;
if (allowRotateAroundUp && allowRotateAroundRight)
{
rotation = Quaternion.FromToRotation(sourceRot * lookAxis, worldDir);
}
else if (allowRotateAroundUp)
{
Vector3 axis = sourceRot * upAxis;
// Project on the up plane
worldDir = Vector3.ProjectOnPlane(worldDir, axis);
float degrees = Vector3.SignedAngle(sourceRot * lookAxis, worldDir, axis);
rotation = Quaternion.AngleAxis(degrees, axis);
}
else if (allowRotateAroundRight)
{
Vector3 axis = sourceRot * Vector3.Cross(upAxis, lookAxis);
// Project on the right plane
worldDir = Vector3.ProjectOnPlane(worldDir, axis);
float degrees = Vector3.SignedAngle(sourceRot * lookAxis, worldDir, axis);
rotation = Quaternion.AngleAxis(degrees, axis);
}
gameObject.transform.rotation = rotation * sourceRot;
}
///
/// Performs look at.
///
/// Whether to force the look-at
private void PerformLookAt(bool force)
{
if (_repeat || force)
{
PerformLookAt(gameObject, _initialLocalRotation, _mode, _target, _matchDirection, _lookAxis, _upAxis, _allowRotateAroundUp, _allowRotateAroundRight, _invertedLookAxis);
if (_onlyOnce)
{
_repeat = false;
}
}
}
#endregion
#region Private Types & Data
private bool _repeat = true;
private Quaternion _initialLocalRotation;
#endregion
}
}