// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System.Globalization; 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 Vector2Ext { #region Public Types & Data /// /// Represents a NaN vector. /// public static ref readonly Vector2 NaN => ref s_nan; #endregion #region Public Methods /// /// Compares two Unity Vector2 objects for equality with a specified precision threshold. /// /// The first Vector2 to compare /// The second Vector2 to compare /// /// The precision threshold for float comparisons. Defaults to /// . /// /// /// true if the Vector2 objects are equal; otherwise, false. /// /// /// This method performs a component-wise comparison between two Vector2 objects. /// Each component is compared using the specified precision threshold for float comparisons. /// public static bool EqualsUsingPrecision(this Vector2 a, Vector2 b, float precisionThreshold = UxrConstants.Math.DefaultPrecisionThreshold) { return Mathf.Abs(a.x - b.x) <= precisionThreshold && Mathf.Abs(a.y - b.y) <= precisionThreshold; } /// /// Checks whether the given vector has any NaN component. /// /// Source vector /// Whether any of the vector components has a NaN value public static bool IsNaN(this in Vector2 self) { return float.IsNaN(self.x) || float.IsNaN(self.y); } /// /// Checks whether the given vector has any infinity component. /// /// Source vector /// Whether any of the vector components has an infinity value public static bool IsInfinity(this in Vector2 self) { return float.IsInfinity(self.x) || float.IsInfinity(self.y); } /// /// Checks whether the given vector contains valid data. /// /// Source vector /// Whether the vector contains all valid values public static bool IsValid(this in Vector2 self) { return !self.IsNaN() && !self.IsInfinity(); } /// /// Replaces NaN component values with valid values. /// /// Vector whose NaN values to replace /// Vector with valid values /// Result vector public static Vector2 FillNanWith(this in Vector2 self, in Vector2 other) { float[] result = new float[VectorLength]; for (int i = 0; i < VectorLength; ++i) { result[i] = float.IsNaN(self[i]) ? other[i] : self[i]; } return result.ToVector2(); } /// /// Computes the absolute value of each component in a vector. /// /// Source vector /// Vector whose components are the absolute values public static Vector2 Abs(this in Vector2 self) { return new Vector2(Mathf.Abs(self.x), Mathf.Abs(self.y)); } /// /// Clamps values component by component. /// /// Vector whose components to clamp /// Minimum values /// Maximum values /// Clamped vector public static Vector2 Clamp(this in Vector2 self, in Vector2 min, in Vector2 max) { float[] result = new float[VectorLength]; for (int i = 0; i < VectorLength; ++i) { result[i] = Mathf.Clamp(self[i], min[i], max[i]); } return result.ToVector2(); } /// /// returns a vector with all components containing 1/component, checking for divisions by 0. Divisions by 0 have a /// result of 0. /// /// Source vector /// Result vector public static Vector2 Inverse(this in Vector2 self) { return new Vector2(Mathf.Approximately(self.x, 0f) ? 0f : 1f / self.x, Mathf.Approximately(self.y, 0f) ? 0f : 1f / self.y); } /// /// Multiplies two component by component. /// /// Operand A /// Operand B /// Result of multiplying both vectors component by component public static Vector2 Multiply(this in Vector2 self, in Vector2 other) { return new Vector2(self.x * other.x, self.y * other.y); } /// /// Divides a by another, checking for divisions by 0. Divisions by 0 have a result of 0. /// /// Dividend /// Divisor /// Result vector public static Vector2 Divide(this in Vector2 self, in Vector2 divisor) { return self.Multiply(divisor.Inverse()); } /// /// Transforms an array of floats to a component by component. If there are not enough values to /// read, the remaining values are set to NaN. /// /// Source data /// Result vector public static Vector2 ToVector2(this float[] data) { return data.Length switch { 0 => NaN, 1 => new Vector2(data[0], float.NaN), _ => new Vector2(data[0], data[1]) }; } /// /// Tries to parse a from a string. /// /// Source string /// Parsed vector or NaN if there was an error /// Whether the vector was parsed successfully public static bool TryParse(string s, out Vector2 result) { try { result = Parse(s); return true; } catch { result = NaN; return false; } } /// /// Parses a from a string. /// /// Source string /// Parsed vector public static Vector2 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.ToVector2(); } /// /// Tries to parse a from a string, asynchronously. /// /// Source string /// Optional cancellation token, to cancel the operation /// Awaitable task returning the parsed vector or null if there was an error public static Task ParseAsync(string s, CancellationToken ct = default) { return Task.Run(() => TryParse(s, out Vector2 result) ? result : (Vector2?)null, ct); } #endregion #region Private Types & Data private const int VectorLength = 2; private const string CardinalSeparator = ","; private static readonly char[] s_cardinalSeparator = CardinalSeparator.ToCharArray(); private static readonly Vector2 s_nan = float.NaN * Vector2.one; #endregion } }