// -------------------------------------------------------------------------------------------------------------------- // // 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 } }