Add ultimate xr
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f1ff835b97dd6f439498b64c5c9394d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4c05ae95c781bc4d933d3426cda4dd9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d593b8ddf2258041a634b667c08a789
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6951a64e8a7d31648bcde9c54e39eb06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7392a554436d3d248aa19a303cdc65e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dec4dbd92ff86464196213d4637743e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fd65efcccc44c04db1fb0623a460cf3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user