// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UltimateXR.Core;
using UltimateXR.Extensions.System;
using UnityEngine;
namespace UltimateXR.Extensions.Unity.Math
{
///
/// extensions.
///
public static class QuaternionExt
{
#region Public Types & Data
///
/// Represents a NaN Quaternion.
///
public static ref readonly Quaternion NaN => ref s_nan;
#endregion
#region Public Methods
///
/// Compares two Unity Quaternion objects for equality with a specified precision threshold.
///
/// The first Quaternion to compare
/// The second Quaternion to compare
///
/// The precision threshold for float comparisons. Defaults to
/// .
///
///
/// true if the Quaternion objects are equal; otherwise, false.
///
///
/// This method performs a component-wise comparison between two Quaternion objects.
/// Each component is compared using the specified precision threshold for float comparisons.
///
public static bool EqualsUsingPrecision(this Quaternion a, Quaternion b, float precisionThreshold = UxrConstants.Math.DefaultPrecisionThreshold)
{
return Mathf.Abs(a.x - b.x) <= precisionThreshold &&
Mathf.Abs(a.y - b.y) <= precisionThreshold &&
Mathf.Abs(a.z - b.z) <= precisionThreshold &&
Mathf.Abs(a.w - b.w) <= precisionThreshold;
}
///
/// Transforms a to a component by component.
///
/// Source quaternion
/// with the components of the
public static Vector4 ToVector4(this in Quaternion self)
{
return new Vector4(self.x, self.y, self.z, self.w);
}
///
/// Checks whether the given has any NaN value.
///
/// Source quaternion
/// Whether the quaternion has any NaN value
public static bool IsNaN(this in Quaternion self)
{
return float.IsNaN(self.x) || float.IsNaN(self.y) || float.IsNaN(self.z) || float.IsNaN(self.w);
}
///
/// Checks whether the given has any infinity value.
///
/// Source quaternion
/// Whether the quaternion has any infinity value
public static bool IsInfinity(this in Quaternion self)
{
return float.IsInfinity(self.x) || float.IsInfinity(self.y) || float.IsInfinity(self.z) || float.IsInfinity(self.w);
}
///
/// Checks whether the given has any 0 value.
///
/// Source quaternion
/// Whether the quaternion has any 0 value
public static bool IsZero(this in Quaternion self)
{
return self.x == 0.0f && self.y == 0.0f && self.z == 0.0f && self.w == 0.0f;
}
///
/// Checks whether the given contains valid data.
///
/// Source quaternion
/// Whether the quaternion contains valid data
public static bool IsValid(this in Quaternion self)
{
return !self.IsNaN() && !self.IsInfinity() && !self.IsZero();
}
///
/// Multiplies two quaternions component by component.
///
/// Operand A
/// Operand B
/// Result quaternion
public static Quaternion Multiply(this in Quaternion self, in Quaternion other)
{
return new Quaternion(self.x * other.x,
self.y * other.y,
self.z * other.z,
self.w * other.w);
}
///
/// Computes the inverse of a quaternion component by component (1 / value), checking for divisions by 0. Divisions by
/// 0 have a result of 0.
///
/// Source quaternion
/// Result quaternion
public static Quaternion Inverse(this in Quaternion self)
{
return new Quaternion(Mathf.Approximately(self.x, 0f) ? 0f : 1f / self.x,
Mathf.Approximately(self.y, 0f) ? 0f : 1f / self.y,
Mathf.Approximately(self.z, 0f) ? 0f : 1f / self.z,
Mathf.Approximately(self.w, 0f) ? 0f : 1f / self.w);
}
///
/// Divides two quaternions component by component, checking for divisions by 0. Divisions by 0 have a result of 0.
///
/// Dividend
/// Divisor
/// Result quaternion
public static Quaternion Divide(this in Quaternion self, in Quaternion divisor)
{
return self.Multiply(divisor.Inverse());
}
///
/// Computes the average quaternion from a list.
///
/// List of quaternions
/// The default value to return if the list of quaternions is empty
/// Average quaternion
///
/// From
/// https://gamedev.stackexchange.com/questions/119688/calculate-average-of-arbitrary-amount-of-quaternions-recursion
///
public static Quaternion Average(IEnumerable quaternions, Quaternion defaultIfEmpty = default)
{
if (quaternions == null || !quaternions.Any())
{
return defaultIfEmpty;
}
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
float w = 0.0f;
foreach (Quaternion q in quaternions)
{
x += q.x;
y += q.y;
z += q.z;
w += q.w;
}
float k = 1.0f / Mathf.Sqrt(x * x + y * y + z * z + w * w);
return new Quaternion(x * k, y * k, z * k, w * k);
}
///
/// Applies the transformation to make a rotation defined by rotate towards
/// .
///
/// Quaternion to apply the rotation to
/// Source rotation that will try to match
/// Target rotation to match
/// Optional interpolation value [0.0, 1.0]
public static void ApplyAlignment(this Quaternion self, Quaternion sourceRotation, Quaternion targetRotation, float t = 1.0f)
{
Quaternion rotationTowards = Quaternion.RotateTowards(sourceRotation, targetRotation, 180.0f);
Quaternion relative = Quaternion.Inverse(sourceRotation) * self;
Quaternion result = Quaternion.Slerp(self, rotationTowards * relative, t);
self.Set(result.x, result.y, result.z, result.w);
}
///
/// Parses a .
///
/// Source string
/// Result quaternion
public static Quaternion Parse(string s)
{
s.ThrowIfNullOrWhitespace(nameof(s));
// Remove the parentheses
s = s.TrimStart(' ', '(', '[');
s = s.TrimEnd(' ', ')', ']');
// split the items
string[] sArray = s.Split(s_cardinalSeparator, VectorLength);
// store as an array
float[] result = new float[VectorLength];
for (int i = 0; i < sArray.Length; ++i)
{
result[i] = float.TryParse(sArray[i],
NumberStyles.Float,
CultureInfo.InvariantCulture.NumberFormat,
out float f)
? f
: float.NaN;
}
return result.ToQuaternion();
}
///
/// Tries to parse a .
///
/// Source string
/// Parsed quaternion
/// Whether the quaternion was successfully parsed
public static bool TryParse(string s, out Quaternion result)
{
try
{
result = Parse(s);
return true;
}
catch
{
result = NaN;
return false;
}
}
///
/// Creates a from a float array. If the array does not contain enough elements, the missing
/// components will contain NaN.
///
/// Source data
/// Result quaternion
public static Quaternion ToQuaternion(this float[] data)
{
return data.Length switch
{
0 => NaN,
1 => new Quaternion(data[0], float.NaN, float.NaN, float.NaN),
2 => new Quaternion(data[0], data[1], float.NaN, float.NaN),
3 => new Quaternion(data[0], data[1], data[2], float.NaN),
_ => new Quaternion(data[0], data[1], data[2], data[3])
};
}
///
/// Parses a asynchronously.
///
/// Source string
/// Optional cancellation token, to cancel the operation
/// Awaitable that returns the parsed or null if there was an error
public static Task ParseAsync(string s, CancellationToken ct = default)
{
return Task.Run(() => TryParse(s, out Quaternion result) ? result : (Quaternion?)null, ct);
}
#endregion
#region Private Types & Data
private const int VectorLength = 4;
private const string CardinalSeparator = ",";
private static readonly char[] s_cardinalSeparator = CardinalSeparator.ToCharArray();
private static readonly Quaternion s_nan = new Quaternion(float.NaN, float.NaN, float.NaN, float.NaN);
#endregion
}
}