diff --git a/LinearAlgebra/.editorconfig b/LinearAlgebra/.editorconfig
new file mode 100644
index 0000000..1ec7f97
--- /dev/null
+++ b/LinearAlgebra/.editorconfig
@@ -0,0 +1,19 @@
+# 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
\ No newline at end of file
diff --git a/LinearAlgebra/.gitignore b/LinearAlgebra/.gitignore
new file mode 100644
index 0000000..f0b2f47
--- /dev/null
+++ b/LinearAlgebra/.gitignore
@@ -0,0 +1,6 @@
+DoxyGen/DoxyWarnLogfile.txt
+.vscode/settings.json
+**bin
+**obj
+**.meta
+*.sln
diff --git a/LinearAlgebra/src/Angle.cs b/LinearAlgebra/src/Angle.cs
new file mode 100644
index 0000000..1490983
--- /dev/null
+++ b/LinearAlgebra/src/Angle.cs
@@ -0,0 +1,233 @@
+using System;
+using System.Reflection.Emit;
+
+namespace LinearAlgebra {
+
+ public class Angle {
+ public const float Rad2Deg = 360.0f / ((float)Math.PI * 2); //0.0174532924F;
+ public const float Deg2Rad = (float)Math.PI * 2 / 360.0f; //57.29578F;
+
+ private Angle(float degrees) {
+ this.value = degrees;
+ }
+ private readonly float value = 0;
+
+ public static readonly Angle zero = new(0);
+
+ public static Angle Degrees(float degrees) {
+ // Reduce it to (-180..180]
+ if (float.IsFinite(degrees)) {
+ while (degrees < -180)
+ degrees += 360;
+ while (degrees >= 180)
+ degrees -= 360;
+ }
+ return new Angle(degrees);
+ }
+
+ public static Angle Radians(float radians) {
+ // Reduce it to (-pi..pi]
+ if (float.IsFinite(radians)) {
+ while (radians <= -Math.PI)
+ radians += 2 * (float)Math.PI;
+ while (radians > Math.PI)
+ radians -= 2 * (float)Math.PI;
+ }
+
+ return new Angle(radians * Rad2Deg);
+
+ }
+
+ public static Angle Revolutions(float revolutions) {
+ // reduce it to (-0.5 .. 0.5]
+ if (float.IsFinite(revolutions)) {
+ // Get the integer part
+ int integerPart = (int)revolutions;
+
+ // Get the decimal part
+ revolutions -= integerPart;
+ if (revolutions < -0.5)
+ revolutions += 1;
+ if (revolutions >= 0.5)
+ revolutions -= 1;
+ }
+ return new Angle(revolutions * 360);
+ }
+
+ public float inDegrees {
+ get { return this.value; }
+ }
+
+ public float inRadians {
+ get { return this.value * Deg2Rad; }
+ }
+
+ public float inRevolutions {
+ get { return this.value / 360.0f; }
+ }
+
+ ///
+ /// Get the sign of the angle
+ ///
+ /// The angle
+ /// -1 when the angle is negative, 1 when it is positive and 0 in all other cases
+ public static int Sign(Angle a) {
+ if (a.value < 0)
+ return -1;
+ if (a.value > 0)
+ return 1;
+ return 0;
+ }
+
+ ///
+ /// Returns the magnitude of the angle
+ ///
+ /// The angle
+ /// The positive magnitude of the angle
+ /// Negative values are negated to get a positive result
+ public static Angle Abs(Angle a) {
+ if (Sign(a) < 0)
+ return -a;
+ else
+ return a;
+ }
+
+ ///
+ /// Negate the angle
+ ///
+ /// The angle
+ /// The negated angle
+ /// The negation of -180 is still -180 because the range is (-180..180]
+ public static Angle operator -(Angle a) {
+ Angle r = new(-a.value);
+ return r;
+ }
+
+ ///
+ /// Subtract two angles
+ ///
+ /// Angle 1
+ /// Angle 2
+ /// The result of the subtraction
+ public static Angle operator -(Angle a1, Angle a2) {
+ Angle r = new(a1.value - a2.value);
+ return r;
+ }
+ ///
+ /// Add two angles
+ ///
+ /// Angle 1
+ /// Angle 2
+ /// The result of the addition
+ public static Angle operator +(Angle a1, Angle a2) {
+ Angle r = new(a1.value + a2.value);
+ return r;
+ }
+
+ ///
+ /// Clamp the angle between the given min and max values
+ ///
+ /// The angle to clamp
+ /// The minimum angle
+ /// The maximum angle
+ /// The clamped angle
+ /// Angles are normalized
+ public static float Clamp(Angle angle, Angle min, Angle max) {
+ return Float.Clamp(angle.inDegrees, min.inDegrees, max.inDegrees);
+ }
+
+ ///
+ /// Rotate from one angle to the other with a maximum degrees
+ ///
+ /// Starting angle
+ /// Target angle
+ /// Maximum angle to rotate
+ /// The resulting angle
+ /// This function is compatible with radian and degrees angles
+ public static Angle MoveTowards(Angle fromAngle, Angle toAngle, float maxDegrees) {
+ maxDegrees = Math.Max(0, maxDegrees); // filter out negative distances
+ Angle d = toAngle - fromAngle;
+ float dDegrees = Abs(d).inDegrees;
+ d = Degrees(Float.Clamp(dDegrees, 0, maxDegrees));
+ if (Sign(d) < 0)
+ d = -d;
+ return fromAngle + d;
+ }
+ }
+
+ ///
+ /// %Angle utilities
+ ///
+ public static class Angles {
+ public const float pi = 3.1415927410125732421875F;
+ // public static float Rad2Deg = 360.0f / ((float)Math.PI * 2);
+ // public static float Deg2Rad = ((float)Math.PI * 2) / 360.0f;
+
+
+ ///
+ /// Determine the angle difference, result is a normalized angle
+ ///
+ /// First first angle
+ /// The second angle
+ /// the angle between the two angles
+ /// Angle values should be degrees
+ public static float Difference(float a, float b) {
+ float r = Normalize(b - a);
+ return r;
+ }
+
+ ///
+ /// Normalize an angle to the range -180 < angle <= 180
+ ///
+ /// The angle to normalize
+ /// The normalized angle in interval (-180..180]
+ /// Angle values should be in degrees
+ public static float Normalize(float angle) {
+ if (float.IsInfinity(angle))
+ return angle;
+
+ while (angle <= -180) angle += 360;
+ while (angle > 180) angle -= 360;
+ return angle;
+ }
+
+ ///
+ /// Map interval of angles between vectors [0..Pi] to interval [0..1]
+ ///
+ /// The first vector
+ /// The second vector
+ /// The resulting factor in interval [0..1]
+ /// Vectors a and b must be normalized
+ /// \deprecated Please use Vector2.ToFactor instead.
+ [Obsolete("Please use Vector2.ToFactor instead.")]
+ public static float ToFactor(Vector2 v1, Vector2 v2) {
+ return (1 - Vector2.Dot(v1, v2)) / 2;
+ }
+
+ // Normalize all vector angles to the range -180 < angle < 180
+ //public static Vector3 Normalize(Vector3 angles) {
+ // float x = Normalize(angles.x);
+ // float y = Normalize(angles.y);
+ // float z = Normalize(angles.z);
+ // return new Vector3(x, y, z);
+ //}
+
+ // Returns the signed angle in degrees between from and to.
+ //public static float SignedAngle(Vector3 from, Vector3 to) {
+ // float angle = Vector3.Angle(from, to);
+ // Vector3 cross = Vector3.Cross(from, to);
+ // if (cross.y < 0) angle = -angle;
+ // return angle;
+ //}
+
+ // Returns the signed angle in degrees between from and to.
+ //public static float SignedAngle(Vector2 from, Vector2 to) {
+ // float sign = Math.Sign(from.y * to.x - from.x * to.y);
+ // return Vector2.Angle(from, to) * sign;
+ //}
+
+ //public static Quaternion ToQuaternion(Rotation orientation) {
+ // return new Quaternion(orientation.x, orientation.y, orientation.z, orientation.w);
+ //}
+ }
+}
\ No newline at end of file
diff --git a/LinearAlgebra/src/Decomposition.cs b/LinearAlgebra/src/Decomposition.cs
new file mode 100644
index 0000000..ddaf434
--- /dev/null
+++ b/LinearAlgebra/src/Decomposition.cs
@@ -0,0 +1,287 @@
+using System;
+namespace LinearAlgebra {
+ class QR {
+ // QR Decomposition of a matrix A
+ public static (Matrix2 Q, Matrix2 R) Decomposition(Matrix2 A) {
+ int nRows = A.nRows;
+ int nCols = A.nCols;
+
+ float[,] Q = new float[nRows, nCols];
+ float[,] R = new float[nCols, nCols];
+
+ // Perform Gram-Schmidt orthogonalization
+ for (uint colIx = 0; colIx < nCols; colIx++) {
+
+ // Step 1: v = column(ix) of A
+ float[] v = new float[nRows];
+ for (int rowIx = 0; rowIx < nRows; rowIx++)
+ v[rowIx] = A.data[rowIx, colIx];
+
+ // Step 2: Subtract projections of v onto previous q's (orthogonalize)
+ for (uint colIx2 = 0; colIx2 < colIx; colIx2++) {
+ float dotProd = 0;
+ for (int i = 0; i < nRows; i++)
+ dotProd += Q[i, colIx2] * v[i];
+ for (int i = 0; i < nRows; i++)
+ v[i] -= dotProd * Q[i, colIx2];
+ }
+
+ // Step 3: Normalize v to get column(ix) of Q
+ float norm = 0;
+ for (int rowIx = 0; rowIx < nRows; rowIx++)
+ norm += v[rowIx] * v[rowIx];
+ norm = (float)Math.Sqrt(norm);
+
+ for (int rowIx = 0; rowIx < nRows; rowIx++)
+ Q[rowIx, colIx] = v[rowIx] / norm;
+
+ // Store the coefficients of R
+ for (int colIx2 = 0; colIx2 <= colIx; colIx2++) {
+ R[colIx2, colIx] = 0;
+ for (int k = 0; k < nRows; k++)
+ R[colIx2, colIx] += Q[k, colIx2] * A.data[k, colIx];
+ }
+ }
+ return (new Matrix2(Q), new Matrix2(R));
+ }
+
+ // Reduced QR Decomposition of a matrix A
+ public static (Matrix2 Q, Matrix2 R) ReducedDecomposition(Matrix2 A) {
+ int nRows = A.nRows;
+ int nCols = A.nCols;
+
+ float[,] Q = new float[nRows, nCols];
+ float[,] R = new float[nCols, nCols];
+
+ // Perform Gram-Schmidt orthogonalization
+ for (int colIx = 0; colIx < nCols; colIx++) {
+
+ // Step 1: v = column(colIx) of A
+ float[] columnIx = new float[nRows];
+ bool isZeroColumn = true;
+ for (int rowIx = 0; rowIx < nRows; rowIx++) {
+ columnIx[rowIx] = A.data[rowIx, colIx];
+ if (columnIx[rowIx] != 0)
+ isZeroColumn = false;
+ }
+ if (isZeroColumn) {
+ for (int rowIx = 0; rowIx < nRows; rowIx++)
+ Q[rowIx, colIx] = 0;
+ // Set corresponding R element to 0
+ R[colIx, colIx] = 0;
+
+ Console.WriteLine($"zero column {colIx}");
+
+ continue;
+ }
+
+ // Step 2: Subtract projections of v onto previous q's (orthogonalize)
+ for (int colIx2 = 0; colIx2 < colIx; colIx2++) {
+ // Compute the dot product of v and column(colIx2) of Q
+ float dotProduct = 0;
+ for (int rowIx2 = 0; rowIx2 < nRows; rowIx2++)
+ dotProduct += columnIx[rowIx2] * Q[rowIx2, colIx2];
+ // Subtract the projection from v
+ for (int rowIx2 = 0; rowIx2 < nRows; rowIx2++)
+ columnIx[rowIx2] -= dotProduct * Q[rowIx2, colIx2];
+ }
+
+ // Step 3: Normalize v to get column(colIx) of Q
+ float norm = 0;
+ for (int rowIx = 0; rowIx < nRows; rowIx++)
+ norm += columnIx[rowIx] * columnIx[rowIx];
+ if (norm == 0)
+ throw new Exception("invalid value");
+
+ norm = (float)Math.Sqrt(norm);
+
+ for (int rowIx = 0; rowIx < nRows; rowIx++)
+ Q[rowIx, colIx] = columnIx[rowIx] / norm;
+
+ // Here is where it deviates from the Full QR Decomposition !
+
+ // Step 4: Compute the row(colIx) of R
+ for (int colIx2 = colIx; colIx2 < nCols; colIx2++) {
+ float dotProduct = 0;
+ for (int rowIx2 = 0; rowIx2 < nRows; rowIx2++)
+ dotProduct += Q[rowIx2, colIx] * A.data[rowIx2, colIx2];
+ R[colIx, colIx2] = dotProduct;
+ }
+ }
+ if (!float.IsFinite(R[0, 0]))
+ throw new Exception("invalid value");
+
+ return (new Matrix2(Q), new Matrix2(R));
+ }
+ }
+
+ class SVD {
+ // According to ChatGPT, Mathnet uses Golub-Reinsch SVD algorithm
+ // 1. Bidiagonalization: The input matrix AA is reduced to a bidiagonal form using Golub-Kahan bidiagonalization.
+ // This process involves applying a sequence of Householder reflections to AA to create a bidiagonal matrix.
+ // This step reduces the complexity by making the matrix simpler while retaining the essential structure needed for SVD.
+ //
+ // 2. Diagonalization: Once the matrix is in bidiagonal form,
+ // the singular values are computed using an iterative process
+ // (typically involving QR factorization or Jacobi rotations) until convergence.
+ // This process diagonalizes the bidiagonal matrix and allows extraction of the singular values.
+ //
+ // 3. Computing UU and VTVT: After obtaining the singular values,
+ // the left singular vectors UU and right singular vectors VTVT are computed
+ // using the accumulated transformations (such as Householder reflections) from the bidiagonalization step.
+
+ // Bidiagnolizations through Householder transformations
+ public static (Matrix2 U1, Matrix2 B, Matrix2 V1) Bidiagonalization(Matrix2 A) {
+ int m = A.nRows; // Rows of A
+ int n = A.nCols; // Columns of A
+ float[,] U1 = new float[m, m]; // Left orthogonal matrix
+ float[,] V1 = new float[n, n]; // Right orthogonal matrix
+ float[,] B = A.Clone().data; // Copy A to B for transformation
+
+ // Initialize U1 and V1 as identity matrices
+ for (int i = 0; i < m; i++)
+ U1[i, i] = 1;
+ for (int i = 0; i < n; i++)
+ V1[i, i] = 1;
+
+ // Perform Householder reflections to create a bidiagonal matrix B
+ for (int j = 0; j < n; j++) {
+ // Step 1: Construct the Householder vector y
+ float[] y = new float[m - j];
+ for (int i = j; i < m; i++)
+ y[i - j] = B[i, j];
+
+ // Step 2: Compute the norm and scalar alpha
+ float norm = 0;
+ for (int i = 0; i < y.Length; i++)
+ norm += y[i] * y[i];
+ norm = (float)Math.Sqrt(norm);
+
+ if (B[j, j] > 0)
+ norm = -norm;
+
+ float alpha = (float)Math.Sqrt(0.5 * (norm * (norm - B[j, j])));
+ float r = (float)Math.Sqrt(0.5 * (norm * (norm + B[j, j])));
+
+ // Step 3: Apply the reflection to zero out below diagonal
+ for (int k = j; k < n; k++) {
+ float dot = 0;
+ for (int i = j; i < m; i++)
+ dot += y[i - j] * B[i, k];
+ dot /= r;
+
+ for (int i = j; i < m; i++)
+ B[i, k] -= 2 * dot * y[i - j];
+ }
+
+ // Step 4: Update U1 with the Householder reflection (U1 * Householder)
+ for (int i = j; i < m; i++)
+ U1[i, j] = y[i - j] / alpha;
+
+ // Step 5: Update V1 (storing the Householder vector y)
+ // Correct indexing: we only need to store part of y in V1 from index j to n
+ for (int i = j; i < n; i++)
+ V1[j, i] = B[j, i];
+
+ // Repeat steps for further columns if necessary
+ }
+ return (new Matrix2(U1), new Matrix2(B), new Matrix2(V1));
+ }
+
+ public static Matrix2 Bidiagonalize(Matrix2 A) {
+ int m = A.nRows; // Rows of A
+ int n = A.nCols; // Columns of A
+ float[,] B = A.Clone().data; // Copy A to B for transformation
+
+ // Perform Householder reflections to create a bidiagonal matrix B
+ for (int j = 0; j < n; j++) {
+ // Step 1: Construct the Householder vector y
+ float[] y = new float[m - j];
+ for (int i = j; i < m; i++)
+ y[i - j] = B[i, j];
+
+ // Step 2: Compute the norm and scalar alpha
+ float norm = 0;
+ for (int i = 0; i < y.Length; i++)
+ norm += y[i] * y[i];
+ norm = (float)Math.Sqrt(norm);
+
+ if (B[j, j] > 0)
+ norm = -norm;
+
+ float r = (float)Math.Sqrt(0.5 * (norm * (norm + B[j, j])));
+
+ // Step 3: Apply the reflection to zero out below diagonal
+ for (int k = j; k < n; k++) {
+ float dot = 0;
+ for (int i = j; i < m; i++)
+ dot += y[i - j] * B[i, k];
+ dot /= r;
+
+ for (int i = j; i < m; i++)
+ B[i, k] -= 2 * dot * y[i - j];
+ }
+
+ // Repeat steps for further columns if necessary
+ }
+ return new Matrix2(B);
+ }
+
+ // QR Iteration for diagonalization of a bidiagonal matrix B
+ public static (Matrix1 singularValues, Matrix2 U, Matrix2 Vt) QRIteration(Matrix2 B) {
+ int m = B.nRows;
+ int n = B.nCols;
+
+ Matrix2 U = new(m, m); // Left singular vectors (U)
+ Matrix2 Vt = new(n, n); // Right singular vectors (V^T)
+ float[] singularValues = new float[Math.Min(m, n)]; // Singular values
+
+ // Initialize U and Vt as identity matrices
+ for (int i = 0; i < m; i++)
+ U.data[i, i] = 1;
+ for (int i = 0; i < n; i++)
+ Vt.data[i, i] = 1;
+
+ // Perform QR iterations
+ float tolerance = 1e-7f; //1e-12f; for double
+ bool converged = false;
+ while (!converged) {
+ // Perform QR decomposition on the matrix B
+ (Matrix2 Q, Matrix2 R) = QR.Decomposition(B);
+
+ // Update B to be the product Q * R //R * Q
+ B = R * Q;
+
+ // Accumulate the transformations in U and Vt
+ U *= Q;
+ Vt *= R;
+
+ // Check convergence by looking at the off-diagonal elements of B
+ converged = true;
+ for (int i = 0; i < m - 1; i++) {
+ for (int j = i + 1; j < n; j++) {
+ if (Math.Abs(B.data[i, j]) > tolerance) {
+ converged = false;
+ break;
+ }
+ }
+ }
+ }
+
+ // Extract singular values (diagonal elements of B)
+ for (int i = 0; i < Math.Min(m, n); i++)
+ singularValues[i] = B.data[i, i];
+
+ return (new Matrix1(singularValues), U, Vt);
+ }
+
+ public static (Matrix2 U, Matrix1 S, Matrix2 Vt) Decomposition(Matrix2 A) {
+ if (A.nRows != A.nCols)
+ throw new ArgumentException("SVD: matrix A has to be square.");
+
+ Matrix2 B = Bidiagonalize(A);
+ (Matrix1 S, Matrix2 U, Matrix2 Vt) = QRIteration(B);
+ return (U, S, Vt);
+ }
+ }
+}
\ No newline at end of file
diff --git a/LinearAlgebra/src/Direction.cs b/LinearAlgebra/src/Direction.cs
new file mode 100644
index 0000000..297eff9
--- /dev/null
+++ b/LinearAlgebra/src/Direction.cs
@@ -0,0 +1,87 @@
+using System;
+#if UNITY_5_3_OR_NEWER
+using Vector3Float = UnityEngine.Vector3;
+#endif
+
+namespace LinearAlgebra
+{
+
+ ///
+ /// A direction in 3D space
+ ///
+ /// 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 vertical;
+
+ public Direction() {
+ horizontal = 0;
+ vertical = 0;
+ }
+ public Direction(Angle horizontal, Angle vertical) {
+ this.horizontal = horizontal.inDegrees;
+
+ }
+ // public Direction(float horizontal, float vertical) {
+ // this.horizontal = horizontal;
+ // this.vertical = vertical;
+ // //Normalize();
+ // }
+
+ public static Direction Degrees(float horizontal, float vertical) {
+ Direction d = new() {
+ horizontal = horizontal,
+ vertical = vertical
+ };
+ //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 backward = Degrees(-180, 0);
+ public readonly static Direction up = Degrees(0, 90);
+ public readonly static Direction down = Degrees(0, -90);
+ public readonly static Direction left = Degrees(-90, 0);
+ public readonly static Direction right = Degrees(90, 0);
+
+ public void Normalize() {
+ if (this.vertical > 90 || this.vertical < -90) {
+ this.horizontal += 180;
+ this.vertical = 180 - this.vertical;
+ }
+ }
+
+ public Vector3Float ToVector3()
+ {
+ float verticalRad = ((float)Math.PI / 2) - this.vertical * Angle.Deg2Rad;
+ float horizontalRad = this.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 = sinVertical * sinHorizontal;
+ float y = cosVertical;
+ float z = sinVertical * cosHorizontal;
+
+ Vector3Float v = new(x, y, z);
+ return v;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/LinearAlgebra/src/Float.cs b/LinearAlgebra/src/Float.cs
new file mode 100644
index 0000000..2217b84
--- /dev/null
+++ b/LinearAlgebra/src/Float.cs
@@ -0,0 +1,41 @@
+namespace LinearAlgebra {
+
+ ///
+ /// Float number utilities
+ ///
+ public class Float {
+ ///
+ /// The precision of float numbers
+ ///
+ public const float epsilon = 1E-05f;
+ ///
+ /// The square of the float number precision
+ ///
+ public const float sqrEpsilon = 1e-10f;
+
+ ///
+ /// Clamp the value between the given minimum and maximum values
+ ///
+ /// The value to clamp
+ /// The minimum value
+ /// The maximum value
+ /// The clamped value
+ public static float Clamp(float f, float min, float max) {
+ if (f < min)
+ return min;
+ if (f > max)
+ return max;
+ return f;
+ }
+
+ ///
+ /// Clamp the value between to the interval [0..1]
+ ///
+ /// The value to clamp
+ /// The clamped value
+ public static float Clamp01(float f) {
+ return Clamp(f, 0, 1);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/LinearAlgebra/src/LinearAlgebra.csproj b/LinearAlgebra/src/LinearAlgebra.csproj
new file mode 100644
index 0000000..14d3947
--- /dev/null
+++ b/LinearAlgebra/src/LinearAlgebra.csproj
@@ -0,0 +1,14 @@
+
+
+
+ false
+ false
+ net5.0
+
+
+
+
+
+
+
+
diff --git a/LinearAlgebra/src/Matrix.cs b/LinearAlgebra/src/Matrix.cs
new file mode 100644
index 0000000..5196d48
--- /dev/null
+++ b/LinearAlgebra/src/Matrix.cs
@@ -0,0 +1,689 @@
+using System;
+#if UNITY_5_3_OR_NEWER
+using Vector3Float = UnityEngine.Vector3;
+using Vector2Float = UnityEngine.Vector2;
+using Quaternion = UnityEngine.Quaternion;
+#endif
+
+namespace LinearAlgebra {
+
+ public readonly struct Slice
+ {
+ public int start { get; }
+ public int stop { get; }
+ public Slice(int start, int stop)
+ {
+ this.start = start;
+ this.stop = stop;
+ }
+ }
+
+ public class Matrix2 {
+ public float[,] data { get; }
+
+ public int nRows => data.GetLength(0);
+ public int nCols => data.GetLength(1);
+
+ public Matrix2(int nRows, int nCols)
+ {
+ this.data = new float[nRows, nCols];
+ }
+ public Matrix2(float[,] data) {
+ this.data = data;
+ }
+
+ public Matrix2 Clone() {
+ float[,] data = new float[this.nRows, nCols];
+ for (int rowIx = 0; rowIx < this.nRows; rowIx++) {
+ for (int colIx = 0; colIx < this.nCols; colIx++)
+ data[rowIx, colIx] = this.data[rowIx, colIx];
+ }
+ return new Matrix2(data);
+ }
+
+ public static Matrix2 Zero(int nRows, int nCols)
+ {
+ return new Matrix2(nRows, nCols);
+ }
+
+ public static Matrix2 FromVector3(Vector3Float v) {
+ float[,] result = new float[3, 1];
+ result[0, 0] = v.x;
+ result[1, 0] = v.y;
+ result[2, 0] = v.z;
+ return new Matrix2(result);
+ }
+
+ public static Matrix2 Identity(int size)
+ {
+ return Diagonal(1, size);
+ }
+ public static Matrix2 Identity(int nRows, int nCols)
+ {
+ Matrix2 m = Zero(nRows, nCols);
+ m.FillDiagonal(1);
+ return m;
+ }
+
+ public static Matrix2 Diagonal(Matrix1 v) {
+ float[,] resultData = new float[v.size, v.size];
+ for (int ix = 0; ix < v.size; ix++)
+ resultData[ix, ix] = v.data[ix];
+ return new Matrix2(resultData);
+ }
+ public static Matrix2 Diagonal(float f, int size)
+ {
+ float[,] resultData = new float[size, size];
+ for (int ix = 0; ix < size; ix++)
+ resultData[ix, ix] = f;
+ return new Matrix2(resultData);
+ }
+ public void FillDiagonal(Matrix1 v)
+ {
+ int n = (int)Math.Min(Math.Min(this.nRows, this.nCols), v.size);
+ for (int ix = 0; ix < n; ix++)
+ this.data[ix, ix] = v.data[ix];
+ }
+ public void FillDiagonal(float f)
+ {
+ int n = Math.Min(this.nRows, this.nCols);
+ for (int ix = 0; ix < n; ix++)
+ this.data[ix, ix] = f;
+ }
+
+ public static Matrix2 SkewMatrix(Vector3Float v) {
+ float[,] result = new float[3, 3] {
+ {0, -v.z, v.y},
+ {v.z, 0, -v.x},
+ {-v.y, v.x, 0}
+ };
+ return new Matrix2(result);
+ }
+
+ public Matrix1 GetRow(int rowIx) {
+ float[] row = new float[this.nCols];
+ for (int colIx = 0; colIx < this.nCols; colIx++) {
+ row[colIx] = this.data[rowIx, colIx];
+ }
+ return new Matrix1(row);
+ }
+
+#if UNITY_5_3_OR_NEWER
+ public Vector3Float GetRow3(int rowIx) {
+ int cols = this.nCols;
+ Vector3Float row = new() {
+ x = this.data[rowIx, 0],
+ y = this.data[rowIx, 1],
+ z = this.data[rowIx, 2]
+ };
+ return row;
+ }
+#endif
+ public void SetRow(int rowIx, Matrix1 v) {
+ for (uint ix = 0; ix < v.size; ix++)
+ this.data[rowIx, ix] = v.data[ix];
+ }
+ public void SetRow3(int rowIx, Vector3Float v) {
+ this.data[rowIx, 0] = v.x;
+ this.data[rowIx, 1] = v.y;
+ this.data[rowIx, 2] = v.z;
+ }
+
+ public void SwapRows(int row1, int row2) {
+ for (uint ix = 0; ix < this.nCols; ix++) {
+ float temp = this.data[row1, ix];
+ this.data[row1, ix] = this.data[row2, ix];
+ this.data[row2, ix] = temp;
+ }
+ }
+
+ public Matrix1 GetColumn(int colIx)
+ {
+ float[] column = new float[this.nRows];
+ for (int i = 0; i < this.nRows; i++) {
+ column[i] = this.data[i, colIx];
+ }
+ return new Matrix1(column);
+ }
+ public void SetColumn(int colIx, Matrix1 v) {
+ for (uint ix = 0; ix < v.size; ix++)
+ this.data[ix, colIx] = v.data[ix];
+ }
+ public void SetColumn(int colIx, Vector3Float v) {
+ this.data[0, colIx] = v.x;
+ this.data[1, colIx] = v.y;
+ this.data[2, colIx] = v.z;
+ }
+
+ 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++) {
+ float d = MathF.Abs(A.data[i, j] - B.data[i, j]);
+ if (d > atol)
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public Matrix2 Transpose() {
+ float[,] resultData = new float[this.nCols, this.nRows];
+ for (uint rowIx = 0; rowIx < this.nRows; rowIx++) {
+ for (uint colIx = 0; colIx < this.nCols; colIx++)
+ resultData[colIx, rowIx] = this.data[rowIx, colIx];
+ }
+ return new Matrix2(resultData);
+ // double checked code
+ }
+ public Matrix2 transposed {
+ get => Transpose();
+ }
+
+ public static Matrix2 operator -(Matrix2 m) {
+ float[,] result = new float[m.nRows, m.nCols];
+
+ for (int i = 0; i < m.nRows; i++) {
+ for (int j = 0; j < m.nCols; j++)
+ result[i, j] = -m.data[i, j];
+ }
+ return new Matrix2(result);
+ }
+
+ public static Matrix2 operator -(Matrix2 A, Matrix2 B) {
+ if (A.nRows != B.nRows || A.nCols != B.nCols)
+ throw new System.ArgumentException("Size of A must match size of B.");
+
+ float[,] result = new float[A.nRows, B.nCols];
+
+ for (int i = 0; i < A.nRows; i++) {
+ for (int j = 0; j < A.nCols; j++)
+ result[i, j] = A.data[i, j] - B.data[i, j];
+ }
+ return new Matrix2(result);
+ }
+
+ public static Matrix2 operator +(Matrix2 A, Matrix2 B) {
+ if (A.nRows != B.nRows || A.nCols != B.nCols)
+ throw new System.ArgumentException("Size of A must match size of B.");
+
+ float[,] result = new float[A.nRows, B.nCols];
+
+ for (int i = 0; i < A.nRows; i++) {
+ for (int j = 0; j < A.nCols; j++)
+ result[i, j] = A.data[i, j] + B.data[i, j];
+ }
+ return new Matrix2(result);
+ }
+
+ public static Matrix2 operator *(Matrix2 A, Matrix2 B) {
+ if (A.nCols != B.nRows)
+ throw new System.ArgumentException("Number of columns in A must match number of rows in B.");
+
+ float[,] result = new float[A.nRows, B.nCols];
+
+ for (int i = 0; i < A.nRows; i++) {
+ for (int j = 0; j < B.nCols; j++) {
+ float sum = 0.0f;
+ for (int k = 0; k < A.nCols; k++)
+ sum += A.data[i, k] * B.data[k, j];
+
+ result[i, j] = sum;
+ }
+ }
+
+ return new Matrix2(result);
+ // double checked code
+ }
+
+ public static Matrix1 operator *(Matrix2 A, Matrix1 v) {
+ float[] result = new float[A.nRows];
+
+ for (int i = 0; i < A.nRows; i++) {
+ for (int j = 0; j < A.nCols; j++) {
+ result[i] += A.data[i, j] * v.data[j];
+ }
+ }
+
+ return new Matrix1(result);
+ }
+
+ public static Vector3Float operator *(Matrix2 A, Vector3Float v) {
+ return new Vector3Float(
+ 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[2, 0] * v.x + A.data[2, 1] * v.y + A.data[2, 2] * v.z
+ );
+ }
+
+ public static Matrix2 operator *(Matrix2 A, float s) {
+ float[,] result = new float[A.nRows, A.nCols];
+
+ for (int i = 0; i < A.nRows; i++) {
+ for (int j = 0; j < A.nCols; j++)
+ result[i, j] = A.data[i, j] * s;
+ }
+
+ return new Matrix2(result);
+ }
+ public static Matrix2 operator *(float s, Matrix2 A) {
+ return A * s;
+ }
+
+ public static Matrix2 operator /(Matrix2 A, float s) {
+ float[,] result = new float[A.nRows, A.nCols];
+
+ for (int i = 0; i < A.nRows; i++) {
+ for (int j = 0; j < A.nCols; j++)
+ result[i, j] = A.data[i, j] / s;
+ }
+
+ return new Matrix2(result);
+ }
+ public static Matrix2 operator /(float s, Matrix2 A) {
+ float[,] result = new float[A.nRows, A.nCols];
+
+ for (int i = 0; i < A.nRows; i++) {
+ for (int j = 0; j < A.nCols; j++)
+ result[i, j] = s / A.data[i, j];
+ }
+
+ return new Matrix2(result);
+ }
+
+ public Matrix2 GetRows(Slice slice) {
+ return GetRows(slice.start, slice.stop);
+ }
+ public Matrix2 GetRows(int from, int to) {
+ if (from < 0 || to >= this.nRows)
+ throw new System.ArgumentException("Slice index out of range.");
+
+ float[,] result = new float[to - from, this.nCols];
+ int resultRowIx = 0;
+ for (int rowIx = from; rowIx < to; rowIx++) {
+ for (int colIx = 0; colIx < this.nCols; colIx++)
+ result[resultRowIx, colIx] = this.data[rowIx, colIx];
+
+ resultRowIx++;
+ }
+
+ return new Matrix2(result);
+ }
+
+ public Matrix2 Slice(Slice slice)
+ {
+ return Slice(slice.start, slice.stop);
+ }
+ public Matrix2 Slice(int from, int to)
+ {
+ if (from < 0 || to >= this.nRows)
+ throw new System.ArgumentException("Slice index out of range.");
+
+ float[,] result = new float[to - from, this.nCols];
+ int resultRowIx = 0;
+ for (int rowIx = from; rowIx < to; rowIx++)
+ {
+ for (int colIx = 0; colIx < this.nCols; colIx++)
+ {
+ result[resultRowIx, colIx] = this.data[rowIx, colIx];
+ }
+ resultRowIx++;
+ }
+
+ return new Matrix2(result);
+ }
+ public Matrix2 Slice(Slice rowRange, Slice colRange) {
+ return Slice((rowRange.start, rowRange.stop), (colRange.start, colRange.stop));
+ }
+ public Matrix2 Slice((int start, int stop) rowRange, (int start, int stop) colRange)
+ {
+ float[,] result = new float[rowRange.stop - rowRange.start, colRange.stop - colRange.start];
+
+ int resultRowIx = 0;
+ int resultColIx = 0;
+ for (int i = rowRange.start; i < rowRange.stop; i++)
+ {
+ for (int j = colRange.start; j < colRange.stop; j++)
+ result[resultRowIx, resultColIx] = this.data[i, j];
+ }
+ return new Matrix2(result);
+ }
+
+ public void UpdateSlice(Slice slice, Matrix2 m) {
+ UpdateSlice((slice.start, slice.stop), m);
+ }
+ public void UpdateSlice((int start, int stop) slice, Matrix2 m) {
+ // if (slice.start == slice.stop)
+ // Console.WriteLine("WARNING: no data is updates when start equals stop in a slice!");
+ int mRowIx = 0;
+ for (int rowIx = slice.start; rowIx < slice.stop; rowIx++, mRowIx++) {
+ for (int colIx = 0; colIx < this.nCols; colIx++)
+ this.data[rowIx, colIx] = m.data[mRowIx, colIx];
+ }
+ }
+
+ public void UpdateSlice(Slice rowRange, Slice colRange, Matrix2 m)
+ {
+ UpdateSlice((rowRange.start, rowRange.stop), (colRange.start, colRange.stop), m);
+ }
+ public void UpdateSlice((int start, int stop) rowRange, (int start, int stop) colRange, Matrix2 m)
+ {
+ for (int i = rowRange.start; i < rowRange.stop; i++)
+ {
+ for (int j = colRange.start; j < colRange.stop; j++)
+ this.data[i, j] = m.data[i - rowRange.start, j - colRange.start];
+ }
+ }
+
+ public Matrix2 Inverse() {
+ Matrix2 A = this;
+ // unchecked
+ int n = A.nRows;
+
+ // Create an identity matrix of the same size as the original matrix
+ float[,] augmentedMatrix = new float[n, 2 * n];
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < n; j++) {
+ augmentedMatrix[i, j] = A.data[i, j];
+ augmentedMatrix[i, j + n] = (i == j) ? 1 : 0; // Identity matrix
+ }
+ }
+
+ // Perform Gaussian elimination
+ for (int i = 0; i < n; i++) {
+ // Find the pivot row
+ float pivot = augmentedMatrix[i, i];
+ if (Math.Abs(pivot) < 1e-10) // Check for singular matrix
+ throw new InvalidOperationException("Matrix is singular and cannot be inverted.");
+
+ // Normalize the pivot row
+ for (int j = 0; j < 2 * n; j++)
+ augmentedMatrix[i, j] /= pivot;
+
+ // Eliminate the column below the pivot
+ for (int j = i + 1; j < n; j++) {
+ float factor = augmentedMatrix[j, i];
+ for (int k = 0; k < 2 * n; k++)
+ augmentedMatrix[j, k] -= factor * augmentedMatrix[i, k];
+ }
+ }
+
+ // Back substitution
+ for (int i = n - 1; i >= 0; i--)
+ {
+ // Eliminate the column above the pivot
+ for (int j = i - 1; j >= 0; j--)
+ {
+ float factor = augmentedMatrix[j, i];
+ for (int k = 0; k < 2 * n; k++)
+ augmentedMatrix[j, k] -= factor * augmentedMatrix[i, k];
+ }
+ }
+
+ // Extract the inverse matrix from the augmented matrix
+ float[,] inverse = new float[n, n];
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < n; j++)
+ inverse[i, j] = augmentedMatrix[i, j + n];
+ }
+
+ return new Matrix2(inverse);
+ }
+
+ public float Determinant()
+ {
+ int n = this.nRows;
+ if (n != this.nCols)
+ throw new System.ArgumentException("Matrix must be square.");
+
+ if (n == 1)
+ return this.data[0, 0]; // Base case for 1x1 matrix
+
+ if (n == 2) // Base case for 2x2 matrix
+ return this.data[0, 0] * this.data[1, 1] - this.data[0, 1] * this.data[1, 0];
+
+ float det = 0;
+ for (int col = 0; col < n; col++)
+ det += (col % 2 == 0 ? 1 : -1) * this.data[0, col] * this.Minor(0, col).Determinant();
+
+ return det;
+ }
+
+ // Helper function to compute the minor of a matrix
+ private Matrix2 Minor(int rowToRemove, int colToRemove)
+ {
+ int n = this.nRows;
+ float[,] minor = new float[n - 1, n - 1];
+
+ int r = 0, c = 0;
+ for (int i = 0; i < n; i++) {
+ if (i == rowToRemove) continue;
+
+ c = 0;
+ for (int j = 0; j < n; j++) {
+ if (j == colToRemove) continue;
+
+ minor[r, c] = this.data[i, j];
+ c++;
+ }
+ r++;
+ }
+
+ return new Matrix2(minor);
+ }
+
+ public static Matrix2 DeleteRows(Matrix2 A, Slice rowRange) {
+ float[,] result = new float[A.nRows - (rowRange.stop - rowRange.start), A.nCols];
+
+ int resultRowIx = 0;
+ for (int i = 0; i < A.nRows; i++) {
+ if (i >= rowRange.start && i < rowRange.stop)
+ continue;
+
+ for (int j = 0; j < A.nCols; j++)
+ result[resultRowIx, j] = A.data[i, j];
+
+ resultRowIx++;
+ }
+ return new Matrix2(result);
+ }
+
+ internal static Matrix2 DeleteColumns(Matrix2 A, Slice colRange) {
+ float[,] result = new float[A.nRows, A.nCols - (colRange.stop - colRange.start)];
+
+ for (int i = 0; i < A.nRows; i++) {
+ int resultColIx = 0;
+ for (int j = 0; j < A.nCols; j++) {
+ if (j >= colRange.start && j < colRange.stop)
+ continue;
+
+ result[i, resultColIx++] = A.data[i, j];
+ }
+ }
+ return new Matrix2(result);
+ }
+ }
+
+ public class Matrix1
+ {
+ public float[] data { get; }
+
+ public int size => data.GetLength(0);
+
+ public Matrix1(int size)
+ {
+ this.data = new float[size];
+ }
+
+ public Matrix1(float[] data) {
+ this.data = data;
+ }
+
+ public static Matrix1 Zero(int size)
+ {
+ return new Matrix1(size);
+ }
+
+ public static Matrix1 FromVector2(Vector2Float v) {
+ float[] result = new float[2];
+ result[0] = v.x;
+ result[1] = v.y;
+ return new Matrix1(result);
+ }
+
+ public static Matrix1 FromVector3(Vector3Float v) {
+ float[] result = new float[3];
+ result[0] = v.x;
+ result[1] = v.y;
+ result[2] = v.z;
+ return new Matrix1(result);
+ }
+
+#if UNITY_5_3_OR_NEWER
+ public static Matrix1 FromQuaternion(Quaternion q) {
+ float[] result = new float[4];
+ result[0] = q.x;
+ result[1] = q.y;
+ result[2] = q.z;
+ result[3] = q.w;
+ return new Matrix1(result);
+ }
+#endif
+
+ public Vector2Float vector2 {
+ get {
+ if (this.size != 2)
+ throw new System.ArgumentException("Matrix1 must be of size 2");
+ return new Vector2Float(this.data[0], this.data[1]);
+ }
+ }
+ public Vector3Float vector3 {
+ get {
+ if (this.size != 3)
+ throw new System.ArgumentException("Matrix1 must be of size 3");
+ return new Vector3Float(this.data[0], this.data[1], this.data[2]);
+ }
+ }
+
+#if UNITY_5_3_OR_NEWER
+ public Quaternion quaternion {
+ get {
+ if (this.size != 4)
+ throw new System.ArgumentException("Matrix1 must be of size 4");
+ return new Quaternion(this.data[0], this.data[1], this.data[2], this.data[3]);
+ }
+ }
+#endif
+
+ public Matrix1 Clone() {
+ float[] data = new float[this.size];
+ for (int rowIx = 0; rowIx < this.size; rowIx++)
+ data[rowIx] = this.data[rowIx];
+ return new Matrix1(data);
+ }
+
+
+ public float magnitude {
+ get {
+ float sum = 0;
+ foreach (var elm in data)
+ sum += elm;
+ return sum / data.Length;
+ }
+ }
+ public static Matrix1 operator +(Matrix1 A, Matrix1 B) {
+ if (A.size != B.size)
+ throw new System.ArgumentException("Size of A must match size of B.");
+
+ float[] result = new float[A.size];
+
+ for (int i = 0; i < A.size; i++) {
+ result[i] = A.data[i] + B.data[i];
+ }
+ return new Matrix1(result);
+ }
+
+ public Matrix2 Transpose() {
+ float[,] r = new float[1, this.size];
+ for (uint colIx = 0; colIx < this.size; colIx++)
+ r[1, colIx] = this.data[colIx];
+
+ return new Matrix2(r);
+ }
+
+ public static float Dot(Matrix1 a, Matrix1 b) {
+ if (a.size != b.size)
+ throw new System.ArgumentException("Vectors must be of the same length.");
+
+ float result = 0.0f;
+ for (int i = 0; i < a.size; i++) {
+ result += a.data[i] * b.data[i];
+ }
+ return result;
+ }
+
+ public static Matrix1 operator -(Matrix1 A, Matrix1 B) {
+ if (A.size != B.size)
+ throw new System.ArgumentException("Size of A must match size of B.");
+
+ float[] result = new float[A.size];
+
+ for (int i = 0; i < A.size; i++) {
+ result[i] = A.data[i] - B.data[i];
+ }
+ return new Matrix1(result);
+ }
+
+ public static Matrix1 operator *(Matrix1 A, float f)
+ {
+ float[] result = new float[A.size];
+
+ for (int i = 0; i < A.size; i++)
+ result[i] += A.data[i] * f;
+
+ return new Matrix1(result);
+ }
+ public static Matrix1 operator *(float f, Matrix1 A) {
+ return A * f;
+ }
+
+ public static Matrix1 operator /(Matrix1 A, float f) {
+ float[] result = new float[A.size];
+
+ for (int i = 0; i < A.size; i++)
+ result[i] = A.data[i] / f;
+
+ return new Matrix1(result);
+ }
+ public static Matrix1 operator /(float f, Matrix1 A) {
+ float[] result = new float[A.size];
+
+ for (int i = 0; i < A.size; i++)
+ result[i] = f / A.data[i];
+
+ return new Matrix1(result);
+ }
+
+ public Matrix1 Slice(Slice range)
+ {
+ return Slice(range.start, range.stop);
+ }
+ public Matrix1 Slice(int from, int to)
+ {
+ if (from < 0 || to >= this.size)
+ throw new System.ArgumentException("Slice index out of range.");
+
+ float[] result = new float[to - from];
+ int resultIx = 0;
+ for (int ix = from; ix < to; ix++)
+ result[resultIx++] = this.data[ix];
+
+ return new Matrix1(result);
+ }
+ public void UpdateSlice(Slice slice, Matrix1 v) {
+ int vIx = 0;
+ for (int ix = slice.start; ix < slice.stop; ix++, vIx++)
+ this.data[ix] = v.data[vIx];
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/LinearAlgebra/src/Quat32.cs b/LinearAlgebra/src/Quat32.cs
new file mode 100644
index 0000000..f13266d
--- /dev/null
+++ b/LinearAlgebra/src/Quat32.cs
@@ -0,0 +1,87 @@
+using System;
+
+namespace LinearAlgebra {
+ public class Quat32 {
+ public float x;
+ public float y;
+ public float z;
+ public float w;
+
+ public Quat32() {
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+ this.w = 1;
+ }
+
+ public Quat32(float x, float y, float z, float w) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ }
+
+ public static Quat32 FromSwingTwist(SwingTwist s) {
+ Quat32 q32 = Quat32.Euler(-s.swing.vertical, s.swing.horizontal, s.twist);
+ return q32;
+ }
+
+ public static Quat32 Euler(float yaw, float pitch, float roll) {
+ float rollOver2 = roll * Angle.Deg2Rad * 0.5f;
+ float sinRollOver2 = (float)Math.Sin((float)rollOver2);
+ float cosRollOver2 = (float)Math.Cos((float)rollOver2);
+ float pitchOver2 = pitch * 0.5f;
+ float sinPitchOver2 = (float)Math.Sin((float)pitchOver2);
+ float cosPitchOver2 = (float)Math.Cos((float)pitchOver2);
+ float yawOver2 = yaw * 0.5f;
+ float sinYawOver2 = (float)Math.Sin((float)yawOver2);
+ float cosYawOver2 = (float)Math.Cos((float)yawOver2);
+ Quat32 result = new Quat32() {
+ w = cosYawOver2 * cosPitchOver2 * cosRollOver2 +
+ sinYawOver2 * sinPitchOver2 * sinRollOver2,
+ x = sinYawOver2 * cosPitchOver2 * cosRollOver2 +
+ cosYawOver2 * sinPitchOver2 * sinRollOver2,
+ y = cosYawOver2 * sinPitchOver2 * cosRollOver2 -
+ sinYawOver2 * cosPitchOver2 * sinRollOver2,
+ z = cosYawOver2 * cosPitchOver2 * sinRollOver2 -
+ sinYawOver2 * sinPitchOver2 * cosRollOver2
+ };
+ return result;
+ }
+
+ public void ToAngles(out float right, out float up, out float forward) {
+ float test = this.x * this.y + this.z * this.w;
+ if (test > 0.499f) { // singularity at north pole
+ right = 0;
+ up = 2 * (float)Math.Atan2(this.x, this.w) * Angle.Rad2Deg;
+ forward = 90;
+ return;
+ //return Vector3(0, 2 * (float)atan2(this.x, this.w) * Angle.Rad2Deg, 90);
+ }
+ else if (test < -0.499f) { // singularity at south pole
+ right = 0;
+ up = -2 * (float)Math.Atan2(this.x, this.w) * Angle.Rad2Deg;
+ forward = -90;
+ return;
+ //return Vector3(0, -2 * (float)atan2(this.x, this.w) * Angle.Rad2Deg, -90);
+ }
+ else {
+ float sqx = this.x * this.x;
+ float sqy = this.y * this.y;
+ float sqz = this.z * this.z;
+
+ right = (float)Math.Atan2(2 * this.x * this.w - 2 * this.y * this.z, 1 - 2 * sqx - 2 * sqz) * Angle.Rad2Deg;
+ up = (float)Math.Atan2(2 * this.y * this.w - 2 * this.x * this.z, 1 - 2 * sqy - 2 * sqz) * Angle.Rad2Deg;
+ forward = (float)Math.Asin(2 * test) * Angle.Rad2Deg;
+ return;
+ // return Vector3(
+ // atan2f(2 * this.x * this.w - 2 * this.y * this.z, 1 - 2 * sqx - 2 * sqz) *
+ // Rad2Deg,
+ // atan2f(2 * this.y * this.w - 2 * this.x * this.z, 1 - 2 * sqy - 2 * sqz) *
+ // Rad2Deg,
+ // asinf(2 * test) * Angle.Rad2Deg);
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/LinearAlgebra/src/Quaternion.cs b/LinearAlgebra/src/Quaternion.cs
new file mode 100644
index 0000000..52dd26b
--- /dev/null
+++ b/LinearAlgebra/src/Quaternion.cs
@@ -0,0 +1,81 @@
+using System;
+#if UNITY_5_3_OR_NEWER
+using Quaternion = UnityEngine.Quaternion;
+#endif
+
+namespace LinearAlgebra {
+
+ public class QuaternionOf {
+ public T x;
+ public T y;
+ public T z;
+ public T w;
+
+ public QuaternionOf(T x, T y, T z, T w) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ }
+
+#if UNITY_5_3_OR_NEWER
+ public static Quaternion Reflect(Quaternion q) {
+ return new(-q.x, -q.y, -q.z, q.w);
+ }
+
+ public static Matrix2 ToRotationMatrix(Quaternion q) {
+ float w = q.x, x = q.y, y = q.z, z = q.w;
+
+ float[,] result = new float[,]
+ {
+ { 1 - 2 * (y * y + z * z), 2 * (x * y - w * z), 2 * (x * z + w * y) },
+ { 2 * (x * y + w * z), 1 - 2 * (x * x + z * z), 2 * (y * z - w * x) },
+ { 2 * (x * z - w * y), 2 * (y * z + w * x), 1 - 2 * (x * x + y * y) }
+ };
+ return new Matrix2(result);
+ }
+
+ public static Quaternion FromRotationMatrix(Matrix2 m) {
+ float trace = m.data[0, 0] + m.data[1, 1] + m.data[2, 2];
+ float w, x, y, z;
+
+ if (trace > 0) {
+ float s = 0.5f / (float)Math.Sqrt(trace + 1.0f);
+ w = 0.25f / s;
+ x = (m.data[2, 1] - m.data[1, 2]) * s;
+ y = (m.data[0, 2] - m.data[2, 0]) * s;
+ z = (m.data[1, 0] - m.data[0, 1]) * s;
+ }
+ else {
+ if (m.data[0, 0] > m.data[1, 1] && m.data[0, 0] > m.data[2, 2]) {
+ float s = 2.0f * (float)Math.Sqrt(1.0f + m.data[0, 0] - m.data[1, 1] - m.data[2, 2]);
+ w = (m.data[2, 1] - m.data[1, 2]) / s;
+ x = 0.25f * s;
+ y = (m.data[0, 1] + m.data[1, 0]) / s;
+ z = (m.data[0, 2] + m.data[2, 0]) / s;
+ }
+ else if (m.data[1, 1] > m.data[2, 2]) {
+ float s = 2.0f * (float)Math.Sqrt(1.0f + m.data[1, 1] - m.data[0, 0] - m.data[2, 2]);
+ w = (m.data[0, 2] - m.data[2, 0]) / s;
+ x = (m.data[0, 1] + m.data[1, 0]) / s;
+ y = 0.25f * s;
+ z = (m.data[1, 2] + m.data[2, 1]) / s;
+ }
+ else {
+ float s = 2.0f * (float)Math.Sqrt(1.0f + m.data[2, 2] - m.data[0, 0] - m.data[1, 1]);
+ w = (m.data[1, 0] - m.data[0, 1]) / s;
+ x = (m.data[0, 2] + m.data[2, 0]) / s;
+ y = (m.data[1, 2] + m.data[2, 1]) / s;
+ z = 0.25f * s;
+ }
+ }
+
+ return new Quaternion(x, y, z, w);
+ }
+#endif
+ }
+
+ // public class Quaternion : QuaternionOf {
+ // public Quaternion(float x, float y, float z, float w) : base(x, y, z, w) { }
+ // }
+}
\ No newline at end of file
diff --git a/LinearAlgebra/src/Spherical.cs b/LinearAlgebra/src/Spherical.cs
new file mode 100644
index 0000000..183aa01
--- /dev/null
+++ b/LinearAlgebra/src/Spherical.cs
@@ -0,0 +1,134 @@
+using System;
+#if UNITY_5_3_OR_NEWER
+//using Vector3Float = UnityEngine.Vector3;
+using Vector3 = UnityEngine.Vector3;
+#endif
+
+namespace LinearAlgebra {
+ ///
+ /// A spherical vector
+ ///
+ public class Spherical {
+
+ ///
+ /// Create a default vector with zero distance
+ ///
+ public Spherical() {
+ this.distance = 0;
+ this.direction = new Direction();
+ }
+
+ ///
+ /// Create a spherical vector
+ ///
+ /// The distance in meters
+ /// The direction of the vector
+ public Spherical(float distance, Direction direction) {
+ this.distance = distance;
+ this.direction = direction;
+ }
+
+ ///
+ /// Create spherical vector. All given angles are in degrees
+ ///
+ /// The distance in meters
+ /// The horizontal angle in degrees
+ /// The vertical angle in degrees
+ ///
+ 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;
+ }
+
+ ///
+ /// The distance in meters
+ ///
+ /// @remark The distance should never be negative
+ public float distance;
+ ///
+ /// The direction of the vector
+ ///
+ public Direction direction;
+
+ ///
+ /// A spherical vector with zero degree angles and distance
+ ///
+ public readonly static Spherical zero = new(0, Direction.forward);
+ ///
+ /// A normalized forward-oriented vector
+ ///
+ public readonly static Spherical forward = new(1, Direction.forward);
+
+
+ // public static Spherical FromVector3Float(Vector3Float v) {
+ // 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 static Spherical FromVector3(Vector3 v) {
+ float distance = v.magnitude;
+ if (distance == 0.0f)
+ return Spherical.zero;
+ else {
+ float verticalAngle = (float)(Math.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 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;
+
+ // Vector3Float v = new(x, y, z);
+ // return v;
+ // }
+
+ public Vector3 ToVector3() {
+ float verticalRad = (float)(Math.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;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/LinearAlgebra/src/SwingTwist.cs b/LinearAlgebra/src/SwingTwist.cs
new file mode 100644
index 0000000..22eb0bb
--- /dev/null
+++ b/LinearAlgebra/src/SwingTwist.cs
@@ -0,0 +1,41 @@
+using System.Numerics;
+#if UNITY_5_3_OR_NEWER
+using Quaternion = UnityEngine.Quaternion;
+#endif
+
+namespace LinearAlgebra {
+
+ public class SwingTwist {
+ public Direction swing;
+ public float twist;
+
+ public static readonly SwingTwist zero = new SwingTwist(0, 0, 0);
+
+ public SwingTwist(Direction swing, float twist) {
+ this.swing = swing;
+ this.twist = twist;
+ }
+ public SwingTwist(float horizontalSwing, float verticalSwing, float twist) {
+ this.swing = Direction.Degrees(horizontalSwing, verticalSwing);
+ this.swing.Normalize();
+ this.twist = twist;
+ }
+ public static SwingTwist FromQuat32(Quat32 q32) {
+ // UnityEngine.Quaternion q = new(q32.x, q32.y, q32.z, q32.w);
+ // SwingTwist r = new(q.eulerAngles.y, q.eulerAngles.x, q.eulerAngles.z);
+ q32.ToAngles(out float right, out float up, out float forward);
+ SwingTwist r = new SwingTwist(up, right, forward);
+ return r;
+ }
+
+#if UNITY_5_3_OR_NEWER
+ public Quaternion ToQuaternion() {
+ Quaternion q = Quaternion.Euler(-this.swing.vertical,
+ this.swing.horizontal,
+ this.twist);
+ return q;
+ }
+#endif
+ }
+
+}
\ No newline at end of file
diff --git a/LinearAlgebra/src/Vector2.cs b/LinearAlgebra/src/Vector2.cs
new file mode 100644
index 0000000..1840a7a
--- /dev/null
+++ b/LinearAlgebra/src/Vector2.cs
@@ -0,0 +1,363 @@
+using System;
+using System.Numerics;
+
+namespace LinearAlgebra {
+
+ public class Vector2Of where T : IComparable {
+ public T x;
+ public T y;
+
+ public Vector2Of(T x, T y) {
+ this.x = x;
+ this.y = y;
+ }
+ }
+
+ public class Vector2Int : Vector2Of {
+ public Vector2Int(int x, int y) : base(x, y) { }
+
+ public static Vector2Int operator -(Vector2Int v1, Vector2Int v2) {
+ return new Vector2Int(v1.x - v2.x, v1.y - v2.y);
+ }
+
+ public float magnitude {
+ get {
+ return (float)Math.Sqrt(this.x * this.x + this.y * this.y);
+ }
+ }
+
+ public static float Distance(Vector2Int v1, Vector2Int v2) {
+ return (v1 - v2).magnitude;
+ }
+ }
+
+ public class Vector2Float : Vector2Of {
+ public Vector2Float(float x, float y) : base(x, y) { }
+
+ public static Vector2Float operator -(Vector2Float v1, Vector2Float v2) {
+ return new Vector2Float(v1.x - v2.x, v1.y - v2.y);
+ }
+
+ public float magnitude {
+ get {
+ return (float)Math.Sqrt(this.x * this.x + this.y * this.y);
+ }
+ }
+
+ public static float Distance(Vector2Float v1, Vector2Float v2) {
+ return (v1 - v2).magnitude;
+ }
+ }
+
+ ///
+ /// 2-dimensional vectors
+ ///
+ public struct Vector2 : IEquatable {
+
+ ///
+ /// The right axis of the vector
+ ///
+ public float x; // left/right
+ ///
+ /// The upward/forward axis of the vector
+ ///
+ public float y; // forward/backward
+ // directions are to be inline with Vector3 as much as possible...
+
+ ///
+ /// Create a new 2-dimensional vector
+ ///
+ /// x axis value
+ /// y axis value
+ public Vector2(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ ///
+ /// A vector with zero for all axis
+ ///
+ public static readonly Vector2 zero = new Vector2(0, 0);
+ ///
+ /// A vector with values (1, 1)
+ ///
+ public static readonly Vector2 one = new Vector2(1, 1);
+ ///
+ /// A vector with values (0, 1)
+ ///
+ public static readonly Vector2 up = new Vector2(0, 1);
+ ///
+ /// A vector with values (0, -1)
+ ///
+ public static readonly Vector2 down = new Vector2(0, -1);
+ ///
+ /// A vector with values (0, 1)
+ ///
+ public static readonly Vector2 forward = new Vector2(0, 1);
+ ///
+ /// A vector with values (0, -1)
+ ///
+ public static readonly Vector2 back = new Vector2(0, -1);
+ ///
+ /// A vector3 with values (-1, 0)
+ ///
+ public static readonly Vector2 left = new Vector2(-1, 0);
+ ///
+ /// A vector with values (1, 0)
+ ///
+ public static readonly Vector2 right = new Vector2(1, 0);
+
+ ///
+ /// The squared length of this vector
+ ///
+ /// The squared length
+ /// The squared length is computationally simpler than the real length.
+ /// Think of Pythagoras A^2 + B^2 = C^2.
+ /// This leaves out the calculation of the squared root of C.
+ public float sqrMagnitude {
+ get {
+ float d = x * x + y * y;
+ return d;
+ }
+ }
+
+ ///
+ /// The length of this vector
+ ///
+ /// The length of this vector
+ public float magnitude {
+ get {
+ float d = (float)Math.Sqrt(x * x + y * y);
+ return d;
+ }
+ }
+
+ ///
+ /// Convert the vector to a length of a 1
+ ///
+ /// The vector with length 1
+ public Vector2 normalized {
+ get {
+ float l = magnitude;
+ Vector2 v = zero;
+ if (l > Float.epsilon)
+ v = this / l;
+ return v;
+ }
+ }
+
+ ///
+ /// Add two vectors
+ ///
+ /// The first vector
+ /// The second vector
+ /// The result of adding the two vectors
+ public static Vector2 operator +(Vector2 v1, Vector2 v2) {
+ Vector2 v = new Vector2(v1.x + v2.x, v1.y + v2.y);
+ return v;
+ }
+
+ ///
+ /// Subtract two vectors
+ ///
+ /// The first vector
+ /// The second vector
+ /// The result of adding the two vectors
+ public static Vector2 operator -(Vector2 v1, Vector2 v2) {
+ Vector2 v = new Vector2(v1.x - v2.x, v1.y - v2.y);
+ return v;
+ }
+
+ ///
+ /// Negate the vector
+ ///
+ /// The vector to negate
+ /// The negated vector
+ /// This will result in a vector pointing in the opposite direction
+ public static Vector2 operator -(Vector2 v1) {
+ Vector2 v = new Vector2(-v1.x, -v1.y);
+ return v;
+ }
+
+ ///
+ /// Scale a vector uniformly up
+ ///
+ /// The vector to scale
+ /// The scaling factor
+ /// The scaled vector
+ /// Each component of the vector will be multipled with the same factor.
+ public static Vector2 operator *(Vector2 v1, float f) {
+ Vector2 v = new Vector2(v1.x * f, v1.y * f);
+ return v;
+ }
+
+ ///
+ /// Scale a vector uniformly up
+ ///
+ /// The scaling factor
+ /// The vector to scale
+ /// The scaled vector
+ /// Each component of the vector will be multipled with the same factor.
+ public static Vector2 operator *(float f, Vector2 v1) {
+ Vector2 v = new Vector2(f * v1.x, f * v1.y);
+ return v;
+ }
+
+ ///
+ /// Scale a vector uniformly down
+ ///
+ /// The vector to scale
+ /// The scaling factor
+ /// The scaled vector
+ /// Each component of the vector will be devided by the same factor.
+ public static Vector2 operator /(Vector2 v1, float f) {
+ Vector2 v = new Vector2(v1.x / f, v1.y / f);
+ return v;
+ }
+
+ ///
+ /// Tests if the vector has equal values as the given vector
+ ///
+ /// The vector to compare to
+ /// true if the vector values are equal
+ public bool Equals(Vector2 v1) => x == v1.x && y == v1.y;
+
+ ///
+ /// Tests if the vector is equal to the given object
+ ///
+ /// The object to compare to
+ /// false when the object is not a Vector2 or does not have equal values
+ public override bool Equals(object obj) {
+ if (!(obj is Vector2 v))
+ return false;
+
+ return (x == v.x && y == v.y);
+ }
+
+ ///
+ /// Tests if the two vectors have equal values
+ ///
+ /// The first vector
+ /// The second vector
+ /// truewhen the vectors have equal values
+ /// 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
+ /// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon
+ public static bool operator ==(Vector2 v1, Vector2 v2) {
+ return (v1.x == v2.x && v1.y == v2.y);
+ }
+
+ ///
+ /// Tests if two vectors have different values
+ ///
+ /// The first vector
+ /// The second vector
+ /// truewhen the vectors have different values
+ /// 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.
+ /// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon
+ public static bool operator !=(Vector2 v1, Vector2 v2) {
+ return (v1.x != v2.x || v1.y != v2.y);
+ }
+
+ ///
+ /// Get an hash code for the vector
+ ///
+ /// The hash code
+ public override int GetHashCode() {
+ return (x, y).GetHashCode();
+ }
+
+ ///
+ /// Get the distance between two vectors
+ ///
+ /// The first vector
+ /// The second vector
+ /// The distance between the two vectors
+ public static float Distance(Vector2 v1, Vector2 v2) {
+ float x = v1.x - v2.x;
+ float y = v1.y - v2.y;
+ float d = (float)Math.Sqrt(x * x + y * y);
+ return d;
+ }
+
+ ///
+ /// The dot product of two vectors
+ ///
+ /// The first vector
+ /// The second vector
+ /// The dot product of the two vectors
+ public static float Dot(Vector2 v1, Vector2 v2) {
+ return v1.x * v2.x + v1.y * v2.y;
+ }
+
+ ///
+ /// Lerp between two vectors
+ ///
+ /// The from vector
+ /// The to vector
+ /// The interpolation distance [0..1]
+ /// The lerped vector
+ /// 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
+ /// between *v1* and *v2* etc.
+ public static Vector2 Lerp(Vector2 v1, Vector2 v2, float f) {
+ Vector2 v = v1 + (v2 - v1) * f;
+ return v;
+ }
+
+ ///
+ /// Calculate the signed angle between two vectors.
+ ///
+ /// The starting vector
+ /// The ending vector
+ /// The axis to rotate around
+ /// The signed angle in degrees
+ public static float SignedAngle(Vector2 from, Vector2 to) {
+ //float sign = Math.Sign(v1.y * v2.x - v1.x * v2.y);
+ //return Vector2.Angle(v1, v2) * sign;
+
+ float sqrMagFrom = from.sqrMagnitude;
+ float sqrMagTo = to.sqrMagnitude;
+
+ if (sqrMagFrom == 0 || sqrMagTo == 0)
+ return 0;
+ //if (!isfinite(sqrMagFrom) || !isfinite(sqrMagTo))
+ // return nanf("");
+
+ float angleFrom = (float)Math.Atan2(from.y, from.x);
+ float angleTo = (float)Math.Atan2(to.y, to.x);
+ return (angleTo - angleFrom) * Angle.Rad2Deg;
+ }
+
+ ///
+ /// Rotates the vector with the given angle
+ ///
+ /// The vector to rotate
+ /// The angle in degrees
+ ///
+ public static Vector2 Rotate(Vector2 v1, float angle) {
+ float sin = (float)Math.Sin(angle * Angle.Deg2Rad);
+ float cos = (float)Math.Cos(angle * Angle.Deg2Rad);
+
+ float tx = v1.x;
+ float ty = v1.y;
+ Vector2 v = new Vector2() {
+ x = (cos * tx) - (sin * ty),
+ y = (sin * tx) + (cos * ty)
+ };
+ return v;
+ }
+
+ ///
+ /// Map interval of angles between vectors [0..Pi] to interval [0..1]
+ ///
+ /// The first vector
+ /// The second vector
+ /// The resulting factor in interval [0..1]
+ /// Vectors a and b must be normalized
+ public static float ToFactor(Vector2 v1, Vector2 v2) {
+ return (1 - Vector2.Dot(v1, v2)) / 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/LinearAlgebra/src/Vector3.cs b/LinearAlgebra/src/Vector3.cs
new file mode 100644
index 0000000..7994dcb
--- /dev/null
+++ b/LinearAlgebra/src/Vector3.cs
@@ -0,0 +1,179 @@
+#if !UNITY_5_3_OR_NEWER
+using System;
+
+namespace LinearAlgebra {
+ public class Vector3Of {
+ public T x;
+ public T y;
+ public T z;
+
+ public Vector3Of(T x, T y, T z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ // public uint magnitude {
+ // get => (float)Math.Sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
+ // }
+ }
+
+ public class Vector3Int : Vector3Of {
+ public Vector3Int(int x, int y, int z) : base(x, y, z) { }
+ }
+ public class Vector3Float : Vector3Of {
+ public Vector3Float(float x, float y, float z) : base(x, y, z) { }
+
+ public float magnitude {
+ get => (float)Math.Sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
+ }
+ }
+
+ ///
+ /// 3-dimensional vectors
+ ///
+ /// This uses the right-handed coordinate system.
+ public struct Vector3 : IEquatable {
+
+ ///
+ /// The right axis of the vector
+ ///
+ public float x; //> left/right
+ ///
+ /// The upward axis of the vector
+ ///
+ public float y; //> up/down
+ ///
+ /// The forward axis of the vector
+ ///
+ public float z; //> forward/backward
+
+ ///
+ /// Create a new 3-dimensional vector
+ ///
+ /// x axis value
+ /// y axis value
+ /// z axis value
+ public Vector3(float x, float y, float z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ ///
+ /// A vector with zero for all axis
+ ///
+ public static readonly Vector3 zero = new Vector3(0, 0, 0);
+ ///
+ /// A vector with one for all axis
+ ///
+ public static readonly Vector3 one = new Vector3(1, 1, 1);
+ ///
+ /// A vector3 with values (-1, 0, 0)
+ ///
+ public static readonly Vector3 left = new Vector3(-1, 0, 0);
+ ///
+ /// A vector with values (1, 0, 0)
+ ///
+ public static readonly Vector3 right = new Vector3(1, 0, 0);
+ ///
+ /// A vector with values (0, -1, 0)
+ ///
+ public static readonly Vector3 down = new Vector3(0, -1, 0);
+ ///
+ /// A vector with values (0, 1, 0)
+ ///
+ public static readonly Vector3 up = new Vector3(0, 1, 0);
+ ///
+ /// A vector with values (0, 0, -1)
+ ///
+ public static readonly Vector3 back = new Vector3(0, -1, 0);
+ ///
+ /// A vector with values (0, 0, 1)
+ ///
+ public static readonly Vector3 forward = new Vector3(0, 1, 0);
+
+ public readonly float magnitude {
+ get {
+ float d = (float)Math.Sqrt(x * x + y * y + z * z);
+ return d;
+ }
+ }
+
+ public Vector3 normalized {
+ get {
+ float l = magnitude;
+ Vector3 v = zero;
+ if (l > Float.epsilon)
+ v = this / l;
+ return v;
+ }
+ }
+
+ public static Vector3 operator +(Vector3 v1, Vector3 v2) {
+ Vector3 v = new Vector3(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
+ return v;
+ }
+
+ public static Vector3 operator -(Vector3 v1, Vector3 v2) {
+ Vector3 v = new Vector3(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
+ return v;
+ }
+
+ public static Vector3 operator -(Vector3 v1) {
+ Vector3 v = new Vector3(-v1.x, -v1.y, -v1.z);
+ return v;
+ }
+
+ public static Vector3 operator *(Vector3 v1, float d) {
+ Vector3 v = new Vector3(v1.x * d, v1.y * d, v1.z * d);
+ return v;
+ }
+
+ public static Vector3 operator *(float d, Vector3 v1) {
+ Vector3 v = new Vector3(d * v1.x, d * v1.y, d * v1.z);
+ return v;
+ }
+
+ public static Vector3 operator /(Vector3 v1, float d) {
+ Vector3 v = new Vector3(v1.x / d, v1.y / d, v1.z / d);
+ return v;
+ }
+
+ public bool Equals(Vector3 v) => (x == v.x && y == v.y && z == v.z);
+
+ public override bool Equals(object obj) {
+ if (!(obj is Vector3 v))
+ return false;
+
+ return (x == v.x && y == v.y && z == v.z);
+ }
+
+ public static bool operator ==(Vector3 v1, Vector3 v2) {
+ return (v1.x == v2.x && v1.y == v2.y && v1.z == v2.z);
+ }
+
+ public static bool operator !=(Vector3 v1, Vector3 v2) {
+ return (v1.x != v2.x || v1.y != v2.y || v1.z != v2.z);
+ }
+
+ public override int GetHashCode() {
+ return (x, y, z).GetHashCode();
+ }
+
+ public static float Distance(Vector3 v1, Vector3 v2) {
+ return (v2 - v1).magnitude;
+ }
+
+ public static float Dot(Vector3 v1, Vector3 v2) {
+ return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
+ }
+
+ public static Vector3 Lerp(Vector3 v1, Vector3 v2, float f) {
+ Vector3 v = v1 + (v2 - v1) * f;
+ return v;
+ }
+
+ }
+}
+#endif
\ No newline at end of file
diff --git a/LinearAlgebra/src/float16.cs b/LinearAlgebra/src/float16.cs
new file mode 100644
index 0000000..4b58cdd
--- /dev/null
+++ b/LinearAlgebra/src/float16.cs
@@ -0,0 +1,322 @@
+using System;
+
+namespace LinearAlgebra {
+
+ public class float16 {
+ //
+ // FILE: float16.cpp
+ // AUTHOR: Rob Tillaart
+ // VERSION: 0.1.8
+ // PURPOSE: library for Float16s for Arduino
+ // URL: http://en.wikipedia.org/wiki/Half-precision_floating-point_format
+
+ ushort _value;
+
+ public float16() { _value = 0; }
+
+ public float16(float f) {
+ //_value = f32tof16(f);
+ _value = F32ToF16__(f);
+ }
+
+ public float toFloat() {
+ return f16tof32(_value);
+ }
+
+ public ushort GetBinary() { return _value; }
+ public void SetBinary(ushort value) { _value = value; }
+
+ //////////////////////////////////////////////////////////
+ //
+ // EQUALITIES
+ //
+ /*
+ bool float16::operator ==(const float16 &f) { return (_value == f._value); }
+
+ bool float16::operator !=(const float16 &f) { return (_value != f._value); }
+
+ bool float16::operator >(const float16 &f) {
+ if ((_value & 0x8000) && (f._value & 0x8000))
+ return _value < f._value;
+ if (_value & 0x8000)
+ return false;
+ if (f._value & 0x8000)
+ return true;
+ return _value > f._value;
+ }
+
+ bool float16::operator >=(const float16 &f) {
+ if ((_value & 0x8000) && (f._value & 0x8000))
+ return _value <= f._value;
+ if (_value & 0x8000)
+ return false;
+ if (f._value & 0x8000)
+ return true;
+ return _value >= f._value;
+ }
+
+ bool float16::operator <(const float16 &f) {
+ if ((_value & 0x8000) && (f._value & 0x8000))
+ return _value > f._value;
+ if (_value & 0x8000)
+ return true;
+ if (f._value & 0x8000)
+ return false;
+ return _value < f._value;
+ }
+
+ bool float16::operator <=(const float16 &f) {
+ if ((_value & 0x8000) && (f._value & 0x8000))
+ return _value >= f._value;
+ if (_value & 0x8000)
+ return true;
+ if (f._value & 0x8000)
+ return false;
+ return _value <= f._value;
+ }
+
+ //////////////////////////////////////////////////////////
+ //
+ // NEGATION
+ //
+ float16 float16::operator -() {
+ float16 f16;
+ f16.setBinary(_value ^ 0x8000);
+ return f16;
+ }
+
+ //////////////////////////////////////////////////////////
+ //
+ // MATH
+ //
+ float16 float16::operator +(const float16 &f) {
+ return float16(this->toDouble() + f.toDouble());
+ }
+
+ float16 float16::operator -(const float16 &f) {
+ return float16(this->toDouble() - f.toDouble());
+ }
+
+ float16 float16::operator *(const float16 &f) {
+ return float16(this->toDouble() * f.toDouble());
+ }
+
+ float16 float16::operator /(const float16 &f) {
+ return float16(this->toDouble() / f.toDouble());
+ }
+
+ float16 & float16::operator+=(const float16 &f) {
+ *this = this->toDouble() + f.toDouble();
+ return *this;
+ }
+
+ float16 & float16::operator-=(const float16 &f) {
+ *this = this->toDouble() - f.toDouble();
+ return *this;
+ }
+
+ float16 & float16::operator*=(const float16 &f) {
+ *this = this->toDouble() * f.toDouble();
+ return *this;
+ }
+
+ float16 & float16::operator/=(const float16 &f) {
+ *this = this->toDouble() / f.toDouble();
+ return *this;
+ }
+
+ //////////////////////////////////////////////////////////
+ //
+ // MATH HELPER FUNCTIONS
+ //
+ int float16::sign() {
+ if (_value & 0x8000)
+ return -1;
+ if (_value & 0xFFFF)
+ return 1;
+ return 0;
+ }
+
+ bool float16::isZero() { return ((_value & 0x7FFF) == 0x0000); }
+
+ bool float16::isNaN() {
+ if ((_value & 0x7C00) != 0x7C00)
+ return false;
+ if ((_value & 0x03FF) == 0x0000)
+ return false;
+ return true;
+ }
+
+ bool float16::isInf() { return ((_value == 0x7C00) || (_value == 0xFC00)); }
+ */
+ //////////////////////////////////////////////////////////
+ //
+ // CORE CONVERSION
+ //
+ float f16tof32(ushort _value) {
+ //ushort sgn;
+ ushort man;
+ int exp;
+ float f;
+
+ //Debug.Log($"{_value}");
+
+ bool sgn = (_value & 0x8000) > 0;
+ exp = (_value & 0x7C00) >> 10;
+ man = (ushort)(_value & 0x03FF);
+
+ //Debug.Log($"{sgn} {exp} {man}");
+
+ // ZERO
+ if ((_value & 0x7FFF) == 0) {
+ return sgn ? -0 : 0;
+ }
+ // NAN & INF
+ if (exp == 0x001F) {
+ if (man == 0)
+ return sgn ? float.NegativeInfinity : float.PositiveInfinity; //-INFINITY : INFINITY;
+ else
+ return float.NaN; // NAN;
+ }
+
+ // SUBNORMAL/NORMAL
+ if (exp == 0)
+ f = 0;
+ else
+ f = 1;
+
+ // PROCESS MANTISSE
+ for (int i = 9; i >= 0; i--) {
+ f *= 2;
+ if ((man & (1 << i)) != 0)
+ f = f + 1;
+ }
+ //Debug.Log($"{f}");
+ f = f * (float)Math.Pow(2.0f, exp - 25);
+ if (exp == 0) {
+ f = f * (float)Math.Pow(2.0f, -13); // 5.96046447754e-8;
+ }
+ //Debug.Log($"{f}");
+ return sgn ? -f : f;
+ }
+
+ public static uint SingleToInt32Bits(float value) {
+ byte[] bytes = BitConverter.GetBytes(value);
+ if (BitConverter.IsLittleEndian)
+ Array.Reverse(bytes); // If the system is little-endian, reverse the byte order
+ return BitConverter.ToUInt32(bytes, 0);
+ }
+
+ public ushort F32ToF16__(float f) {
+ uint t = BitConverter.ToUInt32(BitConverter.GetBytes(f), 0);
+ ushort man = (ushort)((t & 0x007FFFFF) >> 12);
+ int exp = (int)((t & 0x7F800000) >> 23);
+ bool sgn = (t & 0x80000000) != 0;
+
+ // handle 0
+ if ((t & 0x7FFFFFFF) == 0) {
+ return sgn ? (ushort)0x8000 : (ushort)0x0000;
+ }
+ // denormalized float32 does not fit in float16
+ if (exp == 0x00) {
+ return sgn ? (ushort)0x8000 : (ushort)0x0000;
+ }
+ // handle infinity & NAN
+ if (exp == 0x00FF) {
+ if (man != 0)
+ return 0xFE00; // NAN
+ return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF
+ }
+
+ // normal numbers
+ exp = exp - 127 + 15;
+ // overflow does not fit => INF
+ if (exp > 30) {
+ return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF
+ }
+ // subnormal numbers
+ if (exp < -38) {
+ return sgn ? (ushort)0x8000 : (ushort)0x0000; // -0 or 0 ? just 0 ?
+ }
+ if (exp <= 0) // subnormal
+ {
+ man >>= (exp + 14);
+ // rounding
+ man++;
+ man >>= 1;
+ if (sgn)
+ return (ushort)(0x8000 | man);
+ return man;
+ }
+
+ // normal
+ // TODO rounding
+ exp <<= 10;
+ man++;
+ man >>= 1;
+ if (sgn)
+ return (ushort)(0x8000 | exp | man);
+ return (ushort)(exp | man);
+ }
+
+ //This function is faulty!!!!
+ ushort f32tof16(float f) {
+ //uint t = *(uint*)&f;
+ //uint t = (uint)BitConverter.SingleToInt32Bits(f);
+ uint t = SingleToInt32Bits(f);
+ // man bits = 10; but we keep 11 for rounding
+ ushort man = (ushort)((t & 0x007FFFFF) >> 12);
+ short exp = (short)((t & 0x7F800000) >> 23);
+ bool sgn = (t & 0x80000000) != 0;
+
+ // handle 0
+ if ((t & 0x7FFFFFFF) == 0) {
+ return sgn ? (ushort)0x8000 : (ushort)0x0000;
+ }
+ // denormalized float32 does not fit in float16
+ if (exp == 0x00) {
+ return sgn ? (ushort)0x8000 : (ushort)0x0000;
+ }
+ // handle infinity & NAN
+ if (exp == 0x00FF) {
+ if (man != 0)
+ return 0xFE00; // NAN
+ return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF
+ }
+
+ // normal numbers
+ exp = (short)(exp - 127 + 15);
+ // overflow does not fit => INF
+ if (exp > 30) {
+ return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF
+ }
+ // subnormal numbers
+ if (exp < -38) {
+ return sgn ? (ushort)0x8000 : (ushort)0x0000; // -0 or 0 ? just 0 ?
+ }
+ if (exp <= 0) // subnormal
+ {
+ man >>= (exp + 14);
+ // rounding
+ man++;
+ man >>= 1;
+ if (sgn)
+ return (ushort)(0x8000 | man);
+ return man;
+ }
+
+ // normal
+ // TODO rounding
+ exp <<= 10;
+ man++;
+ man >>= 1;
+ ushort uexp = (ushort)exp;
+ if (sgn)
+ return (ushort)(0x8000 | uexp | man);
+ return (ushort)(uexp | man);
+ }
+
+ // -- END OF FILE --
+ }
+
+}
\ No newline at end of file
diff --git a/LinearAlgebra/test/AngleTest.cs b/LinearAlgebra/test/AngleTest.cs
new file mode 100644
index 0000000..00e4981
--- /dev/null
+++ b/LinearAlgebra/test/AngleTest.cs
@@ -0,0 +1,209 @@
+#if !UNITY_5_6_OR_NEWER
+using System;
+using System.Formats.Asn1;
+using NUnit.Framework;
+
+namespace LinearAlgebra.Test {
+ public class AngleTests {
+ [SetUp]
+ public void Setup() {
+ }
+
+ [Test]
+ public void Construct() {
+ // Degrees
+ float angle = 0.0f;
+ Angle a = Angle.Degrees(angle);
+ Assert.AreEqual(angle, a.inDegrees);
+
+ angle = -180.0f;
+ a = Angle.Degrees(angle);
+ Assert.AreEqual(angle, a.inDegrees);
+
+ angle = 270.0f;
+ a = Angle.Degrees(angle);
+ Assert.AreEqual(-90, a.inDegrees);
+
+ // Radians
+ angle = 0.0f;
+ a = Angle.Radians(angle);
+ Assert.AreEqual(angle, a.inRadians);
+
+ angle = (float)-Math.PI;
+ a = Angle.Radians(angle);
+ Assert.AreEqual(angle, a.inRadians);
+
+ angle = (float)Math.PI * 1.5f;
+ a = Angle.Radians(angle);
+ Assert.AreEqual(-Math.PI * 0.5f, a.inRadians);
+
+ // Revolutions
+ angle = 0.0f;
+ a = Angle.Revolutions(angle);
+ Assert.AreEqual(angle, a.inRevolutions);
+
+ angle = -0.5f;
+ a = Angle.Revolutions(angle);
+ Assert.AreEqual(angle, a.inRevolutions);
+
+ angle = 0.75f;
+ a = Angle.Revolutions(angle);
+ Assert.AreEqual(-0.25f, a.inRevolutions);
+
+ }
+
+ // [Test]
+ // public void Normalize() {
+ // float r = 0;
+
+ // r = Angle.Normalize(90);
+ // Assert.AreEqual(r, 90, "Normalize 90");
+
+ // r = Angle.Normalize(-90);
+ // Assert.AreEqual(r, -90, "Normalize -90");
+
+ // r = Angle.Normalize(270);
+ // Assert.AreEqual(r, -90, "Normalize 270");
+
+ // r = Angle.Normalize(270 + 360);
+ // Assert.AreEqual(r, -90, "Normalize 270+360");
+
+ // r = Angle.Normalize(-270);
+ // Assert.AreEqual(r, 90, "Normalize -270");
+
+ // r = Angle.Normalize(-270 - 360);
+ // Assert.AreEqual(r, 90, "Normalize -270-360");
+
+ // r = Angle.Normalize(0);
+ // Assert.AreEqual(r, 0, "Normalize 0");
+
+ // r = Angle.Normalize(float.PositiveInfinity);
+ // Assert.AreEqual(r, float.PositiveInfinity, "Normalize INFINITY");
+
+ // r = Angle.Normalize(float.NegativeInfinity);
+ // Assert.AreEqual(r, float.NegativeInfinity, "Normalize INFINITY");
+ // }
+
+ [Test]
+ public void Clamp() {
+ float r = 0;
+
+ r = Angle.Clamp(Angle.Degrees(1), Angle.Degrees(0), Angle.Degrees(2));
+ Assert.AreEqual(r, 1, "Clamp 1 0 2");
+
+ r = Angle.Clamp(Angle.Degrees(-1), Angle.Degrees(0), Angle.Degrees(2));
+ Assert.AreEqual(r, 0, "Clamp -1 0 2");
+
+ r = Angle.Clamp(Angle.Degrees(3), Angle.Degrees(0), Angle.Degrees(2));
+ Assert.AreEqual(r, 2, "Clamp 3 0 2");
+
+ r = Angle.Clamp(Angle.Degrees(1), Angle.Degrees(0), Angle.Degrees(0));
+ Assert.AreEqual(r, 0, "Clamp 1 0 0");
+
+ r = Angle.Clamp(Angle.Degrees(0), Angle.Degrees(0), Angle.Degrees(0));
+ Assert.AreEqual(r, 0, "Clamp 0 0 0");
+
+ r = Angle.Clamp(Angle.Degrees(0), Angle.Degrees(1), Angle.Degrees(-1));
+ Assert.AreEqual(r, 1, "Clamp 0 1 -1");
+
+ r = Angle.Clamp(Angle.Degrees(1), Angle.Degrees(0), Angle.Degrees(float.PositiveInfinity));
+ Assert.AreEqual(r, 1, "Clamp 1 0 INFINITY");
+
+ r = Angle.Clamp(Angle.Degrees(1), Angle.Degrees(float.NegativeInfinity), Angle.Degrees(1));
+ Assert.AreEqual(r, 1, "Clamp 1 -INFINITY 1");
+ }
+
+ // [Test]
+ // public void Difference() {
+ // float r = 0;
+
+ // r = Angle.Difference(0, 90);
+ // Assert.AreEqual(r, 90, "Difference 0 90");
+
+ // r = Angle.Difference(0, -90);
+ // Assert.AreEqual(r, -90, "Difference 0 -90");
+
+ // r = Angle.Difference(0, 270);
+ // Assert.AreEqual(r, -90, "Difference 0 270");
+
+ // r = Angle.Difference(0, -270);
+ // Assert.AreEqual(r, 90, "Difference 0 -270");
+
+ // r = Angle.Difference(90, 0);
+ // Assert.AreEqual(r, -90, "Difference 90 0");
+
+ // r = Angle.Difference(-90, 0);
+ // Assert.AreEqual(r, 90, "Difference -90 0");
+
+ // r = Angle.Difference(0, 0);
+ // Assert.AreEqual(r, 0, "Difference 0 0");
+
+ // r = Angle.Difference(90, 90);
+ // Assert.AreEqual(r, 0, "Difference 90 90");
+
+ // r = Angle.Difference(0, float.PositiveInfinity);
+ // Assert.AreEqual(r, float.PositiveInfinity, "Difference 0 INFINITY");
+
+ // r = Angle.Difference(0, float.NegativeInfinity);
+ // Assert.AreEqual(r, float.NegativeInfinity, "Difference 0 -INFINITY");
+
+ // r = Angle.Difference(float.NegativeInfinity, float.PositiveInfinity);
+ // Assert.AreEqual(r, float.PositiveInfinity, "Difference -INFINITY INFINITY");
+ // }
+
+ [Test]
+ public void MoveTowards() {
+ Angle r = Angle.zero;
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(90), 30);
+ Assert.AreEqual(r.inDegrees, 30, "MoveTowards 0 90 30");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(90), 90);
+ Assert.AreEqual(r.inDegrees, 90, "MoveTowards 0 90 90");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(90), 180);
+ Assert.AreEqual(r.inDegrees, 90, "MoveTowards 0 90 180");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(90), 270);
+ Assert.AreEqual(r.inDegrees, 90, "MoveTowrads 0 90 270");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(90), -30);
+ Assert.AreEqual(r.inDegrees, 0, "MoveTowards 0 90 -30");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(-90), -30);
+ Assert.AreEqual(r.inDegrees, 0, "MoveTowards 0 -90 -30");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(-90), -90);
+ Assert.AreEqual(r.inDegrees, 0, "MoveTowards 0 -90 -90");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(-90), -180);
+ Assert.AreEqual(r.inDegrees, 0, "MoveTowards 0 -90 -180");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(-90), -270);
+ Assert.AreEqual(r.inDegrees, 0, "MoveTowrads 0 -90 -270");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(90), 0);
+ Assert.AreEqual(r.inDegrees, 0, "MoveTowards 0 90 0");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(0), 0);
+ Assert.AreEqual(r.inDegrees, 0, "MoveTowards 0 0 0");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(0), 30);
+ Assert.AreEqual(r.inDegrees, 0, "MoveTowrads 0 0 30");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(90), float.PositiveInfinity);
+ Assert.AreEqual(r.inDegrees, 90, "MoveTowards 0 90 INFINITY");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(float.PositiveInfinity), 30);
+ Assert.AreEqual(r.inDegrees, 30, "MoveTowrads 0 INFINITY 30");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(-90), float.NegativeInfinity);
+ Assert.AreEqual(r.inDegrees, 0, "MoveTowards 0 -90 -INFINITY");
+
+ r = Angle.MoveTowards(Angle.Degrees(0), Angle.Degrees(float.NegativeInfinity), -30);
+ Assert.AreEqual(r.inDegrees, 0, "MoveTowrads 0 -INFINITY -30");
+
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/LinearAlgebra/test/DirectionTest.cs b/LinearAlgebra/test/DirectionTest.cs
new file mode 100644
index 0000000..e31af4c
--- /dev/null
+++ b/LinearAlgebra/test/DirectionTest.cs
@@ -0,0 +1,17 @@
+using NUnit.Framework;
+
+namespace LinearAlgebra.Test {
+ public class DirectionTest {
+ [SetUp]
+ public void Setup() {
+ }
+
+ [Test]
+ public void Compare() {
+ Direction d = Direction.Degrees(45, 135);
+ bool r;
+ r = d == new Direction(Angle.Degrees(45), Angle.Degrees(135));
+ Assert.True(r);
+ }
+ };
+}
\ No newline at end of file
diff --git a/LinearAlgebra/test/LinearAlgebra_Test.csproj b/LinearAlgebra/test/LinearAlgebra_Test.csproj
new file mode 100644
index 0000000..3ee2230
--- /dev/null
+++ b/LinearAlgebra/test/LinearAlgebra_Test.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net5.0
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LinearAlgebra/test/SphericalTest.cs b/LinearAlgebra/test/SphericalTest.cs
new file mode 100644
index 0000000..8539dc0
--- /dev/null
+++ b/LinearAlgebra/test/SphericalTest.cs
@@ -0,0 +1,53 @@
+#if !UNITY_5_6_OR_NEWER
+using System;
+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, 1.0E-05F, "s.vert 0 0 1");
+
+ v = new(0, 1, 0);
+ s = Spherical.FromVector3(v);
+ Assert.AreEqual(1.0f, s.distance, "s.distance 0 1 0");
+ Assert.AreEqual(0.0f, s.direction.horizontal, "s.hor 0 1 0");
+ Assert.AreEqual(90.0f, s.direction.vertical, "s.vert 0 1 0");
+
+ v = new(1, 0, 0);
+ s = Spherical.FromVector3(v);
+ Assert.AreEqual(1.0f, s.distance, "s.distance 1 0 0");
+ Assert.AreEqual(90.0f, s.direction.horizontal, "s.hor 1 0 0");
+ Assert.AreEqual(0.0f, s.direction.vertical, 1.0E-05F, "s.vert 1 0 0");
+ }
+
+ [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, 1.0E-05F, "Addition(0,0,0)");
+
+ r = v1;
+ r += v2;
+ Assert.AreEqual(v1.distance, r.distance, 1.0E-05F, "Addition(0,0,0)");
+
+ v2 = Spherical.Degrees(1, 0, 90);
+ r = v1 + v2;
+ Assert.AreEqual(Math.Sqrt(2), r.distance, 1.0E-05F, "Addition(1 0 90)");
+ Assert.AreEqual(45.0f, r.direction.horizontal, "Addition(1 0 90)");
+ Assert.AreEqual(45.0f, r.direction.vertical, "Addition(1 0 90)");
+ }
+ }
+}
+#endif
\ No newline at end of file