Compare commits

..

No commits in common. "67cf8b31fb9c6d4ec4c053da39ea1162f0e066f8" and "8357c5d6231947a3ecd32a81b723506640fff441" have entirely different histories.

16 changed files with 401 additions and 371 deletions

View File

@ -1,19 +0,0 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = crlf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false
max_line_length = 80
[*.cs]
csharp_new_line_before_open_brace = none
# Suppress warnings everywhere
dotnet_diagnostic.IDE1006.severity = none
dotnet_diagnostic.IDE0130.severity = none

View File

@ -1,11 +1,13 @@
using System; using System;
namespace LinearAlgebra { namespace LinearAlgebra
{
/// <summary> /// <summary>
/// %Angle utilities /// %Angle utilities
/// </summary> /// </summary>
public static class Angle { public static class Angle
{
public const float pi = 3.1415927410125732421875F; public const float pi = 3.1415927410125732421875F;
// public static float Rad2Deg = 360.0f / ((float)Math.PI * 2); // public static float Rad2Deg = 360.0f / ((float)Math.PI * 2);
// public static float Deg2Rad = ((float)Math.PI * 2) / 360.0f; // public static float Deg2Rad = ((float)Math.PI * 2) / 360.0f;
@ -21,7 +23,8 @@ namespace LinearAlgebra {
/// <param name="max">The maximum angle</param> /// <param name="max">The maximum angle</param>
/// <returns>The clamped angle</returns> /// <returns>The clamped angle</returns>
/// Angles are normalized /// Angles are normalized
public static float Clamp(float angle, float min, float max) { public static float Clamp(float angle, float min, float max)
{
float normalizedAngle = Normalize(angle); float normalizedAngle = Normalize(angle);
return Float.Clamp(normalizedAngle, min, max); return Float.Clamp(normalizedAngle, min, max);
} }
@ -33,7 +36,8 @@ namespace LinearAlgebra {
/// <param name="b">The second angle</param> /// <param name="b">The second angle</param>
/// <returns>the angle between the two angles</returns> /// <returns>the angle between the two angles</returns>
/// Angle values should be degrees /// Angle values should be degrees
public static float Difference(float a, float b) { public static float Difference(float a, float b)
{
float r = Normalize(b - a); float r = Normalize(b - a);
return r; return r;
} }
@ -44,7 +48,8 @@ namespace LinearAlgebra {
/// <param name="angle">The angle to normalize</param> /// <param name="angle">The angle to normalize</param>
/// <returns>The normalized angle in interval (-180..180] </returns> /// <returns>The normalized angle in interval (-180..180] </returns>
/// Angle values should be in degrees /// Angle values should be in degrees
public static float Normalize(float angle) { public static float Normalize(float angle)
{
if (float.IsInfinity(angle)) if (float.IsInfinity(angle))
return angle; return angle;
@ -61,7 +66,8 @@ namespace LinearAlgebra {
/// <param name="maxAngle">Maximum angle to rotate</param> /// <param name="maxAngle">Maximum angle to rotate</param>
/// <returns>The resulting angle</returns> /// <returns>The resulting angle</returns>
/// This function is compatible with radian and degrees angles /// This function is compatible with radian and degrees angles
public static float MoveTowards(float fromAngle, float toAngle, float maxAngle) { public static float MoveTowards(float fromAngle, float toAngle, float maxAngle)
{
float d = toAngle - fromAngle; float d = toAngle - fromAngle;
d = Normalize(d); d = Normalize(d);
d = Math.Sign(d) * Float.Clamp(Math.Abs(d), 0, maxAngle); d = Math.Sign(d) * Float.Clamp(Math.Abs(d), 0, maxAngle);
@ -77,7 +83,8 @@ namespace LinearAlgebra {
/// Vectors a and b must be normalized /// Vectors a and b must be normalized
/// \deprecated Please use Vector2.ToFactor instead. /// \deprecated Please use Vector2.ToFactor instead.
[Obsolete("Please use Vector2.ToFactor instead.")] [Obsolete("Please use Vector2.ToFactor instead.")]
public static float ToFactor(Vector2 v1, Vector2 v2) { public static float ToFactor(Vector2 v1, Vector2 v2)
{
return (1 - Vector2.Dot(v1, v2)) / 2; return (1 - Vector2.Dot(v1, v2)) / 2;
} }

View File

@ -6,57 +6,34 @@ using Vector3Float = UnityEngine.Vector3;
namespace LinearAlgebra namespace LinearAlgebra
{ {
/// <summary> public class Direction
/// A direction in 3D space {
/// </summary>
/// A direction is represented using two angles:
/// * The horizontal angle ranging from -180 (inclusive) to 180 (exclusive)
/// degrees which is a rotation in the horizontal plane
/// * A vertical angle ranging from -90 (inclusive) to 90 (exclusive) degrees
/// which is the rotation in the up/down direction applied after the horizontal
/// rotation has been applied.
/// The angles are automatically normalized to stay within the abovenmentioned
/// ranges.
public class Direction {
public float horizontal; public float horizontal;
public float vertical; public float vertical;
public Direction() { public Direction()
{
horizontal = 0; horizontal = 0;
vertical = 0; vertical = 0;
} }
// public Direction(float horizontal, float vertical) { public Direction(float horizontal, float vertical)
// this.horizontal = horizontal; {
// this.vertical = vertical; this.horizontal = horizontal;
// //Normalize(); this.vertical = vertical;
// }
public static Direction Degrees(float horizontal, float vertical) {
Direction d = new() {
horizontal = horizontal,
vertical = vertical
};
//Normalize(); //Normalize();
return d;
}
public static Direction Radians(float horizontal, float vertical) {
Direction d = new() {
horizontal = horizontal * Angle.Rad2Deg,
vertical = vertical * Angle.Rad2Deg
};
//Normalize();
return d;
} }
public readonly static Direction forward = Degrees(0, 0); public readonly static Direction forward = new Direction(0, 0);
public readonly static Direction backward = Degrees(-180, 0); public readonly static Direction backward = new Direction(-180, 0);
public readonly static Direction up = Degrees(0, 90); public readonly static Direction up = new Direction(0, 90);
public readonly static Direction down = Degrees(0, -90); public readonly static Direction down = new Direction(0, -90);
public readonly static Direction left = Degrees(-90, 0); public readonly static Direction left = new Direction(-90, 0);
public readonly static Direction right = Degrees(90, 0); public readonly static Direction right = new Direction(90, 0);
public void Normalize() { public void Normalize()
if (this.vertical > 90 || this.vertical < -90) { {
if (this.vertical > 90 || this.vertical < -90)
{
this.horizontal += 180; this.horizontal += 180;
this.vertical = 180 - this.vertical; this.vertical = 180 - this.vertical;
} }

View File

@ -1,9 +1,11 @@
namespace LinearAlgebra { namespace LinearAlgebra
{
/// <summary> /// <summary>
/// Float number utilities /// Float number utilities
/// </summary> /// </summary>
public class Float { public class Float
{
/// <summary> /// <summary>
/// The precision of float numbers /// The precision of float numbers
/// </summary> /// </summary>
@ -20,7 +22,8 @@ namespace LinearAlgebra {
/// <param name="min">The minimum value</param> /// <param name="min">The minimum value</param>
/// <param name="max">The maximum value</param> /// <param name="max">The maximum value</param>
/// <returns>The clamped value</returns> /// <returns>The clamped value</returns>
public static float Clamp(float f, float min, float max) { public static float Clamp(float f, float min, float max)
{
if (f < min) if (f < min)
return min; return min;
if (f > max) if (f > max)
@ -33,7 +36,8 @@ namespace LinearAlgebra {
/// </summary> /// </summary>
/// <param name="f">The value to clamp</param> /// <param name="f">The value to clamp</param>
/// <returns>The clamped value</returns> /// <returns>The clamped value</returns>
public static float Clamp01(float f) { public static float Clamp01(float f)
{
return Clamp(f, 0, 1); return Clamp(f, 0, 1);
} }
} }

View File

@ -5,7 +5,8 @@ using Vector2Float = UnityEngine.Vector2;
using Quaternion = UnityEngine.Quaternion; using Quaternion = UnityEngine.Quaternion;
#endif #endif
namespace LinearAlgebra { namespace LinearAlgebra
{
public readonly struct Slice public readonly struct Slice
{ {
@ -18,7 +19,8 @@ namespace LinearAlgebra {
} }
} }
public class Matrix2 { public class Matrix2
{
public float[,] data { get; } public float[,] data { get; }
public int nRows => data.GetLength(0); public int nRows => data.GetLength(0);
@ -28,13 +30,16 @@ namespace LinearAlgebra {
{ {
this.data = new float[nRows, nCols]; this.data = new float[nRows, nCols];
} }
public Matrix2(float[,] data) { public Matrix2(float[,] data)
{
this.data = data; this.data = data;
} }
public Matrix2 Clone() { public Matrix2 Clone()
{
float[,] data = new float[this.nRows, nCols]; float[,] data = new float[this.nRows, nCols];
for (int rowIx = 0; rowIx < this.nRows; rowIx++) { for (int rowIx = 0; rowIx < this.nRows; rowIx++)
{
for (int colIx = 0; colIx < this.nCols; colIx++) for (int colIx = 0; colIx < this.nCols; colIx++)
data[rowIx, colIx] = this.data[rowIx, colIx]; data[rowIx, colIx] = this.data[rowIx, colIx];
} }
@ -46,7 +51,8 @@ namespace LinearAlgebra {
return new Matrix2(nRows, nCols); return new Matrix2(nRows, nCols);
} }
public static Matrix2 FromVector3(Vector3Float v) { public static Matrix2 FromVector3(Vector3Float v)
{
float[,] result = new float[3, 1]; float[,] result = new float[3, 1];
result[0, 0] = v.x; result[0, 0] = v.x;
result[1, 0] = v.y; result[1, 0] = v.y;
@ -65,7 +71,8 @@ namespace LinearAlgebra {
return m; return m;
} }
public static Matrix2 Diagonal(Matrix1 v) { public static Matrix2 Diagonal(Matrix1 v)
{
float[,] resultData = new float[v.size, v.size]; float[,] resultData = new float[v.size, v.size];
for (int ix = 0; ix < v.size; ix++) for (int ix = 0; ix < v.size; ix++)
resultData[ix, ix] = v.data[ix]; resultData[ix, ix] = v.data[ix];
@ -91,7 +98,8 @@ namespace LinearAlgebra {
this.data[ix, ix] = f; this.data[ix, ix] = f;
} }
public static Matrix2 SkewMatrix(Vector3Float v) { public static Matrix2 SkewMatrix(Vector3Float v)
{
float[,] result = new float[3, 3] { float[,] result = new float[3, 3] {
{0, -v.z, v.y}, {0, -v.z, v.y},
{v.z, 0, -v.x}, {v.z, 0, -v.x},
@ -119,11 +127,13 @@ namespace LinearAlgebra {
return row; return row;
} }
#endif #endif
public void SetRow(int rowIx, Matrix1 v) { public void SetRow(int rowIx, Matrix1 v)
{
for (uint ix = 0; ix < v.size; ix++) for (uint ix = 0; ix < v.size; ix++)
this.data[rowIx, ix] = v.data[ix]; this.data[rowIx, ix] = v.data[ix];
} }
public void SetRow3(int rowIx, Vector3Float v) { public void SetRow3(int rowIx, Vector3Float v)
{
this.data[rowIx, 0] = v.x; this.data[rowIx, 0] = v.x;
this.data[rowIx, 1] = v.y; this.data[rowIx, 1] = v.y;
this.data[rowIx, 2] = v.z; this.data[rowIx, 2] = v.z;
@ -140,7 +150,8 @@ namespace LinearAlgebra {
public Matrix1 GetColumn(int colIx) public Matrix1 GetColumn(int colIx)
{ {
float[] column = new float[this.nRows]; float[] column = new float[this.nRows];
for (int i = 0; i < this.nRows; i++) { for (int i = 0; i < this.nRows; i++)
{
column[i] = this.data[i, colIx]; column[i] = this.data[i, colIx];
} }
return new Matrix1(column); return new Matrix1(column);
@ -155,9 +166,12 @@ namespace LinearAlgebra {
this.data[2, colIx] = v.z; this.data[2, colIx] = v.z;
} }
public static bool AllClose(Matrix2 A, Matrix2 B, float atol = 1e-08f) { public static bool AllClose(Matrix2 A, Matrix2 B, float atol = 1e-08f)
for (int i = 0; i < A.nRows; i++) { {
for (int j = 0; j < A.nCols; j++) { for (int i = 0; i < A.nRows; i++)
{
for (int j = 0; j < A.nCols; j++)
{
float d = MathF.Abs(A.data[i, j] - B.data[i, j]); float d = MathF.Abs(A.data[i, j] - B.data[i, j]);
if (d > atol) if (d > atol)
return false; return false;
@ -166,63 +180,75 @@ namespace LinearAlgebra {
return true; return true;
} }
public Matrix2 Transpose() { public Matrix2 Transpose()
{
float[,] resultData = new float[this.nCols, this.nRows]; float[,] resultData = new float[this.nCols, this.nRows];
for (uint rowIx = 0; rowIx < this.nRows; rowIx++) { for (uint rowIx = 0; rowIx < this.nRows; rowIx++)
{
for (uint colIx = 0; colIx < this.nCols; colIx++) for (uint colIx = 0; colIx < this.nCols; colIx++)
resultData[colIx, rowIx] = this.data[rowIx, colIx]; resultData[colIx, rowIx] = this.data[rowIx, colIx];
} }
return new Matrix2(resultData); return new Matrix2(resultData);
// double checked code // double checked code
} }
public Matrix2 transposed { public Matrix2 transposed
{
get => Transpose(); get => Transpose();
} }
public static Matrix2 operator -(Matrix2 m) { public static Matrix2 operator -(Matrix2 m)
{
float[,] result = new float[m.nRows, m.nCols]; float[,] result = new float[m.nRows, m.nCols];
for (int i = 0; i < m.nRows; i++) { for (int i = 0; i < m.nRows; i++)
{
for (int j = 0; j < m.nCols; j++) for (int j = 0; j < m.nCols; j++)
result[i, j] = -m.data[i, j]; result[i, j] = -m.data[i, j];
} }
return new Matrix2(result); return new Matrix2(result);
} }
public static Matrix2 operator -(Matrix2 A, Matrix2 B) { public static Matrix2 operator -(Matrix2 A, Matrix2 B)
{
if (A.nRows != B.nRows || A.nCols != B.nCols) if (A.nRows != B.nRows || A.nCols != B.nCols)
throw new System.ArgumentException("Size of A must match size of B."); throw new System.ArgumentException("Size of A must match size of B.");
float[,] result = new float[A.nRows, B.nCols]; float[,] result = new float[A.nRows, B.nCols];
for (int i = 0; i < A.nRows; i++) { for (int i = 0; i < A.nRows; i++)
{
for (int j = 0; j < A.nCols; j++) for (int j = 0; j < A.nCols; j++)
result[i, j] = A.data[i, j] - B.data[i, j]; result[i, j] = A.data[i, j] - B.data[i, j];
} }
return new Matrix2(result); return new Matrix2(result);
} }
public static Matrix2 operator +(Matrix2 A, Matrix2 B) { public static Matrix2 operator +(Matrix2 A, Matrix2 B)
{
if (A.nRows != B.nRows || A.nCols != B.nCols) if (A.nRows != B.nRows || A.nCols != B.nCols)
throw new System.ArgumentException("Size of A must match size of B."); throw new System.ArgumentException("Size of A must match size of B.");
float[,] result = new float[A.nRows, B.nCols]; float[,] result = new float[A.nRows, B.nCols];
for (int i = 0; i < A.nRows; i++) { for (int i = 0; i < A.nRows; i++)
{
for (int j = 0; j < A.nCols; j++) for (int j = 0; j < A.nCols; j++)
result[i, j] = A.data[i, j] + B.data[i, j]; result[i, j] = A.data[i, j] + B.data[i, j];
} }
return new Matrix2(result); return new Matrix2(result);
} }
public static Matrix2 operator *(Matrix2 A, Matrix2 B) { public static Matrix2 operator *(Matrix2 A, Matrix2 B)
{
if (A.nCols != B.nRows) if (A.nCols != B.nRows)
throw new System.ArgumentException("Number of columns in A must match number of rows in B."); throw new System.ArgumentException("Number of columns in A must match number of rows in B.");
float[,] result = new float[A.nRows, B.nCols]; float[,] result = new float[A.nRows, B.nCols];
for (int i = 0; i < A.nRows; i++) { for (int i = 0; i < A.nRows; i++)
for (int j = 0; j < B.nCols; j++) { {
for (int j = 0; j < B.nCols; j++)
{
float sum = 0.0f; float sum = 0.0f;
for (int k = 0; k < A.nCols; k++) for (int k = 0; k < A.nCols; k++)
sum += A.data[i, k] * B.data[k, j]; sum += A.data[i, k] * B.data[k, j];
@ -235,11 +261,14 @@ namespace LinearAlgebra {
// double checked code // double checked code
} }
public static Matrix1 operator *(Matrix2 A, Matrix1 v) { public static Matrix1 operator *(Matrix2 A, Matrix1 v)
{
float[] result = new float[A.nRows]; float[] result = new float[A.nRows];
for (int i = 0; i < A.nRows; i++) { for (int i = 0; i < A.nRows; i++)
for (int j = 0; j < A.nCols; j++) { {
for (int j = 0; j < A.nCols; j++)
{
result[i] += A.data[i, j] * v.data[j]; result[i] += A.data[i, j] * v.data[j];
} }
} }
@ -247,7 +276,8 @@ namespace LinearAlgebra {
return new Matrix1(result); return new Matrix1(result);
} }
public static Vector3Float operator *(Matrix2 A, Vector3Float v) { public static Vector3Float operator *(Matrix2 A, Vector3Float v)
{
return new Vector3Float( return new Vector3Float(
A.data[0, 0] * v.x + A.data[0, 1] * v.y + A.data[0, 2] * v.z, A.data[0, 0] * v.x + A.data[0, 1] * v.y + A.data[0, 2] * v.z,
A.data[1, 0] * v.x + A.data[1, 1] * v.y + A.data[1, 2] * v.z, A.data[1, 0] * v.x + A.data[1, 1] * v.y + A.data[1, 2] * v.z,
@ -255,34 +285,41 @@ namespace LinearAlgebra {
); );
} }
public static Matrix2 operator *(Matrix2 A, float s) { public static Matrix2 operator *(Matrix2 A, float s)
{
float[,] result = new float[A.nRows, A.nCols]; float[,] result = new float[A.nRows, A.nCols];
for (int i = 0; i < A.nRows; i++) { for (int i = 0; i < A.nRows; i++)
{
for (int j = 0; j < A.nCols; j++) for (int j = 0; j < A.nCols; j++)
result[i, j] = A.data[i, j] * s; result[i, j] = A.data[i, j] * s;
} }
return new Matrix2(result); return new Matrix2(result);
} }
public static Matrix2 operator *(float s, Matrix2 A) { public static Matrix2 operator *(float s, Matrix2 A)
{
return A * s; return A * s;
} }
public static Matrix2 operator /(Matrix2 A, float s) { public static Matrix2 operator /(Matrix2 A, float s)
{
float[,] result = new float[A.nRows, A.nCols]; float[,] result = new float[A.nRows, A.nCols];
for (int i = 0; i < A.nRows; i++) { for (int i = 0; i < A.nRows; i++)
{
for (int j = 0; j < A.nCols; j++) for (int j = 0; j < A.nCols; j++)
result[i, j] = A.data[i, j] / s; result[i, j] = A.data[i, j] / s;
} }
return new Matrix2(result); return new Matrix2(result);
} }
public static Matrix2 operator /(float s, Matrix2 A) { public static Matrix2 operator /(float s, Matrix2 A)
{
float[,] result = new float[A.nRows, A.nCols]; float[,] result = new float[A.nRows, A.nCols];
for (int i = 0; i < A.nRows; i++) { for (int i = 0; i < A.nRows; i++)
{
for (int j = 0; j < A.nCols; j++) for (int j = 0; j < A.nCols; j++)
result[i, j] = s / A.data[i, j]; result[i, j] = s / A.data[i, j];
} }
@ -331,7 +368,8 @@ namespace LinearAlgebra {
return new Matrix2(result); return new Matrix2(result);
} }
public Matrix2 Slice(Slice rowRange, Slice colRange) { public Matrix2 Slice(Slice rowRange, Slice colRange)
{
return Slice((rowRange.start, rowRange.stop), (colRange.start, colRange.stop)); return Slice((rowRange.start, rowRange.stop), (colRange.start, colRange.stop));
} }
public Matrix2 Slice((int start, int stop) rowRange, (int start, int stop) colRange) public Matrix2 Slice((int start, int stop) rowRange, (int start, int stop) colRange)
@ -374,22 +412,26 @@ namespace LinearAlgebra {
} }
} }
public Matrix2 Inverse() { public Matrix2 Inverse()
{
Matrix2 A = this; Matrix2 A = this;
// unchecked // unchecked
int n = A.nRows; int n = A.nRows;
// Create an identity matrix of the same size as the original matrix // Create an identity matrix of the same size as the original matrix
float[,] augmentedMatrix = new float[n, 2 * n]; float[,] augmentedMatrix = new float[n, 2 * n];
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) { {
for (int j = 0; j < n; j++)
{
augmentedMatrix[i, j] = A.data[i, j]; augmentedMatrix[i, j] = A.data[i, j];
augmentedMatrix[i, j + n] = (i == j) ? 1 : 0; // Identity matrix augmentedMatrix[i, j + n] = (i == j) ? 1 : 0; // Identity matrix
} }
} }
// Perform Gaussian elimination // Perform Gaussian elimination
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++)
{
// Find the pivot row // Find the pivot row
float pivot = augmentedMatrix[i, i]; float pivot = augmentedMatrix[i, i];
if (Math.Abs(pivot) < 1e-10) // Check for singular matrix if (Math.Abs(pivot) < 1e-10) // Check for singular matrix
@ -400,7 +442,8 @@ namespace LinearAlgebra {
augmentedMatrix[i, j] /= pivot; augmentedMatrix[i, j] /= pivot;
// Eliminate the column below the pivot // Eliminate the column below the pivot
for (int j = i + 1; j < n; j++) { for (int j = i + 1; j < n; j++)
{
float factor = augmentedMatrix[j, i]; float factor = augmentedMatrix[j, i];
for (int k = 0; k < 2 * n; k++) for (int k = 0; k < 2 * n; k++)
augmentedMatrix[j, k] -= factor * augmentedMatrix[i, k]; augmentedMatrix[j, k] -= factor * augmentedMatrix[i, k];
@ -421,7 +464,8 @@ namespace LinearAlgebra {
// Extract the inverse matrix from the augmented matrix // Extract the inverse matrix from the augmented matrix
float[,] inverse = new float[n, n]; float[,] inverse = new float[n, n];
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++) for (int j = 0; j < n; j++)
inverse[i, j] = augmentedMatrix[i, j + n]; inverse[i, j] = augmentedMatrix[i, j + n];
} }
@ -455,11 +499,13 @@ namespace LinearAlgebra {
float[,] minor = new float[n - 1, n - 1]; float[,] minor = new float[n - 1, n - 1];
int r = 0, c = 0; int r = 0, c = 0;
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++)
{
if (i == rowToRemove) continue; if (i == rowToRemove) continue;
c = 0; c = 0;
for (int j = 0; j < n; j++) { for (int j = 0; j < n; j++)
{
if (j == colToRemove) continue; if (j == colToRemove) continue;
minor[r, c] = this.data[i, j]; minor[r, c] = this.data[i, j];
@ -514,7 +560,8 @@ namespace LinearAlgebra {
this.data = new float[size]; this.data = new float[size];
} }
public Matrix1(float[] data) { public Matrix1(float[] data)
{
this.data = data; this.data = data;
} }
@ -523,14 +570,16 @@ namespace LinearAlgebra {
return new Matrix1(size); return new Matrix1(size);
} }
public static Matrix1 FromVector2(Vector2Float v) { public static Matrix1 FromVector2(Vector2Float v)
{
float[] result = new float[2]; float[] result = new float[2];
result[0] = v.x; result[0] = v.x;
result[1] = v.y; result[1] = v.y;
return new Matrix1(result); return new Matrix1(result);
} }
public static Matrix1 FromVector3(Vector3Float v) { public static Matrix1 FromVector3(Vector3Float v)
{
float[] result = new float[3]; float[] result = new float[3];
result[0] = v.x; result[0] = v.x;
result[1] = v.y; result[1] = v.y;
@ -549,15 +598,19 @@ namespace LinearAlgebra {
} }
#endif #endif
public Vector2Float vector2 { public Vector2Float vector2
get { {
get
{
if (this.size != 2) if (this.size != 2)
throw new System.ArgumentException("Matrix1 must be of size 2"); throw new System.ArgumentException("Matrix1 must be of size 2");
return new Vector2Float(this.data[0], this.data[1]); return new Vector2Float(this.data[0], this.data[1]);
} }
} }
public Vector3Float vector3 { public Vector3Float vector3
get { {
get
{
if (this.size != 3) if (this.size != 3)
throw new System.ArgumentException("Matrix1 must be of size 3"); throw new System.ArgumentException("Matrix1 must be of size 3");
return new Vector3Float(this.data[0], this.data[1], this.data[2]); return new Vector3Float(this.data[0], this.data[1], this.data[2]);
@ -574,7 +627,8 @@ namespace LinearAlgebra {
} }
#endif #endif
public Matrix1 Clone() { public Matrix1 Clone()
{
float[] data = new float[this.size]; float[] data = new float[this.size];
for (int rowIx = 0; rowIx < this.size; rowIx++) for (int rowIx = 0; rowIx < this.size; rowIx++)
data[rowIx] = this.data[rowIx]; data[rowIx] = this.data[rowIx];
@ -582,27 +636,32 @@ namespace LinearAlgebra {
} }
public float magnitude { public float magnitude
get { {
get
{
float sum = 0; float sum = 0;
foreach (var elm in data) foreach (var elm in data)
sum += elm; sum += elm;
return sum / data.Length; return sum / data.Length;
} }
} }
public static Matrix1 operator +(Matrix1 A, Matrix1 B) { public static Matrix1 operator +(Matrix1 A, Matrix1 B)
{
if (A.size != B.size) if (A.size != B.size)
throw new System.ArgumentException("Size of A must match size of B."); throw new System.ArgumentException("Size of A must match size of B.");
float[] result = new float[A.size]; float[] result = new float[A.size];
for (int i = 0; i < A.size; i++) { for (int i = 0; i < A.size; i++)
{
result[i] = A.data[i] + B.data[i]; result[i] = A.data[i] + B.data[i];
} }
return new Matrix1(result); return new Matrix1(result);
} }
public Matrix2 Transpose() { public Matrix2 Transpose()
{
float[,] r = new float[1, this.size]; float[,] r = new float[1, this.size];
for (uint colIx = 0; colIx < this.size; colIx++) for (uint colIx = 0; colIx < this.size; colIx++)
r[1, colIx] = this.data[colIx]; r[1, colIx] = this.data[colIx];
@ -610,12 +669,14 @@ namespace LinearAlgebra {
return new Matrix2(r); return new Matrix2(r);
} }
public static float Dot(Matrix1 a, Matrix1 b) { public static float Dot(Matrix1 a, Matrix1 b)
{
if (a.size != b.size) if (a.size != b.size)
throw new System.ArgumentException("Vectors must be of the same length."); throw new System.ArgumentException("Vectors must be of the same length.");
float result = 0.0f; float result = 0.0f;
for (int i = 0; i < a.size; i++) { for (int i = 0; i < a.size; i++)
{
result += a.data[i] * b.data[i]; result += a.data[i] * b.data[i];
} }
return result; return result;
@ -642,7 +703,8 @@ namespace LinearAlgebra {
return new Matrix1(result); return new Matrix1(result);
} }
public static Matrix1 operator *(float f, Matrix1 A) { public static Matrix1 operator *(float f, Matrix1 A)
{
return A * f; return A * f;
} }
@ -679,7 +741,8 @@ namespace LinearAlgebra {
return new Matrix1(result); return new Matrix1(result);
} }
public void UpdateSlice(Slice slice, Matrix1 v) { public void UpdateSlice(Slice slice, Matrix1 v)
{
int vIx = 0; int vIx = 0;
for (int ix = slice.start; ix < slice.stop; ix++, vIx++) for (int ix = slice.start; ix < slice.stop; ix++, vIx++)
this.data[ix] = v.data[vIx]; this.data[ix] = v.data[vIx];

View File

@ -1,32 +1,38 @@
using System; using System;
namespace LinearAlgebra { namespace LinearAlgebra
public class Quat32 { {
public class Quat32
{
public float x; public float x;
public float y; public float y;
public float z; public float z;
public float w; public float w;
public Quat32() { public Quat32()
{
this.x = 0; this.x = 0;
this.y = 0; this.y = 0;
this.z = 0; this.z = 0;
this.w = 1; this.w = 1;
} }
public Quat32(float x, float y, float z, float w) { public Quat32(float x, float y, float z, float w)
{
this.x = x; this.x = x;
this.y = y; this.y = y;
this.z = z; this.z = z;
this.w = w; this.w = w;
} }
public static Quat32 FromSwingTwist(SwingTwist s) { public static Quat32 FromSwingTwist(SwingTwist s)
{
Quat32 q32 = Quat32.Euler(-s.swing.vertical, s.swing.horizontal, s.twist); Quat32 q32 = Quat32.Euler(-s.swing.vertical, s.swing.horizontal, s.twist);
return q32; return q32;
} }
public static Quat32 Euler(float yaw, float pitch, float roll) { public static Quat32 Euler(float yaw, float pitch, float roll)
{
float rollOver2 = roll * Angle.Deg2Rad * 0.5f; float rollOver2 = roll * Angle.Deg2Rad * 0.5f;
float sinRollOver2 = (float)Math.Sin((float)rollOver2); float sinRollOver2 = (float)Math.Sin((float)rollOver2);
float cosRollOver2 = (float)Math.Cos((float)rollOver2); float cosRollOver2 = (float)Math.Cos((float)rollOver2);
@ -36,7 +42,8 @@ namespace LinearAlgebra {
float yawOver2 = yaw * 0.5f; float yawOver2 = yaw * 0.5f;
float sinYawOver2 = (float)Math.Sin((float)yawOver2); float sinYawOver2 = (float)Math.Sin((float)yawOver2);
float cosYawOver2 = (float)Math.Cos((float)yawOver2); float cosYawOver2 = (float)Math.Cos((float)yawOver2);
Quat32 result = new Quat32() { Quat32 result = new Quat32()
{
w = cosYawOver2 * cosPitchOver2 * cosRollOver2 + w = cosYawOver2 * cosPitchOver2 * cosRollOver2 +
sinYawOver2 * sinPitchOver2 * sinRollOver2, sinYawOver2 * sinPitchOver2 * sinRollOver2,
x = sinYawOver2 * cosPitchOver2 * cosRollOver2 + x = sinYawOver2 * cosPitchOver2 * cosRollOver2 +
@ -49,23 +56,27 @@ namespace LinearAlgebra {
return result; return result;
} }
public void ToAngles(out float right, out float up, out float forward) { public void ToAngles(out float right, out float up, out float forward)
{
float test = this.x * this.y + this.z * this.w; float test = this.x * this.y + this.z * this.w;
if (test > 0.499f) { // singularity at north pole if (test > 0.499f)
{ // singularity at north pole
right = 0; right = 0;
up = 2 * (float)Math.Atan2(this.x, this.w) * Angle.Rad2Deg; up = 2 * (float)Math.Atan2(this.x, this.w) * Angle.Rad2Deg;
forward = 90; forward = 90;
return; return;
//return Vector3(0, 2 * (float)atan2(this.x, this.w) * Angle.Rad2Deg, 90); //return Vector3(0, 2 * (float)atan2(this.x, this.w) * Angle.Rad2Deg, 90);
} }
else if (test < -0.499f) { // singularity at south pole else if (test < -0.499f)
{ // singularity at south pole
right = 0; right = 0;
up = -2 * (float)Math.Atan2(this.x, this.w) * Angle.Rad2Deg; up = -2 * (float)Math.Atan2(this.x, this.w) * Angle.Rad2Deg;
forward = -90; forward = -90;
return; return;
//return Vector3(0, -2 * (float)atan2(this.x, this.w) * Angle.Rad2Deg, -90); //return Vector3(0, -2 * (float)atan2(this.x, this.w) * Angle.Rad2Deg, -90);
} }
else { else
{
float sqx = this.x * this.x; float sqx = this.x * this.x;
float sqy = this.y * this.y; float sqy = this.y * this.y;
float sqz = this.z * this.z; float sqz = this.z * this.z;

View File

@ -18,11 +18,12 @@ namespace LinearAlgebra {
this.w = w; this.w = w;
} }
#if UNITY_5_3_OR_NEWER
public static Quaternion Reflect(Quaternion q) { public static Quaternion Reflect(Quaternion q) {
return new(-q.x, -q.y, -q.z, q.w); return new(-q.x, -q.y, -q.z, q.w);
} }
#if UNITY_5_3_OR_NEWER
public static Matrix2 ToRotationMatrix(Quaternion q) { public static Matrix2 ToRotationMatrix(Quaternion q) {
float w = q.x, x = q.y, y = q.z, z = q.w; float w = q.x, x = q.y, y = q.z, z = q.w;

View File

@ -3,92 +3,42 @@ using System;
using Vector3Float = UnityEngine.Vector3; using Vector3Float = UnityEngine.Vector3;
#endif #endif
namespace LinearAlgebra { namespace LinearAlgebra
/// <summary> {
/// A spherical vector public class Spherical
/// </summary> {
public class Spherical { public float distance;
public Direction direction;
/// <summary> public static Spherical zero = new Spherical(0, 0, 0);
/// Create a default vector with zero distance public static Spherical forward = new Spherical(1, 0, 0);
/// </summary>
public Spherical() { public Spherical(float distance, float horizontal, float vertical)
this.distance = 0; {
this.direction = new Direction(); this.distance = distance;
this.direction = new Direction(horizontal, vertical);
} }
public Spherical(float distance, Direction direction)
/// <summary> {
/// Create a spherical vector
/// </summary>
/// <param name="distance">The distance in meters</param>
/// <param name="direction">The direction of the vector</param>
public Spherical(float distance, Direction direction) {
this.distance = distance; this.distance = distance;
this.direction = direction; this.direction = direction;
} }
/// <summary> public static Spherical FromVector3(Vector3Float v)
/// Create spherical vector. All given angles are in degrees {
/// </summary>
/// <param name="distance">The distance in meters</param>
/// <param name="horizontal">The horizontal angle in degrees</param>
/// <param name="vertical">The vertical angle in degrees</param>
/// <returns></returns>
public static Spherical Degrees(float distance, float horizontal, float vertical) {
Direction direction = Direction.Degrees(horizontal, vertical);
Spherical s = new(distance, direction);
return s;
}
public static Spherical Radians(float distance, float horizontal, float vertical) {
Direction direction = Direction.Radians(horizontal, vertical);
Spherical s = new(distance, direction);
return s;
}
/// <summary>
/// The distance in meters
/// </summary>
/// @remark The distance should never be negative
public float distance;
/// <summary>
/// The direction of the vector
/// </summary>
public Direction direction;
/// <summary>
/// A spherical vector with zero degree angles and distance
/// </summary>
public readonly static Spherical zero = new(0, Direction.forward);
/// <summary>
/// A normalized forward-oriented vector
/// </summary>
public readonly static Spherical forward = new(1, Direction.forward);
public static Spherical FromVector3Float(Vector3Float v) {
float distance = v.magnitude; float distance = v.magnitude;
if (distance == 0.0f) if (distance == 0.0f)
return Spherical.zero; return new Spherical(distance, 0, 0);
else { else
{
float verticalAngle = (float)((Angle.pi / 2 - Math.Acos(v.y / distance)) * Angle.Rad2Deg); float verticalAngle = (float)((Angle.pi / 2 - Math.Acos(v.y / distance)) * Angle.Rad2Deg);
float horizontalAngle = (float)Math.Atan2(v.x, v.z) * Angle.Rad2Deg; float horizontalAngle = (float)Math.Atan2(v.x, v.z) * Angle.Rad2Deg;
return Spherical.Degrees(distance, horizontalAngle, verticalAngle); return new Spherical(distance, horizontalAngle, verticalAngle);
} }
} }
public static Spherical FromVector3(Vector3 v) { public Vector3Float ToVector3()
float distance = v.magnitude; {
if (distance == 0.0f)
return Spherical.zero;
else {
float verticalAngle = (float)((Angle.pi / 2 - Math.Acos(v.y / distance)) * Angle.Rad2Deg);
float horizontalAngle = (float)Math.Atan2(v.x, v.z) * Angle.Rad2Deg;
return Spherical.Degrees(distance, horizontalAngle, verticalAngle);
}
}
public Vector3Float ToVector3Float() {
float verticalRad = (Angle.pi / 2) - this.direction.vertical * Angle.Deg2Rad; float verticalRad = (Angle.pi / 2) - this.direction.vertical * Angle.Deg2Rad;
float horizontalRad = this.direction.horizontal * Angle.Deg2Rad; float horizontalRad = this.direction.horizontal * Angle.Deg2Rad;
float cosVertical = (float)Math.Cos(verticalRad); float cosVertical = (float)Math.Cos(verticalRad);
@ -100,34 +50,8 @@ namespace LinearAlgebra {
float y = this.distance * cosVertical; float y = this.distance * cosVertical;
float z = this.distance * sinVertical * cosHorizontal; float z = this.distance * sinVertical * cosHorizontal;
Vector3Float v = new(x, y, z); Vector3Float v = new Vector3Float(x, y, z);
return v; return v;
} }
public Vector3 ToVector3() {
float verticalRad = (Angle.pi / 2) - this.direction.vertical * Angle.Deg2Rad;
float horizontalRad = this.direction.horizontal * Angle.Deg2Rad;
float cosVertical = (float)Math.Cos(verticalRad);
float sinVertical = (float)Math.Sin(verticalRad);
float cosHorizontal = (float)Math.Cos(horizontalRad);
float sinHorizontal = (float)Math.Sin(horizontalRad);
float x = this.distance * sinVertical * sinHorizontal;
float y = this.distance * cosVertical;
float z = this.distance * sinVertical * cosHorizontal;
Vector3 v = new(x, y, z);
return v;
}
public static Spherical operator +(Spherical s1, Spherical s2) {
// let's do it the easy way...
Vector3 v1 = s1.ToVector3();
Vector3 v2 = s2.ToVector3();
Vector3 v = v1 + v2;
Spherical r = FromVector3(v);
return r;
}
} }
} }

View File

@ -3,24 +3,29 @@ using System.Numerics;
using Quaternion = UnityEngine.Quaternion; using Quaternion = UnityEngine.Quaternion;
#endif #endif
namespace LinearAlgebra { namespace LinearAlgebra
{
public class SwingTwist { public class SwingTwist
{
public Direction swing; public Direction swing;
public float twist; public float twist;
public static readonly SwingTwist zero = new SwingTwist(0, 0, 0); public static readonly SwingTwist zero = new SwingTwist(0, 0, 0);
public SwingTwist(Direction swing, float twist) { public SwingTwist(Direction swing, float twist)
{
this.swing = swing; this.swing = swing;
this.twist = twist; this.twist = twist;
} }
public SwingTwist(float horizontalSwing, float verticalSwing, float twist) { public SwingTwist(float horizontalSwing, float verticalSwing, float twist)
this.swing = Direction.Degrees(horizontalSwing, verticalSwing); {
this.swing = new Direction(horizontalSwing, verticalSwing);
this.swing.Normalize(); this.swing.Normalize();
this.twist = twist; this.twist = twist;
} }
public static SwingTwist FromQuat32(Quat32 q32) { public static SwingTwist FromQuat32(Quat32 q32)
{
// UnityEngine.Quaternion q = new(q32.x, q32.y, q32.z, q32.w); // UnityEngine.Quaternion q = new(q32.x, q32.y, q32.z, q32.w);
// SwingTwist r = new(q.eulerAngles.y, q.eulerAngles.x, q.eulerAngles.z); // SwingTwist r = new(q.eulerAngles.y, q.eulerAngles.x, q.eulerAngles.z);
q32.ToAngles(out float right, out float up, out float forward); q32.ToAngles(out float right, out float up, out float forward);

View File

@ -1,50 +1,63 @@
using System; using System;
using System.Numerics; using System.Numerics;
namespace LinearAlgebra { namespace LinearAlgebra
{
public class Vector2Of<T> where T : IComparable<T> { public class Vector2Of<T> where T : IComparable<T>
{
public T x; public T x;
public T y; public T y;
public Vector2Of(T x, T y) { public Vector2Of(T x, T y)
{
this.x = x; this.x = x;
this.y = y; this.y = y;
} }
} }
public class Vector2Int : Vector2Of<int> { public class Vector2Int : Vector2Of<int>
{
public Vector2Int(int x, int y) : base(x, y) { } public Vector2Int(int x, int y) : base(x, y) { }
public static Vector2Int operator -(Vector2Int v1, Vector2Int v2) { public static Vector2Int operator -(Vector2Int v1, Vector2Int v2)
{
return new Vector2Int(v1.x - v2.x, v1.y - v2.y); return new Vector2Int(v1.x - v2.x, v1.y - v2.y);
} }
public float magnitude { public float magnitude
get { {
get
{
return (float)Math.Sqrt(this.x * this.x + this.y * this.y); return (float)Math.Sqrt(this.x * this.x + this.y * this.y);
} }
} }
public static float Distance(Vector2Int v1, Vector2Int v2) { public static float Distance(Vector2Int v1, Vector2Int v2)
{
return (v1 - v2).magnitude; return (v1 - v2).magnitude;
} }
} }
public class Vector2Float : Vector2Of<float> { public class Vector2Float : Vector2Of<float>
{
public Vector2Float(float x, float y) : base(x, y) { } public Vector2Float(float x, float y) : base(x, y) { }
public static Vector2Float operator -(Vector2Float v1, Vector2Float v2) { public static Vector2Float operator -(Vector2Float v1, Vector2Float v2)
{
return new Vector2Float(v1.x - v2.x, v1.y - v2.y); return new Vector2Float(v1.x - v2.x, v1.y - v2.y);
} }
public float magnitude { public float magnitude
get { {
get
{
return (float)Math.Sqrt(this.x * this.x + this.y * this.y); return (float)Math.Sqrt(this.x * this.x + this.y * this.y);
} }
} }
public static float Distance(Vector2Float v1, Vector2Float v2) { public static float Distance(Vector2Float v1, Vector2Float v2)
{
return (v1 - v2).magnitude; return (v1 - v2).magnitude;
} }
} }
@ -52,7 +65,8 @@ namespace LinearAlgebra {
/// <summary> /// <summary>
/// 2-dimensional vectors /// 2-dimensional vectors
/// </summary> /// </summary>
public struct Vector2 : IEquatable<Vector2> { public struct Vector2 : IEquatable<Vector2>
{
/// <summary> /// <summary>
/// The right axis of the vector /// The right axis of the vector
@ -69,7 +83,8 @@ namespace LinearAlgebra {
/// </summary> /// </summary>
/// <param name="x">x axis value</param> /// <param name="x">x axis value</param>
/// <param name="y">y axis value</param> /// <param name="y">y axis value</param>
public Vector2(float x, float y) { public Vector2(float x, float y)
{
this.x = x; this.x = x;
this.y = y; this.y = y;
} }
@ -114,8 +129,10 @@ namespace LinearAlgebra {
/// The squared length is computationally simpler than the real length. /// The squared length is computationally simpler than the real length.
/// Think of Pythagoras A^2 + B^2 = C^2. /// Think of Pythagoras A^2 + B^2 = C^2.
/// This leaves out the calculation of the squared root of C. /// This leaves out the calculation of the squared root of C.
public float sqrMagnitude { public float sqrMagnitude
get { {
get
{
float d = x * x + y * y; float d = x * x + y * y;
return d; return d;
} }
@ -125,8 +142,10 @@ namespace LinearAlgebra {
/// The length of this vector /// The length of this vector
/// </summary> /// </summary>
/// <returns>The length of this vector</returns> /// <returns>The length of this vector</returns>
public float magnitude { public float magnitude
get { {
get
{
float d = (float)Math.Sqrt(x * x + y * y); float d = (float)Math.Sqrt(x * x + y * y);
return d; return d;
} }
@ -136,8 +155,10 @@ namespace LinearAlgebra {
/// Convert the vector to a length of a 1 /// Convert the vector to a length of a 1
/// </summary> /// </summary>
/// <returns>The vector with length 1</returns> /// <returns>The vector with length 1</returns>
public Vector2 normalized { public Vector2 normalized
get { {
get
{
float l = magnitude; float l = magnitude;
Vector2 v = zero; Vector2 v = zero;
if (l > Float.epsilon) if (l > Float.epsilon)
@ -152,7 +173,8 @@ namespace LinearAlgebra {
/// <param name="v1">The first vector</param> /// <param name="v1">The first vector</param>
/// <param name="v2">The second vector</param> /// <param name="v2">The second vector</param>
/// <returns>The result of adding the two vectors</returns> /// <returns>The result of adding the two vectors</returns>
public static Vector2 operator +(Vector2 v1, Vector2 v2) { public static Vector2 operator +(Vector2 v1, Vector2 v2)
{
Vector2 v = new Vector2(v1.x + v2.x, v1.y + v2.y); Vector2 v = new Vector2(v1.x + v2.x, v1.y + v2.y);
return v; return v;
} }
@ -163,7 +185,8 @@ namespace LinearAlgebra {
/// <param name="v1">The first vector</param> /// <param name="v1">The first vector</param>
/// <param name="v2">The second vector</param> /// <param name="v2">The second vector</param>
/// <returns>The result of adding the two vectors</returns> /// <returns>The result of adding the two vectors</returns>
public static Vector2 operator -(Vector2 v1, Vector2 v2) { public static Vector2 operator -(Vector2 v1, Vector2 v2)
{
Vector2 v = new Vector2(v1.x - v2.x, v1.y - v2.y); Vector2 v = new Vector2(v1.x - v2.x, v1.y - v2.y);
return v; return v;
} }
@ -174,7 +197,8 @@ namespace LinearAlgebra {
/// <param name="v1">The vector to negate</param> /// <param name="v1">The vector to negate</param>
/// <returns>The negated vector</returns> /// <returns>The negated vector</returns>
/// This will result in a vector pointing in the opposite direction /// This will result in a vector pointing in the opposite direction
public static Vector2 operator -(Vector2 v1) { public static Vector2 operator -(Vector2 v1)
{
Vector2 v = new Vector2(-v1.x, -v1.y); Vector2 v = new Vector2(-v1.x, -v1.y);
return v; return v;
} }
@ -186,7 +210,8 @@ namespace LinearAlgebra {
/// <param name="f">The scaling factor</param> /// <param name="f">The scaling factor</param>
/// <returns>The scaled vector</returns> /// <returns>The scaled vector</returns>
/// Each component of the vector will be multipled with the same factor. /// Each component of the vector will be multipled with the same factor.
public static Vector2 operator *(Vector2 v1, float f) { public static Vector2 operator *(Vector2 v1, float f)
{
Vector2 v = new Vector2(v1.x * f, v1.y * f); Vector2 v = new Vector2(v1.x * f, v1.y * f);
return v; return v;
} }
@ -198,7 +223,8 @@ namespace LinearAlgebra {
/// <param name="v1">The vector to scale</param> /// <param name="v1">The vector to scale</param>
/// <returns>The scaled vector</returns> /// <returns>The scaled vector</returns>
/// Each component of the vector will be multipled with the same factor. /// Each component of the vector will be multipled with the same factor.
public static Vector2 operator *(float f, Vector2 v1) { public static Vector2 operator *(float f, Vector2 v1)
{
Vector2 v = new Vector2(f * v1.x, f * v1.y); Vector2 v = new Vector2(f * v1.x, f * v1.y);
return v; return v;
} }
@ -210,7 +236,8 @@ namespace LinearAlgebra {
/// <param name="f">The scaling factor</param> /// <param name="f">The scaling factor</param>
/// <returns>The scaled vector</returns> /// <returns>The scaled vector</returns>
/// Each component of the vector will be devided by the same factor. /// Each component of the vector will be devided by the same factor.
public static Vector2 operator /(Vector2 v1, float f) { public static Vector2 operator /(Vector2 v1, float f)
{
Vector2 v = new Vector2(v1.x / f, v1.y / f); Vector2 v = new Vector2(v1.x / f, v1.y / f);
return v; return v;
} }
@ -227,7 +254,8 @@ namespace LinearAlgebra {
/// </summary> /// </summary>
/// <param name="obj">The object to compare to</param> /// <param name="obj">The object to compare to</param>
/// <returns><em>false</em> when the object is not a Vector2 or does not have equal values</returns> /// <returns><em>false</em> when the object is not a Vector2 or does not have equal values</returns>
public override bool Equals(object obj) { public override bool Equals(object obj)
{
if (!(obj is Vector2 v)) if (!(obj is Vector2 v))
return false; return false;
@ -243,7 +271,8 @@ namespace LinearAlgebra {
/// Note that this uses a Float equality check which cannot be not exact in all cases. /// Note that this uses a Float equality check which cannot be not exact in all cases.
/// In most cases it is better to check if the Vector2.Distance between the vectors is smaller than Float.epsilon /// In most cases it is better to check if the Vector2.Distance between the vectors is smaller than Float.epsilon
/// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon /// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon
public static bool operator ==(Vector2 v1, Vector2 v2) { public static bool operator ==(Vector2 v1, Vector2 v2)
{
return (v1.x == v2.x && v1.y == v2.y); return (v1.x == v2.x && v1.y == v2.y);
} }
@ -256,7 +285,8 @@ namespace LinearAlgebra {
/// Note that this uses a Float equality check which cannot be not exact in all case. /// Note that this uses a Float equality check which cannot be not exact in all case.
/// In most cases it is better to check if the Vector2.Distance between the vectors is smaller than Float.epsilon. /// In most cases it is better to check if the Vector2.Distance between the vectors is smaller than Float.epsilon.
/// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon /// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon
public static bool operator !=(Vector2 v1, Vector2 v2) { public static bool operator !=(Vector2 v1, Vector2 v2)
{
return (v1.x != v2.x || v1.y != v2.y); return (v1.x != v2.x || v1.y != v2.y);
} }
@ -264,7 +294,8 @@ namespace LinearAlgebra {
/// Get an hash code for the vector /// Get an hash code for the vector
/// </summary> /// </summary>
/// <returns>The hash code</returns> /// <returns>The hash code</returns>
public override int GetHashCode() { public override int GetHashCode()
{
return (x, y).GetHashCode(); return (x, y).GetHashCode();
} }
@ -274,7 +305,8 @@ namespace LinearAlgebra {
/// <param name="v1">The first vector</param> /// <param name="v1">The first vector</param>
/// <param name="v2">The second vector</param> /// <param name="v2">The second vector</param>
/// <returns>The distance between the two vectors</returns> /// <returns>The distance between the two vectors</returns>
public static float Distance(Vector2 v1, Vector2 v2) { public static float Distance(Vector2 v1, Vector2 v2)
{
float x = v1.x - v2.x; float x = v1.x - v2.x;
float y = v1.y - v2.y; float y = v1.y - v2.y;
float d = (float)Math.Sqrt(x * x + y * y); float d = (float)Math.Sqrt(x * x + y * y);
@ -287,7 +319,8 @@ namespace LinearAlgebra {
/// <param name="v1">The first vector</param> /// <param name="v1">The first vector</param>
/// <param name="v2">The second vector</param> /// <param name="v2">The second vector</param>
/// <returns>The dot product of the two vectors</returns> /// <returns>The dot product of the two vectors</returns>
public static float Dot(Vector2 v1, Vector2 v2) { public static float Dot(Vector2 v1, Vector2 v2)
{
return v1.x * v2.x + v1.y * v2.y; return v1.x * v2.x + v1.y * v2.y;
} }
@ -301,7 +334,8 @@ namespace LinearAlgebra {
/// The factor f is unclamped. Value 0 matches the *v1* vector, Value 1 /// The factor f is unclamped. Value 0 matches the *v1* vector, Value 1
/// matches the *v2* vector Value -1 is *v1* vector minus the difference /// matches the *v2* vector Value -1 is *v1* vector minus the difference
/// between *v1* and *v2* etc. /// between *v1* and *v2* etc.
public static Vector2 Lerp(Vector2 v1, Vector2 v2, float f) { public static Vector2 Lerp(Vector2 v1, Vector2 v2, float f)
{
Vector2 v = v1 + (v2 - v1) * f; Vector2 v = v1 + (v2 - v1) * f;
return v; return v;
} }
@ -313,7 +347,8 @@ namespace LinearAlgebra {
/// <param name="to">The ending vector</param> /// <param name="to">The ending vector</param>
/// <param name="axis">The axis to rotate around</param> /// <param name="axis">The axis to rotate around</param>
/// <returns>The signed angle in degrees</returns> /// <returns>The signed angle in degrees</returns>
public static float SignedAngle(Vector2 from, Vector2 to) { public static float SignedAngle(Vector2 from, Vector2 to)
{
//float sign = Math.Sign(v1.y * v2.x - v1.x * v2.y); //float sign = Math.Sign(v1.y * v2.x - v1.x * v2.y);
//return Vector2.Angle(v1, v2) * sign; //return Vector2.Angle(v1, v2) * sign;
@ -336,13 +371,15 @@ namespace LinearAlgebra {
/// <param name="v1">The vector to rotate</param> /// <param name="v1">The vector to rotate</param>
/// <param name="angle">The angle in degrees</param> /// <param name="angle">The angle in degrees</param>
/// <returns></returns> /// <returns></returns>
public static Vector2 Rotate(Vector2 v1, float angle) { public static Vector2 Rotate(Vector2 v1, float angle)
{
float sin = (float)Math.Sin(angle * Angle.Deg2Rad); float sin = (float)Math.Sin(angle * Angle.Deg2Rad);
float cos = (float)Math.Cos(angle * Angle.Deg2Rad); float cos = (float)Math.Cos(angle * Angle.Deg2Rad);
float tx = v1.x; float tx = v1.x;
float ty = v1.y; float ty = v1.y;
Vector2 v = new Vector2() { Vector2 v = new Vector2()
{
x = (cos * tx) - (sin * ty), x = (cos * tx) - (sin * ty),
y = (sin * tx) + (cos * ty) y = (sin * tx) + (cos * ty)
}; };
@ -356,7 +393,8 @@ namespace LinearAlgebra {
/// <param name="v2">The second vector</param> /// <param name="v2">The second vector</param>
/// <returns>The resulting factor in interval [0..1]</returns> /// <returns>The resulting factor in interval [0..1]</returns>
/// Vectors a and b must be normalized /// Vectors a and b must be normalized
public static float ToFactor(Vector2 v1, Vector2 v2) { public static float ToFactor(Vector2 v1, Vector2 v2)
{
return (1 - Vector2.Dot(v1, v2)) / 2; return (1 - Vector2.Dot(v1, v2)) / 2;
} }
} }

View File

@ -1,13 +1,16 @@
#if !UNITY_5_3_OR_NEWER #if !UNITY_5_3_OR_NEWER
using System; using System;
namespace LinearAlgebra { namespace LinearAlgebra
public class Vector3Of<T> { {
public class Vector3Of<T>
{
public T x; public T x;
public T y; public T y;
public T z; public T z;
public Vector3Of(T x, T y, T z) { public Vector3Of(T x, T y, T z)
{
this.x = x; this.x = x;
this.y = y; this.y = y;
this.z = z; this.z = z;
@ -18,13 +21,16 @@ namespace LinearAlgebra {
// } // }
} }
public class Vector3Int : Vector3Of<int> { public class Vector3Int : Vector3Of<int>
{
public Vector3Int(int x, int y, int z) : base(x, y, z) { } public Vector3Int(int x, int y, int z) : base(x, y, z) { }
} }
public class Vector3Float : Vector3Of<float> { public class Vector3Float : Vector3Of<float>
{
public Vector3Float(float x, float y, float z) : base(x, y, z) { } public Vector3Float(float x, float y, float z) : base(x, y, z) { }
public float magnitude { public float magnitude
{
get => (float)Math.Sqrt(this.x * this.x + this.y * this.y + this.z * this.z); get => (float)Math.Sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
} }
} }
@ -33,7 +39,8 @@ namespace LinearAlgebra {
/// 3-dimensional vectors /// 3-dimensional vectors
/// </summary> /// </summary>
/// This uses the right-handed coordinate system. /// This uses the right-handed coordinate system.
public struct Vector3 : IEquatable<Vector3> { public struct Vector3 : IEquatable<Vector3>
{
/// <summary> /// <summary>
/// The right axis of the vector /// The right axis of the vector
@ -54,7 +61,8 @@ namespace LinearAlgebra {
/// <param name="x">x axis value</param> /// <param name="x">x axis value</param>
/// <param name="y">y axis value</param> /// <param name="y">y axis value</param>
/// <param name="z">z axis value</param> /// <param name="z">z axis value</param>
public Vector3(float x, float y, float z) { public Vector3(float x, float y, float z)
{
this.x = x; this.x = x;
this.y = y; this.y = y;
this.z = z; this.z = z;
@ -93,15 +101,19 @@ namespace LinearAlgebra {
/// </summary> /// </summary>
public static readonly Vector3 forward = new Vector3(0, 1, 0); public static readonly Vector3 forward = new Vector3(0, 1, 0);
public readonly float magnitude { public float magnitude
get { {
float d = (float)Math.Sqrt(x * x + y * y + z * z); get
{
float d = (float)Math.Sqrt(x * x + y * y);
return d; return d;
} }
} }
public Vector3 normalized { public Vector3 normalized
get { {
get
{
float l = magnitude; float l = magnitude;
Vector3 v = zero; Vector3 v = zero;
if (l > Float.epsilon) if (l > Float.epsilon)
@ -110,66 +122,79 @@ namespace LinearAlgebra {
} }
} }
public static Vector3 operator +(Vector3 v1, Vector3 v2) { public static Vector3 operator +(Vector3 v1, Vector3 v2)
{
Vector3 v = new Vector3(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); Vector3 v = new Vector3(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
return v; return v;
} }
public static Vector3 operator -(Vector3 v1, Vector3 v2) { public static Vector3 operator -(Vector3 v1, Vector3 v2)
{
Vector3 v = new Vector3(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); Vector3 v = new Vector3(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
return v; return v;
} }
public static Vector3 operator -(Vector3 v1) { public static Vector3 operator -(Vector3 v1)
{
Vector3 v = new Vector3(-v1.x, -v1.y, -v1.z); Vector3 v = new Vector3(-v1.x, -v1.y, -v1.z);
return v; return v;
} }
public static Vector3 operator *(Vector3 v1, float d) { public static Vector3 operator *(Vector3 v1, float d)
{
Vector3 v = new Vector3(v1.x * d, v1.y * d, v1.z * d); Vector3 v = new Vector3(v1.x * d, v1.y * d, v1.z * d);
return v; return v;
} }
public static Vector3 operator *(float d, Vector3 v1) { public static Vector3 operator *(float d, Vector3 v1)
{
Vector3 v = new Vector3(d * v1.x, d * v1.y, d * v1.z); Vector3 v = new Vector3(d * v1.x, d * v1.y, d * v1.z);
return v; return v;
} }
public static Vector3 operator /(Vector3 v1, float d) { public static Vector3 operator /(Vector3 v1, float d)
{
Vector3 v = new Vector3(v1.x / d, v1.y / d, v1.z / d); Vector3 v = new Vector3(v1.x / d, v1.y / d, v1.z / d);
return v; return v;
} }
public bool Equals(Vector3 v) => (x == v.x && y == v.y && z == v.z); public bool Equals(Vector3 v) => (x == v.x && y == v.y && z == v.z);
public override bool Equals(object obj) { public override bool Equals(object obj)
{
if (!(obj is Vector3 v)) if (!(obj is Vector3 v))
return false; return false;
return (x == v.x && y == v.y && z == v.z); return (x == v.x && y == v.y && z == v.z);
} }
public static bool operator ==(Vector3 v1, Vector3 v2) { public static bool operator ==(Vector3 v1, Vector3 v2)
{
return (v1.x == v2.x && v1.y == v2.y && v1.z == v2.z); return (v1.x == v2.x && v1.y == v2.y && v1.z == v2.z);
} }
public static bool operator !=(Vector3 v1, Vector3 v2) { public static bool operator !=(Vector3 v1, Vector3 v2)
{
return (v1.x != v2.x || v1.y != v2.y || v1.z != v2.z); return (v1.x != v2.x || v1.y != v2.y || v1.z != v2.z);
} }
public override int GetHashCode() { public override int GetHashCode()
{
return (x, y, z).GetHashCode(); return (x, y, z).GetHashCode();
} }
public static float Distance(Vector3 v1, Vector3 v2) { public static float Distance(Vector3 v1, Vector3 v2)
{
return (v2 - v1).magnitude; return (v2 - v1).magnitude;
} }
public static float Dot(Vector3 v1, Vector3 v2) { public static float Dot(Vector3 v1, Vector3 v2)
{
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
} }
public static Vector3 Lerp(Vector3 v1, Vector3 v2, float f) { public static Vector3 Lerp(Vector3 v1, Vector3 v2, float f)
{
Vector3 v = v1 + (v2 - v1) * f; Vector3 v = v1 + (v2 - v1) * f;
return v; return v;
} }

View File

@ -1,8 +1,10 @@
using System; using System;
namespace LinearAlgebra { namespace LinearAlgebra
{
public class float16 { public class float16
{
// //
// FILE: float16.cpp // FILE: float16.cpp
// AUTHOR: Rob Tillaart // AUTHOR: Rob Tillaart
@ -14,12 +16,14 @@ namespace LinearAlgebra {
public float16() { _value = 0; } public float16() { _value = 0; }
public float16(float f) { public float16(float f)
{
//_value = f32tof16(f); //_value = f32tof16(f);
_value = F32ToF16__(f); _value = F32ToF16__(f);
} }
public float toFloat() { public float toFloat()
{
return f16tof32(_value); return f16tof32(_value);
} }
@ -153,7 +157,8 @@ namespace LinearAlgebra {
// //
// CORE CONVERSION // CORE CONVERSION
// //
float f16tof32(ushort _value) { float f16tof32(ushort _value)
{
//ushort sgn; //ushort sgn;
ushort man; ushort man;
int exp; int exp;
@ -168,11 +173,13 @@ namespace LinearAlgebra {
//Debug.Log($"{sgn} {exp} {man}"); //Debug.Log($"{sgn} {exp} {man}");
// ZERO // ZERO
if ((_value & 0x7FFF) == 0) { if ((_value & 0x7FFF) == 0)
{
return sgn ? -0 : 0; return sgn ? -0 : 0;
} }
// NAN & INF // NAN & INF
if (exp == 0x001F) { if (exp == 0x001F)
{
if (man == 0) if (man == 0)
return sgn ? float.NegativeInfinity : float.PositiveInfinity; //-INFINITY : INFINITY; return sgn ? float.NegativeInfinity : float.PositiveInfinity; //-INFINITY : INFINITY;
else else
@ -186,43 +193,50 @@ namespace LinearAlgebra {
f = 1; f = 1;
// PROCESS MANTISSE // PROCESS MANTISSE
for (int i = 9; i >= 0; i--) { for (int i = 9; i >= 0; i--)
{
f *= 2; f *= 2;
if ((man & (1 << i)) != 0) if ((man & (1 << i)) != 0)
f = f + 1; f = f + 1;
} }
//Debug.Log($"{f}"); //Debug.Log($"{f}");
f = f * (float)Math.Pow(2.0f, exp - 25); f = f * (float)Math.Pow(2.0f, exp - 25);
if (exp == 0) { if (exp == 0)
{
f = f * (float)Math.Pow(2.0f, -13); // 5.96046447754e-8; f = f * (float)Math.Pow(2.0f, -13); // 5.96046447754e-8;
} }
//Debug.Log($"{f}"); //Debug.Log($"{f}");
return sgn ? -f : f; return sgn ? -f : f;
} }
public static uint SingleToInt32Bits(float value) { public static uint SingleToInt32Bits(float value)
{
byte[] bytes = BitConverter.GetBytes(value); byte[] bytes = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
Array.Reverse(bytes); // If the system is little-endian, reverse the byte order Array.Reverse(bytes); // If the system is little-endian, reverse the byte order
return BitConverter.ToUInt32(bytes, 0); return BitConverter.ToUInt32(bytes, 0);
} }
public ushort F32ToF16__(float f) { public ushort F32ToF16__(float f)
{
uint t = BitConverter.ToUInt32(BitConverter.GetBytes(f), 0); uint t = BitConverter.ToUInt32(BitConverter.GetBytes(f), 0);
ushort man = (ushort)((t & 0x007FFFFF) >> 12); ushort man = (ushort)((t & 0x007FFFFF) >> 12);
int exp = (int)((t & 0x7F800000) >> 23); int exp = (int)((t & 0x7F800000) >> 23);
bool sgn = (t & 0x80000000) != 0; bool sgn = (t & 0x80000000) != 0;
// handle 0 // handle 0
if ((t & 0x7FFFFFFF) == 0) { if ((t & 0x7FFFFFFF) == 0)
{
return sgn ? (ushort)0x8000 : (ushort)0x0000; return sgn ? (ushort)0x8000 : (ushort)0x0000;
} }
// denormalized float32 does not fit in float16 // denormalized float32 does not fit in float16
if (exp == 0x00) { if (exp == 0x00)
{
return sgn ? (ushort)0x8000 : (ushort)0x0000; return sgn ? (ushort)0x8000 : (ushort)0x0000;
} }
// handle infinity & NAN // handle infinity & NAN
if (exp == 0x00FF) { if (exp == 0x00FF)
{
if (man != 0) if (man != 0)
return 0xFE00; // NAN return 0xFE00; // NAN
return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF
@ -231,11 +245,13 @@ namespace LinearAlgebra {
// normal numbers // normal numbers
exp = exp - 127 + 15; exp = exp - 127 + 15;
// overflow does not fit => INF // overflow does not fit => INF
if (exp > 30) { if (exp > 30)
{
return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF
} }
// subnormal numbers // subnormal numbers
if (exp < -38) { if (exp < -38)
{
return sgn ? (ushort)0x8000 : (ushort)0x0000; // -0 or 0 ? just 0 ? return sgn ? (ushort)0x8000 : (ushort)0x0000; // -0 or 0 ? just 0 ?
} }
if (exp <= 0) // subnormal if (exp <= 0) // subnormal
@ -260,7 +276,8 @@ namespace LinearAlgebra {
} }
//This function is faulty!!!! //This function is faulty!!!!
ushort f32tof16(float f) { ushort f32tof16(float f)
{
//uint t = *(uint*)&f; //uint t = *(uint*)&f;
//uint t = (uint)BitConverter.SingleToInt32Bits(f); //uint t = (uint)BitConverter.SingleToInt32Bits(f);
uint t = SingleToInt32Bits(f); uint t = SingleToInt32Bits(f);
@ -270,15 +287,18 @@ namespace LinearAlgebra {
bool sgn = (t & 0x80000000) != 0; bool sgn = (t & 0x80000000) != 0;
// handle 0 // handle 0
if ((t & 0x7FFFFFFF) == 0) { if ((t & 0x7FFFFFFF) == 0)
{
return sgn ? (ushort)0x8000 : (ushort)0x0000; return sgn ? (ushort)0x8000 : (ushort)0x0000;
} }
// denormalized float32 does not fit in float16 // denormalized float32 does not fit in float16
if (exp == 0x00) { if (exp == 0x00)
{
return sgn ? (ushort)0x8000 : (ushort)0x0000; return sgn ? (ushort)0x8000 : (ushort)0x0000;
} }
// handle infinity & NAN // handle infinity & NAN
if (exp == 0x00FF) { if (exp == 0x00FF)
{
if (man != 0) if (man != 0)
return 0xFE00; // NAN return 0xFE00; // NAN
return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF
@ -287,11 +307,13 @@ namespace LinearAlgebra {
// normal numbers // normal numbers
exp = (short)(exp - 127 + 15); exp = (short)(exp - 127 + 15);
// overflow does not fit => INF // overflow does not fit => INF
if (exp > 30) { if (exp > 30)
{
return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF
} }
// subnormal numbers // subnormal numbers
if (exp < -38) { if (exp < -38)
{
return sgn ? (ushort)0x8000 : (ushort)0x0000; // -0 or 0 ? just 0 ? return sgn ? (ushort)0x8000 : (ushort)0x0000; // -0 or 0 ? just 0 ?
} }
if (exp <= 0) // subnormal if (exp <= 0) // subnormal

View File

@ -11,7 +11,7 @@ namespace LinearAlgebra.Test
} }
[Test] [Test]
public void Normalize() public void Test_Normalize()
{ {
float r = 0; float r = 0;

View File

@ -1,28 +0,0 @@
using NUnit.Framework;
namespace LinearAlgebra.Test {
public class SphericalTest {
[SetUp]
public void Setup() {
}
[Test]
public void FromVector3() {
Vector3 v = new(0, 0, 1);
Spherical s = Spherical.FromVector3(v);
Assert.AreEqual(1.0f, s.distance, "s.distance 0 0 1");
Assert.AreEqual(0.0f, s.direction.horizontal, "s.hor 0 0 1");
Assert.AreEqual(0.0f, s.direction.vertical, "s.vert 0 0 1");
}
[Test]
public void Addition() {
Spherical v1 = Spherical.Degrees(1, 45, 0);
Spherical v2 = Spherical.zero;
Spherical r = Spherical.zero;
r = v1 + v2;
Assert.AreEqual(v1.distance, r.distance, "Addition(0,0,0)");
}
}
}

View File

@ -13,7 +13,7 @@ namespace RoboidControl {
float distance = ReceiveFloat16(data, ref ix); float distance = ReceiveFloat16(data, ref ix);
float horizontal = ReceiveAngle8(data, ref ix); float horizontal = ReceiveAngle8(data, ref ix);
float vertical = ReceiveAngle8(data, ref ix); float vertical = ReceiveAngle8(data, ref ix);
Spherical v = Spherical.Degrees(distance, horizontal, vertical); Spherical v = new Spherical(distance, horizontal, vertical);
return v; return v;
} }

View File

@ -13,7 +13,7 @@ namespace RoboidControl.test {
[Test] [Test]
public void Test_Participant() { public void Test_Participant() {
ParticipantUDP participant = new ParticipantUDP("127.0.0.1", 7682); LocalParticipant participant = new LocalParticipant("127.0.0.1", 7682);
ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
ulong startTime = milliseconds; ulong startTime = milliseconds;
@ -46,7 +46,7 @@ namespace RoboidControl.test {
[Test] [Test]
public void Test_SiteParticipant() { public void Test_SiteParticipant() {
SiteServer siteServer = new SiteServer(7681); SiteServer siteServer = new SiteServer(7681);
ParticipantUDP participant = new ParticipantUDP("127.0.0.1", 7681); LocalParticipant participant = new LocalParticipant("127.0.0.1", 7681);
ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
ulong startTime = milliseconds; ulong startTime = milliseconds;
@ -64,7 +64,7 @@ namespace RoboidControl.test {
[Test] [Test]
public void Test_ThingMsg() { public void Test_ThingMsg() {
SiteServer siteServer = new SiteServer(7681); SiteServer siteServer = new SiteServer(7681);
ParticipantUDP participant = new ParticipantUDP("127.0.0.1"); LocalParticipant participant = new LocalParticipant("127.0.0.1");
Thing thing = new Thing(participant) { Thing thing = new Thing(participant) {
name = "First Thing", name = "First Thing",
modelUrl = "https://passer.life/extras/ant.jpg" modelUrl = "https://passer.life/extras/ant.jpg"