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