Add ultimate xr

This commit is contained in:
2024-08-06 21:58:35 +02:00
parent 864033bf10
commit 7165bacd9d
3952 changed files with 2162037 additions and 35 deletions

View File

@@ -0,0 +1,88 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Matrix4x4Ext.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Extensions.Unity.Math
{
/// <summary>
/// <see cref="Matrix4x4" /> extensions.
/// </summary>
public static class Matrix4x4Ext
{
#region Public Methods
/// <summary>
/// Interpolates two matrices by decomposing the position, rotation and scale values and interpolating them separately.
/// </summary>
/// <param name="matrixA">Source matrix</param>
/// <param name="matrixB">Destination matrix</param>
/// <param name="blendValue">Interpolation value [0.0, 1.0]</param>
/// <returns>Interpolated matrix</returns>
public static Matrix4x4 Interpolate(Matrix4x4 matrixA, Matrix4x4 matrixB, float blendValue)
{
Vector3 position = Vector3.Lerp(matrixA.MultiplyPoint(Vector3.zero), matrixB.MultiplyPoint(Vector3.zero), blendValue);
Quaternion rotation = Quaternion.Slerp(matrixA.rotation, matrixB.rotation, blendValue);
Vector3 scale = Vector3.Lerp(matrixA.lossyScale, matrixB.lossyScale, blendValue);
return Matrix4x4.TRS(position, rotation, scale);
}
/// <summary>
/// Computes a projection matrix so that it has an oblique near clip plane.
/// </summary>
/// <param name="projection">Projection matrix</param>
/// <param name="clipPlane">Clipping plane in camera space</param>
/// <returns>Projection matrix with oblique clip plane</returns>
public static Matrix4x4 GetObliqueMatrix(this Matrix4x4 projection, Vector4 clipPlane)
{
Matrix4x4 oblique = projection;
Vector4 q = projection.inverse * new Vector4(Mathf.Sign(clipPlane.x), Mathf.Sign(clipPlane.y), 1.0f, 1.0f);
Vector4 c = clipPlane * (2.0F / Vector4.Dot(clipPlane, q));
//third row = clip plane - fourth row
oblique[2] = c.x - projection[3];
oblique[6] = c.y - projection[7];
oblique[10] = c.z - projection[11];
oblique[14] = c.w - projection[15];
return oblique;
}
/// <summary>
/// Computes the reflection matrix around the given plane.
/// </summary>
/// <param name="plane">Reflection plane</param>
/// <returns>Reflected matrix</returns>
public static Matrix4x4 GetReflectionMatrix(Vector4 plane)
{
Matrix4x4 reflectionMat;
reflectionMat.m00 = 1F - 2F * plane[0] * plane[0];
reflectionMat.m01 = -2F * plane[0] * plane[1];
reflectionMat.m02 = -2F * plane[0] * plane[2];
reflectionMat.m03 = -2F * plane[3] * plane[0];
reflectionMat.m10 = -2F * plane[1] * plane[0];
reflectionMat.m11 = 1F - 2F * plane[1] * plane[1];
reflectionMat.m12 = -2F * plane[1] * plane[2];
reflectionMat.m13 = -2F * plane[3] * plane[1];
reflectionMat.m20 = -2F * plane[2] * plane[0];
reflectionMat.m21 = -2F * plane[2] * plane[1];
reflectionMat.m22 = 1F - 2F * plane[2] * plane[2];
reflectionMat.m23 = -2F * plane[3] * plane[2];
reflectionMat.m30 = 0F;
reflectionMat.m31 = 0F;
reflectionMat.m32 = 0F;
reflectionMat.m33 = 1F;
return reflectionMat;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3f1ff835b97dd6f439498b64c5c9394d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,288 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="QuaternionExt.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
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
{
/// <summary>
/// <see cref="Quaternion" /> extensions.
/// </summary>
public static class QuaternionExt
{
#region Public Types & Data
/// <summary>
/// Represents a NaN Quaternion.
/// </summary>
public static ref readonly Quaternion NaN => ref s_nan;
#endregion
#region Public Methods
/// <summary>
/// Compares two Unity Quaternion objects for equality with a specified precision threshold.
/// </summary>
/// <param name="a">The first Quaternion to compare</param>
/// <param name="b">The second Quaternion to compare</param>
/// <param name="precisionThreshold">
/// The precision threshold for float comparisons. Defaults to
/// <see cref="UxrConstants.Math.DefaultPrecisionThreshold" />.
/// </param>
/// <returns>
/// <c>true</c> if the Quaternion objects are equal; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// This method performs a component-wise comparison between two Quaternion objects.
/// Each component is compared using the specified precision threshold for float comparisons.
/// </remarks>
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;
}
/// <summary>
/// Transforms a <see cref="Quaternion" /> to a <see cref="Vector4" /> component by component.
/// </summary>
/// <param name="self">Source quaternion</param>
/// <returns><see cref="Vector4" /> with the components of the <see cref="Quaternion" /></returns>
public static Vector4 ToVector4(this in Quaternion self)
{
return new Vector4(self.x, self.y, self.z, self.w);
}
/// <summary>
/// Checks whether the given <see cref="Quaternion" /> has any NaN value.
/// </summary>
/// <param name="self">Source quaternion</param>
/// <returns>Whether the quaternion has any NaN value</returns>
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);
}
/// <summary>
/// Checks whether the given <see cref="Quaternion" /> has any infinity value.
/// </summary>
/// <param name="self">Source quaternion</param>
/// <returns>Whether the quaternion has any infinity value</returns>
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);
}
/// <summary>
/// Checks whether the given <see cref="Quaternion" /> has any 0 value.
/// </summary>
/// <param name="self">Source quaternion</param>
/// <returns>Whether the quaternion has any 0 value</returns>
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;
}
/// <summary>
/// Checks whether the given <see cref="Quaternion" /> contains valid data.
/// </summary>
/// <param name="self">Source quaternion</param>
/// <returns>Whether the quaternion contains valid data</returns>
public static bool IsValid(this in Quaternion self)
{
return !self.IsNaN() && !self.IsInfinity() && !self.IsZero();
}
/// <summary>
/// Multiplies two quaternions component by component.
/// </summary>
/// <param name="self">Operand A</param>
/// <param name="other">Operand B</param>
/// <returns>Result quaternion</returns>
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);
}
/// <summary>
/// Computes the inverse of a quaternion component by component (1 / value), checking for divisions by 0. Divisions by
/// 0 have a result of 0.
/// </summary>
/// <param name="self">Source quaternion</param>
/// <returns>Result quaternion</returns>
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);
}
/// <summary>
/// Divides two quaternions component by component, checking for divisions by 0. Divisions by 0 have a result of 0.
/// </summary>
/// <param name="self">Dividend</param>
/// <param name="divisor">Divisor</param>
/// <returns>Result quaternion</returns>
public static Quaternion Divide(this in Quaternion self, in Quaternion divisor)
{
return self.Multiply(divisor.Inverse());
}
/// <summary>
/// Computes the average quaternion from a list.
/// </summary>
/// <param name="quaternions">List of quaternions</param>
/// <param name="defaultIfEmpty">The default value to return if the list of quaternions is empty</param>
/// <returns>Average quaternion</returns>
/// <remarks>
/// From
/// https://gamedev.stackexchange.com/questions/119688/calculate-average-of-arbitrary-amount-of-quaternions-recursion
/// </remarks>
public static Quaternion Average(IEnumerable<Quaternion> 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);
}
/// <summary>
/// Applies the transformation to make a rotation defined by <paramref name="sourceRotation" /> rotate towards
/// <paramref name="targetRotation" />.
/// </summary>
/// <param name="self">Quaternion to apply the rotation to</param>
/// <param name="sourceRotation">Source rotation that will try to match <paramref name="targetRotation" /></param>
/// <param name="targetRotation">Target rotation to match</param>
/// <param name="t">Optional interpolation value [0.0, 1.0]</param>
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);
}
/// <summary>
/// Parses a <see cref="Quaternion" />.
/// </summary>
/// <param name="s">Source string</param>
/// <returns>Result quaternion</returns>
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();
}
/// <summary>
/// Tries to parse a <see cref="Quaternion" />.
/// </summary>
/// <param name="s">Source string</param>
/// <param name="result">Parsed quaternion</param>
/// <returns>Whether the quaternion was successfully parsed</returns>
public static bool TryParse(string s, out Quaternion result)
{
try
{
result = Parse(s);
return true;
}
catch
{
result = NaN;
return false;
}
}
/// <summary>
/// Creates a <see cref="Quaternion" /> from a float array. If the array does not contain enough elements, the missing
/// components will contain NaN.
/// </summary>
/// <param name="data">Source data</param>
/// <returns>Result quaternion</returns>
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])
};
}
/// <summary>
/// Parses a <see cref="Quaternion" /> asynchronously.
/// </summary>
/// <param name="s">Source string</param>
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
/// <returns>Awaitable <see cref="Task" /> that returns the parsed <see cref="Quaternion" /> or null if there was an error</returns>
public static Task<Quaternion?> 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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f4c05ae95c781bc4d933d3426cda4dd9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9d593b8ddf2258041a634b667c08a789
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,172 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Vector2IntExt.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using UltimateXR.Extensions.System;
using UnityEngine;
namespace UltimateXR.Extensions.Unity.Math
{
/// <summary>
/// <see cref="Vector2Int" /> extensions.
/// </summary>
public static class Vector2IntExt
{
#region Public Types & Data
/// <summary>
/// Representation of the negative infinity vector.
/// </summary>
public static ref readonly Vector2Int NegativeInfinity => ref s_negativeInfinity;
/// <summary>
/// Representation of the positive infinity vector.
/// </summary>
public static ref readonly Vector2Int PositiveInfinity => ref s_positiveInfinity;
#endregion
#region Public Methods
/// <summary>
/// Checks whether any vector component stores an infinity value.
/// </summary>
/// <param name="self">Vector to check</param>
/// <returns>Whether any component has an infinity value</returns>
public static bool IsInfinity(this in Vector2Int self)
{
return self.x == int.MinValue || self.x == int.MaxValue || self.y == int.MinValue || self.y == int.MaxValue;
}
/// <summary>
/// Computes the absolute values of each vector component.
/// </summary>
/// <param name="self">Input vector</param>
/// <returns>Result vector where each component is the absolute value of the input value component</returns>
public static Vector2Int Abs(this in Vector2Int self)
{
return new Vector2Int(Mathf.Abs(self.x), Mathf.Abs(self.y));
}
/// <summary>
/// Clamps the vector components between min and max values.
/// </summary>
/// <param name="self">Input vector whose values to clamp</param>
/// <param name="min">Minimum component values</param>
/// <param name="max">Maximum component values</param>
/// <returns>Clamped vector</returns>
public static Vector2Int Clamp(this in Vector2Int self, in Vector2Int min, in Vector2Int max)
{
int[] result = new int[VectorLength];
for (int i = 0; i < VectorLength; ++i)
{
result[i] = Mathf.Clamp(self[i], min[i], max[i]);
}
return result.ToVector2Int();
}
/// <summary>
/// Replaces NaN component values with <paramref name="other" /> valid values.
/// </summary>
/// <param name="self">Vector whose NaN values to replace</param>
/// <param name="other">Vector with valid values</param>
/// <returns>Result vector</returns>
public static Vector2Int FillNanWith(this in Vector2Int self, in Vector2Int other)
{
int[] result = new int[VectorLength];
for (int i = 0; i < VectorLength; ++i)
{
result[i] = self.x == int.MinValue || self.x == int.MaxValue ? other[i] : self[i];
}
return result.ToVector2Int();
}
/// <summary>
/// Transforms an array of ints to a <see cref="Vector2Int" /> component by component.
/// </summary>
/// <param name="data">Source data</param>
/// <returns>Result vector</returns>
public static Vector2Int ToVector2Int(this int[] data)
{
Array.Resize(ref data, VectorLength);
return new Vector2Int(data[0], data[1]);
}
/// <summary>
/// Tries to parse a <see cref="Vector2Int" /> from a string.
/// </summary>
/// <param name="s">Source string</param>
/// <param name="result">Parsed vector or <see cref="PositiveInfinity" /> if there was an error</param>
/// <returns>Whether the vector was parsed successfully</returns>
public static bool TryParse(string s, out Vector2Int result)
{
try
{
result = Parse(s);
return true;
}
catch
{
result = PositiveInfinity;
return false;
}
}
/// <summary>
/// Parses a <see cref="Vector2Int" /> from a string.
/// </summary>
/// <param name="s">Source string</param>
/// <returns>Parsed vector</returns>
public static Vector2Int 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
int[] result = new int[VectorLength];
for (int i = 0; i < sArray.Length; ++i)
{
result[i] = int.Parse(sArray[i], CultureInfo.InvariantCulture.NumberFormat);
}
return result.ToVector2Int();
}
/// <summary>
/// Tries to parse a <see cref="Vector2Int" /> from a string, asynchronously.
/// </summary>
/// <param name="s">Source string</param>
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
/// <returns>Awaitable task returning the parsed vector or null if there was an error</returns>
public static Task<Vector2Int?> ParseAsync(string s, CancellationToken ct = default)
{
return Task.Run(() => TryParse(s, out Vector2Int result) ? result : (Vector2Int?)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 Vector2Int s_negativeInfinity = int.MinValue * Vector2Int.one;
private static readonly Vector2Int s_positiveInfinity = int.MaxValue * Vector2Int.one;
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6951a64e8a7d31648bcde9c54e39eb06
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,727 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Vector3Ext.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
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 UltimateXR.Extensions.System.Math;
using UnityEngine;
namespace UltimateXR.Extensions.Unity.Math
{
/// <summary>
/// <see cref="Vector3" /> extensions.
/// </summary>
public static class Vector3Ext
{
#region Public Types & Data
/// <summary>
/// Represents the NaN vector, an invalid value.
/// </summary>
public static ref readonly Vector3 NaN => ref s_nan;
/// <summary>
/// Represents the Vector3 with minimum float values per component.
/// </summary>
public static ref readonly Vector3 MinValue => ref s_minValue;
/// <summary>
/// Represents the Vector3 with maximum float values per component.
/// </summary>
public static ref readonly Vector3 MaxValue => ref s_maxValue;
#endregion
#region Public Methods
/// <summary>
/// Compares two Unity Vector3 objects for equality with a specified precision threshold.
/// </summary>
/// <param name="a">The first Vector3 to compare</param>
/// <param name="b">The second Vector3 to compare</param>
/// <param name="precisionThreshold">
/// The precision threshold for float comparisons. Defaults to
/// <see cref="UxrConstants.Math.DefaultPrecisionThreshold" />.
/// </param>
/// <returns>
/// <c>true</c> if the Vector3 objects are equal; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// This method performs a component-wise comparison between two Vector3 objects.
/// Each component is compared using the specified precision threshold for float comparisons.
/// </remarks>
public static bool EqualsUsingPrecision(this Vector3 a, Vector3 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;
}
/// <summary>
/// Checks whether the given vector has any NaN component.
/// </summary>
/// <param name="self">Source vector</param>
/// <returns>Whether any of the vector components has a NaN value</returns>
public static bool IsNaN(this in Vector3 self)
{
return float.IsNaN(self.x) || float.IsNaN(self.y) || float.IsNaN(self.z);
}
/// <summary>
/// Checks whether the given vector has any infinity component.
/// </summary>
/// <param name="self">Source vector</param>
/// <returns>Whether any of the vector components has an infinity value</returns>
public static bool IsInfinity(this in Vector3 self)
{
return float.IsInfinity(self.x) || float.IsInfinity(self.y) || float.IsInfinity(self.z);
}
/// <summary>
/// Checks whether the given vector contains valid data.
/// </summary>
/// <param name="self">Source vector</param>
/// <returns>Whether the vector contains all valid values</returns>
public static bool IsValid(this in Vector3 self)
{
return !self.IsNaN() && !self.IsInfinity();
}
/// <summary>
/// Replaces NaN component values with <paramref name="other" /> valid values.
/// </summary>
/// <param name="self">Vector whose NaN values to replace</param>
/// <param name="other">Vector with valid values</param>
/// <returns>Result vector</returns>
public static Vector3 FillNanWith(this in Vector3 self, in Vector3 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.ToVector3();
}
/// <summary>
/// Computes the absolute value of each component in a vector.
/// </summary>
/// <param name="self">Source vector</param>
/// <returns>Vector whose components are the absolute values</returns>
public static Vector3 Abs(this in Vector3 self)
{
return new Vector3(Mathf.Abs(self.x), Mathf.Abs(self.y), Mathf.Abs(self.z));
}
/// <summary>
/// Clamps <see cref="Vector3" /> values component by component.
/// </summary>
/// <param name="self">Vector whose components to clamp</param>
/// <param name="min">Minimum values</param>
/// <param name="max">Maximum values</param>
/// <returns>Clamped vector</returns>
public static Vector3 Clamp(this in Vector3 self, in Vector3 min, in Vector3 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.ToVector3();
}
/// <summary>
/// Fixes Euler angles so that they are always in the -180, 180 degrees range.
/// </summary>
/// <param name="self">Euler angles to fix</param>
/// <returns>Euler angles in the -180, 180 degrees range</returns>
public static Vector3 ToEuler180(this in Vector3 self)
{
float[] result = new float[VectorLength];
for (int i = 0; i < VectorLength; ++i)
{
result[i] = self[i].ToEuler180();
}
return result.ToVector3();
}
/// <summary>
/// Computes the average of a set of vectors.
/// </summary>
/// <param name="vectors">Input vectors</param>
/// <returns>Vector with components averaged</returns>
public static Vector3 Average(params Vector3[] vectors)
{
return new Vector3(vectors.Average(v => v.x),
vectors.Average(v => v.y),
vectors.Average(v => v.z));
}
/// <summary>
/// Computes the average of a set of vectors.
/// </summary>
/// <param name="vectors">Input vectors</param>
/// <param name="defaultIfEmpty">The default value to return if the list is empty</param>
/// <returns>Vector with components averaged</returns>
public static Vector3 Average(IEnumerable<Vector3> vectors, Vector3 defaultIfEmpty = default)
{
if (vectors == null || !vectors.Any())
{
return defaultIfEmpty;
}
return new Vector3(vectors.Average(v => v.x),
vectors.Average(v => v.y),
vectors.Average(v => v.z));
}
/// <summary>
/// Computes the maximum values of a set of vectors.
/// </summary>
/// <param name="vectors">Input vectors</param>
/// <returns>Vector with maximum component values</returns>
public static Vector3 Max(params Vector3[] vectors)
{
return new Vector3(vectors.Max(v => v.x),
vectors.Max(v => v.y),
vectors.Max(v => v.z));
}
/// <summary>
/// Computes the maximum values of a set of vectors.
/// </summary>
/// <param name="vectors">Input vectors</param>
/// <param name="defaultIfEmpty">The default value to return if the list is empty</param>
/// <returns>Vector with maximum component values</returns>
public static Vector3 Max(IEnumerable<Vector3> vectors, Vector3 defaultIfEmpty = default)
{
if (vectors == null || !vectors.Any())
{
return defaultIfEmpty;
}
return new Vector3(vectors.Max(v => v.x),
vectors.Max(v => v.y),
vectors.Max(v => v.z));
}
/// <summary>
/// Computes the minimum values of a set of vectors.
/// </summary>
/// <param name="vectors">Input vectors</param>
/// <returns>Vector with minimum component values</returns>
public static Vector3 Min(params Vector3[] vectors)
{
return new Vector3(vectors.Min(v => v.x),
vectors.Min(v => v.y),
vectors.Min(v => v.z));
}
/// <summary>
/// Computes the minimum values of a set of vectors.
/// </summary>
/// <param name="vectors">Input vectors</param>
/// <param name="defaultIfEmpty">The default value to return if the list is empty</param>
/// <returns>Vector with minimum component values</returns>
public static Vector3 Min(IEnumerable<Vector3> vectors, Vector3 defaultIfEmpty = default)
{
if (vectors == null || !vectors.Any())
{
return defaultIfEmpty;
}
return new Vector3(vectors.Min(v => v.x),
vectors.Min(v => v.y),
vectors.Min(v => v.z));
}
/// <summary>
/// returns a vector with all components containing 1/component, checking for divisions by 0. Divisions by 0 have a
/// result of 0.
/// </summary>
/// <param name="self">Source vector</param>
/// <returns>Result vector</returns>
public static Vector3 Inverse(this in Vector3 self)
{
return new Vector3(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);
}
/// <summary>
/// Gets the number of components that are different between two vectors.
/// </summary>
/// <param name="a">First vector</param>
/// <param name="b">Second vector</param>
/// <returns>The number of components [0, 3] that are different</returns>
public static int DifferentComponentCount(Vector3 a, Vector3 b)
{
int count = 0;
for (int axisIndex = 0; axisIndex < 3; ++axisIndex)
{
if (!Mathf.Approximately(a[axisIndex], b[axisIndex]))
{
count++;
}
}
return count;
}
/// <summary>
/// Multiplies two <see cref="Vector3" /> component by component.
/// </summary>
/// <param name="self">Operand A</param>
/// <param name="other">Operand B</param>
/// <returns>Result of multiplying both vectors component by component</returns>
public static Vector3 Multiply(this in Vector3 self, in Vector3 other)
{
return new Vector3(self.x * other.x,
self.y * other.y,
self.z * other.z);
}
/// <summary>
/// Divides a <see cref="Vector3" /> by another, checking for divisions by 0. Divisions by 0 have a result of 0.
/// </summary>
/// <param name="self">Dividend</param>
/// <param name="divisor">Divisor</param>
/// <returns>Result vector</returns>
public static Vector3 Divide(this in Vector3 self, in Vector3 divisor)
{
return self.Multiply(divisor.Inverse());
}
/// <summary>
/// Transforms an array of floats to a <see cref="Vector3" /> component by component. If there are not enough values to
/// read, the remaining values are set to NaN.
/// </summary>
/// <param name="data">Source data</param>
/// <returns>Result vector</returns>
public static Vector3 ToVector3(this float[] data)
{
return data.Length switch
{
0 => NaN,
1 => new Vector3(data[0], float.NaN, float.NaN),
2 => new Vector3(data[0], data[1], float.NaN),
_ => new Vector3(data[0], data[1], data[2])
};
}
/// <summary>
/// Tries to parse a <see cref="Vector3" /> from a string.
/// </summary>
/// <param name="s">Source string</param>
/// <param name="result">Parsed vector or <see cref="NaN" /> if there was an error</param>
/// <returns>Whether the vector was parsed successfully</returns>
public static bool TryParse(string s, out Vector3 result)
{
try
{
result = Parse(s);
return true;
}
catch
{
result = NaN;
return false;
}
}
/// <summary>
/// Parses a <see cref="Vector3" /> from a string.
/// </summary>
/// <param name="s">Source string</param>
/// <returns>Parsed vector</returns>
public static Vector3 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.ToVector3();
}
/// <summary>
/// Tries to parse a <see cref="Vector3" /> from a string, asynchronously.
/// </summary>
/// <param name="s">Source string</param>
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
/// <returns>Awaitable task returning the parsed vector or null if there was an error</returns>
public static Task<Vector3?> ParseAsync(string s, CancellationToken ct = default)
{
return Task.Run(() => TryParse(s, out Vector3 result) ? result : (Vector3?)null, ct);
}
/// <summary>
/// Gets the vector which is the dominant negative or positive axis it is mostly pointing towards.
/// </summary>
/// <param name="vector">Vector to process</param>
/// <returns>
/// Can return <see cref="Vector3.right" />, <see cref="Vector3.up" />, <see cref="Vector3.forward" />, -
/// <see cref="Vector3.right" />, -<see cref="Vector3.up" /> or -<see cref="Vector3.forward" />.
/// </returns>
public static Vector3 GetClosestAxis(this Vector3 vector)
{
float absX = Mathf.Abs(vector.x);
float absY = Mathf.Abs(vector.y);
float absZ = Mathf.Abs(vector.z);
if (absX > absY)
{
return absX > absZ ? Mathf.Sign(vector.x) * Vector3.right : Mathf.Sign(vector.z) * Vector3.forward;
}
return absY > absZ ? Mathf.Sign(vector.y) * Vector3.up : Mathf.Sign(vector.z) * Vector3.forward;
}
/// <summary>
/// Computes a perpendicular vector.
/// </summary>
/// <param name="vector">Vector to compute another perpendicular to</param>
/// <returns>Perpendicular vector in 3D space</returns>
public static Vector3 GetPerpendicularVector(this Vector3 vector)
{
if (Mathf.Approximately(vector.x, 0.0f) == false)
{
return new Vector3(-vector.y, vector.x, 0.0f);
}
if (Mathf.Approximately(vector.y, 0.0f) == false)
{
return new Vector3(0.0f, -vector.z, vector.y);
}
return new Vector3(vector.z, 0.0f, -vector.y);
}
/// <summary>
/// Computes the signed distance from a point to a plane.
/// </summary>
/// <param name="point">The point to compute the distance from</param>
/// <param name="planePoint">Point in a plane</param>
/// <param name="planeNormal">Plane normal</param>
/// <returns>Signed distance from a point to a plane</returns>
public static float DistanceToPlane(this Vector3 point, Vector3 planePoint, Vector3 planeNormal)
{
return new Plane(planeNormal, planePoint).GetDistanceToPoint(point);
}
/// <summary>
/// Computes the distance from a point to a line.
/// </summary>
/// <param name="point">The point to compute the distance from</param>
/// <param name="lineA">Point A in the line</param>
/// <param name="lineB">Point B in the line</param>
/// <returns>Distance from point to the line</returns>
public static float DistanceToLine(this Vector3 point, Vector3 lineA, Vector3 lineB)
{
return Vector3.Cross(lineB - lineA, point - lineA).magnitude;
}
/// <summary>
/// Computes the distance from a point to a segment.
/// </summary>
/// <param name="point">The point to compute the distance from</param>
/// <param name="segmentA">Segment start point</param>
/// <param name="segmentB">Segment end point</param>
/// <returns>Distance from point to the segment</returns>
public static float DistanceToSegment(this Vector3 point, Vector3 segmentA, Vector3 segmentB)
{
Vector3 ab = segmentB - segmentA;
Vector3 av = point - segmentA;
if (Vector3.Dot(av, ab) <= 0.0f)
{
return av.magnitude;
}
Vector3 bv = point - segmentB;
if (Vector3.Dot(bv, ab) >= 0.0)
{
return bv.magnitude;
}
return Vector3.Cross(ab, av).magnitude / ab.magnitude;
}
/// <summary>
/// Computes the closest point in a segment to another point.
/// </summary>
/// <param name="point">The point to project</param>
/// <param name="segmentA">Segment start point</param>
/// <param name="segmentB">Segment end point</param>
/// <returns>Closest point in the segment</returns>
public static Vector3 ProjectOnSegment(this Vector3 point, Vector3 segmentA, Vector3 segmentB)
{
Vector3 ab = segmentB - segmentA;
Vector3 av = point - segmentA;
if (Vector3.Dot(av, ab) <= 0.0f)
{
return segmentA;
}
Vector3 bv = point - segmentB;
if (Vector3.Dot(bv, ab) >= 0.0)
{
return segmentB;
}
return segmentA + Vector3.Project(av, ab.normalized);
}
/// <summary>
/// Computes the closest point in a line to another point.
/// </summary>
/// <param name="point">The point to project</param>
/// <param name="pointInLine">Point in the line</param>
/// <param name="lineDirection">Line direction</param>
/// <returns>Point projected on the line</returns>
public static Vector3 ProjectOnLine(this Vector3 point, Vector3 pointInLine, Vector3 lineDirection)
{
return pointInLine + Vector3.Project(point - pointInLine, lineDirection);
}
/// <summary>
/// Checks if a point is inside a sphere. Supports spheres without uniform scaling.
/// </summary>
/// <param name="point">Point to check</param>
/// <param name="sphere">Sphere collider to test against</param>
/// <returns>Boolean telling whether the point is inside</returns>
public static bool IsInsideSphere(this Vector3 point, SphereCollider sphere)
{
Vector3 localPos = sphere.transform.InverseTransformPoint(point);
return localPos.magnitude <= sphere.radius;
}
/// <summary>
/// Checks if a point is inside of a BoxCollider.
/// </summary>
/// <param name="point">Point in world coordinates</param>
/// <param name="box">Box collider to test against</param>
/// <param name="margin">Optional margin to be added to the each of the box sides</param>
/// <param name="marginIsWorld">Whether the margin is specified in world coordinates or local</param>
/// <returns>Whether point is inside</returns>
public static bool IsInsideBox(this Vector3 point, BoxCollider box, Vector3 margin = default, bool marginIsWorld = true)
{
if (box == null)
{
return false;
}
Vector3 localPos = box.transform.InverseTransformPoint(point);
if (marginIsWorld && box.transform.lossyScale != Vector3.one)
{
Vector3 pointPlusX = box.transform.InverseTransformPoint(point + box.transform.right);
Vector3 pointPlusY = box.transform.InverseTransformPoint(point + box.transform.up);
Vector3 pointPlusZ = box.transform.InverseTransformPoint(point + box.transform.forward);
margin.x *= Vector3.Distance(localPos, pointPlusX);
margin.y *= Vector3.Distance(localPos, pointPlusY);
margin.z *= Vector3.Distance(localPos, pointPlusZ);
}
if (localPos.x - box.center.x >= -box.size.x * 0.5f - margin.x && localPos.x - box.center.x <= box.size.x * 0.5f + margin.x)
{
if (localPos.y - box.center.y >= -box.size.y * 0.5f - margin.y && localPos.y - box.center.y <= box.size.y * 0.5f + margin.y)
{
if (localPos.z - box.center.z >= -box.size.z * 0.5f - margin.z && localPos.z - box.center.z <= box.size.z * 0.5f + margin.z)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Checks if a point is inside of a box.
/// </summary>
/// <param name="point">Point in world coordinates</param>
/// <param name="boxPosition">The box position in world space</param>
/// <param name="boxRotation">The box rotation in world space</param>
/// <param name="boxScale">The box scale</param>
/// <param name="boxCenter">The box center in local box coordinates</param>
/// <param name="boxSize">The box size in local box coordinates</param>
/// <param name="margin">Optional margin to be added to the each of the box sides</param>
/// <returns>True if it is inside, false if not</returns>
public static bool IsInsideBox(this Vector3 point,
Vector3 boxPosition,
Quaternion boxRotation,
Vector3 boxScale,
Vector3 boxCenter,
Vector3 boxSize,
Vector3 margin = default)
{
Matrix4x4 boxMatrix = Matrix4x4.TRS(boxPosition, boxRotation, boxScale);
Matrix4x4 inverseBoxMatrix = boxMatrix.inverse;
Vector3 localPos = inverseBoxMatrix.MultiplyPoint(point);
if (localPos.x - boxCenter.x >= -boxSize.x * 0.5f - margin.x && localPos.x - boxCenter.x <= boxSize.x * 0.5f + margin.x)
{
if (localPos.y - boxCenter.y >= -boxSize.y * 0.5f - margin.y && localPos.y - boxCenter.y <= boxSize.y * 0.5f + margin.y)
{
if (localPos.z - boxCenter.z >= -boxSize.z * 0.5f - margin.z && localPos.z - boxCenter.z <= boxSize.z * 0.5f + margin.z)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Checks if a point is inside of a BoxCollider. If it is outside, it is clamped to remain inside.
/// </summary>
/// <param name="point">Point in world coordinates</param>
/// <param name="box">Box collider to test against</param>
/// <returns>Point clamped inside given box volume</returns>
public static Vector3 ClampToBox(this Vector3 point, BoxCollider box)
{
if (box == null)
{
return point;
}
Vector3 pos = box.transform.InverseTransformPoint(point);
Vector3 center = box.center;
Vector3 halfBoxSize = box.size * 0.5f;
if (pos.x < center.x - halfBoxSize.x)
{
pos.x = center.x - halfBoxSize.x;
}
if (pos.x > center.x + halfBoxSize.x)
{
pos.x = center.x + halfBoxSize.x;
}
if (pos.y < center.y - halfBoxSize.y)
{
pos.y = center.y - halfBoxSize.y;
}
if (pos.y > center.y + halfBoxSize.y)
{
pos.y = center.y + halfBoxSize.y;
}
if (pos.z < center.z - halfBoxSize.z)
{
pos.z = center.z - halfBoxSize.z;
}
if (pos.z > center.z + halfBoxSize.z)
{
pos.z = center.z + halfBoxSize.z;
}
return box.transform.TransformPoint(pos);
}
/// <summary>
/// Checks if a point is inside of a SphereCollider. If it is outside, it is clamped to remain inside.
/// </summary>
/// <param name="point">Point in world coordinates</param>
/// <param name="sphere">Sphere collider to test against</param>
/// <returns>Point restricted to the given sphere volume</returns>
public static Vector3 ClampToSphere(this Vector3 point, SphereCollider sphere)
{
if (sphere == null)
{
return point;
}
Vector3 pos = sphere.transform.InverseTransformPoint(point);
Vector3 center = sphere.center;
float distance = Vector3.Distance(center, pos);
if (distance > sphere.radius)
{
pos = center + (pos - center).normalized * sphere.radius;
return sphere.transform.TransformPoint(pos);
}
return point;
}
/// <summary>
/// Computes the rotation of a direction around an axis.
/// </summary>
/// <param name="direction">Direction to rotate</param>
/// <param name="axis">The rotation axis to use for the rotation</param>
/// <param name="degrees">Rotation angle</param>
/// <returns>Rotated direction</returns>
public static Vector3 GetRotationAround(this Vector3 direction, Vector3 axis, float degrees)
{
return Quaternion.AngleAxis(degrees, axis) * direction;
}
/// <summary>
/// Computes the rotation of a point around a pivot and an axis.
/// </summary>
/// <param name="point">Point to rotate</param>
/// <param name="pivot">Pivot to rotate it around to</param>
/// <param name="axis">The rotation axis to use for the rotation</param>
/// <param name="degrees">Rotation angle</param>
/// <returns>Rotated point</returns>
public static Vector3 GetRotationAround(this Vector3 point, Vector3 pivot, Vector3 axis, float degrees)
{
Vector3 dir = point - pivot;
dir = Quaternion.AngleAxis(degrees, axis) * dir;
return dir + pivot;
}
#endregion
#region Private Types & Data
private const int VectorLength = 3;
private const string CardinalSeparator = ",";
private static readonly char[] s_cardinalSeparator = CardinalSeparator.ToCharArray();
private static readonly Vector3 s_nan = float.NaN * Vector3.one;
private static readonly Vector3 s_minValue = new Vector3(float.MinValue, float.MinValue, float.MinValue);
private static readonly Vector3 s_maxValue = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7392a554436d3d248aa19a303cdc65e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,174 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Vector3IntExt.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using UltimateXR.Extensions.System;
using UnityEngine;
namespace UltimateXR.Extensions.Unity.Math
{
/// <summary>
/// <see cref="Vector3Int" /> extensions.
/// </summary>
public static class Vector3IntExt
{
#region Public Types & Data
/// <summary>
/// Representation of the minimum int values per component.
/// </summary>
public static ref readonly Vector3Int MinValue => ref s_minValue;
/// <summary>
/// Representation of the maximum int values per component.
/// </summary>
public static ref readonly Vector3Int MaxValue => ref s_maxValue;
#endregion
#region Public Methods
/// <summary>
/// Checks whether any vector component stores an infinity value.
/// </summary>
/// <param name="self">Vector to check</param>
/// <returns>Whether any component has an infinity value</returns>
public static bool IsInfinity(this in Vector3Int self)
{
return self.x == int.MinValue || self.x == int.MaxValue ||
self.y == int.MinValue || self.y == int.MaxValue ||
self.z == int.MinValue || self.z == int.MaxValue;
}
/// <summary>
/// Computes the absolute values of each vector component.
/// </summary>
/// <param name="self">Input vector</param>
/// <returns>Result vector where each component is the absolute value of the input value component</returns>
public static Vector3Int Abs(this in Vector3Int self)
{
return new Vector3Int(Mathf.Abs(self.x), Mathf.Abs(self.y), Mathf.Abs(self.z));
}
/// <summary>
/// Clamps the vector components between min and max values.
/// </summary>
/// <param name="self">Input vector whose values to clamp</param>
/// <param name="min">Minimum component values</param>
/// <param name="max">Maximum component values</param>
/// <returns>Clamped vector</returns>
public static Vector3Int Clamp(this in Vector3Int self, in Vector3Int min, in Vector3Int max)
{
int[] result = new int[VectorLength];
for (int i = 0; i < VectorLength; ++i)
{
result[i] = Mathf.Clamp(self[i], min[i], max[i]);
}
return result.ToVector3Int();
}
/// <summary>
/// Replaces NaN component values with <paramref name="other" /> valid values.
/// </summary>
/// <param name="self">Vector whose NaN values to replace</param>
/// <param name="other">Vector with valid values</param>
/// <returns>Result vector</returns>
public static Vector3Int FillNaNWith(this in Vector3Int self, in Vector3Int other)
{
int[] result = new int[VectorLength];
for (int i = 0; i < VectorLength; ++i)
{
result[i] = self.x == int.MinValue || self.x == int.MaxValue ? other[i] : self[i];
}
return result.ToVector3Int();
}
/// <summary>
/// Transforms an array of ints to a <see cref="Vector3Int" /> component by component.
/// </summary>
/// <param name="data">Source data</param>
/// <returns>Result vector</returns>
public static Vector3Int ToVector3Int(this int[] data)
{
Array.Resize(ref data, VectorLength);
return new Vector3Int(data[0], data[1], data[2]);
}
/// <summary>
/// Tries to parse a <see cref="Vector3Int" /> from a string.
/// </summary>
/// <param name="s">Source string</param>
/// <param name="result">Parsed vector or <see cref="MaxValue" /> if there was an error</param>
/// <returns>Whether the vector was parsed successfully</returns>
public static bool TryParse(string s, out Vector3Int result)
{
try
{
result = Parse(s);
return true;
}
catch
{
result = MaxValue;
return false;
}
}
/// <summary>
/// Parses a <see cref="Vector3Int" /> from a string.
/// </summary>
/// <param name="s">Source string</param>
/// <returns>Parsed vector</returns>
public static Vector3Int 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
int[] result = new int[VectorLength];
for (int i = 0; i < sArray.Length; ++i)
{
result[i] = int.Parse(sArray[i], CultureInfo.InvariantCulture.NumberFormat);
}
return result.ToVector3Int();
}
/// <summary>
/// Tries to parse a <see cref="Vector3Int" /> from a string, asynchronously.
/// </summary>
/// <param name="s">Source string</param>
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
/// <returns>Awaitable task returning the parsed vector or null if there was an error</returns>
public static Task<Vector3Int?> ParseAsync(string s, CancellationToken ct = default)
{
return Task.Run(() => TryParse(s, out Vector3Int result) ? result : (Vector3Int?)null, ct);
}
#endregion
#region Private Types & Data
private const int VectorLength = 3;
private const string CardinalSeparator = ",";
private static readonly char[] s_cardinalSeparator = CardinalSeparator.ToCharArray();
private static readonly Vector3Int s_minValue = int.MinValue * Vector3Int.one;
private static readonly Vector3Int s_maxValue = int.MaxValue * Vector3Int.one;
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dec4dbd92ff86464196213d4637743e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4fd65efcccc44c04db1fb0623a460cf3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: