From e06a49c6349cce096eb8c99939b87e9c33ddb239 Mon Sep 17 00:00:00 2001 From: That-One-Nerd Date: Tue, 2 Aug 2022 12:31:54 -0400 Subject: [PATCH] Version 2.3.0 has been released. More to come in the rest of 2.3! --- .gitignore | 6 +- Changelog.md | 259 ++++---- Nerd_STF/Exceptions/InvalidSizeException.cs | 12 + Nerd_STF/Exceptions/NoInverseException.cs | 18 + Nerd_STF/Extensions/Container2DExtension.cs | 24 + Nerd_STF/Extensions/ConversionExtension.cs | 13 + Nerd_STF/Extensions/ToFillExtension.cs | 6 + Nerd_STF/Foreach.cs | 4 + Nerd_STF/Graphics/CMYKA.cs | 11 +- Nerd_STF/Graphics/CMYKAByte.cs | 9 +- Nerd_STF/Graphics/HSVA.cs | 19 +- Nerd_STF/Graphics/HSVAByte.cs | 9 +- Nerd_STF/Graphics/Image.cs | 4 +- Nerd_STF/Graphics/Material.cs | 5 +- Nerd_STF/Graphics/RGBA.cs | 13 +- Nerd_STF/Graphics/RGBAByte.cs | 11 +- Nerd_STF/IGroup.cs | 1 + Nerd_STF/IGroup2D.cs | 7 + Nerd_STF/Mathematics/Algebra/IMatrix.cs | 18 + Nerd_STF/Mathematics/Algebra/Matrix.cs | 337 +++++++++++ Nerd_STF/Mathematics/Algebra/Matrix2x2.cs | 275 +++++++++ Nerd_STF/Mathematics/Algebra/Matrix3x3.cs | 411 +++++++++++++ Nerd_STF/Mathematics/Algebra/Matrix4x4.cs | 566 ++++++++++++++++++ Nerd_STF/Mathematics/Algebra/Vector2d.cs | 158 +++++ Nerd_STF/Mathematics/Algebra/Vector3d.cs | 200 +++++++ Nerd_STF/Mathematics/Angle.cs | 15 +- Nerd_STF/Mathematics/Calculus.cs | 10 +- Nerd_STF/Mathematics/Float2.cs | 37 +- Nerd_STF/Mathematics/Float3.cs | 23 +- Nerd_STF/Mathematics/Float4.cs | 19 +- Nerd_STF/Mathematics/Geometry/Box2D.cs | 2 +- Nerd_STF/Mathematics/Geometry/Box3D.cs | 2 +- Nerd_STF/Mathematics/Geometry/Line.cs | 11 +- Nerd_STF/Mathematics/Geometry/Polygon.cs | 7 +- .../Mathematics/Geometry/Quadrilateral.cs | 7 +- Nerd_STF/Mathematics/Geometry/Sphere.cs | 4 +- Nerd_STF/Mathematics/Geometry/Triangle.cs | 15 +- Nerd_STF/Mathematics/Geometry/Vert.cs | 11 +- Nerd_STF/Mathematics/Int2.cs | 17 +- Nerd_STF/Mathematics/Int3.cs | 17 +- Nerd_STF/Mathematics/Int4.cs | 15 +- Nerd_STF/Mathematics/Mathf.cs | 43 +- Nerd_STF/Mathematics/NumberSystems/Complex.cs | 193 ++++++ .../Mathematics/NumberSystems/Quaternion.cs | 309 ++++++++++ .../Mathematics/{ => Samples}/Constants.cs | 2 +- Nerd_STF/Mathematics/Samples/Equations.cs | 26 + Nerd_STF/Miscellaneous/GlobalUsings.cs | 4 + Nerd_STF/Modifier.cs | 5 + Nerd_STF/Modifier2D.cs | 5 + Nerd_STF/bin/Release/net6.0/ref/Nerd_STF.dll | Bin 77312 -> 111616 bytes 50 files changed, 3020 insertions(+), 175 deletions(-) create mode 100644 Nerd_STF/Exceptions/InvalidSizeException.cs create mode 100644 Nerd_STF/Exceptions/NoInverseException.cs create mode 100644 Nerd_STF/Extensions/Container2DExtension.cs create mode 100644 Nerd_STF/Extensions/ConversionExtension.cs create mode 100644 Nerd_STF/Extensions/ToFillExtension.cs create mode 100644 Nerd_STF/Foreach.cs create mode 100644 Nerd_STF/IGroup2D.cs create mode 100644 Nerd_STF/Mathematics/Algebra/IMatrix.cs create mode 100644 Nerd_STF/Mathematics/Algebra/Matrix.cs create mode 100644 Nerd_STF/Mathematics/Algebra/Matrix2x2.cs create mode 100644 Nerd_STF/Mathematics/Algebra/Matrix3x3.cs create mode 100644 Nerd_STF/Mathematics/Algebra/Matrix4x4.cs create mode 100644 Nerd_STF/Mathematics/Algebra/Vector2d.cs create mode 100644 Nerd_STF/Mathematics/Algebra/Vector3d.cs create mode 100644 Nerd_STF/Mathematics/NumberSystems/Complex.cs create mode 100644 Nerd_STF/Mathematics/NumberSystems/Quaternion.cs rename Nerd_STF/Mathematics/{ => Samples}/Constants.cs (99%) create mode 100644 Nerd_STF/Mathematics/Samples/Equations.cs create mode 100644 Nerd_STF/Modifier.cs create mode 100644 Nerd_STF/Modifier2D.cs diff --git a/.gitignore b/.gitignore index 5944fca..b47618a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Visual Studio stuff -*.sln *.csproj +*.editorconfig +*.sln /Nerd_STF/.vs/ /Nerd_STF/obj /Nerd_STF/bin/Debug @@ -8,6 +9,9 @@ /Nerd_STF/bin/Release/net6.0/Nerd_STF.dll /Nerd_STF/bin/Release/net6.0/Nerd_STF.pdb +# Testing project +/Testing + # Nuget /Nerd_STF/LICENSE *.nupkg diff --git a/Changelog.md b/Changelog.md index c76776a..7731b65 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,124 +1,173 @@ -# Nerd_STF v2.2.0 +# Nerd_STF v2.3.0 -This update adds many types of graphics-based objects, as well as some math functions and constants. +This update adds lots of linear algebra tools, like matrixes and vectors, as well as new number systems, like complex numbers and quaternions. ``` * Nerd_STF - + delegate Fill2D(int, int) + + Extensions + + ConversionExtension + + Container2DExtension + + ToFillExtension + + Foreach + + IGroup2D + + Modifier + + Modifier2D + * IGroup + + ToFill() * Exceptions - + FileParsingException - + FileType - + Graphics - + ColorChannel - + CMYKA - + CMYKAByte - + HSVA - + HSVAByte - + IColor - + IColorByte - + IlluminationFlags - + IlluminationModel - + Image - + Material - + RGBA - + RGBAByte + + InvalidSizeException + + NoInverseException + * Graphics + * CMYKA + + ToFill() + + static Round(CMYKA) + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * CMYKAByte + + ToFill() + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * HSVA + + ToFill() + + static Round(HSVA, Angle.Type) + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * HSVAByte + + ToFill() + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * Image + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * Material + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + = Merged 2 if statements into 1 in `override bool Equals(object?)` + * RGBA + + ToFill() + + ToVector() + + static Round(RGBA) + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * RGBAByte + + ToFill() + + ToVector() + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` * Mathematics - * Angle - + Normalized - = Made fancier `ToString()` formatting - * Type - + Normalized - + Constants - * Float2 - + static SplitArray(params Float2[]) - = Renamed `static Multiply(params Float2[])` to `Product` - * Float3 - + static SplitArray(params Float3[]) - + explicit operator Float3(RGBA) - + explicit operator Float3(HSVA) - + explicit operator Float3(RGBAByte) - + explicit operator Float3(HSVAByte) - = Renamed `static Multiply(params Float3[])` to `Product` - * Float4 - + static SplitArray(params Float4[]) - + implicit operator Float4(RGBA) - + explicit operator Float4(CMYKA) - + implicit operator Float4(HSVA) - + implicit operator Float4(RGBAByte) - + explicit operator Float4(CMYKAByte) - + implicit operator Float4(HSVAByte) - = Renamed `static Multiply(params Float4[])` to `Product` + + Algebra + + IMatrix + + Matrix2x2 + + Matrix3x3 + + Matrix4x4 + + Vector2d + + Vector3d + + NumberSystems + + Complex + + Quaternion + + Samples + + Equations + + ScaleType + = Moved `Constants` file to Samples folder. * Geometry + * Box2D + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * Box3D + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` * Line - + Midpoint - = Renamed `ToDoubleArray()` to `ToFloatArray` - = Renamed `ToDoubleList()` to `ToFloatList` + + ToFill() + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` * Polygon - + Midpoint - = Renamed `ToDoubleArray()` to `ToFloatArray` - = Renamed `ToDoubleList()` to `ToFloatList` - = Renamed `static ToDoubleArrayAll(params Triangle[])` to `ToFloatArrayAll` - = Renamed `static ToDoubleListAll(params Triangle[])` to `ToFloatListAll` + + ToFill() + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` * Quadrilateral - + Midpoint - = Renamed `ToDoubleArray()` to `ToFloatArray` - = Renamed `ToDoubleList()` to `ToFloatList` - = Renamed `static ToDoubleArrayAll(params Triangle[])` to `ToFloatArrayAll` - = Renamed `static ToDoubleListAll(params Triangle[])` to `ToFloatListAll` + + ToFill() + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * Sphere + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` * Triangle - + Midpoint - = Renamed `ToDoubleArray()` to `ToFloatArray` - = Renamed `ToDoubleList()` to `ToFloatList` - = Renamed `static ToDoubleArrayAll(params Triangle[])` to `ToFloatArrayAll` - = Renamed `static ToDoubleListAll(params Triangle[])` to `ToFloatListAll` + + ToFill() + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` * Vert - = Renamed `static ToDouble3Array(params Vert[])` to `ToFloat3Array` - = Renamed `static ToDouble3List(params Vert[])` to `ToFloat3List` + + ToFill() + + ToVector() + = Made `Vert(Float2)` not recreate a float group, and instead use itself. + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * Angle + + static Down + + static Left + + static Right + + static Up + + static Round(Angle, Type) + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * Float2 + + ToFill() + + ToVector() + + static Round(Float2) + + implicit operator Float2(Complex) + + explicit operator Float2(Quaternion) + + explicit operator Float2(Matrix) + + operator *(Float2, Matrix) + + operator /(Float2, Matrix) + = Made `Normalized` multiply by the inverse square root instead of dividing by the square root. + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * Float3 + + ToFill() + + ToVector() + + static Round(Float3) + + implicit operator Float3(Complex) + + explicit operator Float3(Quaternion) + + explicit operator Float3(Matrix) + + operator *(Float3, Matrix) + + operator /(Float3, Matrix) + = Made `Normalized` multiply by the inverse square root instead of dividing by the square root. + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + * Float4 + + ToFill() + + static Round(Float4) + + implicit operator Float4(Complex) + + implicit operator Float4(Quaternion) + + explicit operator Float4(Matrix) + + operator *(Float4, Matrix) + + operator /(Float4, Matrix) + = Made `Normalized` multiply by the inverse square root instead of dividing by the square root. + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` + = Renamed `Float4.Deep` to `Float4.Near` * Int2 - + static SplitArray(params Int[]) - = Renamed `static Multiply(params Int2[])` to `Product` + + ToFill() + + explicit operator Int2(Complex) + + explicit operator Int2(Quaternion) + + explicit operator Int2(Matrix) + + operator *(Int2, Matrix) + + operator /(Int2, Matrix) + = Made `Normalized` multiply by the inverse square root instead of dividing by the square root. + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` * Int3 - + static SplitArray(params Int3[]) - + explicit operator Int3(RGBA) - + explicit operator Int3(HSVA) - + explicit operator Int3(RGBAByte) - + explicit operator Int3(HSVAByte) - = Renamed `static Multiply(params Int3[])` to `Product` + + ToFill() + + explicit operator Int3(Complex) + + explicit operator Int3(Quaternion) + + explicit operator Int3(Matrix) + + operator *(Int3, Matrix) + + operator /(Int3, Matrix) + = Made `Normalized` multiply by the inverse square root instead of dividing by the square root. + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` * Int4 - + static SplitArray(params Int4[]) - + explicit operator Int4(RGBA) - + explicit operator Int4(CMYKA) - + explicit operator Int4(HSVA) - + implicit operator Int4(RGBAByte) - + explicit operator Int4(CMYKAByte) - + implicit operator Int4(HSVAByte) - = Renamed `static Multiply(params Int4[])` to `Product` + + explicit operator Int4(Complex) + + explicit operator Int4(Quaternion) + + explicit operator Int4(Matrix) + + operator *(Int4, Matrix) + + operator /(Int4, Matrix) + = Made `Normalized` multiply by the inverse square root instead of dividing by the square root. + = Replaced a false statement with `base.Equals(object?)` in `override bool Equals(object?)` * Mathf - + static Combinations(int, int) - + static GreatestCommonFactor(params int[]) - + static InverseSqrt(float) - + static LeastCommonMultiple(params int[]) - + static Mode(params T[]) where T : IEquatable - + static Permutations(int, int) - + static Pow(float, int) - + static Product(Equation, float, float, float) - + static Sum(Equation, float, float, float) - + static UniqueItems(params T[]) where T : IEquatable - + static ZScore(float, params float[]) - + static ZScore(float, float, float) - - const RadToDeg - - const E - - const GoldenRatio - - const HalfPi - - const Pi - - const DegToRad - - const Tau - = GreatestCommonFactor actually works now - = Pow has been fixed - = Mode actually works - * static Average(params int[]) - = Replaced its `int` return type with `float` + + Cos(Angle) + + Cot(Angle) + + Csc(Angle) + + Dot(float[], float[]) + + Dot(float[][]) + + Max(T[]) where T : IComparable + + Median(T[]) + + Min(T[]) where T : IComparable + + Sec(Angle) + + Sin(Angle) + + Tan(Angle) * Miscellaneous * GlobalUsings.cs - + global using Nerd_STF.Graphics; + + global using Nerd_STF.Collections + + global using Nerd_STF.Extensions + + global using Nerd_STF.Mathematics.Algebra + + global using Nerd_STF.Mathematics.NumberSystems + + global using Nerd_STF.Mathematics.Samples ``` diff --git a/Nerd_STF/Exceptions/InvalidSizeException.cs b/Nerd_STF/Exceptions/InvalidSizeException.cs new file mode 100644 index 0000000..6d16e83 --- /dev/null +++ b/Nerd_STF/Exceptions/InvalidSizeException.cs @@ -0,0 +1,12 @@ +using System.Runtime.Serialization; + +namespace Nerd_STF.Exceptions; + +[Serializable] +public class InvalidSizeException : Nerd_STFException +{ + public InvalidSizeException() : this("Argument size is invalid.") { } + public InvalidSizeException(string message) : base(message) { } + public InvalidSizeException(string message, Exception inner) : base(message, inner) { } + protected InvalidSizeException(SerializationInfo info, StreamingContext context) : base(info, context) { } +} diff --git a/Nerd_STF/Exceptions/NoInverseException.cs b/Nerd_STF/Exceptions/NoInverseException.cs new file mode 100644 index 0000000..a7eca4e --- /dev/null +++ b/Nerd_STF/Exceptions/NoInverseException.cs @@ -0,0 +1,18 @@ +using System.Runtime.Serialization; + +namespace Nerd_STF.Exceptions; + +[Serializable] +public class NoInverseException : Exception +{ + public Matrix? Matrix; + + public NoInverseException() : base("This matrix does not have an inverse.") { } + public NoInverseException(string message) : base(message) { } + public NoInverseException(string message, Exception inner) : base(message, inner) { } + public NoInverseException(Matrix? matrix) : this() => Matrix = matrix; + public NoInverseException(Matrix? matrix, string message) : this(message) => Matrix = matrix; + public NoInverseException(Matrix? matrix, string message, Exception inner) : this(message, inner) => + Matrix = matrix; + protected NoInverseException(SerializationInfo info, StreamingContext context) : base(info, context) { } +} diff --git a/Nerd_STF/Extensions/Container2DExtension.cs b/Nerd_STF/Extensions/Container2DExtension.cs new file mode 100644 index 0000000..6a90731 --- /dev/null +++ b/Nerd_STF/Extensions/Container2DExtension.cs @@ -0,0 +1,24 @@ +namespace Nerd_STF.Extensions; + +public static class Container2DExtension +{ + public static T[] Flatten(this T[,] array, Int2 size) + { + T[] res = new T[size.x * size.y]; + for (int x = 0; x < size.x; x++) for (int y = 0; y < size.y; y++) res[x + y * size.y] = array[x, y]; + return res; + } + + public static T[] GetColumn(this T[,] array, int column, int length) + { + T[] res = new T[length]; + for (int i = 0; i < length; i++) res[i] = array[column, i]; + return res; + } + public static T[] GetRow(this T[,] array, int row, int length) + { + T[] res = new T[length]; + for (int i = 0; i < length; i++) res[i] = array[i, row]; + return res; + } +} diff --git a/Nerd_STF/Extensions/ConversionExtension.cs b/Nerd_STF/Extensions/ConversionExtension.cs new file mode 100644 index 0000000..83c9c3c --- /dev/null +++ b/Nerd_STF/Extensions/ConversionExtension.cs @@ -0,0 +1,13 @@ +namespace Nerd_STF.Extensions; + +public static class ConversionExtension +{ + public static Dictionary ToDictionary + (this IEnumerable> pairs) + where TKey : notnull + { + Dictionary res = new(); + foreach (KeyValuePair pair in pairs) res.Add(pair.Key, pair.Value); + return res; + } +} diff --git a/Nerd_STF/Extensions/ToFillExtension.cs b/Nerd_STF/Extensions/ToFillExtension.cs new file mode 100644 index 0000000..a0897ab --- /dev/null +++ b/Nerd_STF/Extensions/ToFillExtension.cs @@ -0,0 +1,6 @@ +namespace Nerd_STF.Extensions; + +public static class ToFillExtension +{ + public static Fill ToFill(this IEnumerable group) => i => group.ElementAt(i); +} diff --git a/Nerd_STF/Foreach.cs b/Nerd_STF/Foreach.cs new file mode 100644 index 0000000..624aa57 --- /dev/null +++ b/Nerd_STF/Foreach.cs @@ -0,0 +1,4 @@ +namespace Nerd_STF; + +public delegate void Foreach(object item); +public delegate void Foreach(T item); diff --git a/Nerd_STF/Graphics/CMYKA.cs b/Nerd_STF/Graphics/CMYKA.cs index 806c02d..65809f0 100644 --- a/Nerd_STF/Graphics/CMYKA.cs +++ b/Nerd_STF/Graphics/CMYKA.cs @@ -146,6 +146,8 @@ public struct CMYKA : IColor, IEquatable (float[] Cs, float[] Ms, float[] Ys, float[] Ks, float[] As) = SplitArray(vals); return new(Mathf.Min(Cs), Mathf.Min(Ms), Mathf.Min(Ys), Mathf.Min(Ks), Mathf.Min(As)); } + public static CMYKA Round(CMYKA val) => new(Mathf.Round(val.C), Mathf.Round(val.M), + Mathf.Round(val.Y), Mathf.Round(val.K), Mathf.Round(val.A)); public static (float[] Cs, float[] Ms, float[] Ys, float[] Ks, float[] As) SplitArray(params CMYKA[] vals) { @@ -169,7 +171,7 @@ public struct CMYKA : IColor, IEquatable && Y == col.Y && K == col.K && A == col.A; public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null) return false; + if (obj == null) return base.Equals(obj); Type t = obj.GetType(); if (t == typeof(CMYKA)) return Equals((CMYKA)obj); else if (t == typeof(RGBA)) return Equals((IColor)obj); @@ -180,7 +182,7 @@ public struct CMYKA : IColor, IEquatable else if (t == typeof(HSVAByte)) return Equals((IColorByte)obj); else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); - return false; + return base.Equals(obj); } public override int GetHashCode() => C.GetHashCode() ^ M.GetHashCode() ^ Y.GetHashCode() ^ K.GetHashCode() ^ A.GetHashCode(); public string ToString(IFormatProvider provider) => "C: " + C.ToString(provider) + " M: " + M.ToString(provider) @@ -205,6 +207,11 @@ public struct CMYKA : IColor, IEquatable public HSVAByte ToHSVAByte() => ToRGBA().ToHSVAByte(); public float[] ToArray() => new[] { C, M, Y, K, A }; + public Fill ToFill() + { + CMYKA @this = this; + return i => @this[i]; + } public List ToList() => new() { C, M, Y, K, A }; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/Nerd_STF/Graphics/CMYKAByte.cs b/Nerd_STF/Graphics/CMYKAByte.cs index 54c8119..92ce249 100644 --- a/Nerd_STF/Graphics/CMYKAByte.cs +++ b/Nerd_STF/Graphics/CMYKAByte.cs @@ -149,7 +149,7 @@ public struct CMYKAByte : IColorByte, IEquatable && Y == col.Y && K == col.K && A == col.A; public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null) return false; + if (obj == null) return base.Equals(obj); Type t = obj.GetType(); if (t == typeof(CMYKAByte)) return Equals((CMYKAByte)obj); else if (t == typeof(RGBA)) return Equals((IColor)obj); @@ -160,7 +160,7 @@ public struct CMYKAByte : IColorByte, IEquatable else if (t == typeof(HSVAByte)) return Equals((IColorByte)obj); else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); - return false; + return base.Equals(obj); } public override int GetHashCode() => C.GetHashCode() ^ M.GetHashCode() ^ Y.GetHashCode() ^ K.GetHashCode() ^ A.GetHashCode(); @@ -181,6 +181,11 @@ public struct CMYKAByte : IColorByte, IEquatable public HSVAByte ToHSVAByte() => ToRGBA().ToHSVAByte(); public byte[] ToArray() => new[] { C, M, Y, K, A }; + public Fill ToFill() + { + CMYKAByte @this = this; + return i => @this[i]; + } public List ToList() => new() { C, M, Y, K, A }; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/Nerd_STF/Graphics/HSVA.cs b/Nerd_STF/Graphics/HSVA.cs index 706b992..f2b2d0f 100644 --- a/Nerd_STF/Graphics/HSVA.cs +++ b/Nerd_STF/Graphics/HSVA.cs @@ -100,15 +100,15 @@ public struct HSVA : IColor, IEquatable for (int i = 0; i < vals.Length; i++) val += vals[i]; return val / vals.Length; } - public static HSVA Ceiling(HSVA val, Angle.Type type) => new(Angle.Ceiling(val.H, type), Mathf.Ceiling(val.S), - Mathf.Ceiling(val.V), Mathf.Ceiling(val.A)); + public static HSVA Ceiling(HSVA val, Angle.Type type = Angle.Type.Degrees) => new(Angle.Ceiling(val.H, type), + Mathf.Ceiling(val.S), Mathf.Ceiling(val.V), Mathf.Ceiling(val.A)); public static HSVA Clamp(HSVA val, HSVA min, HSVA max) => new(Angle.Clamp(val.H, min.H, max.H), Mathf.Clamp(val.S, min.S, max.S), Mathf.Clamp(val.V, min.V, max.V), Mathf.Clamp(val.A, min.A, max.A)); - public static HSVA Floor(HSVA val, Angle.Type type) => new(Angle.Floor(val.H, type), Mathf.Floor(val.S), - Mathf.Floor(val.V), Mathf.Floor(val.A)); + public static HSVA Floor(HSVA val, Angle.Type type = Angle.Type.Degrees) => new(Angle.Floor(val.H, type), + Mathf.Floor(val.S), Mathf.Floor(val.V), Mathf.Floor(val.A)); public static HSVA Lerp(HSVA a, HSVA b, float t, bool clamp = true) => new(Angle.Lerp(a.H, b.H, t, clamp), Mathf.Lerp(a.S, b.S, t, clamp), Mathf.Lerp(a.V, b.V, t, clamp), Mathf.Lerp(a.A, b.A, t, clamp)); @@ -136,6 +136,8 @@ public struct HSVA : IColor, IEquatable (Angle[] Hs, float[] Ss, float[] Vs, float[] As) = SplitArray(vals); return new(Angle.Min(Hs), Mathf.Min(Ss), Mathf.Min(Vs), Mathf.Min(As)); } + public static HSVA Round(HSVA val, Angle.Type type = Angle.Type.Degrees) => new(Angle.Round(val.H, type), + Mathf.Round(val.S), Mathf.Round(val.V), Mathf.Round(val.A)); public static (Angle[] Hs, float[] Ss, float[] Vs, float[] As) SplitArray(params HSVA[] vals) { @@ -171,7 +173,7 @@ public struct HSVA : IColor, IEquatable || H == col.H && S == col.S && V == col.V && A == col.A; public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null) return false; + if (obj == null) return base.Equals(obj); Type t = obj.GetType(); if (t == typeof(HSVA)) return Equals((HSVA)obj); else if (t == typeof(CMYKA)) return Equals((IColor)obj); @@ -182,7 +184,7 @@ public struct HSVA : IColor, IEquatable else if (t == typeof(HSVAByte)) return Equals((IColorByte)obj); else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); - return false; + return base.Equals(obj); } public override int GetHashCode() => H.GetHashCode() ^ S.GetHashCode() ^ V.GetHashCode() ^ A.GetHashCode(); public string ToString(IFormatProvider provider) => "H: " + H.ToString(provider) + " S: " + S.ToString(provider) @@ -212,6 +214,11 @@ public struct HSVA : IColor, IEquatable Mathf.RoundInt(V * 255), Mathf.RoundInt(A * 255)); public float[] ToArray() => new[] { H.Normalized, S, V, A }; + public Fill ToFill() + { + HSVA @this = this; + return i => @this[i]; + } public List ToList() => new() { H.Normalized, S, V, A }; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/Nerd_STF/Graphics/HSVAByte.cs b/Nerd_STF/Graphics/HSVAByte.cs index e8a87c1..2134cdc 100644 --- a/Nerd_STF/Graphics/HSVAByte.cs +++ b/Nerd_STF/Graphics/HSVAByte.cs @@ -138,7 +138,7 @@ public struct HSVAByte : IColorByte, IEquatable || H == col.H && S == col.S && V == col.V && A == col.A; public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null) return false; + if (obj == null) return base.Equals(obj); Type t = obj.GetType(); if (t == typeof(HSVAByte)) return Equals((HSVAByte)obj); else if (t == typeof(CMYKA)) return Equals((IColor)obj); @@ -149,7 +149,7 @@ public struct HSVAByte : IColorByte, IEquatable else if (t == typeof(HSVA)) return Equals((IColor)obj); else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); - return false; + return base.Equals(obj); } public override int GetHashCode() => H.GetHashCode() ^ S.GetHashCode() ^ V.GetHashCode() ^ A.GetHashCode(); public string ToString(IFormatProvider provider) => "H: " + H.ToString(provider) + " S: " + S.ToString(provider) @@ -167,6 +167,11 @@ public struct HSVAByte : IColorByte, IEquatable public HSVAByte ToHSVAByte() => this; public byte[] ToArray() => new[] { H, S, V, A }; + public Fill ToFill() + { + HSVAByte @this = this; + return i => @this[i]; + } public List ToList() => new() { H, S, V, A }; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/Nerd_STF/Graphics/Image.cs b/Nerd_STF/Graphics/Image.cs index 2b7f670..f6a35b1 100644 --- a/Nerd_STF/Graphics/Image.cs +++ b/Nerd_STF/Graphics/Image.cs @@ -75,9 +75,9 @@ public struct Image : ICloneable, IEnumerable, IEquatable public bool Equals(Image other) => Pixels == other.Pixels; public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null) return false; + if (obj == null) return base.Equals(obj); if (obj.GetType() == typeof(Image)) return Equals((Image)obj); - return false; + return base.Equals(obj); } public override int GetHashCode() => Pixels.GetHashCode(); diff --git a/Nerd_STF/Graphics/Material.cs b/Nerd_STF/Graphics/Material.cs index 47e2389..d92a20c 100644 --- a/Nerd_STF/Graphics/Material.cs +++ b/Nerd_STF/Graphics/Material.cs @@ -171,9 +171,8 @@ public struct Material : ICloneable, IEquatable SpecularHighlightTexture.Equals(other.SpecularHighlightTexture) && StencilTexture.Equals(other.StencilTexture); public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null) return false; - if (obj.GetType() == typeof(Material)) return Equals((Material)obj); - return false; + if (obj == null || obj.GetType() != typeof(Material)) return base.Equals(obj); + return Equals((Material)obj); } public override int GetHashCode() => Alpha.GetHashCode() ^ AmbientColor.GetHashCode() ^ Anisotropy.GetHashCode() ^ AnisotropyRoughness.GetHashCode() ^ ClearcoatRoughness.GetHashCode() ^ ClearcoatThickness.GetHashCode() ^ diff --git a/Nerd_STF/Graphics/RGBA.cs b/Nerd_STF/Graphics/RGBA.cs index 333557b..5f8211d 100644 --- a/Nerd_STF/Graphics/RGBA.cs +++ b/Nerd_STF/Graphics/RGBA.cs @@ -132,6 +132,8 @@ public struct RGBA : IColor, IEquatable (float[] Rs, float[] Gs, float[] Bs, float[] As) = SplitArray(vals); return new(Mathf.Min(Rs), Mathf.Min(Gs), Mathf.Min(Bs), Mathf.Min(As)); } + public static RGBA Round(RGBA val) => + new(Mathf.Round(val.R), Mathf.Round(val.G), Mathf.Round(val.B), Mathf.Round(val.A)); public static (float[] Rs, float[] Gs, float[] Bs, float[] As) SplitArray(params RGBA[] vals) { @@ -152,7 +154,7 @@ public struct RGBA : IColor, IEquatable public bool Equals(RGBA col) => A == 0 && col.A == 0 || R == col.R && G == col.G && B == col.B && A == col.A; public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null) return false; + if (obj == null) return base.Equals(obj); Type t = obj.GetType(); if (t == typeof(RGBA)) return Equals((RGBA)obj); else if (t == typeof(CMYKA)) return Equals((IColor)obj); @@ -163,7 +165,7 @@ public struct RGBA : IColor, IEquatable else if (t == typeof(HSVAByte)) return Equals((IColorByte)obj); else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); - return false; + return base.Equals(obj); } public override int GetHashCode() => R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode() ^ A.GetHashCode(); public string ToString(IFormatProvider provider) => "R: " + R.ToString(provider) + " G: " + G.ToString(provider) + @@ -205,6 +207,11 @@ public struct RGBA : IColor, IEquatable public HSVAByte ToHSVAByte() => ToHSVA().ToHSVAByte(); public float[] ToArray() => new[] { R, G, B, A }; + public Fill ToFill() + { + RGBA @this = this; + return i => @this[i]; + } public List ToList() => new() { R, G, B, A }; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -218,6 +225,8 @@ public struct RGBA : IColor, IEquatable public object Clone() => new RGBA(R, G, B, A); + public Vector3d ToVector() => ((Float3)this).ToVector(); + public static RGBA operator +(RGBA a, RGBA b) => new(a.R + b.R, a.G + b.G, a.B + b.B, a.A + b.A); public static RGBA operator -(RGBA c) => new(1 - c.R, 1 - c.G, 1 - c.B, c.A != 1 ? 1 - c.A : 1); public static RGBA operator -(RGBA a, RGBA b) => new(a.R - b.R, a.G - b.G, a.B - b.B, a.A - b.A); diff --git a/Nerd_STF/Graphics/RGBAByte.cs b/Nerd_STF/Graphics/RGBAByte.cs index d363449..26a07ae 100644 --- a/Nerd_STF/Graphics/RGBAByte.cs +++ b/Nerd_STF/Graphics/RGBAByte.cs @@ -137,7 +137,7 @@ public struct RGBAByte : IColorByte, IEquatable public bool Equals(RGBAByte col) => A == 0 && col.A == 0 || R == col.R && G == col.G && B == col.B && A == col.A; public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null) return false; + if (obj == null) return base.Equals(obj); Type t = obj.GetType(); if (t == typeof(RGBAByte)) return Equals((RGBAByte)obj); else if (t == typeof(CMYKA)) return Equals((IColor)obj); @@ -148,7 +148,7 @@ public struct RGBAByte : IColorByte, IEquatable else if (t == typeof(HSVAByte)) return Equals((IColorByte)obj); else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); - return false; + return base.Equals(obj); } public override int GetHashCode() => R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode() ^ A.GetHashCode(); public string ToString(IFormatProvider provider) => "R: " + R.ToString(provider) + " G: " + G.ToString(provider) + @@ -166,6 +166,11 @@ public struct RGBAByte : IColorByte, IEquatable public HSVAByte ToHSVAByte() => ToRGBA().ToHSVAByte(); public byte[] ToArray() => new[] { R, G, B, A }; + public Fill ToFill() + { + HSVAByte @this = this; + return i => @this[i]; + } public List ToList() => new() { R, G, B, A }; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -179,6 +184,8 @@ public struct RGBAByte : IColorByte, IEquatable public object Clone() => new RGBAByte(R, G, B, A); + public Vector3d ToVector() => ((RGBA)this).ToVector(); + public static RGBAByte operator +(RGBAByte a, RGBAByte b) => new(a.R + b.R, a.G + b.G, a.B + b.B, a.A + b.A); public static RGBAByte operator -(RGBAByte c) => new(255 - c.R, 255 - c.G, 255 - c.B, c.A != 255 ? 255 - c.A : 255); public static RGBAByte operator -(RGBAByte a, RGBAByte b) => new(a.R - b.R, a.G - b.G, a.B - b.B, a.A - b.A); diff --git a/Nerd_STF/IGroup.cs b/Nerd_STF/IGroup.cs index 9b7bc54..17c6b65 100644 --- a/Nerd_STF/IGroup.cs +++ b/Nerd_STF/IGroup.cs @@ -3,5 +3,6 @@ public interface IGroup : IEnumerable { public T[] ToArray(); + public Fill ToFill(); public List ToList(); } diff --git a/Nerd_STF/IGroup2D.cs b/Nerd_STF/IGroup2D.cs new file mode 100644 index 0000000..ae1e3fc --- /dev/null +++ b/Nerd_STF/IGroup2D.cs @@ -0,0 +1,7 @@ +namespace Nerd_STF; + +public interface IGroup2D : IGroup +{ + public T[,] ToArray2D(); + public Fill2D ToFill2D(); +} diff --git a/Nerd_STF/Mathematics/Algebra/IMatrix.cs b/Nerd_STF/Mathematics/Algebra/IMatrix.cs new file mode 100644 index 0000000..12aa38b --- /dev/null +++ b/Nerd_STF/Mathematics/Algebra/IMatrix.cs @@ -0,0 +1,18 @@ +namespace Nerd_STF.Mathematics.Algebra; + +public interface IMatrix : ICloneable, IEnumerable, IEquatable, IGroup2D + where T : IMatrix +{ + public T Adjugate(); + public float Determinant(); + public T Inverse(); + public T Transpose(); + + public Dictionary ToDictionary(); +} + +public interface IMatrix : IMatrix where This : IMatrix + where TMinor : IMatrix +{ + public TMinor[,] Minors(); +} diff --git a/Nerd_STF/Mathematics/Algebra/Matrix.cs b/Nerd_STF/Mathematics/Algebra/Matrix.cs new file mode 100644 index 0000000..a0a16ae --- /dev/null +++ b/Nerd_STF/Mathematics/Algebra/Matrix.cs @@ -0,0 +1,337 @@ +namespace Nerd_STF.Mathematics.Algebra; + +public struct Matrix : IMatrix +{ + public static Matrix Identity(Int2 size) + { + Matrix m = Zero(size); + int max = Mathf.Min(size.x, size.y); + for (int i = 0; i < max; i++) m[i, i] = 1; + return m; + } + public static Matrix One(Int2 size) => new(size, 1); + public static Matrix SignGrid(Int2 size) => new(size, Equations.SgnFill); + public static Matrix Zero(Int2 size) => new(size); + + public bool HasMinors => Size.x > 1 && Size.y > 1; + public bool IsSquare => Size.x == Size.y; + + public Int2 Size { get; private init; } + + private readonly float[,] array; + + public Matrix() : this(Int2.Zero) { } + public Matrix(Int2 size, float all = 0) + { + Size = size; + array = new float[size.x, size.y]; + + if (all == 0) return; + for (int r = 0; r < size.y; r++) for (int c = 0; c < size.x; c++) array[c, r] = all; + } + public Matrix(Int2 size, float[] vals) + { + Size = size; + array = new float[size.x, size.y]; + + if (vals.Length < size.x * size.y) + throw new InvalidSizeException("Array must contain enough values to fill the matrix."); + + for (int r = 0; r < size.y; r++) for (int c = 0; c < size.x; c++) array[c, r] = vals[c + r * size.y]; + } + public Matrix(Int2 size, int[] vals) + { + Size = size; + array = new float[size.x, size.y]; + + if (vals.Length < size.x * size.y) + throw new InvalidSizeException("Array must contain enough values to fill the matrix."); + + for (int r = 0; r < size.y; r++) for (int c = 0; c < size.x; c++) array[c, r] = vals[c + r * size.y]; + } + public Matrix(Int2 size, Fill vals) + { + Size = size; + array = new float[size.x, size.y]; + + for (int r = 0; r < size.y; r++) for (int c = 0; c < size.x; c++) array[c, r] = vals(c + r * size.y); + } + public Matrix(Int2 size, Fill vals) + { + Size = size; + array = new float[size.x, size.y]; + + for (int r = 0; r < size.y; r++) for (int c = 0; c < size.x; c++) array[c, r] = vals(c + r * size.y); + } + public Matrix(Int2 size, float[,] vals) + { + Size = size; + array = new float[size.x, size.y]; + for (int r = 0; r < size.y; r++) for (int c = 0; c < size.x; c++) array[c, r] = vals[c, r]; + } + public Matrix(Int2 size, int[,] vals) + { + Size = size; + array = new float[size.x, size.y]; + for (int r = 0; r < size.y; r++) for (int c = 0; c < size.x; c++) array[c, r] = vals[c, r]; + } + public Matrix(Int2 size, Fill2D vals) + { + Size = size; + array = new float[size.x, size.y]; + for (int r = 0; r < size.y; r++) for (int c = 0; c < size.x; c++) array[c, r] = vals(c, r); + } + public Matrix(Int2 size, Fill2D vals) + { + Size = size; + array = new float[size.x, size.y]; + for (int r = 0; r < size.y; r++) for (int c = 0; c < size.x; c++) array[c, r] = vals(c, r); + } + + public float this[int r, int c] + { + get => array[r, c]; + set => array[r, c] = value; + } + public float this[Int2 index] + { + get => this[index.x, index.y]; + set => this[index.x, index.y] = value; + } + + public static Matrix Absolute(Matrix val) => new(val.Size, (r, c) => Mathf.Absolute(val[r, c])); + public static Matrix Ceiling(Matrix val) => new(val.Size, (r, c) => Mathf.Ceiling(val[r, c])); + public static Matrix Clamp(Matrix val, Matrix min, Matrix max) => + new(val.Size, (r, c) => Mathf.Clamp(val[r, c], min[r, c], max[r, c])); + public static Matrix Divide(Matrix num, params Matrix[] vals) + { + foreach (Matrix m in vals) num /= m; + return num; + } + public static Matrix Floor(Matrix val) => new(val.Size, (r, c) => Mathf.Floor(val[r, c])); + public static Matrix Lerp(Matrix a, Matrix b, float t, bool clamp = true) => + new(a.Size, (r, c) => Mathf.Lerp(a[r, c], b[r, c], t, clamp)); + public static Matrix Product(params Matrix[] vals) + { + if (vals.Length < 1) throw new InvalidSizeException("Array must contain at least one matrix."); + if (!CheckSize(vals)) throw new InvalidSizeException("All matricies must be the same size."); + Matrix val = Identity(vals[0].Size); + foreach (Matrix m in vals) val *= m; + return val; + } + public static Matrix Round(Matrix val) => new(val.Size, (r, c) => Mathf.Round(val[r, c])); + public static Matrix Subtract(Matrix num, params Matrix[] vals) + { + foreach (Matrix m in vals) num -= m; + return num; + } + public static Matrix Sum(params Matrix[] vals) + { + if (!CheckSize(vals)) throw new InvalidSizeException("All matricies must be the same size."); + Matrix val = Zero(vals[0].Size); + foreach (Matrix m in vals) val += m; + return val; + } + + public void Apply(Modifier2D modifier) + { + for (int r = 0; r < Size.y; r++) for (int c = 0; c < Size.x; c++) + array[r, c] = modifier(new(r, c), array[r, c]); + } + + public float[] GetColumn(int column) + { + float[] vals = new float[Size.y]; + for (int i = 0; i < Size.y; i++) vals[i] = array[i, column]; + return vals; + } + public float[] GetRow(int row) + { + float[] vals = new float[Size.x]; + for (int i = 0; i < Size.x; i++) vals[i] = array[row, i]; + return vals; + } + + public void SetColumn(int column, float[] vals) + { + if (vals.Length < Size.y) + throw new InvalidSizeException("Array must contain enough values to fill the column."); + for (int i = 0; i < Size.y; i++) array[i, column] = vals[i]; + } + public void SetRow(int row, float[] vals) + { + if (vals.Length < Size.x) + throw new InvalidSizeException("Array must contain enough values to fill the row."); + for (int i = 0; i < Size.x; i++) array[row, i] = vals[i]; + } + + public Matrix Adjugate() + { + Matrix dets = new(Size); + Matrix[,] minors = Minors(); + for (int r = 0; r < Size.y; r++) for (int c = 0; c < Size.x; c++) dets[c, r] = minors[c, r].Determinant(); + return dets ^ SignGrid(Size); + } + public float Determinant() + { + if (!IsSquare) throw new InvalidSizeException("Matrix must be square to calculate determinant."); + if (Size.x <= 0 || Size.y <= 0) return 0; + if (Size.x == 1 || Size.y == 1) return array[0, 0]; + + Matrix[] minors = Minors().GetRow(0, Size.x); + float det = 0; + for (int i = 0; i < minors.Length; i++) det += minors[i].Determinant() * (i % 2 == 0 ? 1 : -1); + + return det; + } + public Matrix Inverse() + { + float d = Determinant(); + if (d == 0) throw new NoInverseException(); + return Transpose().Adjugate() / d; + } + public Matrix[,] Minors() + { + // This will absolutely blow my mind if it works. + // Remember that whole "don't have a way to test" thing? + + if (!HasMinors) return new Matrix[0,0]; + + Int2 newSize = Size - Int2.One; + Matrix[,] array = new Matrix[Size.x, Size.y]; + for (int r1 = 0; r1 < Size.y; r1++) for (int c1 = 0; c1 < Size.x; c1++) + { + Matrix m = new(newSize); + for (int r2 = 0; r2 < newSize.y; r2++) for (int c2 = 0; c2 < newSize.x; c2++) + { + int toSkip = c2 + r2 * newSize.y; + for (int r3 = 0; r3 < newSize.y; r3++) for (int c3 = 0; c3 < newSize.x; c3++) + { + if (r3 == r1 || c3 == c1) continue; + if (toSkip > 0) + { + toSkip--; + continue; + } + m[c2, r2] = this.array[c3, r3]; + break; + } + } + array[c1, r1] = m; + } + return array; + } + public Matrix Transpose() + { + Matrix m = new(new(Size.y, Size.x)); + for (int r = 0; r < Size.y; r++) m.SetColumn(r, GetRow(r)); + for (int c = 0; c < Size.x; c++) m.SetRow(c, GetColumn(c)); + return m; + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null) return base.Equals(obj); + Type t = obj.GetType(); + if (t == typeof(Matrix)) return Equals((Matrix)obj); + else if (t == typeof(Matrix2x2)) return Equals((Matrix)obj); + else if (t == typeof(Matrix3x3)) return Equals((Matrix)obj); + else if (t == typeof(Matrix4x4)) return Equals((Matrix)obj); + + return base.Equals(obj); + } + public bool Equals(Matrix other) => array == other.array; + public override int GetHashCode() => array.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(string? provider) + { + string res = ""; + for (int r = 0; r < Size.y; r++) + { + for (int c = 0; c < Size.x; c++) res += array[c, r].ToString(provider) + " "; + res += "\n"; + } + return res; + } + public string ToString(IFormatProvider provider) + { + string res = ""; + for (int r = 0; r < Size.y; r++) + { + for (int c = 0; c < Size.x; c++) res += array[c, r].ToString(provider) + " "; + res += "\n"; + } + return res; + } + + public object Clone() => new Matrix(Size, array); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + for (int r = 0; r < Size.y; r++) for (int c = 0; c < Size.x; c++) yield return array[c, r]; + } + + public float[] ToArray() => array.Flatten(Size); + public float[,] ToArray2D() => array; + public Dictionary ToDictionary() + { + Dictionary dict = new(); + for (int r = 0; r < Size.y; r++) for (int c = 0; c < Size.x; c++) dict.Add(new(c, r), array[c, r]); + return dict; + } + public Fill ToFill() => ToFillExtension.ToFill(this); + public Fill2D ToFill2D() + { + Matrix @this = this; + return (x, y) => @this[x, y]; + } + public List ToList() => ToArray().ToList(); + + public static Matrix operator +(Matrix a, Matrix b) => new(a.Size, (r, c) => a[r, c] + b[r, c]); + public static Matrix operator -(Matrix m) => m.Inverse(); + public static Matrix operator -(Matrix a, Matrix b) => new(a.Size, (r, c) => a[r, c] - b[r, c]); + public static Matrix operator *(Matrix a, float b) => new(a.Size, (r, c) => a[r, c] * b); + public static Matrix operator *(Matrix a, Matrix b) => + new(new(a.Size.y, b.Size.x), (r, c) => Mathf.Dot(a.GetRow(r), b.GetColumn(c))); + public static Complex operator *(Matrix a, Complex b) => (Complex)(a * (Matrix)b); + public static Quaternion operator *(Matrix a, Quaternion b) => (Quaternion)(a * (Matrix)b); + public static Float2 operator *(Matrix a, Float2 b) => (Float2)(a * (Matrix)b); + public static Float3 operator *(Matrix a, Float3 b) => (Float3)(a * (Matrix)b); + public static Float4 operator *(Matrix a, Float4 b) => (Float4)(a * (Matrix)b); + public static Vector2d operator *(Matrix a, Vector2d b) => (Vector2d)(a * (Matrix)b); + public static Vector3d operator *(Matrix a, Vector3d b) => (Vector3d)(a * (Matrix)b); + public static Matrix operator /(Matrix a, float b) => new(a.Size, (r, c) => a[r, c] / b); + public static Matrix operator /(Matrix a, Matrix b) => a * b.Inverse(); + public static Complex operator /(Matrix a, Complex b) => (Complex)(a / (Matrix)b); + public static Quaternion operator /(Matrix a, Quaternion b) => (Quaternion)(a / (Matrix)b); + public static Float2 operator /(Matrix a, Float2 b) => (Float2)(a / (Matrix)b); + public static Float3 operator /(Matrix a, Float3 b) => (Float3)(a / (Matrix)b); + public static Float4 operator /(Matrix a, Float4 b) => (Float4)(a / (Matrix)b); + public static Vector2d operator /(Matrix a, Vector2d b) => (Vector2d)(a / (Matrix)b); + public static Vector3d operator /(Matrix a, Vector3d b) => (Vector3d)(a / (Matrix)b); + // Single number multiplication + public static Matrix operator ^(Matrix a, Matrix b) => new(a.Size, (r, c) => a[r, c] * b[r, c]); + public static bool operator ==(Matrix a, Matrix b) => a.Equals(b); + public static bool operator !=(Matrix a, Matrix b) => !a.Equals(b); + + public static explicit operator Matrix(Complex c) => (Matrix)(Float2)c; + public static explicit operator Matrix(Quaternion c) => (Matrix)(Float4)c; + public static explicit operator Matrix(Float2 f) => new(new(2, 1), i => f[i]); + public static explicit operator Matrix(Float3 f) => new(new(3, 1), i => f[i]); + public static explicit operator Matrix(Float4 f) => new(new(4, 1), i => f[i]); + public static implicit operator Matrix(Matrix2x2 m) => new(new(2, 2), m.ToFill2D()); + public static implicit operator Matrix(Matrix3x3 m) => new(new(3, 3), m.ToFill2D()); + public static implicit operator Matrix(Matrix4x4 m) => new(new(4, 4), m.ToFill2D()); + public static explicit operator Matrix(Vector2d v) => (Matrix)v.ToXYZ(); + public static explicit operator Matrix(Vector3d v) => (Matrix)v.ToXYZ(); + + private static bool CheckSize(params Matrix[] vals) + { + if (vals.Length <= 1) return true; + Int2 size = vals[0].Size; + for (int i = 1; i < vals.Length; i++) if (size != vals[i].Size) return false; + + return true; + } +} diff --git a/Nerd_STF/Mathematics/Algebra/Matrix2x2.cs b/Nerd_STF/Mathematics/Algebra/Matrix2x2.cs new file mode 100644 index 0000000..ea02672 --- /dev/null +++ b/Nerd_STF/Mathematics/Algebra/Matrix2x2.cs @@ -0,0 +1,275 @@ +namespace Nerd_STF.Mathematics.Algebra; + +public struct Matrix2x2 : IMatrix +{ + public static Matrix2x2 Identity => new(new[,] + { + { 1, 0 }, + { 0, 1 } + }); + public static Matrix2x2 One => new(1); + public static Matrix2x2 SignGrid => new(new[,] + { + { +1, -1 }, + { -1, +1 } + }); + public static Matrix2x2 Zero => new(0); + + public Float2 Column1 + { + get => new(r1c1, r2c1); + set + { + r1c1 = value.x; + r2c1 = value.y; + } + } + public Float2 Column2 + { + get => new(r1c2, r2c2); + set + { + r1c2 = value.x; + r2c2 = value.y; + } + } + public Float2 Row1 + { + get => new(r1c1, r1c2); + set + { + r1c1 = value.x; + r1c2 = value.y; + } + } + public Float2 Row2 + { + get => new(r2c1, r2c2); + set + { + r2c1 = value.x; + r2c2 = value.y; + } + } + + public float r1c1, r2c1, r1c2, r2c2; + + public Matrix2x2(float all) : this(all, all, all, all) { } + public Matrix2x2(float r1c1, float r2c1, float r1c2, float r2c2) + { + this.r1c1 = r1c1; + this.r2c1 = r2c1; + this.r1c2 = r1c2; + this.r2c2 = r2c2; + } + public Matrix2x2(float[] nums) : this(nums[0], nums[1], nums[2], nums[3]) { } + public Matrix2x2(int[] nums) : this(nums[0], nums[1], nums[2], nums[3]) { } + public Matrix2x2(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + public Matrix2x2(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + public Matrix2x2(float[,] nums) : this(nums[0, 0], nums[0, 1], nums[1, 0], nums[1, 1]) { } + public Matrix2x2(int[,] nums) : this(nums[0, 0], nums[0, 1], nums[1, 0], nums[1, 1]) { } + public Matrix2x2(Fill2D fill) : this(fill(0, 0), fill(0, 1), fill(1, 0), fill(1, 1)) { } + public Matrix2x2(Fill2D fill) : this(fill(0, 0), fill(0, 1), fill(1, 0), fill(1, 1)) { } + public Matrix2x2(Float2 c1, Float2 c2) : this(c1.x, c1.y, c2.x, c2.y) { } + public Matrix2x2(Fill fill) : this(fill(0), fill(1)) { } + public Matrix2x2(Fill fill) : this((IEnumerable)fill(0), fill(1)) { } + public Matrix2x2(IEnumerable c1, IEnumerable c2) : this(c1.ToFill(), c2.ToFill()) { } + public Matrix2x2(IEnumerable c1, IEnumerable c2) : this(c1.ToFill(), c2.ToFill()) { } + public Matrix2x2(Fill c1, Fill c2) : this(c1(0), c1(1), c2(0), c2(1)) { } + public Matrix2x2(Fill c1, Fill c2) : this(c1(0), c1(1), c2(0), c2(1)) { } + + public float this[int r, int c] + { + get => ToArray2D()[c, r]; + set + { + // Maybe this could be improved? + // It's definitely better than it was before. Trust me. + switch ("r" + (r + 1) + "c" + (c + 1)) + { + case "r1c1": + r1c1 = value; + break; + + case "r2c1": + r2c1 = value; + break; + + case "r1c2": + r1c2 = value; + break; + + case "r2c2": + r2c2 = value; + break; + + default: + string @params = ""; + if (r < 0 || r > 1) @params += r; + if (c < 0 || c > 1) @params += string.IsNullOrEmpty(@params) ? c : " and " + c; + throw new IndexOutOfRangeException(@params); + } + } + } + public float this[Int2 index] + { + get => this[index.x, index.y]; + set => this[index.x, index.y] = value; + } + + public static Matrix2x2 Absolute(Matrix2x2 val) => new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r2c1), + Mathf.Absolute(val.r1c2), Mathf.Absolute(val.r2c2)); + public static Matrix2x2 Average(params Matrix2x2[] vals) => Sum(vals) / vals.Length; + public static Matrix2x2 Ceiling(Matrix2x2 val) => new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r2c1), + Mathf.Ceiling(val.r1c2), Mathf.Ceiling(val.r2c2)); + public static Matrix2x2 Clamp(Matrix2x2 val, Matrix2x2 min, Matrix2x2 max) => + new(Mathf.Clamp(val.r1c1, min.r1c1, max.r1c1), Mathf.Clamp(val.r2c1, min.r2c1, max.r2c1), + Mathf.Clamp(val.r1c2, min.r1c2, max.r1c2), Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2)); + public static Matrix2x2 Divide(Matrix2x2 num, params Matrix2x2[] vals) + { + foreach (Matrix2x2 m in vals) num /= m; + return num; + } + public static Matrix2x2 Floor(Matrix2x2 val) => new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r2c1), + Mathf.Floor(val.r1c2), Mathf.Floor(val.r2c2)); + public static Matrix2x2 Lerp(Matrix2x2 a, Matrix2x2 b, float t, bool clamp = true) => + new(Mathf.Lerp(a.r1c1, b.r1c1, t, clamp), Mathf.Lerp(a.r2c1, b.r2c1, t, clamp), + Mathf.Lerp(a.r1c2, b.r1c2, t, clamp), Mathf.Lerp(a.r2c2, b.r2c2, t, clamp)); + public static Matrix2x2 Median(params Matrix2x2[] vals) + { + float index = Mathf.Average(0, vals.Length - 1); + Matrix2x2 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; + return Average(valA, valB); + } + public static Matrix2x2 Product(params Matrix2x2[] vals) + { + if (vals.Length < 1) return Zero; + Matrix2x2 val = Identity; + foreach (Matrix2x2 m in vals) val *= m; + return val; + } + public static Matrix2x2 Round(Matrix2x2 val) => new(Mathf.Round(val.r1c1), Mathf.Round(val.r2c1), + Mathf.Round(val.r1c2), Mathf.Round(val.r2c2)); + public static Matrix2x2 Subtract(Matrix2x2 num, params Matrix2x2[] vals) + { + foreach (Matrix2x2 m in vals) num -= m; + return num; + } + public static Matrix2x2 Sum(params Matrix2x2[] vals) + { + Matrix2x2 val = Zero; + foreach (Matrix2x2 m in vals) val += m; + return val; + } + + public static (float[] r1c1s, float[] r2c1s, float[] r1c2s, float[] r2c2s) SplitArray(params Matrix2x2[] vals) + { + float[] r1c1s = new float[vals.Length], r2c1s = new float[vals.Length], r1c2s = new float[vals.Length], + r2c2s = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + r1c1s[i] = vals[i].r1c1; + r2c1s[i] = vals[i].r2c1; + r1c2s[i] = vals[i].r1c2; + r2c2s[i] = vals[i].r2c2; + } + return (r1c1s, r2c1s, r1c2s, r2c2s); + } + + public Matrix2x2 Adjugate() + { + Matrix2x2 swapped = new(new[,] + { + { r2c2, r1c2 }, + { r2c1, r1c1 } + }); + return swapped ^ SignGrid; + } + public float Determinant() => r1c1 * r2c2 - r1c2 * r2c1; + public Matrix2x2 Inverse() + { + float d = Determinant(); + if (d == 0) throw new NoInverseException(); + return Transpose().Adjugate() / d; + } + public Matrix2x2 Transpose() => new(new[,] + { + { r1c1, r2c1 }, + { r1c2, r2c2 } + }); + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Matrix2x2)) return base.Equals(obj); + return Equals((Matrix2x2)obj); + } + public bool Equals(Matrix2x2 other) => r1c1 == other.r1c1 && r2c1 == other.r2c1 + && r1c2 == other.r1c2&& r2c2 == other.r2c2; + public override int GetHashCode() => r1c1.GetHashCode() ^ r2c1.GetHashCode() + ^ r1c2.GetHashCode() ^ r2c2.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(string? provider) => r1c1.ToString(provider) + " " + r1c2.ToString(provider) + "\n" + + r2c1.ToString(provider) + " " + r2c2.ToString(provider); + public string ToString(IFormatProvider provider) => r1c1.ToString(provider) + " " + r1c2.ToString(provider) + "\n" + + r2c1.ToString(provider) + " " + r2c2.ToString(provider); + + public object Clone() => new Matrix2x2(ToArray2D()); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return r1c1; + yield return r2c1; + yield return r1c2; + yield return r2c2; + } + + public float[] ToArray() => new[] { r1c1, r2c1, r1c2, r2c2 }; + public float[,] ToArray2D() => new[,] + { + { r1c1, r1c2 }, + { r2c1, r2c2 } + }; + public Dictionary ToDictionary() + { + Dictionary dict = new(); + float[] arr = ToArray(); + for (int i = 0; i < arr.Length; i++) dict.Add(new(i % 2, i / 2), arr[i]); + return dict; + } + public Fill ToFill() => ToFillExtension.ToFill(this); + public Fill2D ToFill2D() + { + Matrix2x2 @this = this; + return (x, y) => @this[x, y]; + } + public List ToList() => new() { r1c1, r2c1, r1c2, r2c2 }; + + public static Matrix2x2 operator +(Matrix2x2 a, Matrix2x2 b) => new(a.r1c1 + b.r1c1, a.r2c1 + b.r2c1, + a.r1c2 + b.r1c2, a.r2c2 + b.r2c2); + public static Matrix2x2? operator -(Matrix2x2 m) => m.Inverse(); + public static Matrix2x2 operator -(Matrix2x2 a, Matrix2x2 b) => new(a.r1c1 - b.r1c1, a.r2c1 - b.r2c1, + a.r1c2 - b.r1c2, a.r2c2 + b.r2c2); + public static Matrix2x2 operator *(Matrix2x2 a, float b) => new(a.r1c1 * b, a.r2c1 * b, a.r1c2 * b, a.r2c2 * b); + public static Matrix2x2 operator *(Matrix2x2 a, Matrix2x2 b) => new(new[,] + { + { Float2.Dot(a.Row1, b.Column1), Float2.Dot(a.Row1, b.Column2) }, + { Float2.Dot(a.Row2, b.Column1), Float2.Dot(a.Row2, b.Column2) }, + }); + public static Matrix2x2 operator /(Matrix2x2 a, float b) => new(a.r1c1 / b, a.r2c1 / b, a.r1c2 / b, a.r2c2 / b); + public static Matrix2x2 operator /(Matrix2x2 a, Matrix2x2 b) => a * b.Inverse(); + public static Matrix2x2 operator ^(Matrix2x2 a, Matrix2x2 b) => // Single number multiplication. + new(a.r1c1 * b.r1c1, a.r2c1 * b.r2c1, a.r1c2 * b.r1c2, a.r2c2 * b.r2c2); + public static bool operator ==(Matrix2x2 a, Matrix2x2 b) => a.Equals(b); + public static bool operator !=(Matrix2x2 a, Matrix2x2 b) => !a.Equals(b); + + public static explicit operator Matrix2x2(Matrix m) + { + Matrix2x2 res = Zero, identity = Identity; + for (int r = 0; r < 2; r++) for (int c = 0; c < 2; c++) + res[c, r] = m.Size.x < c && m.Size.y < r ? m[r, c] : identity[r, c]; + return res; + } + public static explicit operator Matrix2x2(Matrix3x3 m) => new(m.r1c1, m.r2c1, m.r1c2, m.r2c2); + public static explicit operator Matrix2x2(Matrix4x4 m) => new(m.r1c1, m.r2c1, m.r1c2, m.r2c2); +} diff --git a/Nerd_STF/Mathematics/Algebra/Matrix3x3.cs b/Nerd_STF/Mathematics/Algebra/Matrix3x3.cs new file mode 100644 index 0000000..7e81f47 --- /dev/null +++ b/Nerd_STF/Mathematics/Algebra/Matrix3x3.cs @@ -0,0 +1,411 @@ +namespace Nerd_STF.Mathematics.Algebra; + +public struct Matrix3x3 : IMatrix +{ + public static Matrix3x3 Identity => new(new[,] + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }); + public static Matrix3x3 One => new(1); + public static Matrix3x3 SignGrid => new(new[,] + { + { +1, -1, +1 }, + { -1, +1, -1 }, + { +1, -1, +1 } + }); + public static Matrix3x3 Zero => new(0); + + public Float3 Column1 + { + get => new(r1c1, r2c1, r3c1); + set + { + r1c1 = value.x; + r2c1 = value.y; + r3c1 = value.z; + } + } + public Float3 Column2 + { + get => new(r1c2, r2c2, r3c2); + set + { + r1c2 = value.x; + r2c2 = value.y; + r3c2 = value.z; + } + } + public Float3 Column3 + { + get => new(r1c3, r2c3, r3c3); + set + { + r1c3 = value.x; + r2c3 = value.y; + r3c3 = value.z; + } + } + public Float3 Row1 + { + get => new(r1c1, r1c2, r1c3); + set + { + r1c1 = value.x; + r1c2 = value.y; + r1c3 = value.z; + } + } + public Float3 Row2 + { + get => new(r2c1, r2c2, r2c3); + set + { + r2c1 = value.x; + r2c2 = value.y; + r2c3 = value.z; + } + } + public Float3 Row3 + { + get => new(r3c1, r3c2, r3c3); + set + { + r3c1 = value.x; + r3c2 = value.y; + r3c3 = value.z; + } + } + + public float r1c1, r2c1, r3c1, r1c2, r2c2, r3c2, r1c3, r2c3, r3c3; + + public Matrix3x3(float all) : this(all, all, all, all, all, all, all, all, all) { } + public Matrix3x3(float r1c1, float r2c1, float r3c1, float r1c2, + float r2c2, float r3c2, float r1c3, float r2c3, float r3c3) + { + this.r1c1 = r1c1; + this.r2c1 = r2c1; + this.r3c1 = r3c1; + this.r1c2 = r1c2; + this.r2c2 = r2c2; + this.r3c2 = r3c2; + this.r1c3 = r1c3; + this.r2c3 = r2c3; + this.r3c3 = r3c3; + } + public Matrix3x3(float[] nums) : this(nums[0], nums[1], nums[2], + nums[3], nums[4], nums[5], nums[6], nums[7], nums[8]) { } + public Matrix3x3(int[] nums) : this(nums[0], nums[1], nums[2], + nums[3], nums[4], nums[5], nums[6], nums[7], nums[8]) { } + public Matrix3x3(Fill fill) : this(fill(0), fill(1), fill(2), + fill(3), fill(4), fill(5), fill(6), fill(7), fill(8)) { } + public Matrix3x3(Fill fill) : this(fill(0), fill(1), fill(2), + fill(3), fill(4), fill(5), fill(6), fill(7), fill(8)) { } + public Matrix3x3(float[,] nums) : this(nums[0, 0], nums[0, 1], nums[0, 2], + nums[1, 0], nums[1, 1], nums[1, 2], nums[2, 0], nums[2, 1], nums[2, 2]) { } + public Matrix3x3(int[,] nums) : this(nums[0, 0], nums[0, 1], nums[0, 2], + nums[1, 0], nums[1, 1], nums[1, 2], nums[2, 0], nums[2, 1], nums[2, 2]) { } + public Matrix3x3(Fill2D fill) : this(fill(0, 0), fill(0, 1), fill(0, 2), + fill(1, 0), fill(1, 1), fill(1, 2), fill(2, 0), fill(2, 1), fill(2, 2)) { } + public Matrix3x3(Fill2D fill) : this(fill(0, 0), fill(0, 1), fill(0, 2), + fill(1, 0), fill(1, 1), fill(1, 2), fill(2, 0), fill(2, 1), fill(2, 2)) { } + public Matrix3x3(Float3 c1, Float3 c2, Float3 c3) : this(c1.x, c1.y, c1.z, c2.x, c2.y, c2.z, c3.x, c3.y, c3.z) { } + public Matrix3x3(Fill fill) : this(fill(0), fill(1), fill(2)) { } + public Matrix3x3(Fill fill) : this((IEnumerable)fill(0), fill(1), fill(2)) { } + public Matrix3x3(IEnumerable c1, IEnumerable c2, IEnumerable c3) + : this(c1.ToFill(), c2.ToFill(), c3.ToFill()) { } + public Matrix3x3(IEnumerable c1, IEnumerable c2, IEnumerable c3) + : this(c1.ToFill(), c2.ToFill(), c3.ToFill()) { } + public Matrix3x3(Fill c1, Fill c2, Fill c3) + : this(c1(0), c1(1), c1(2), c2(0), c2(1), c2(2), c3(0), c3(1), c3(2)) { } + public Matrix3x3(Fill c1, Fill c2, Fill c3) + : this(c1(0), c1(1), c1(2), c2(0), c2(1), c2(2), c3(0), c3(1), c3(2)) { } + + public float this[int r, int c] + { + get => ToArray2D()[c, r]; + set + { + // Maybe this could be improved? + // It's definitely better than it was before. Trust me. + switch ("r" + (r + 1) + "c" + (c + 1)) + { + case "r1c1": + r1c1 = value; + break; + + case "r2c1": + r2c1 = value; + break; + + case "r3c1": + r3c1 = value; + break; + + case "r1c2": + r1c2 = value; + break; + + case "r2c2": + r2c2 = value; + break; + + case "r3c2": + r3c2 = value; + break; + + case "r1c3": + r1c3 = value; + break; + + case "r2c3": + r2c3 = value; + break; + + case "r3c3": + r3c3 = value; + break; + + default: + string @params = ""; + if (r < 0 || r > 2) @params += r; + if (c < 0 || c > 2) @params += string.IsNullOrEmpty(@params) ? c : " and " + c; + throw new IndexOutOfRangeException(@params); + } + } + } + public float this[Int2 index] + { + get => this[index.x, index.y]; + set => this[index.x, index.y] = value; + } + + public static Matrix3x3 Absolute(Matrix3x3 val) => + new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r3c1), + Mathf.Absolute(val.r1c2), Mathf.Absolute(val.r2c2), Mathf.Absolute(val.r3c2), + Mathf.Absolute(val.r1c3), Mathf.Absolute(val.r2c3), Mathf.Absolute(val.r3c3)); + public static Matrix3x3 Average(params Matrix3x3[] vals) => Sum(vals) / vals.Length; + public static Matrix3x3 Ceiling(Matrix3x3 val) => + new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r3c1), + Mathf.Ceiling(val.r1c2), Mathf.Ceiling(val.r2c2), Mathf.Ceiling(val.r3c2), + Mathf.Ceiling(val.r1c3), Mathf.Ceiling(val.r2c3), Mathf.Ceiling(val.r3c3)); + public static Matrix3x3 Clamp(Matrix3x3 val, Matrix3x3 min, Matrix3x3 max) => + new(Mathf.Clamp(val.r1c1, min.r1c1, max.r1c1), Mathf.Clamp(val.r2c1, min.r2c1, max.r2c1), + Mathf.Clamp(val.r3c1, min.r3c1, max.r3c1), Mathf.Clamp(val.r1c2, min.r1c2, max.r1c2), + Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2), Mathf.Clamp(val.r3c2, min.r3c2, max.r3c2), + Mathf.Clamp(val.r1c3, min.r1c3, max.r1c3), Mathf.Clamp(val.r2c3, min.r2c3, max.r2c3), + Mathf.Clamp(val.r3c3, min.r3c3, max.r3c3)); + public static Matrix3x3 Divide(Matrix3x3 num, params Matrix3x3[] vals) + { + foreach (Matrix3x3 m in vals) num /= m; + return num; + } + public static Matrix3x3 Floor(Matrix3x3 val) => + new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r2c1), Mathf.Floor(val.r3c1), + Mathf.Floor(val.r1c2), Mathf.Floor(val.r2c2), Mathf.Floor(val.r3c2), + Mathf.Floor(val.r1c3), Mathf.Floor(val.r2c3), Mathf.Floor(val.r3c3)); + public static Matrix3x3 Lerp(Matrix3x3 a, Matrix3x3 b, float t, bool clamp = true) => + new(Mathf.Lerp(a.r1c1, b.r1c1, t, clamp), Mathf.Lerp(a.r2c1, b.r2c1, t, clamp), + Mathf.Lerp(a.r3c1, b.r3c1, t, clamp), Mathf.Lerp(a.r1c2, b.r1c2, t, clamp), + Mathf.Lerp(a.r2c2, b.r2c2, t, clamp), Mathf.Lerp(a.r3c2, b.r3c2, t, clamp), + Mathf.Lerp(a.r1c3, b.r1c3, t, clamp), Mathf.Lerp(a.r2c3, b.r2c3, t, clamp), + Mathf.Lerp(a.r3c3, b.r3c3, t, clamp)); + public static Matrix3x3 Median(params Matrix3x3[] vals) + { + float index = Mathf.Average(0, vals.Length - 1); + Matrix3x3 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; + return Average(valA, valB); + } + public static Matrix3x3 Product(params Matrix3x3[] vals) + { + if (vals.Length < 1) return Zero; + Matrix3x3 val = Identity; + foreach (Matrix3x3 m in vals) val *= m; + return val; + } + public static Matrix3x3 Round(Matrix3x3 val) => + new(Mathf.Round(val.r1c1), Mathf.Round(val.r2c1), Mathf.Round(val.r3c1), + Mathf.Round(val.r1c2), Mathf.Round(val.r2c2), Mathf.Round(val.r3c2), + Mathf.Round(val.r1c3), Mathf.Round(val.r2c3), Mathf.Round(val.r3c3)); + public static Matrix3x3 Subtract(Matrix3x3 num, params Matrix3x3[] vals) + { + foreach (Matrix3x3 m in vals) num -= m; + return num; + } + public static Matrix3x3 Sum(params Matrix3x3[] vals) + { + Matrix3x3 val = Zero; + foreach (Matrix3x3 m in vals) val += m; + return val; + } + + public static (float[] r1c1s, float[] r2c1s, float[] r3c1s, float[] r1c2s, float[] r2c2s, + float[] r3c2s, float[] r1c3s, float[] r2c3s, float[] r3c3s) SplitArray(params Matrix3x3[] vals) + { + float[] r1c1s = new float[vals.Length], r2c1s = new float[vals.Length], r3c1s = new float[vals.Length], + r1c2s = new float[vals.Length], r2c2s = new float[vals.Length], r3c2s = new float[vals.Length], + r1c3s = new float[vals.Length], r2c3s = new float[vals.Length], r3c3s = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + r1c1s[i] = vals[i].r1c1; + r2c1s[i] = vals[i].r2c1; + r3c1s[i] = vals[i].r3c1; + r1c2s[i] = vals[i].r1c2; + r2c2s[i] = vals[i].r2c2; + r3c2s[i] = vals[i].r3c2; + r1c3s[i] = vals[i].r1c3; + r2c3s[i] = vals[i].r2c3; + r3c3s[i] = vals[i].r3c3; + } + return (r1c1s, r2c1s, r3c1s, r1c2s, r2c2s, r3c2s, r1c3s, r2c3s, r3c3s); + } + + public Matrix3x3 Adjugate() + { + Matrix3x3 dets = new(); + Matrix2x2[,] minors = Minors(); + for (int r = 0; r < 3; r++) for (int c = 0; c < 3; c++) dets[r, c] = minors[r, c].Determinant(); + return dets ^ SignGrid; + } + public float Determinant() + { + Matrix2x2[,] minors = Minors(); + return (r1c1 * minors[0, 0].Determinant()) - (r1c2 * minors[1, 0].Determinant()) + + (r1c3 * minors[2, 0].Determinant()); + } + public Matrix3x3 Inverse() + { + float d = Determinant(); + if (d == 0) throw new NoInverseException(); + return Transpose().Adjugate() / d; + } + public Matrix2x2[,] Minors() => new Matrix2x2[,] + { + { new(r2c2, r3c2, r2c3, r3c3), new(r2c1, r3c1, r2c3, r3c3), new(r2c1, r3c1, r2c2, r3c2) }, + { new(r1c2, r3c2, r1c3, r3c3), new(r1c1, r3c1, r1c3, r3c3), new(r1c1, r3c1, r1c2, r3c2) }, + { new(r1c2, r2c2, r1c3, r2c3), new(r1c1, r2c1, r1c3, r2c3), new(r1c1, r2c1, r1c2, r2c2) } + }; + public Matrix3x3 Transpose() => new(new[,] + { + { r1c1, r2c1, r3c1 }, + { r1c2, r2c2, r3c2 }, + { r1c3, r2c3, r3c3 } + }); + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Matrix3x3)) return base.Equals(obj); + return Equals((Matrix3x3)obj); + } + public bool Equals(Matrix3x3 other) => + r1c1 == other.r1c1 && r2c1 == other.r2c1 && r3c1 == other.r3c1 && + r1c2 == other.r1c2 && r2c2 == other.r2c2 && r3c2 == other.r3c2 && + r1c3 == other.r1c3 && r2c3 == other.r2c3 && r3c3 == other.r3c3; + public override int GetHashCode() => + r1c1.GetHashCode() ^ r2c1.GetHashCode() ^ r3c1.GetHashCode() ^ + r1c2.GetHashCode() ^ r2c2.GetHashCode() ^ r3c2.GetHashCode() ^ + r1c3.GetHashCode() ^ r2c3.GetHashCode() ^ r3c3.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(string? provider) => + r1c1.ToString(provider) + " " + r1c2.ToString(provider) + " " + r1c3.ToString(provider) + "\n" + + r2c1.ToString(provider) + " " + r2c2.ToString(provider) + " " + r2c3.ToString(provider) + "\n" + + r3c1.ToString(provider) + " " + r3c2.ToString(provider) + " " + r3c3.ToString(provider); + public string ToString(IFormatProvider provider) => + r1c1.ToString(provider) + " " + r1c2.ToString(provider) + " " + r1c3.ToString(provider) + "\n" + + r2c1.ToString(provider) + " " + r2c2.ToString(provider) + " " + r2c3.ToString(provider) + "\n" + + r3c1.ToString(provider) + " " + r3c2.ToString(provider) + " " + r3c3.ToString(provider); + + public object Clone() => new Matrix3x3(ToArray2D()); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return r1c1; + yield return r2c1; + yield return r3c1; + yield return r1c2; + yield return r2c2; + yield return r3c2; + yield return r1c3; + yield return r2c3; + yield return r3c3; + } + + public float[] ToArray() => new[] { r1c1, r2c1, r3c1, r1c2, r2c2, r3c2, r1c3, r2c3, r3c3 }; + public float[,] ToArray2D() => new[,] + { + { r1c1, r1c2, r1c3 }, + { r2c1, r2c2, r2c3 }, + { r3c1, r3c2, r3c3 } + }; + public Dictionary ToDictionary() + { + Dictionary dict = new(); + float[] arr = ToArray(); + for (int i = 0; i < arr.Length; i++) dict.Add(new(i % 3, i / 3), arr[i]); + return dict; + } + public Fill ToFill() => ToFillExtension.ToFill(this); + public Fill2D ToFill2D() + { + Matrix3x3 @this = this; + return (x, y) => @this[x, y]; + } + public List ToList() => new() { r1c1, r2c1, r3c1, r1c2, r2c2, r3c2, r1c3, r2c3, r3c3 }; + + public static Matrix3x3 operator +(Matrix3x3 a, Matrix3x3 b) => + new(a.r1c1 + b.r1c1, a.r2c1 + b.r2c1, a.r3c1 + b.r3c1, + a.r1c2 + b.r1c2, a.r2c2 + b.r2c2, a.r3c2 + b.r3c2, + a.r1c3 + b.r1c3, a.r2c3 + b.r2c3, a.r3c3 + b.r3c3); + public static Matrix3x3 operator -(Matrix3x3 m) => m.Inverse(); + public static Matrix3x3 operator -(Matrix3x3 a, Matrix3x3 b) => + new(a.r1c1 - b.r1c1, a.r2c1 - b.r2c1, a.r3c1 - b.r3c1, + a.r1c2 - b.r1c2, a.r2c2 - b.r2c2, a.r3c2 - b.r3c2, + a.r1c3 - b.r1c3, a.r2c3 - b.r2c3, a.r3c3 - b.r3c3); + public static Matrix3x3 operator *(Matrix3x3 a, float b) => + new(a.r1c1 * b, a.r2c1 * b, a.r3c1 * b, + a.r1c2 * b, a.r2c2 * b, a.r3c2 * b, + a.r1c3 * b, a.r2c3 * b, a.r3c3 * b); + public static Matrix3x3 operator *(Matrix3x3 a, Matrix3x3 b) => new(new[,] + { + { Float3.Dot(a.Row1, b.Column1), Float3.Dot(a.Row1, b.Column2), Float3.Dot(a.Row1, b.Column3) }, + { Float3.Dot(a.Row2, b.Column1), Float3.Dot(a.Row2, b.Column2), Float3.Dot(a.Row2, b.Column3) }, + { Float3.Dot(a.Row3, b.Column1), Float3.Dot(a.Row3, b.Column2), Float3.Dot(a.Row3, b.Column3) }, + }); + public static Matrix3x3 operator /(Matrix3x3 a, float b) => + new(a.r1c1 / b, a.r2c1 / b, a.r3c1 / b, + a.r1c2 / b, a.r2c2 / b, a.r3c2 / b, + a.r1c3 / b, a.r2c3 / b, a.r3c3 / b); + public static Matrix3x3 operator /(Matrix3x3 a, Matrix3x3 b) => a * b.Inverse(); + public static Matrix3x3 operator ^(Matrix3x3 a, Matrix3x3 b) => // Single number multiplication + new(a.r1c1 * b.r1c1, a.r2c1 * b.r2c1, a.r3c1 * b.r3c1, + a.r1c2 * b.r1c2, a.r2c2 * b.r2c2, a.r3c2 * b.r3c2, + a.r1c3 * b.r1c3, a.r2c3 * b.r2c3, a.r3c3 * b.r3c3); + public static bool operator ==(Matrix3x3 a, Matrix3x3 b) => a.Equals(b); + public static bool operator !=(Matrix3x3 a, Matrix3x3 b) => !a.Equals(b); + + public static explicit operator Matrix3x3(Matrix m) + { + Matrix3x3 res = Zero, identity = Identity; + for (int r = 0; r < 3; r++) for (int c = 0; c < 3; c++) + res[c, r] = m.Size.x < c && m.Size.y < r ? m[r, c] : identity[r, c]; + return res; + } + public static implicit operator Matrix3x3(Matrix2x2 m) + { + Matrix3x3 identity = Identity; + return new(new[,] + { + { m.r1c1, m.r1c2, identity.r1c3 }, + { m.r2c1, m.r2c2, identity.r2c3 }, + { identity.r3c1, identity.r3c2, identity.r3c3 } + }); + } + public static explicit operator Matrix3x3(Matrix4x4 m) => new(new[,] + { + { m.r1c1, m.r1c2, m.r1c3 }, + { m.r2c1, m.r2c2, m.r2c3 }, + { m.r3c1, m.r3c2, m.r3c3 } + }); +} diff --git a/Nerd_STF/Mathematics/Algebra/Matrix4x4.cs b/Nerd_STF/Mathematics/Algebra/Matrix4x4.cs new file mode 100644 index 0000000..6b0cd2c --- /dev/null +++ b/Nerd_STF/Mathematics/Algebra/Matrix4x4.cs @@ -0,0 +1,566 @@ +namespace Nerd_STF.Mathematics.Algebra; + +public struct Matrix4x4 : IMatrix +{ + public static Matrix4x4 Identity => new(new[,] + { + { 1, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 1 }, + }); + public static Matrix4x4 One => new(1); + public static Matrix4x4 SignGrid => new(new[,] + { + { +1, -1, +1, -1 }, + { -1, +1, -1, +1 }, + { +1, -1, +1, -1 }, + { -1, +1, -1, +1 } + }); + public static Matrix4x4 Zero => new(0); + + public Float4 Column1 + { + get => new(r1c1, r2c1, r3c1, r4c1); + set + { + r1c1 = value.x; + r2c1 = value.y; + r3c1 = value.z; + r4c1 = value.w; + } + } + public Float4 Column2 + { + get => new(r1c2, r2c2, r3c2, r4c2); + set + { + r1c2 = value.x; + r2c2 = value.y; + r3c2 = value.z; + r4c2 = value.w; + } + } + public Float4 Column3 + { + get => new(r1c3, r2c3, r3c3, r4c3); + set + { + r1c3 = value.x; + r2c3 = value.y; + r3c3 = value.z; + r4c3 = value.w; + } + } + public Float4 Column4 + { + get => new(r1c4, r2c4, r3c4, r4c4); + set + { + r1c4 = value.x; + r2c4 = value.y; + r3c4 = value.z; + r4c4 = value.w; + } + } + public Float4 Row1 + { + get => new(r1c1, r1c2, r1c3, r1c3); + set + { + r1c1 = value.x; + r1c2 = value.y; + r1c3 = value.z; + r1c4 = value.w; + } + } + public Float4 Row2 + { + get => new(r2c1, r2c2, r2c3, r2c3); + set + { + r2c1 = value.x; + r2c2 = value.y; + r2c3 = value.z; + r2c4 = value.w; + } + } + public Float4 Row3 + { + get => new(r3c1, r3c2, r3c3, r3c3); + set + { + r3c1 = value.x; + r3c2 = value.y; + r3c3 = value.z; + r3c4 = value.w; + } + } + public Float4 Row4 + { + get => new(r4c1, r4c2, r4c3, r4c3); + set + { + r4c1 = value.x; + r4c2 = value.y; + r4c3 = value.z; + r4c4 = value.w; + } + } + + public float r1c1, r2c1, r3c1, r4c1, r1c2, r2c2, r3c2, r4c2, r1c3, r2c3, r3c3, r4c3, r1c4, r2c4, r3c4, r4c4; + + public Matrix4x4(float all) : this(all, all, all, all, all, + all, all, all, all, all, all, all, all, all, all, all) { } + public Matrix4x4(float r1c1, float r2c1, float r3c1, float r4c1, float r1c2, float r2c2, float r3c2, + float r4c2, float r1c3, float r2c3, float r3c3, float r4c3, float r1c4, float r2c4, float r3c4, float r4c4) + { + this.r1c1 = r1c1; + this.r2c1 = r2c1; + this.r3c1 = r3c1; + this.r4c1 = r4c1; + this.r1c2 = r1c2; + this.r2c2 = r2c2; + this.r3c2 = r3c2; + this.r4c2 = r4c2; + this.r1c3 = r1c3; + this.r2c3 = r2c3; + this.r3c3 = r3c3; + this.r4c3 = r4c3; + this.r1c4 = r1c4; + this.r2c4 = r2c4; + this.r3c4 = r3c4; + this.r4c4 = r4c4; + } + public Matrix4x4(float[] nums) : this(nums[0], nums[1], nums[2], nums[3], nums[4], nums[5], nums[6], + nums[7], nums[8], nums[9], nums[10], nums[11], nums[12], nums[13], nums[14], nums[15]) { } + public Matrix4x4(int[] nums) : this(nums[0], nums[1], nums[2], nums[3], nums[4], nums[5], nums[6], + nums[7], nums[8], nums[9], nums[10], nums[11], nums[12], nums[13], nums[14], nums[15]) { } + public Matrix4x4(Fill fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4), fill(5), fill(6), + fill(7), fill(8), fill(9), fill(10), fill(11), fill(12), fill(13), fill(14), fill(15)) { } + public Matrix4x4(Fill fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4), fill(5), fill(6), + fill(7), fill(8), fill(9), fill(10), fill(11), fill(12), fill(13), fill(14), fill(15)) { } + public Matrix4x4(float[,] nums) : this(nums[0, 0], nums[0, 1], nums[0, 2], nums[0, 3], nums[1, 0], + nums[1, 1], nums[1, 2], nums[1, 3], nums[2, 0], nums[2, 1], nums[2, 2], nums[2, 3], nums[3, 0], + nums[3, 1], nums[3, 2], nums[3, 3]) { } + public Matrix4x4(int[,] nums) : this(nums[0, 0], nums[0, 1], nums[0, 2], nums[0, 3], nums[1, 0], + nums[1, 1], nums[1, 2], nums[1, 3], nums[2, 0], nums[2, 1], nums[2, 2], nums[2, 3], nums[3, 0], + nums[3, 1], nums[3, 2], nums[3, 3]) { } + public Matrix4x4(Fill2D fill) : this(fill(0, 0), fill(0, 1), fill(0, 2), fill(0, 3), fill(1, 0), + fill(1, 1), fill(1, 2), fill(1, 3), fill(2, 0), fill(2, 1), fill(2, 2), fill(2, 3), fill(3, 0), + fill(3, 1), fill(3, 2), fill(3, 3)) { } + public Matrix4x4(Fill2D fill) : this(fill(0, 0), fill(0, 1), fill(0, 2), fill(0, 3), fill(1, 0), + fill(1, 1), fill(1, 2), fill(1, 3), fill(2, 0), fill(2, 1), fill(2, 2), fill(2, 3), fill(3, 0), + fill(3, 1), fill(3, 2), fill(3, 3)) { } + public Matrix4x4(Float4 c1, Float4 c2, Float4 c3, Float4 c4) : this(c1.x, c1.y, c1.z, + c1.w, c2.x, c2.y, c2.z, c2.w, c3.x, c3.y, c3.z, c3.w, c4.x, c4.y, c4.z, c4.w) { } + public Matrix4x4(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + public Matrix4x4(Fill fill) : this((IEnumerable)fill(0), fill(1), fill(2), fill(3)) { } + public Matrix4x4(IEnumerable c1, IEnumerable c2, IEnumerable c3, IEnumerable c4) + : this(c1.ToFill(), c2.ToFill(), c3.ToFill(), c4.ToFill()) { } + public Matrix4x4(IEnumerable c1, IEnumerable c2, IEnumerable c3, IEnumerable c4) + : this(c1.ToFill(), c2.ToFill(), c3.ToFill(), c4.ToFill()) { } + public Matrix4x4(Fill c1, Fill c2, Fill c3, Fill c4) : this(c1(0), c1(1), + c1(2), c1(3), c2(0), c2(1), c2(2), c2(3), c3(0), c3(1), c3(2), c3(3), c4(0), c4(1), c4(2), c4(3)) { } + public Matrix4x4(Fill c1, Fill c2, Fill c3, Fill c4) : this(c1(0), c1(1), + c1(2), c1(3), c2(0), c2(1), c2(2), c2(3), c3(0), c3(1), c3(2), c3(3), c4(0), c4(1), c4(2), c4(3)) { } + + public float this[int r, int c] + { + get => ToArray2D()[c, r]; + set + { + // Maybe this could be improved? + // It's definitely better than it was before. Trust me. + switch ("r" + (r + 1) + "c" + (c + 1)) + { + case "r1c1": + r1c1 = value; + break; + + case "r2c1": + r2c1 = value; + break; + + case "r3c1": + r3c1 = value; + break; + + case "r4c1": + r4c1 = value; + break; + + case "r1c2": + r1c2 = value; + break; + + case "r2c2": + r2c2 = value; + break; + + case "r3c2": + r3c2 = value; + break; + + case "r4c2": + r4c2 = value; + break; + + case "r1c3": + r1c3 = value; + break; + + case "r2c3": + r2c3 = value; + break; + + case "r3c3": + r3c3 = value; + break; + + case "r4c3": + r4c3 = value; + break; + + case "r1c4": + r1c4 = value; + break; + + case "r2c4": + r2c4 = value; + break; + + case "r3c4": + r3c4 = value; + break; + + case "r4c4": + r4c4 = value; + break; + + default: + string @params = ""; + if (r < 0 || r > 2) @params += r; + if (c < 0 || c > 2) @params += string.IsNullOrEmpty(@params) ? c : " and " + c; + throw new IndexOutOfRangeException(@params); + } + } + } + public float this[Int2 index] + { + get => this[index.x, index.y]; + set => this[index.x, index.y] = value; + } + + public static Matrix4x4 Absolute(Matrix4x4 val) => + new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r3c1), Mathf.Absolute(val.r4c1), + Mathf.Absolute(val.r1c2), Mathf.Absolute(val.r2c2), Mathf.Absolute(val.r3c2), Mathf.Absolute(val.r4c2), + Mathf.Absolute(val.r1c3), Mathf.Absolute(val.r2c3), Mathf.Absolute(val.r3c3), Mathf.Absolute(val.r4c2), + Mathf.Absolute(val.r1c4), Mathf.Absolute(val.r2c4), Mathf.Absolute(val.r3c4), Mathf.Absolute(val.r4c4)); + public static Matrix4x4 Average(params Matrix4x4[] vals) => Sum(vals) / vals.Length; + public static Matrix4x4 Ceiling(Matrix4x4 val) => + new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r3c1), Mathf.Ceiling(val.r4c1), + Mathf.Ceiling(val.r1c2), Mathf.Ceiling(val.r2c2), Mathf.Ceiling(val.r3c2), Mathf.Ceiling(val.r4c2), + Mathf.Ceiling(val.r1c3), Mathf.Ceiling(val.r2c3), Mathf.Ceiling(val.r3c3), Mathf.Ceiling(val.r4c2), + Mathf.Ceiling(val.r1c4), Mathf.Ceiling(val.r2c4), Mathf.Ceiling(val.r3c4), Mathf.Ceiling(val.r4c4)); + public static Matrix4x4 Clamp(Matrix4x4 val, Matrix4x4 min, Matrix4x4 max) => + new(Mathf.Clamp(val.r1c1, min.r1c1, max.r1c1), Mathf.Clamp(val.r2c1, min.r2c1, max.r2c1), + Mathf.Clamp(val.r3c1, min.r3c1, max.r3c1), Mathf.Clamp(val.r4c1, min.r4c1, max.r4c1), + Mathf.Clamp(val.r1c2, min.r1c2, max.r1c2), Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2), + Mathf.Clamp(val.r3c2, min.r3c2, max.r3c2), Mathf.Clamp(val.r4c2, min.r4c2, max.r4c2), + Mathf.Clamp(val.r1c3, min.r1c3, max.r1c3), Mathf.Clamp(val.r2c3, min.r2c3, max.r2c3), + Mathf.Clamp(val.r3c3, min.r3c3, max.r3c3), Mathf.Clamp(val.r4c3, min.r4c3, max.r4c3), + Mathf.Clamp(val.r1c4, min.r1c4, max.r1c4), Mathf.Clamp(val.r2c4, min.r2c4, max.r2c4), + Mathf.Clamp(val.r3c4, min.r3c4, max.r3c4), Mathf.Clamp(val.r4c4, min.r4c4, max.r4c4)); + public static Matrix4x4 Divide(Matrix4x4 num, params Matrix4x4[] vals) + { + foreach (Matrix4x4 m in vals) num /= m; + return num; + } + public static Matrix4x4 Floor(Matrix4x4 val) => + new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r2c1), Mathf.Floor(val.r3c1), Mathf.Floor(val.r4c1), + Mathf.Floor(val.r1c2), Mathf.Floor(val.r2c2), Mathf.Floor(val.r3c2), Mathf.Floor(val.r4c2), + Mathf.Floor(val.r1c3), Mathf.Floor(val.r2c3), Mathf.Floor(val.r3c3), Mathf.Floor(val.r4c2), + Mathf.Floor(val.r1c4), Mathf.Floor(val.r2c4), Mathf.Floor(val.r3c4), Mathf.Floor(val.r4c4)); + public static Matrix4x4 Lerp(Matrix4x4 a, Matrix4x4 b, float t, bool clamp = true) => + new(Mathf.Lerp(a.r1c1, b.r1c1, t, clamp), Mathf.Lerp(a.r2c1, b.r2c1, t, clamp), + Mathf.Lerp(a.r3c1, b.r3c1, t, clamp), Mathf.Lerp(a.r4c1, b.r4c1, t, clamp), + Mathf.Lerp(a.r1c2, b.r1c2, t, clamp), Mathf.Lerp(a.r2c2, b.r2c2, t, clamp), + Mathf.Lerp(a.r3c2, b.r3c2, t, clamp), Mathf.Lerp(a.r4c2, b.r4c2, t, clamp), + Mathf.Lerp(a.r1c3, b.r1c3, t, clamp), Mathf.Lerp(a.r2c3, b.r2c3, t, clamp), + Mathf.Lerp(a.r3c3, b.r3c3, t, clamp), Mathf.Lerp(a.r4c3, b.r4c3, t, clamp), + Mathf.Lerp(a.r1c4, b.r1c4, t, clamp), Mathf.Lerp(a.r2c4, b.r2c4, t, clamp), + Mathf.Lerp(a.r3c4, b.r3c4, t, clamp), Mathf.Lerp(a.r4c4, b.r4c4, t, clamp)); + public static Matrix4x4 Median(params Matrix4x4[] vals) + { + float index = Mathf.Average(0, vals.Length - 1); + Matrix4x4 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; + return Average(valA, valB); + } + public static Matrix4x4 Product(params Matrix4x4[] vals) + { + if (vals.Length < 1) return Zero; + Matrix4x4 val = Identity; + foreach (Matrix4x4 m in vals) val *= m; + return val; + } + public static Matrix4x4 Round(Matrix4x4 val) => + new(Mathf.Round(val.r1c1), Mathf.Round(val.r2c1), Mathf.Round(val.r3c1), Mathf.Round(val.r4c1), + Mathf.Round(val.r1c2), Mathf.Round(val.r2c2), Mathf.Round(val.r3c2), Mathf.Round(val.r4c2), + Mathf.Round(val.r1c3), Mathf.Round(val.r2c3), Mathf.Round(val.r3c3), Mathf.Round(val.r4c2), + Mathf.Round(val.r1c4), Mathf.Round(val.r2c4), Mathf.Round(val.r3c4), Mathf.Round(val.r4c4)); + public static Matrix4x4 Subtract(Matrix4x4 num, params Matrix4x4[] vals) + { + foreach (Matrix4x4 m in vals) num -= m; + return num; + } + public static Matrix4x4 Sum(params Matrix4x4[] vals) + { + Matrix4x4 val = Zero; + foreach (Matrix4x4 m in vals) val += m; + return val; + } + + public static (float[] r1c1s, float[] r2c1s, float[] r3c1s, float[] r4c1s, float[] r1c2s, float[] r2c2s, + float[] r3c2s, float[] r4c2s, float[] r1c3s, float[] r2c3s, float[] r3c3s, float[] r4c3s, float[] r1c4s, + float[] r2c4s, float[] r3c4s, float[] r4c4s) SplitArray(params Matrix4x4[] vals) + { + float[] r1c1s = new float[vals.Length], r2c1s = new float[vals.Length], r3c1s = new float[vals.Length], + r4c1s = new float[vals.Length], r1c2s = new float[vals.Length], r2c2s = new float[vals.Length], + r3c2s = new float[vals.Length], r4c2s = new float[vals.Length], r1c3s = new float[vals.Length], + r2c3s = new float[vals.Length], r3c3s = new float[vals.Length], r4c3s = new float[vals.Length], + r1c4s = new float[vals.Length], r2c4s = new float[vals.Length], r3c4s = new float[vals.Length], + r4c4s = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + r1c1s[i] = vals[i].r1c1; + r2c1s[i] = vals[i].r2c1; + r3c1s[i] = vals[i].r3c1; + r4c1s[i] = vals[i].r4c1; + r1c2s[i] = vals[i].r1c2; + r2c2s[i] = vals[i].r2c2; + r3c2s[i] = vals[i].r3c2; + r4c2s[i] = vals[i].r4c2; + r1c3s[i] = vals[i].r1c3; + r2c3s[i] = vals[i].r2c3; + r3c3s[i] = vals[i].r3c3; + r4c3s[i] = vals[i].r4c3; + r4c4s[i] = vals[i].r4c4; + } + return (r1c1s, r2c1s, r3c1s, r4c1s, r1c2s, r2c2s, r3c2s, r4c2s, + r1c3s, r2c3s, r3c3s, r4c3s, r1c4s, r2c4s, r3c4s, r4c4s); + } + + public Matrix4x4 Adjugate() + { + Matrix4x4 dets = new(); + Matrix3x3[,] minors = Minors(); + for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) dets[r, c] = minors[r, c].Determinant(); + return dets ^ SignGrid; + } + public float Determinant() + { + Matrix3x3[,] minors = Minors(); + return (r1c1 * minors[0, 0].Determinant()) - (r1c2 * minors[1, 0].Determinant()) + + (r1c3 * minors[2, 0].Determinant()) - (r1c4 * minors[3, 0].Determinant()); + } + public Matrix4x4 Inverse() + { + float d = Determinant(); + if (d == 0) throw new NoInverseException(); + return Transpose().Adjugate() / d; + } + public Matrix3x3[,] Minors() => new Matrix3x3[,] + { + { + new(r2c2, r3c2, r4c2, r2c3, r3c3, r4c3, r2c4, r3c4, r4c4), + new(r2c1, r3c1, r4c1, r2c3, r3c3, r4c3, r2c4, r3c4, r4c4), + new(r2c1, r3c1, r4c1, r2c2, r3c2, r4c2, r2c4, r3c4, r4c4), + new(r2c1, r3c1, r4c1, r2c2, r3c2, r4c2, r2c3, r3c3, r4c3) + }, + { + new(r1c2, r3c2, r4c2, r1c3, r3c3, r4c3, r1c4, r3c4, r4c4), + new(r1c1, r3c1, r4c1, r1c3, r3c3, r4c3, r1c4, r3c4, r4c4), + new(r1c1, r3c1, r4c1, r1c2, r3c2, r4c2, r1c4, r3c4, r4c4), + new(r1c1, r3c1, r4c1, r1c2, r3c2, r4c2, r1c3, r3c3, r4c3) + }, + { + new(r1c2, r2c2, r4c2, r1c3, r2c3, r4c3, r1c4, r2c4, r4c4), + new(r1c1, r2c1, r4c1, r1c3, r2c3, r4c3, r1c4, r2c4, r4c4), + new(r1c1, r2c1, r4c1, r1c2, r2c2, r4c2, r1c4, r2c4, r4c4), + new(r1c1, r2c1, r4c1, r1c2, r2c2, r4c2, r1c3, r2c3, r4c3) + }, + { + new(r1c2, r2c2, r3c2, r1c3, r2c3, r3c3, r1c4, r2c4, r3c4), + new(r1c1, r2c1, r3c1, r1c3, r2c3, r3c3, r1c4, r2c4, r3c4), + new(r1c1, r2c1, r3c1, r1c2, r2c2, r3c2, r1c4, r2c4, r3c4), + new(r1c1, r2c1, r3c1, r1c2, r2c2, r3c2, r1c3, r2c3, r3c3) + } + }; + public Matrix4x4 Transpose() => new(new[,] + { + { r1c1, r2c1, r3c1, r4c1 }, + { r1c2, r2c2, r3c2, r4c2 }, + { r1c3, r2c3, r3c3, r4c3 }, + { r1c4, r2c4, r3c4, r4c4 } + }); + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Matrix4x4)) return base.Equals(obj); + return Equals((Matrix4x4)obj); + } + public bool Equals(Matrix4x4 other) => + r1c1 == other.r1c1 && r2c1 == other.r2c1 && r3c1 == other.r3c1 && r4c1 == other.r4c1 && + r1c2 == other.r1c2 && r2c2 == other.r2c2 && r3c2 == other.r3c2 && r4c2 == other.r4c2 && + r1c3 == other.r1c3 && r2c3 == other.r2c3 && r3c3 == other.r3c3 && r4c3 == other.r4c3 && + r1c4 == other.r1c4 && r2c3 == other.r2c4 && r3c4 == other.r3c4 && r4c4 == other.r4c4; + public override int GetHashCode() => + r1c1.GetHashCode() ^ r2c1.GetHashCode() ^ r3c1.GetHashCode() ^ r4c1.GetHashCode() ^ + r1c2.GetHashCode() ^ r2c2.GetHashCode() ^ r3c2.GetHashCode() ^ r4c2.GetHashCode() ^ + r1c3.GetHashCode() ^ r2c3.GetHashCode() ^ r3c3.GetHashCode() ^ r4c3.GetHashCode() ^ + r1c4.GetHashCode() ^ r2c4.GetHashCode() ^ r3c4.GetHashCode() ^ r4c4.GetHashCode(); + public string ToString(string? provider) => + r1c1.ToString(provider) + " " + r1c2.ToString(provider) + " " + r1c3.ToString(provider) + " " + + r1c4.ToString(provider) + "\n" + r2c1.ToString(provider) + " " + r2c2.ToString(provider) + " " + + r2c3.ToString(provider) + " " + r2c4.ToString(provider) + "\n" + r3c1.ToString(provider) + " " + + r3c2.ToString(provider) + " " + r3c3.ToString(provider) + " " + r3c4.ToString(provider) + "\n" + + r4c1.ToString(provider) + " " + r4c2.ToString(provider) + " " + r4c3.ToString(provider) + " " + + r4c4.ToString(provider); + public string ToString(IFormatProvider provider) => + r1c1.ToString(provider) + " " + r1c2.ToString(provider) + " " + r1c3.ToString(provider) + " " + + r1c4.ToString(provider) + "\n" + r2c1.ToString(provider) + " " + r2c2.ToString(provider) + " " + + r2c3.ToString(provider) + " " + r2c4.ToString(provider) + "\n" + r3c1.ToString(provider) + " " + + r3c2.ToString(provider) + " " + r3c3.ToString(provider) + " " + r3c4.ToString(provider) + "\n" + + r4c1.ToString(provider) + " " + r4c2.ToString(provider) + " " + r4c3.ToString(provider) + " " + + r4c4.ToString(provider); + + public object Clone() => new Matrix4x4(ToArray2D()); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return r1c1; + yield return r2c1; + yield return r3c1; + yield return r4c1; + yield return r1c2; + yield return r2c2; + yield return r3c2; + yield return r4c2; + yield return r1c3; + yield return r2c3; + yield return r3c3; + yield return r4c3; + yield return r1c4; + yield return r2c4; + yield return r3c4; + yield return r4c4; + } + + public float[] ToArray() => new[] + { + r1c1, r2c1, r3c1, r4c1, + r1c2, r2c2, r3c2, r4c2, + r1c3, r2c3, r3c3, r4c3, + r1c4, r2c4, r3c4, r4c4 + }; + public float[,] ToArray2D() => new[,] + { + { r1c1, r1c2, r1c3, r1c4 }, + { r2c1, r2c2, r2c3, r2c4 }, + { r3c1, r3c2, r3c3, r3c4 }, + { r4c1, r4c2, r4c3, r4c4 } + }; + public Dictionary ToDictionary() + { + Dictionary dict = new(); + float[] arr = ToArray(); + for (int i = 0; i < arr.Length; i++) dict.Add(new(i % 4, i / 4), arr[i]); + return dict; + } + public Fill ToFill() => ToFillExtension.ToFill(this); + public Fill2D ToFill2D() + { + Matrix4x4 @this = this; + return (x, y) => @this[x, y]; + } + public List ToList() => new() + { + r1c1, r2c1, r3c1, r4c1, + r1c2, r2c2, r3c2, r4c2, + r1c3, r2c3, r3c3, r4c3, + r1c4, r2c4, r3c4, r4c4 + }; + + public static Matrix4x4 operator +(Matrix4x4 a, Matrix4x4 b) => + new(a.r1c1 + b.r1c1, a.r2c1 + b.r2c1, a.r3c1 + b.r3c1, a.r4c1 + b.r4c1, + a.r1c2 + b.r1c2, a.r2c2 + b.r2c2, a.r3c2 + b.r3c2, a.r4c2 + b.r4c2, + a.r1c3 + b.r1c3, a.r2c3 + b.r2c3, a.r3c3 + b.r3c3, a.r4c3 + b.r4c3, + a.r1c4 + b.r1c4, a.r2c4 + b.r2c4, a.r3c4 + b.r3c4, a.r4c4 + b.r4c4); + public static Matrix4x4 operator -(Matrix4x4 m) => m.Inverse(); + public static Matrix4x4 operator -(Matrix4x4 a, Matrix4x4 b) => + new(a.r1c1 - b.r1c1, a.r2c1 - b.r2c1, a.r3c1 - b.r3c1, a.r4c1 - b.r4c1, + a.r1c2 - b.r1c2, a.r2c2 - b.r2c2, a.r3c2 - b.r3c2, a.r4c2 - b.r4c2, + a.r1c3 - b.r1c3, a.r2c3 - b.r2c3, a.r3c3 - b.r3c3, a.r4c3 - b.r4c3, + a.r1c4 - b.r1c4, a.r2c4 - b.r2c4, a.r3c4 - b.r3c4, a.r4c4 - b.r4c4); + public static Matrix4x4 operator *(Matrix4x4 a, float b) => + new(a.r1c1 * b, a.r2c1 * b, a.r3c1 * b, a.r4c1 * b, + a.r1c2 * b, a.r2c2 * b, a.r3c2 * b, a.r4c2 * b, + a.r1c3 * b, a.r2c3 * b, a.r3c3 * b, a.r4c3 * b, + a.r1c4 * b, a.r2c4 * b, a.r3c4 * b, a.r4c4 * b); + public static Matrix4x4 operator *(Matrix4x4 a, Matrix4x4 b) => new(new[,] + { + { Float4.Dot(a.Row1, b.Column1), Float4.Dot(a.Row1, b.Column2), + Float4.Dot(a.Row1, b.Column3), Float4.Dot(a.Row1, b.Column4) }, + { Float4.Dot(a.Row2, b.Column1), Float4.Dot(a.Row2, b.Column2), + Float4.Dot(a.Row2, b.Column3), Float4.Dot(a.Row2, b.Column4) }, + { Float4.Dot(a.Row3, b.Column1), Float4.Dot(a.Row3, b.Column2), + Float4.Dot(a.Row3, b.Column3), Float4.Dot(a.Row3, b.Column4) }, + { Float4.Dot(a.Row4, b.Column1), Float4.Dot(a.Row4, b.Column2), + Float4.Dot(a.Row4, b.Column3), Float4.Dot(a.Row4, b.Column4) } + }); + public static Matrix4x4 operator /(Matrix4x4 a, float b) => + new(a.r1c1 / b, a.r2c1 / b, a.r3c1 / b, a.r4c1 / b, + a.r1c2 / b, a.r2c2 / b, a.r3c2 / b, a.r4c2 / b, + a.r1c3 / b, a.r2c3 / b, a.r3c3 / b, a.r4c3 / b, + a.r1c4 / b, a.r2c4 / b, a.r3c4 / b, a.r4c4 / b); + public static Matrix4x4 operator /(Matrix4x4 a, Matrix4x4 b) => a * b.Inverse(); + public static Matrix4x4 operator ^(Matrix4x4 a, Matrix4x4 b) => // Single number multiplication + new(a.r1c1 * b.r1c1, a.r2c1 * b.r2c1, a.r3c1 * b.r3c1, a.r4c1 * b.r4c1, + a.r1c2 * b.r1c2, a.r2c2 * b.r2c2, a.r3c2 * b.r3c2, a.r4c2 * b.r4c2, + a.r1c3 * b.r1c3, a.r2c3 * b.r2c3, a.r3c3 * b.r3c3, a.r4c3 * b.r4c3, + a.r1c4 * b.r1c4, a.r2c4 * b.r2c4, a.r3c4 * b.r3c4, a.r4c4 * b.r4c4); + public static bool operator ==(Matrix4x4 a, Matrix4x4 b) => a.Equals(b); + public static bool operator !=(Matrix4x4 a, Matrix4x4 b) => !a.Equals(b); + + public static explicit operator Matrix4x4(Matrix m) + { + Matrix4x4 res = Zero, identity = Identity; + for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) + res[c, r] = m.Size.x < c && m.Size.y < r ? m[r, c] : identity[r, c]; + return res; + } + public static implicit operator Matrix4x4(Matrix2x2 m) + { + Matrix4x4 identity = Identity; + return new(new[,] + { + { m.r1c1, m.r1c2, identity.r1c3, identity.r1c4 }, + { m.r2c1, m.r2c2, identity.r2c3, identity.r2c4 }, + { identity.r3c1, identity.r3c2, identity.r3c3, identity.r3c4 }, + { identity.r4c1, identity.r4c2, identity.r4c3, identity.r4c4 } + }); + } + public static implicit operator Matrix4x4(Matrix3x3 m) + { + Matrix4x4 identity = Identity; + return new(new[,] + { + { m.r1c1, m.r1c2, m.r1c3, identity.r1c4 }, + { m.r2c1, m.r2c2, m.r2c3, identity.r2c4 }, + { m.r3c1, m.r3c2, m.r3c3, identity.r3c4 }, + { identity.r4c1, identity.r4c2, identity.r4c3, identity.r4c4 } + }); + } +} diff --git a/Nerd_STF/Mathematics/Algebra/Vector2d.cs b/Nerd_STF/Mathematics/Algebra/Vector2d.cs new file mode 100644 index 0000000..630959b --- /dev/null +++ b/Nerd_STF/Mathematics/Algebra/Vector2d.cs @@ -0,0 +1,158 @@ +namespace Nerd_STF.Mathematics.Algebra; + +public struct Vector2d : ICloneable, IComparable, IEquatable +{ + public static Vector2d Down => new(Angle.Down); + public static Vector2d Left => new(Angle.Left); + public static Vector2d Right => new(Angle.Right); + public static Vector2d Up => new(Angle.Up); + + public static Vector2d One => new(Angle.Zero); + public static Vector2d Zero => new(Angle.Zero, 0); + + public Vector2d Inverse => new(-theta, magnitude); + public Vector2d Normalized => new(theta, 1); + + public Angle theta; + public float magnitude; + + public Vector2d(Angle theta, float mag = 1) + { + this.theta = theta; + magnitude = mag; + } + public Vector2d(float theta, Angle.Type rotType, float mag = 1) : this(new(theta, rotType), mag) { } + + public static Vector2d Absolute(Vector2d val) => new(Angle.Absolute(val.theta), Mathf.Absolute(val.magnitude)); + public static Vector2d Average(params Vector2d[] vals) + { + (Angle[] thetas, float[] Mags) = SplitArray(vals); + return new(Angle.Average(thetas), Mathf.Average(Mags)); + } + public static Vector2d Ceiling(Vector2d val, Angle.Type angleRound = Angle.Type.Degrees) => + new(Angle.Ceiling(val.theta, angleRound), Mathf.Ceiling(val.magnitude)); + public static Vector2d ClampMagnitude(Vector2d val, float minMag, float maxMag) + { + if (maxMag < minMag) throw new ArgumentOutOfRangeException(nameof(maxMag), + nameof(maxMag) + " must be greater than or equal to " + nameof(minMag)); + float mag = Mathf.Clamp(val.magnitude, minMag, maxMag); + return new(val.theta, mag); + } + public static Vector3d Cross(Vector2d a, Vector2d b, bool normalized = false) => + Float2.Cross(a.ToXYZ(), b.ToXYZ(), normalized).ToVector(); + public static Vector2d Divide(Vector2d num, params Vector2d[] vals) + { + foreach (Vector2d v in vals) num /= v; + return num; + } + public static float Dot(Vector2d a, Vector2d b) => Float2.Dot(a.ToXYZ(), b.ToXYZ()); + public static float Dot(params Vector2d[] vals) + { + Float2[] floats = new Float2[vals.Length]; + for (int i = 0; i < vals.Length; i++) floats[i] = vals[i].ToXYZ(); + return Float2.Dot(floats); + } + public static Vector2d Floor(Vector2d val, Angle.Type angleRound = Angle.Type.Degrees) => + new(Angle.Floor(val.theta, angleRound), Mathf.Floor(val.magnitude)); + public static Vector2d Lerp(Vector2d a, Vector2d b, float t, bool clamp = true) => + new(Angle.Lerp(a.theta, b.theta, t, clamp), Mathf.Lerp(a.magnitude, b.magnitude, t, clamp)); + public static Vector2d Median(params Vector2d[] vals) + { + float index = Mathf.Average(0, vals.Length - 1); + Vector2d valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; + return Average(valA, valB); + } + public static Vector2d Max(params Vector2d[] vals) + { + if (vals.Length < 1) return Zero; + Vector2d val = vals[0]; + foreach (Vector2d f in vals) val = f > val ? f : val; + return val; + } + public static Vector2d Min(params Vector2d[] vals) + { + if (vals.Length < 1) return Zero; + Vector2d val = vals[0]; + foreach (Vector2d f in vals) val = f < val ? f : val; + return val; + } + public static Vector2d Product(params Vector2d[] vals) + { + if (vals.Length < 1) return Zero; + Vector2d val = One; + foreach (Vector2d v in vals) val *= v; + return val; + } + public static Vector2d Round(Vector2d val, Angle.Type angleRound = Angle.Type.Degrees) => + new(Angle.Round(val.theta, angleRound), Mathf.Round(val.magnitude)); + public static Vector2d Subtract(Vector2d num, params Vector2d[] vals) + { + foreach (Vector2d v in vals) num -= v; + return num; + } + public static Vector2d Sum(params Vector2d[] vals) + { + if (vals.Length < 1) return Zero; + Vector2d val = One; + foreach (Vector2d v in vals) val += v; + return val; + } + + public static (Angle[] Rots, float[] Mags) SplitArray(params Vector2d[] vals) + { + Angle[] rots = new Angle[vals.Length]; + float[] mags = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + rots[i] = vals[i].theta; + mags[i] = vals[i].magnitude; + } + return (rots, mags); + } + + public int CompareTo(Vector2d other) => magnitude.CompareTo(other.magnitude); + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Vector2d)) return base.Equals(obj); + return Equals((Vector2d)obj); + } + public bool Equals(Vector2d other) => theta == other.theta && magnitude == other.magnitude; + public override int GetHashCode() => theta.GetHashCode() ^ magnitude.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(Angle.Type outputType = Angle.Type.Degrees) => ToString((string?)null, outputType); + public string ToString(string? provider, Angle.Type outputType = Angle.Type.Degrees) => + "Mag: " + magnitude.ToString(provider) + " Rot: " + theta.ToString(provider, outputType); + public string ToString(IFormatProvider provider, Angle.Type outputType = Angle.Type.Degrees) => + "Mag: " + magnitude.ToString(provider) + " Rot: " + theta.ToString(provider, outputType); + + public object Clone() => new Vector2d(theta, magnitude); + + public Float2 ToXYZ() => new(Mathf.Cos(theta)); + + public static Vector2d operator +(Vector2d a, Vector2d b) => new(a.theta + b.theta, a.magnitude + b.magnitude); + public static Vector2d operator -(Vector2d v) => v.Inverse; + public static Vector2d operator -(Vector2d a, Vector2d b) => new(a.theta - b.theta, a.magnitude - b.magnitude); + public static Vector2d operator *(Vector2d a, Angle b) => new(a.theta * b, a.magnitude); + public static Vector2d operator *(Vector2d a, float b) => new(a.theta, a.magnitude * b); + public static Vector2d operator *(Vector2d a, Vector2d b) => new(a.theta * b.theta, a.magnitude * b.magnitude); + public static Vector2d operator *(Vector2d a, Matrix b) => (Vector2d)((Matrix)a * b); + public static Vector2d operator /(Vector2d a, Angle b) => new(a.theta / b, a.magnitude); + public static Vector2d operator /(Vector2d a, float b) => new(a.theta, a.magnitude / b); + public static Vector2d operator /(Vector2d a, Vector2d b) => new(a.theta / b.theta, a.magnitude / b.magnitude); + public static Vector2d operator /(Vector2d a, Matrix b) => (Vector2d)((Matrix)a / b); + public static bool operator ==(Vector2d a, Vector2d b) => a.Equals(b); + public static bool operator !=(Vector2d a, Vector2d b) => !a.Equals(b); + public static bool operator >(Vector2d a, Vector2d b) => a.CompareTo(b) > 0; + public static bool operator <(Vector2d a, Vector2d b) => a.CompareTo(b) < 0; + public static bool operator >=(Vector2d a, Vector2d b) => a == b || a > b; + public static bool operator <=(Vector2d a, Vector2d b) => a == b || a < b; + + public static explicit operator Vector2d(Complex val) => val.ToVector(); + public static explicit operator Vector2d(Float2 val) => val.ToVector(); + public static explicit operator Vector2d(Float3 val) => (Vector2d)val.ToVector(); + public static explicit operator Vector2d(Int2 val) => val.ToVector(); + public static explicit operator Vector2d(Int3 val) => (Vector2d)val.ToVector(); + public static explicit operator Vector2d(Matrix m) => ((Float2)m).ToVector(); + public static explicit operator Vector2d(Vert val) => (Vector2d)val.ToVector(); + public static explicit operator Vector2d(Vector3d val) => new(val.yaw, val.magnitude); +} diff --git a/Nerd_STF/Mathematics/Algebra/Vector3d.cs b/Nerd_STF/Mathematics/Algebra/Vector3d.cs new file mode 100644 index 0000000..6a5393a --- /dev/null +++ b/Nerd_STF/Mathematics/Algebra/Vector3d.cs @@ -0,0 +1,200 @@ +namespace Nerd_STF.Mathematics.Algebra; + +public struct Vector3d : ICloneable, IComparable, IEquatable +{ + public static Vector3d Back => new(Angle.Zero, Angle.Up); + public static Vector3d Down => new(Angle.Down, Angle.Zero); + public static Vector3d Forward => new(Angle.Zero, Angle.Down); + public static Vector3d Left => new(Angle.Left, Angle.Zero); + public static Vector3d Right => new(Angle.Right, Angle.Zero); + public static Vector3d Up => new(Angle.Up, Angle.Zero); + + public static Vector3d One => new(Angle.Zero); + public static Vector3d Zero => new(Angle.Zero, 0); + + public Vector3d Inverse => new(-yaw, -pitch, magnitude); + public Vector3d Normalized => new(yaw, pitch, 1); + + public Angle yaw, pitch; + public float magnitude; + + public Vector3d(Angle allRot, float mag = 1) : this(allRot, allRot, mag) { } + public Vector3d(float allRot, Angle.Type rotType, float mag = 1) : this(allRot, allRot, rotType, mag) { } + public Vector3d(Angle yaw, Angle pitch, float mag = 1) + { + this.yaw = yaw; + this.pitch = pitch; + magnitude = mag; + } + public Vector3d(float yaw, float pitch, Angle.Type rotType, float mag = 1) + : this(new Angle(yaw, rotType), new(pitch, rotType), mag) { } + public Vector3d(Float2 rots, Angle.Type rotType, float mag = 1) : this(rots.x, rots.y, rotType, mag) { } + public Vector3d(Fill fill, float mag = 1) : this(fill(0), fill(1), mag) { } + public Vector3d(Fill fill, Angle.Type rotType, float mag = 1) : this(fill(0), fill(1), rotType, mag) { } + + public Angle this[int index] + { + get => index switch + { + 0 => yaw, + 1 => pitch, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + set + { + switch (index) + { + case 0: + yaw = value; + break; + + case 1: + pitch = value; + break; + + default: throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public static Vector3d Absolute(Vector3d val) => new(Angle.Absolute(val.yaw), Angle.Absolute(val.pitch), + Mathf.Absolute(val.magnitude)); + public static Vector3d Average(params Vector3d[] vals) + { + (Angle[] Thetas, Angle[] Phis, float[] Mags) = SplitArray(vals); + return new(Angle.Average(Thetas), Angle.Average(Phis), Mathf.Average(Mags)); + } + public static Vector3d Ceiling(Vector3d val, Angle.Type angleRound = Angle.Type.Degrees) => + new(Angle.Ceiling(val.yaw, angleRound), Angle.Ceiling(val.pitch, angleRound), + Mathf.Ceiling(val.magnitude)); + public static Vector3d ClampMagnitude(Vector3d val, float minMag, float maxMag) + { + if (maxMag < minMag) throw new ArgumentOutOfRangeException(nameof(maxMag), + nameof(maxMag) + " must be greater than or equal to " + nameof(minMag)); + float mag = Mathf.Clamp(val.magnitude, minMag, maxMag); + return new(val.yaw, val.pitch, mag); + } + public static Vector3d Cross(Vector3d a, Vector3d b, bool normalized = false) => + Float3.Cross(a.ToXYZ(), b.ToXYZ(), normalized).ToVector(); + public static Vector3d Divide(Vector3d num, params Vector3d[] vals) + { + foreach (Vector3d v in vals) num /= v; + return num; + } + public static float Dot(Vector3d a, Vector3d b) => Float3.Dot(a.ToXYZ(), b.ToXYZ()); + public static float Dot(params Vector3d[] vals) + { + Float3[] floats = new Float3[vals.Length]; + for (int i = 0; i < vals.Length; i++) floats[i] = vals[i].ToXYZ(); + return Float3.Dot(floats); + } + public static Vector3d Floor(Vector3d val, Angle.Type angleRound = Angle.Type.Degrees) => + new(Angle.Floor(val.yaw, angleRound), Angle.Floor(val.pitch, angleRound), Mathf.Floor(val.magnitude)); + public static Vector3d Lerp(Vector3d a, Vector3d b, float t, bool clamp = true) => + new(Angle.Lerp(a.yaw, b.yaw, t, clamp), Angle.Lerp(a.pitch, b.pitch, t, clamp), + Mathf.Lerp(a.magnitude, b.magnitude, t, clamp)); + public static Vector3d Median(params Vector3d[] vals) + { + float index = Mathf.Average(0, vals.Length - 1); + Vector3d valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; + return Average(valA, valB); + } + public static Vector3d Max(params Vector3d[] vals) + { + if (vals.Length < 1) return Zero; + Vector3d val = vals[0]; + foreach (Vector3d f in vals) val = f > val ? f : val; + return val; + } + public static Vector3d Min(params Vector3d[] vals) + { + if (vals.Length < 1) return Zero; + Vector3d val = vals[0]; + foreach (Vector3d f in vals) val = f < val ? f : val; + return val; + } + public static Vector3d Product(params Vector3d[] vals) + { + if (vals.Length < 1) return Zero; + Vector3d val = One; + foreach (Vector3d v in vals) val *= v; + return val; + } + public static Vector3d Round(Vector3d val, Angle.Type angleRound = Angle.Type.Degrees) => + new(Angle.Round(val.yaw, angleRound), Angle.Round(val.pitch, angleRound), Mathf.Round(val.magnitude)); + public static Vector3d Subtract(Vector3d num, params Vector3d[] vals) + { + foreach (Vector3d v in vals) num -= v; + return num; + } + public static Vector3d Sum(params Vector3d[] vals) + { + if (vals.Length < 1) return Zero; + Vector3d val = One; + foreach (Vector3d v in vals) val += v; + return val; + } + + public static (Angle[] Thetas, Angle[] Phis, float[] Mags) SplitArray(params Vector3d[] vals) + { + Angle[] yaws = new Angle[vals.Length], pitchs = new Angle[vals.Length]; + float[] mags = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + yaws[i] = vals[i].yaw; + pitchs[i] = vals[i].pitch; + mags[i] = vals[i].magnitude; + } + return (yaws, pitchs, mags); + } + + public int CompareTo(Vector3d other) => magnitude.CompareTo(other.magnitude); + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Vector3d)) return base.Equals(obj); + return Equals((Vector3d)obj); + } + public bool Equals(Vector3d other) => yaw == other.yaw && pitch == other.pitch + && magnitude == other.magnitude; + public override int GetHashCode() => yaw.GetHashCode() ^ pitch.GetHashCode() ^ magnitude.GetHashCode(); + public string ToString(string? provider, Angle.Type outputType = Angle.Type.Degrees) => + "Mag: " + magnitude.ToString(provider) + " Theta: " + yaw.ToString(provider, outputType) + + " Phi: " + pitch.ToString(provider, outputType); + + public object Clone() => new Vector3d(yaw, pitch, magnitude); + + public Float3 ToXYZ() => new(Mathf.Sin(pitch) * Mathf.Cos(yaw) * magnitude, + Mathf.Sin(pitch) * Mathf.Sin(yaw) * magnitude, + Mathf.Cos(pitch) * magnitude); + + public static Vector3d operator +(Vector3d a, Vector3d b) => new(a.yaw + b.yaw, a.pitch + b.pitch, + a.magnitude + b.magnitude); + public static Vector3d operator -(Vector3d v) => v.Inverse; + public static Vector3d operator -(Vector3d a, Vector3d b) => new(a.yaw - b.yaw, a.pitch - b.pitch, + a.magnitude - b.magnitude); + public static Vector3d operator *(Vector3d a, Angle b) => new(a.yaw * b, a.pitch * b, a.magnitude); + public static Vector3d operator *(Vector3d a, float b) => new(a.yaw, a.pitch, a.magnitude * b); + public static Vector3d operator *(Vector3d a, Vector3d b) => new(a.yaw * b.yaw, a.pitch * b.pitch, + a.magnitude * b.magnitude); + public static Vector3d operator *(Vector3d a, Matrix b) => (Vector3d)((Matrix)a * b); + public static Vector3d operator /(Vector3d a, Angle b) => new(a.yaw / b, a.pitch / b, a.magnitude); + public static Vector3d operator /(Vector3d a, float b) => new(a.yaw, a.pitch, a.magnitude / b); + public static Vector3d operator /(Vector3d a, Vector3d b) => new(a.yaw / b.yaw, a.pitch / b.pitch, + a.magnitude / b.magnitude); + public static Vector3d operator /(Vector3d a, Matrix b) => (Vector3d)((Matrix)a / b); + public static bool operator ==(Vector3d a, Vector3d b) => a.Equals(b); + public static bool operator !=(Vector3d a, Vector3d b) => !a.Equals(b); + public static bool operator >(Vector3d a, Vector3d b) => a.CompareTo(b) > 0; + public static bool operator <(Vector3d a, Vector3d b) => a.CompareTo(b) < 0; + public static bool operator >=(Vector3d a, Vector3d b) => a == b || a > b; + public static bool operator <=(Vector3d a, Vector3d b) => a == b || a < b; + + public static explicit operator Vector3d(Complex val) => val.ToVector(); + public static explicit operator Vector3d(Float2 val) => val.ToVector(); + public static explicit operator Vector3d(Float3 val) => val.ToVector(); + public static explicit operator Vector3d(Int2 val) => val.ToVector(); + public static explicit operator Vector3d(Int3 val) => val.ToVector(); + public static explicit operator Vector3d(Matrix m) => ((Float3)m).ToVector(); + public static explicit operator Vector3d(Vert val) => val.ToVector(); + public static implicit operator Vector3d(Vector2d v) => new(v.theta, Angle.Zero, v.magnitude); +} diff --git a/Nerd_STF/Mathematics/Angle.cs b/Nerd_STF/Mathematics/Angle.cs index e0e83fa..9636ca7 100644 --- a/Nerd_STF/Mathematics/Angle.cs +++ b/Nerd_STF/Mathematics/Angle.cs @@ -2,6 +2,11 @@ public struct Angle : ICloneable, IComparable, IEquatable { + public static Angle Down => new(270); + public static Angle Left => new(180); + public static Angle Right => new(0); + public static Angle Up => new(90); + public static Angle Full => new(360); public static Angle Half => new(180); public static Angle One => new(1); @@ -47,14 +52,18 @@ public struct Angle : ICloneable, IComparable, IEquatable public static Angle Absolute(Angle val) => new(Mathf.Absolute(val.p_deg)); public static Angle Average(params Angle[] vals) => new(Mathf.Average(SplitArray(Type.Degrees, vals))); - public static Angle Ceiling(Angle val, Type type = Type.Degrees) => new(Mathf.Ceiling(val.ValueFromType(type))); + public static Angle Ceiling(Angle val, Type type = Type.Degrees) => + new(Mathf.Ceiling(val.ValueFromType(type)), type); public static Angle Clamp(Angle val, Angle min, Angle max) => new(Mathf.Clamp(val.p_deg, min.p_deg, max.p_deg)); - public static Angle Floor(Angle val, Type type = Type.Degrees) => new(Mathf.Floor(val.ValueFromType(type))); + public static Angle Floor(Angle val, Type type = Type.Degrees) => + new(Mathf.Floor(val.ValueFromType(type)), type); public static Angle Lerp(Angle a, Angle b, float t, bool clamp = true) => new(Mathf.Lerp(a.p_deg, b.p_deg, t, clamp)); public static Angle Max(params Angle[] vals) => new(Mathf.Max(SplitArray(Type.Degrees, vals))); public static Angle Median(params Angle[] vals) => new(Mathf.Median(SplitArray(Type.Degrees, vals))); public static Angle Min(params Angle[] vals) => new(Mathf.Min(SplitArray(Type.Degrees, vals))); + public static Angle Round(Angle val, Type type = Type.Degrees) => + new(Mathf.Floor(val.ValueFromType(type)), type); public static float[] SplitArray(Type outputType, params Angle[] vals) { @@ -76,7 +85,7 @@ public struct Angle : ICloneable, IComparable, IEquatable public int CompareTo(Angle other) => p_deg.CompareTo(other.p_deg); public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Angle)) return false; + if (obj == null || obj.GetType() != typeof(Angle)) return base.Equals(obj); return Equals((Angle)obj); } public bool Equals(Angle other) => p_deg == other.p_deg; diff --git a/Nerd_STF/Mathematics/Calculus.cs b/Nerd_STF/Mathematics/Calculus.cs index a117c75..a39cc1b 100644 --- a/Nerd_STF/Mathematics/Calculus.cs +++ b/Nerd_STF/Mathematics/Calculus.cs @@ -18,11 +18,11 @@ public static class Calculus (equ(x + DefaultStep) - equ(x)) / step; public static float GetIntegral(Equation equ, float lowerBound, float upperBound, float step = DefaultStep) - { - float val = 0; - for (float x = lowerBound; x <= upperBound; x += step) val += equ(x) * step; - return val; - } + { + float val = 0; + for (float x = lowerBound; x <= upperBound; x += step) val += equ(x) * step; + return val; + } public static float GradientDescent(Equation equ, float initial, float rate, float stepCount = 1000, float step = DefaultStep) diff --git a/Nerd_STF/Mathematics/Float2.cs b/Nerd_STF/Mathematics/Float2.cs index 8c52472..274ca9b 100644 --- a/Nerd_STF/Mathematics/Float2.cs +++ b/Nerd_STF/Mathematics/Float2.cs @@ -11,7 +11,7 @@ public struct Float2 : ICloneable, IComparable, IEquatable, IGro public static Float2 Zero => new(0, 0); public float Magnitude => Mathf.Sqrt(x * x + y * y); - public Float2 Normalized => this / Magnitude; + public Float2 Normalized => this * Mathf.InverseSqrt(x * x + y * y); public float x, y; @@ -72,7 +72,7 @@ public struct Float2 : ICloneable, IComparable, IEquatable, IGro Float3.Cross(a, b, normalized); public static Float2 Divide(Float2 num, params Float2[] vals) { - foreach (Float2 d in vals) num /= d; + foreach (Float2 f in vals) num /= f; return num; } public static float Dot(Float2 a, Float2 b) => a.x * b.x + a.y * b.y; @@ -80,10 +80,10 @@ public struct Float2 : ICloneable, IComparable, IEquatable, IGro { if (vals.Length < 1) return 0; float x = 1, y = 1; - foreach (Float2 d in vals) + foreach (Float2 f in vals) { - x *= d.x; - y *= d.y; + x *= f.x; + y *= f.y; } return x + y; } @@ -101,32 +101,34 @@ public struct Float2 : ICloneable, IComparable, IEquatable, IGro { if (vals.Length < 1) return Zero; Float2 val = vals[0]; - foreach (Float2 d in vals) val = d > val ? d : val; + foreach (Float2 f in vals) val = f > val ? f : val; return val; } public static Float2 Min(params Float2[] vals) { if (vals.Length < 1) return Zero; Float2 val = vals[0]; - foreach (Float2 d in vals) val = d < val ? d : val; + foreach (Float2 f in vals) val = f < val ? f : val; return val; } public static Float2 Product(params Float2[] vals) { if (vals.Length < 1) return Zero; Float2 val = One; - foreach (Float2 d in vals) val *= d; + foreach (Float2 f in vals) val *= f; return val; } + public static Float2 Round(Float2 val) => + new(Mathf.Round(val.x), Mathf.Round(val.y)); public static Float2 Subtract(Float2 num, params Float2[] vals) { - foreach (Float2 d in vals) num -= d; + foreach (Float2 f in vals) num -= f; return num; } public static Float2 Sum(params Float2[] vals) { Float2 val = Zero; - foreach (Float2 d in vals) val += d; + foreach (Float2 f in vals) val += f; return val; } @@ -144,7 +146,7 @@ public struct Float2 : ICloneable, IComparable, IEquatable, IGro public int CompareTo(Float2 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Float2)) return false; + if (obj == null || obj.GetType() != typeof(Float2)) return base.Equals(obj); return Equals((Float2)obj); } public bool Equals(Float2 other) => x == other.x && y == other.y; @@ -165,15 +167,24 @@ public struct Float2 : ICloneable, IComparable, IEquatable, IGro } public float[] ToArray() => new[] { x, y }; + public Fill ToFill() + { + Float2 @this = this; + return i => @this[i]; + } public List ToList() => new() { x, y }; + public Vector2d ToVector() => new(new(Mathf.ArcTan(y / x), Angle.Type.Radians), Magnitude); + public static Float2 operator +(Float2 a, Float2 b) => new(a.x + b.x, a.y + b.y); public static Float2 operator -(Float2 d) => new(-d.x, -d.y); public static Float2 operator -(Float2 a, Float2 b) => new(a.x - b.x, a.y - b.y); public static Float2 operator *(Float2 a, Float2 b) => new(a.x * b.x, a.y * b.y); public static Float2 operator *(Float2 a, float b) => new(a.x * b, a.y * b); + public static Float2 operator *(Float2 a, Matrix b) => (Float2)((Matrix)a * b); public static Float2 operator /(Float2 a, Float2 b) => new(a.x / b.x, a.y / b.y); public static Float2 operator /(Float2 a, float b) => new(a.x / b, a.y / b); + public static Float2 operator /(Float2 a, Matrix b) => (Float2)((Matrix)a / b); public static bool operator ==(Float2 a, Float2 b) => a.Equals(b); public static bool operator !=(Float2 a, Float2 b) => !a.Equals(b); public static bool operator >(Float2 a, Float2 b) => a.CompareTo(b) > 0; @@ -181,11 +192,15 @@ public struct Float2 : ICloneable, IComparable, IEquatable, IGro public static bool operator >=(Float2 a, Float2 b) => a == b || a > b; public static bool operator <=(Float2 a, Float2 b) => a == b || a < b; + public static implicit operator Float2(Complex val) => new(val.u, val.i); + public static explicit operator Float2(Quaternion val) => new(val.u, val.i); public static explicit operator Float2(Float3 val) => new(val.x, val.y); public static explicit operator Float2(Float4 val) => new(val.x, val.y); public static implicit operator Float2(Int2 val) => new(val.x, val.y); public static explicit operator Float2(Int3 val) => new(val.x, val.y); public static explicit operator Float2(Int4 val) => new(val.x, val.y); + public static explicit operator Float2(Matrix m) => new(m[0, 0], m[1, 0]); + public static explicit operator Float2(Vector2d val) => val.ToXYZ(); public static explicit operator Float2(Vert val) => new(val.position.x, val.position.y); public static implicit operator Float2(Fill fill) => new(fill); public static implicit operator Float2(Fill fill) => new(fill); diff --git a/Nerd_STF/Mathematics/Float3.cs b/Nerd_STF/Mathematics/Float3.cs index 4067b79..08dd48d 100644 --- a/Nerd_STF/Mathematics/Float3.cs +++ b/Nerd_STF/Mathematics/Float3.cs @@ -13,7 +13,7 @@ public struct Float3 : ICloneable, IComparable, IEquatable, IGro public static Float3 Zero => new(0, 0, 0); public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z); - public Float3 Normalized => this / Magnitude; + public Float3 Normalized => this * Mathf.InverseSqrt(x * x + y * y + z * z); public Float2 XY => new(x, y); public Float2 XZ => new(x, z); @@ -138,6 +138,8 @@ public struct Float3 : ICloneable, IComparable, IEquatable, IGro foreach (Float3 d in vals) val *= d; return val; } + public static Float3 Round(Float3 val) => + new(Mathf.Round(val.x), Mathf.Round(val.y), Mathf.Round(val.z)); public static Float3 Subtract(Float3 num, params Float3[] vals) { foreach (Float3 d in vals) num -= d; @@ -165,7 +167,7 @@ public struct Float3 : ICloneable, IComparable, IEquatable, IGro public int CompareTo(Float3 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Float3)) return false; + if (obj == null || obj.GetType() != typeof(Float3)) return base.Equals(obj); return Equals((Float3)obj); } public bool Equals(Float3 other) => x == other.x && y == other.y && z == other.z; @@ -187,15 +189,28 @@ public struct Float3 : ICloneable, IComparable, IEquatable, IGro } public float[] ToArray() => new[] { x, y, z }; + public Fill ToFill() + { + Float3 @this = this; + return i => @this[i]; + } public List ToList() => new() { x, y, z }; + public Vector3d ToVector() + { + float mag = Magnitude; + return new(new Angle(Mathf.ArcTan(y / x), Angle.Type.Radians), new(Mathf.ArcCos(z / mag), Angle.Type.Radians), mag); + } + public static Float3 operator +(Float3 a, Float3 b) => new(a.x + b.x, a.y + b.y, a.z + b.z); public static Float3 operator -(Float3 d) => new(-d.x, -d.y, -d.z); public static Float3 operator -(Float3 a, Float3 b) => new(a.x - b.x, a.y - b.y, a.z - b.z); public static Float3 operator *(Float3 a, Float3 b) => new(a.x * b.x, a.y * b.y, a.z * b.z); public static Float3 operator *(Float3 a, float b) => new(a.x * b, a.y * b, a.z * b); + public static Float3 operator *(Float3 a, Matrix b) => (Float3)((Matrix)a * b); public static Float3 operator /(Float3 a, Float3 b) => new(a.x / b.x, a.y / b.y, a.z / b.z); public static Float3 operator /(Float3 a, float b) => new(a.x / b, a.y / b, a.z / b); + public static Float3 operator /(Float3 a, Matrix b) => (Float3)((Matrix)a / b); public static bool operator ==(Float3 a, Float3 b) => a.Equals(b); public static bool operator !=(Float3 a, Float3 b) => !a.Equals(b); public static bool operator >(Float3 a, Float3 b) => a.CompareTo(b) > 0; @@ -203,11 +218,15 @@ public struct Float3 : ICloneable, IComparable, IEquatable, IGro public static bool operator >=(Float3 a, Float3 b) => a == b || a > b; public static bool operator <=(Float3 a, Float3 b) => a == b || a < b; + public static implicit operator Float3(Complex val) => new(val.u, val.i, 0); + public static explicit operator Float3(Quaternion val) => new(val.u, val.i, val.j); public static implicit operator Float3(Float2 val) => new(val.x, val.y, 0); public static explicit operator Float3(Float4 val) => new(val.x, val.y, val.z); public static implicit operator Float3(Int2 val) => new(val.x, val.y, 0); public static implicit operator Float3(Int3 val) => new(val.x, val.y, val.z); public static explicit operator Float3(Int4 val) => new(val.x, val.y, val.z); + public static explicit operator Float3(Matrix m) => new(m[0, 0], m[1, 0], m[2, 0]); + public static explicit operator Float3(Vector2d val) => val.ToXYZ(); public static implicit operator Float3(Vert val) => new(val.position.x, val.position.y, val.position.z); public static explicit operator Float3(RGBA val) => new(val.R, val.G, val.B); public static explicit operator Float3(HSVA val) => new(val.H.Normalized, val.S, val.V); diff --git a/Nerd_STF/Mathematics/Float4.cs b/Nerd_STF/Mathematics/Float4.cs index 22c471f..7c80274 100644 --- a/Nerd_STF/Mathematics/Float4.cs +++ b/Nerd_STF/Mathematics/Float4.cs @@ -3,11 +3,11 @@ public struct Float4 : ICloneable, IComparable, IEquatable, IGroup { public static Float4 Back => new(0, 0, -1, 0); - public static Float4 Deep => new(0, 0, 0, -1); public static Float4 Down => new(0, -1, 0, 0); public static Float4 Far => new(0, 0, 0, 1); public static Float4 Forward => new(0, 0, 1, 0); public static Float4 Left => new(-1, 0, 0, 0); + public static Float4 Near => new(0, 0, 0, -1); public static Float4 Right => new(1, 0, 0, 0); public static Float4 Up => new(0, 1, 0, 0); @@ -15,7 +15,7 @@ public struct Float4 : ICloneable, IComparable, IEquatable, IGro public static Float4 Zero => new(0, 0, 0, 0); public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z + w * w); - public Float4 Normalized => this / Magnitude; + public Float4 Normalized => this * Mathf.InverseSqrt(x * x + y * y + z * z + w * w); public Float2 XY => new(x, y); public Float2 XZ => new(x, z); @@ -144,6 +144,8 @@ public struct Float4 : ICloneable, IComparable, IEquatable, IGro foreach (Float4 d in vals) val = d < val ? d : val; return val; } + public static Float4 Round(Float4 val) => + new(Mathf.Round(val.x), Mathf.Round(val.y), Mathf.Round(val.z), Mathf.Round(val.w)); public static Float4 Product(params Float4[] vals) { if (vals.Length < 1) return Zero; @@ -180,7 +182,7 @@ public struct Float4 : ICloneable, IComparable, IEquatable, IGro public int CompareTo(Float4 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Float4)) return false; + if (obj == null || obj.GetType() != typeof(Float4)) return base.Equals(obj); return Equals((Float4)obj); } public bool Equals(Float4 other) => x == other.x && y == other.y && z == other.z && w == other.w; @@ -205,6 +207,11 @@ public struct Float4 : ICloneable, IComparable, IEquatable, IGro } public float[] ToArray() => new[] { x, y, z, w }; + public Fill ToFill() + { + Float4 @this = this; + return i => @this[i]; + } public List ToList() => new() { x, y, z, w }; public static Float4 operator +(Float4 a, Float4 b) => new(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); @@ -212,8 +219,10 @@ public struct Float4 : ICloneable, IComparable, IEquatable, IGro public static Float4 operator -(Float4 a, Float4 b) => new(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); public static Float4 operator *(Float4 a, Float4 b) => new(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); public static Float4 operator *(Float4 a, float b) => new(a.x * b, a.y * b, a.z * b, a.w * b); + public static Float4 operator *(Float4 a, Matrix b) => (Float4)((Matrix)a * b); public static Float4 operator /(Float4 a, Float4 b) => new(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); public static Float4 operator /(Float4 a, float b) => new(a.x / b, a.y / b, a.z / b, a.w / b); + public static Float4 operator /(Float4 a, Matrix b) => (Float4)((Matrix)a / b); public static bool operator ==(Float4 a, Float4 b) => a.Equals(b); public static bool operator !=(Float4 a, Float4 b) => !a.Equals(b); public static bool operator >(Float4 a, Float4 b) => a.CompareTo(b) > 0; @@ -221,11 +230,15 @@ public struct Float4 : ICloneable, IComparable, IEquatable, IGro public static bool operator >=(Float4 a, Float4 b) => a == b || a > b; public static bool operator <=(Float4 a, Float4 b) => a == b || a < b; + public static implicit operator Float4(Complex val) => new(val.u, val.i, 0, 0); + public static implicit operator Float4(Quaternion val) => new(val.u, val.i, val.j, val.k); public static implicit operator Float4(Float2 val) => new(val.x, val.y, 0, 0); public static implicit operator Float4(Float3 val) => new(val.x, val.y, val.z, 0); public static implicit operator Float4(Int2 val) => new(val.x, val.y, 0, 0); public static implicit operator Float4(Int3 val) => new(val.x, val.y, val.z, 0); public static implicit operator Float4(Int4 val) => new(val.x, val.y, val.z, val.w); + public static explicit operator Float4(Matrix m) => new(m[0, 0], m[1, 0], m[2, 0], m[3, 0]); + public static explicit operator Float4(Vector2d val) => val.ToXYZ(); public static implicit operator Float4(Vert val) => new(val.position.x, val.position.y, val.position.z, 0); public static implicit operator Float4(RGBA val) => new(val.R, val.G, val.B, val.A); public static explicit operator Float4(CMYKA val) => new(val.C, val.M, val.Y, val.K); diff --git a/Nerd_STF/Mathematics/Geometry/Box2D.cs b/Nerd_STF/Mathematics/Geometry/Box2D.cs index 0ba2df7..b938c2f 100644 --- a/Nerd_STF/Mathematics/Geometry/Box2D.cs +++ b/Nerd_STF/Mathematics/Geometry/Box2D.cs @@ -86,7 +86,7 @@ public struct Box2D : ICloneable, IContainer, IEquatable public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Box2D)) return false; + if (obj == null || obj.GetType() != typeof(Box2D)) return base.Equals(obj); return Equals((Box2D)obj); } public bool Equals(Box2D other) => center == other.center && size == other.size; diff --git a/Nerd_STF/Mathematics/Geometry/Box3D.cs b/Nerd_STF/Mathematics/Geometry/Box3D.cs index 62feb64..7930bff 100644 --- a/Nerd_STF/Mathematics/Geometry/Box3D.cs +++ b/Nerd_STF/Mathematics/Geometry/Box3D.cs @@ -87,7 +87,7 @@ public struct Box3D : ICloneable, IContainer, IEquatable public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Box3D)) return false; + if (obj == null || obj.GetType() != typeof(Box3D)) return base.Equals(obj); return Equals((Box3D)obj); } public bool Equals(Box3D other) => center == other.center && size == other.size; diff --git a/Nerd_STF/Mathematics/Geometry/Line.cs b/Nerd_STF/Mathematics/Geometry/Line.cs index f117a7e..b76242e 100644 --- a/Nerd_STF/Mathematics/Geometry/Line.cs +++ b/Nerd_STF/Mathematics/Geometry/Line.cs @@ -98,7 +98,7 @@ public struct Line : ICloneable, IClosest, IComparable, IContainer a == other.a && b == other.b; @@ -161,12 +161,17 @@ public struct Line : ICloneable, IClosest, IComparable, IContainer new Vert[] { a, b }; + public Fill ToFill() + { + Line @this = this; + return i => @this[i]; + } public List ToList() => new() { a, b }; public float[] ToFloatArray() => new float[] { a.position.x, a.position.y, a.position.z, - b.position.x, b.position.y, b.position.z }; + b.position.x, b.position.y, b.position.z }; public List ToFloatList() => new() { a.position.x, a.position.y, a.position.z, - b.position.x, b.position.y, b.position.z }; + b.position.x, b.position.y, b.position.z }; public static Line operator +(Line a, Line b) => new(a.a + b.a, a.b + b.b); public static Line operator +(Line a, Vert b) => new(a.a + b, a.b + b); diff --git a/Nerd_STF/Mathematics/Geometry/Polygon.cs b/Nerd_STF/Mathematics/Geometry/Polygon.cs index 290f8bc..006a9f1 100644 --- a/Nerd_STF/Mathematics/Geometry/Polygon.cs +++ b/Nerd_STF/Mathematics/Geometry/Polygon.cs @@ -256,7 +256,7 @@ public struct Polygon : ICloneable, IEquatable, IGroup, ISubdivid public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Polygon)) return false; + if (obj == null || obj.GetType() != typeof(Polygon)) return base.Equals(obj); return Equals((Polygon)obj); } public bool Equals(Polygon other) @@ -285,6 +285,11 @@ public struct Polygon : ICloneable, IEquatable, IGroup, ISubdivid public IEnumerator GetEnumerator() { foreach (Vert v in Verts) yield return v; } public Vert[] ToArray() => Verts; + public Fill ToFill() + { + Polygon @this = this; + return i => @this[i]; + } public List ToList() => new(Verts); public float[] ToFloatArray() diff --git a/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs b/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs index 8ee6be2..ebf43b4 100644 --- a/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs +++ b/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs @@ -266,7 +266,7 @@ public struct Quadrilateral : ICloneable, IEquatable, IGroup A == other.A && B == other.B && C == other.C && D == other.D; @@ -289,6 +289,11 @@ public struct Quadrilateral : ICloneable, IEquatable, IGroup new Vert[] { A, B, C, D }; + public Fill ToFill() + { + Quadrilateral @this = this; + return i => @this[i]; + } public List ToList() => new() { A, B, C, D }; public float[] ToFloatArray() => new float[] { A.position.x, A.position.y, A.position.z, diff --git a/Nerd_STF/Mathematics/Geometry/Sphere.cs b/Nerd_STF/Mathematics/Geometry/Sphere.cs index 39668ff..850542e 100644 --- a/Nerd_STF/Mathematics/Geometry/Sphere.cs +++ b/Nerd_STF/Mathematics/Geometry/Sphere.cs @@ -69,11 +69,11 @@ public struct Sphere : ICloneable, IClosest, IComparable, ICompara public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null) return false; + if (obj == null) return base.Equals(obj); Type type = obj.GetType(); if (type == typeof(Sphere)) return Equals((Sphere)obj); if (type == typeof(float)) return Equals((float)obj); - return false; + return base.Equals(obj); } public bool Equals(float other) => Volume == other; public bool Equals(Sphere other) => center == other.center && radius == other.radius; diff --git a/Nerd_STF/Mathematics/Geometry/Triangle.cs b/Nerd_STF/Mathematics/Geometry/Triangle.cs index 39da3a1..b9464c5 100644 --- a/Nerd_STF/Mathematics/Geometry/Triangle.cs +++ b/Nerd_STF/Mathematics/Geometry/Triangle.cs @@ -218,7 +218,7 @@ public struct Triangle : ICloneable, IEquatable, IGroup public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Triangle)) return false; + if (obj == null || obj.GetType() != typeof(Triangle)) return base.Equals(obj); return Equals((Triangle)obj); } public bool Equals(Triangle other) => A == other.A && B == other.B && C == other.C; @@ -240,14 +240,19 @@ public struct Triangle : ICloneable, IEquatable, IGroup } public Vert[] ToArray() => new Vert[] { A, B, C }; + public Fill ToFill() + { + Triangle @this = this; + return i => @this[i]; + } public List ToList() => new() { A, B, C }; public float[] ToFloatArray() => new float[] { A.position.x, A.position.y, A.position.z, - B.position.x, B.position.y, B.position.z, - C.position.x, C.position.y, C.position.z }; + B.position.x, B.position.y, B.position.z, + C.position.x, C.position.y, C.position.z }; public List ToFloatList() => new() { A.position.x, A.position.y, A.position.z, - B.position.x, B.position.y, B.position.z, - C.position.x, C.position.y, C.position.z }; + B.position.x, B.position.y, B.position.z, + C.position.x, C.position.y, C.position.z }; public static Triangle operator +(Triangle a, Triangle b) => new(a.A + b.A, a.B + b.B, a.C + b.C); public static Triangle operator +(Triangle a, Vert b) => new(a.A + b, a.B + b, a.C + b); public static Triangle operator -(Triangle t) => new(-t.A, -t.B, -t.C); diff --git a/Nerd_STF/Mathematics/Geometry/Vert.cs b/Nerd_STF/Mathematics/Geometry/Vert.cs index 1c40344..6609ff7 100644 --- a/Nerd_STF/Mathematics/Geometry/Vert.cs +++ b/Nerd_STF/Mathematics/Geometry/Vert.cs @@ -17,7 +17,7 @@ public struct Vert : ICloneable, IEquatable, IGroup public Float3 position; - public Vert(Float2 pos) : this(pos.x, pos.y, 0) { } + public Vert(Float2 pos) : this((Float3)pos) { } public Vert(Float3 pos) => position = pos; public Vert(float x, float y) : this(new Float2(x, y)) { } public Vert(float x, float y, float z) : this(new Float3(x, y, z)) { } @@ -61,7 +61,7 @@ public struct Vert : ICloneable, IEquatable, IGroup public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Vert)) return false; + if (obj == null || obj.GetType() != typeof(Vert)) return base.Equals(obj); return Equals((Vert)obj); } public bool Equals(Vert other) => position == other.position; @@ -76,8 +76,15 @@ public struct Vert : ICloneable, IEquatable, IGroup public IEnumerator GetEnumerator() => position.GetEnumerator(); public float[] ToArray() => position.ToArray(); + public Fill ToFill() + { + Vert @this = this; + return i => @this[i]; + } public List ToList() => position.ToList(); + public Vector3d ToVector() => ((Float3)this).ToVector(); + public static Vert operator +(Vert a, Vert b) => new(a.position + b.position); public static Vert operator -(Vert d) => new(-d.position); public static Vert operator -(Vert a, Vert b) => new(a.position - b.position); diff --git a/Nerd_STF/Mathematics/Int2.cs b/Nerd_STF/Mathematics/Int2.cs index 9841fa6..ce62c13 100644 --- a/Nerd_STF/Mathematics/Int2.cs +++ b/Nerd_STF/Mathematics/Int2.cs @@ -11,7 +11,7 @@ public struct Int2 : ICloneable, IComparable, IEquatable, IGroup new(0, 0); public float Magnitude => Mathf.Sqrt(x * x + y * y); - public Int2 Normalized => (Int2)((Float2)this / Magnitude); + public Int2 Normalized => (Int2)((Float2)this * Mathf.InverseSqrt(x * x + y * y)); public int x, y; @@ -139,7 +139,7 @@ public struct Int2 : ICloneable, IComparable, IEquatable, IGroup Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Int2)) return false; + if (obj == null || obj.GetType() != typeof(Int2)) return base.Equals(obj); return Equals((Int2)obj); } public bool Equals(Int2 other) => x == other.x && y == other.y; @@ -160,15 +160,24 @@ public struct Int2 : ICloneable, IComparable, IEquatable, IGroup new[] { x, y }; + public Fill ToFill() + { + Int2 @this = this; + return i => @this[i]; + } public List ToList() => new() { x, y }; + public Vector2d ToVector() => ((Float2)this).ToVector(); + public static Int2 operator +(Int2 a, Int2 b) => new(a.x + b.x, a.y + b.y); public static Int2 operator -(Int2 i) => new(-i.x, -i.y); public static Int2 operator -(Int2 a, Int2 b) => new(a.x - b.x, a.y - b.y); public static Int2 operator *(Int2 a, Int2 b) => new(a.x * b.x, a.y * b.y); public static Int2 operator *(Int2 a, int b) => new(a.x * b, a.y * b); + public static Int2 operator *(Int2 a, Matrix b) => (Int2)((Matrix)(Float2)a * b); public static Int2 operator /(Int2 a, Int2 b) => new(a.x / b.x, a.y / b.y); public static Int2 operator /(Int2 a, int b) => new(a.x / b, a.y / b); + public static Int2 operator /(Int2 a, Matrix b) => (Int2)((Matrix)(Float2)a / b); public static Int2 operator &(Int2 a, Int2 b) => new(a.x & b.x, a.y & b.y); public static Int2 operator |(Int2 a, Int2 b) => new(a.x | b.x, a.y | b.y); public static Int2 operator ^(Int2 a, Int2 b) => new(a.x ^ b.x, a.y ^ b.y); @@ -179,9 +188,13 @@ public struct Int2 : ICloneable, IComparable, IEquatable, IGroup=(Int2 a, Int2 b) => a == b || a > b; public static bool operator <=(Int2 a, Int2 b) => a == b || a < b; + public static explicit operator Int2(Complex val) => new((int)val.u, (int)val.i); + public static explicit operator Int2(Quaternion val) => new((int)val.u, (int)val.i); public static explicit operator Int2(Float2 val) => new((int)val.x, (int)val.y); public static explicit operator Int2(Float3 val) => new((int)val.x, (int)val.y); public static explicit operator Int2(Float4 val) => new((int)val.x, (int)val.y); + public static explicit operator Int2(Matrix m) => new((int)m[0, 0], (int)m[1, 0]); + public static explicit operator Int2(Vector2d val) => (Int2)val.ToXYZ(); public static explicit operator Int2(Int3 val) => new(val.x, val.y); public static explicit operator Int2(Int4 val) => new(val.x, val.y); public static explicit operator Int2(Vert val) => new((int)val.position.x, (int)val.position.y); diff --git a/Nerd_STF/Mathematics/Int3.cs b/Nerd_STF/Mathematics/Int3.cs index 0567940..e023215 100644 --- a/Nerd_STF/Mathematics/Int3.cs +++ b/Nerd_STF/Mathematics/Int3.cs @@ -13,7 +13,7 @@ public struct Int3 : ICloneable, IComparable, IEquatable, IGroup new(0, 0, 0); public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z); - public Int3 Normalized => (Int3)((Float3)this / Magnitude); + public Int3 Normalized => (Int3)((Float3)this * Mathf.InverseSqrt(x * x + y * y + z * z)); public Int2 XY => new(x, y); public Int2 XZ => new(x, z); @@ -159,7 +159,7 @@ public struct Int3 : ICloneable, IComparable, IEquatable, IGroup Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Int3)) return false; + if (obj == null || obj.GetType() != typeof(Int3)) return base.Equals(obj); return Equals((Int3)obj); } public bool Equals(Int3 other) => x == other.x && y == other.y && z == other.z; @@ -179,15 +179,24 @@ public struct Int3 : ICloneable, IComparable, IEquatable, IGroup new[] { x, y, z }; + public Fill ToFill() + { + Int3 @this = this; + return i => @this[i]; + } public List ToList() => new() { x, y, z }; + public Vector3d ToVector() => ((Float3)this).ToVector(); + public static Int3 operator +(Int3 a, Int3 b) => new(a.x + b.x, a.y + b.y, a.z + b.z); public static Int3 operator -(Int3 i) => new(-i.x, -i.y, -i.z); public static Int3 operator -(Int3 a, Int3 b) => new(a.x - b.x, a.y - b.y, a.z - b.z); public static Int3 operator *(Int3 a, Int3 b) => new(a.x * b.x, a.y * b.y, a.z * b.z); public static Int3 operator *(Int3 a, int b) => new(a.x * b, a.y * b, a.z * b); + public static Int3 operator *(Int3 a, Matrix b) => (Int3)((Matrix)(Float3)a * b); public static Int3 operator /(Int3 a, Int3 b) => new(a.x / b.x, a.y / b.y, a.z / b.z); public static Int3 operator /(Int3 a, int b) => new(a.x / b, a.y / b, a.z / b); + public static Int3 operator /(Int3 a, Matrix b) => (Int3)((Matrix)(Float3)a / b); public static Int3 operator &(Int3 a, Int3 b) => new(a.x & b.x, a.y & b.y, a.z & b.z); public static Int3 operator |(Int3 a, Int3 b) => new(a.x | b.x, a.y | b.y, a.z | b.z); public static Int3 operator ^(Int3 a, Int3 b) => new(a.x ^ b.x, a.y ^ b.y, a.z ^ b.z); @@ -198,11 +207,15 @@ public struct Int3 : ICloneable, IComparable, IEquatable, IGroup=(Int3 a, Int3 b) => a == b || a > b; public static bool operator <=(Int3 a, Int3 b) => a == b || a < b; + public static explicit operator Int3(Complex val) => new((int)val.u, (int)val.i, 0); + public static explicit operator Int3(Quaternion val) => new((int)val.u, (int)val.i, (int)val.j); public static explicit operator Int3(Float2 val) => new((int)val.x, (int)val.y, 0); public static explicit operator Int3(Float3 val) => new((int)val.x, (int)val.y, (int)val.z); public static explicit operator Int3(Float4 val) => new((int)val.x, (int)val.y, (int)val.z); public static implicit operator Int3(Int2 val) => new(val.x, val.y, 0); public static explicit operator Int3(Int4 val) => new(val.x, val.y, val.z); + public static explicit operator Int3(Matrix m) => new((int)m[0, 0], (int)m[1, 0], (int)m[2, 0]); + public static explicit operator Int3(Vector2d val) => (Int3)val.ToXYZ(); public static explicit operator Int3(Vert val) => new((int)val.position.x, (int)val.position.y, (int)val.position.z); public static explicit operator Int3(RGBA val) => (Int3)val.ToRGBAByte(); diff --git a/Nerd_STF/Mathematics/Int4.cs b/Nerd_STF/Mathematics/Int4.cs index 2157e4f..2007bbc 100644 --- a/Nerd_STF/Mathematics/Int4.cs +++ b/Nerd_STF/Mathematics/Int4.cs @@ -15,7 +15,7 @@ public struct Int4 : ICloneable, IComparable, IEquatable, IGroup new(0, 0, 0, 0); public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z + w * w); - public Int4 Normalized => (Int4)((Float4)this / Magnitude); + public Int4 Normalized => (Int4)((Float4)this * Mathf.InverseSqrt(x * x + y * y + z * z + w * w)); public Int2 XY => new(x, y); public Int2 XZ => new(x, z); @@ -175,7 +175,7 @@ public struct Int4 : ICloneable, IComparable, IEquatable, IGroup Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || obj.GetType() != typeof(Int4)) return false; + if (obj == null || obj.GetType() != typeof(Int4)) return base.Equals(obj); return Equals((Int4)obj); } public bool Equals(Int4 other) => x == other.x && y == other.y && z == other.z && w == other.w; @@ -200,6 +200,11 @@ public struct Int4 : ICloneable, IComparable, IEquatable, IGroup new[] { x, y, z, w }; + public Fill ToFill() + { + Int4 @this = this; + return i => @this[i]; + } public List ToList() => new() { x, y, z, w }; public static Int4 operator +(Int4 a, Int4 b) => new(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); @@ -207,8 +212,10 @@ public struct Int4 : ICloneable, IComparable, IEquatable, IGroup new(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); public static Int4 operator *(Int4 a, Int4 b) => new(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); public static Int4 operator *(Int4 a, int b) => new(a.x * b, a.y * b, a.z * b, a.w * b); + public static Int4 operator *(Int4 a, Matrix b) => (Int4)((Matrix)(Float4)a * b); public static Int4 operator /(Int4 a, Int4 b) => new(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); public static Int4 operator /(Int4 a, int b) => new(a.x / b, a.y / b, a.z / b, a.w / b); + public static Int4 operator /(Int4 a, Matrix b) => (Int4)((Matrix)(Float4)a / b); public static Int4 operator &(Int4 a, Int4 b) => new(a.x & b.x, a.y & b.y, a.z & b.z, a.w & b.w); public static Int4 operator |(Int4 a, Int4 b) => new(a.x | b.x, a.y | b.y, a.z | b.z, a.w | b.w); public static Int4 operator ^(Int4 a, Int4 b) => new(a.x ^ b.x, a.y ^ b.y, a.z ^ b.z, a.w ^ b.w); @@ -219,11 +226,15 @@ public struct Int4 : ICloneable, IComparable, IEquatable, IGroup=(Int4 a, Int4 b) => a == b || a > b; public static bool operator <=(Int4 a, Int4 b) => a == b || a < b; + public static explicit operator Int4(Complex val) => new((int)val.u, (int)val.i, 0, 0); + public static explicit operator Int4(Quaternion val) => new((int)val.u, (int)val.i, (int)val.j, (int)val.k); public static explicit operator Int4(Float2 val) => new((int)val.x, (int)val.y, 0, 0); public static explicit operator Int4(Float3 val) => new((int)val.x, (int)val.y, (int)val.z, 0); public static explicit operator Int4(Float4 val) => new((int)val.x, (int)val.y, (int)val.z, (int)val.w); public static implicit operator Int4(Int2 val) => new(val.x, val.y, 0, 0); public static implicit operator Int4(Int3 val) => new(val.x, val.y, val.z, 0); + public static explicit operator Int4(Matrix m) => new((int)m[0, 0], (int)m[1, 0], (int)m[2, 0], (int)m[3, 0]); + public static explicit operator Int4(Vector2d val) => (Int4)val.ToXYZ(); public static explicit operator Int4(Vert val) => new((int)val.position.x, (int)val.position.y, (int)val.position.z, 0); public static explicit operator Int4(RGBA val) => val.ToRGBAByte(); diff --git a/Nerd_STF/Mathematics/Mathf.cs b/Nerd_STF/Mathematics/Mathf.cs index 30337ca..ec73c8a 100644 --- a/Nerd_STF/Mathematics/Mathf.cs +++ b/Nerd_STF/Mathematics/Mathf.cs @@ -17,6 +17,7 @@ public static class Mathf public static float ArcSin(float value) => (float)Math.Asin(value); public static float ArcTan(float value) => ArcSin(value / Sqrt(1 + value * value)); + public static float ArcTan2(float a, float b) => ArcTan(a / b); public static float Average(Equation equ, float min, float max, float step = Calculus.DefaultStep) { @@ -50,10 +51,13 @@ public static class Mathf public static int Combinations(int total, int size) => Factorial(total) / (Factorial(size) * Factorial(total - size)); + public static float Cos(Angle angle) => Cos(angle.Radians); public static float Cos(float radians) => Sin(radians + Constants.HalfPi); + public static float Cot(Angle angle) => Cot(angle.Radians); public static float Cot(float radians) => Cos(radians) / Sin(radians); + public static float Csc(Angle angle) => Csc(angle.Radians); public static float Csc(float radians) => 1 / Sin(radians); public static float Divide(float val, params float[] dividends) @@ -67,6 +71,25 @@ public static class Mathf return val; } + public static float Dot(float[] a, float[] b) + { + if (a.Length != b.Length) throw new InvalidSizeException("Both arrays must have the same length"); + float[] vals = new float[a.Length]; + for (int i = 0; i < a.Length; i++) vals[i] = a[i] * b[i]; + return Sum(vals); + } + public static float Dot(params float[][] vals) + { + float[] res = new float[vals[0].Length]; + for (int i = 0; i < res.Length; i++) + { + float m = 1; + for (int j = 0; j < vals.Length; j++) m *= vals[j][i]; + res[i] = m; + } + return Sum(res); + } + public static int Factorial(int amount) { if (amount < 0) return 0; @@ -147,6 +170,13 @@ public static class Mathf foreach (int i in vals) val = i > val ? i : val; return val; } + public static T? Max(params T[] vals) where T : IComparable + { + if (vals.Length < 1) return default; + T val = vals[0]; + foreach (T t in vals) val = t.CompareTo(val) > 0 ? t : val; + return val; + } public static float Median(params float[] vals) { @@ -154,7 +184,8 @@ public static class Mathf float valA = vals[Floor(index)], valB = vals[Ceiling(index)]; return Average(valA, valB); } - public static int Median(params int[] vals) => vals[Floor(Average(0, vals.Length - 1))]; + public static int Median(params int[] vals) => Median(vals); + public static T Median(params T[] vals) => vals[Floor(Average(0, vals.Length - 1))]; public static float Min(Equation equ, float min, float max, float step = Calculus.DefaultStep) { @@ -180,6 +211,13 @@ public static class Mathf foreach (int i in vals) val = i < val ? i : val; return val; } + public static T? Min(params T[] vals) where T : IComparable + { + if (vals.Length < 1) return default; + T val = vals[0]; + foreach (T t in vals) val = t.CompareTo(val) < 0 ? t : val; + return val; + } public static (T value, int occurences) Mode(params T[] vals) where T : IEquatable { @@ -245,8 +283,10 @@ public static class Mathf public static float Round(float num, float nearest) => nearest * Round(num / nearest); public static int RoundInt(float num) => (int)Round(num); + public static float Sec(Angle angle) => Sec(angle.Radians); public static float Sec(float radians) => 1 / Cos(radians); + public static float Sin(Angle angle) => Sin(angle.Radians); public static float Sin(float radians) { // Really close polynomial to sin(x) (when modded by 2pi). RMSE of 0.000003833 @@ -303,6 +343,7 @@ public static class Mathf // Known as stdev public static float StandardDeviation(params float[] vals) => Sqrt(Variance(vals)); + public static float Tan(Angle angle) => Tan(angle.Radians); public static float Tan(float radians) => Sin(radians) / Cos(radians); public static T[] UniqueItems(params T[] vals) where T : IEquatable diff --git a/Nerd_STF/Mathematics/NumberSystems/Complex.cs b/Nerd_STF/Mathematics/NumberSystems/Complex.cs new file mode 100644 index 0000000..94a2bc5 --- /dev/null +++ b/Nerd_STF/Mathematics/NumberSystems/Complex.cs @@ -0,0 +1,193 @@ +namespace Nerd_STF.Mathematics.NumberSystems; + +public struct Complex : ICloneable, IComparable, IEquatable, IGroup +{ + public static Complex Down => new(0, -1); + public static Complex Left => new(-1, 0); + public static Complex Right => new(1, 0); + public static Complex Up => new(0, 1); + + public static Complex One => new(1, 1); + public static Complex Zero => new(0, 0); + + public Complex Conjugate => new(u, -i); + public float Magnitude => Mathf.Sqrt(u * u + i * i); + public Complex Normalized => this * Mathf.InverseSqrt(u * u + i * i); + + public float u, i; + + public Complex(float all) : this(all, all) { } + public Complex(float u, float i) + { + this.u = u; + this.i = i; + } + public Complex(Fill fill) : this(fill(0), fill(1)) { } + public Complex(Fill fill) : this(fill(0), fill(1)) { } + + public float this[int index] + { + get => index switch + { + 0 => u, + 1 => i, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + set + { + switch (index) + { + case 0: + u = value; + break; + + case 1: + i = value; + break; + + default: throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public static Complex Absolute(Complex val) => Float2.Absolute(val); + public static Complex Average(params Complex[] vals) + { + List floats = new(); + foreach (Complex c in vals) floats.Add(c); + return Float2.Average(floats.ToArray()); + } + public static Complex Ceiling(Complex val) => Float2.Ceiling(val); + public static Complex Clamp(Complex val, Complex min, Complex max) => Float2.Clamp(val, min, max); + public static Complex ClampMagnitude(Complex val, float minMag, float maxMag) => + Float2.ClampMagnitude(val, minMag, maxMag); + public static Complex Divide(Complex num, params Complex[] vals) + { + List floats = new(); + foreach (Complex c in vals) floats.Add(c); + return Float2.Divide(num, floats.ToArray()); + } + public static float Dot(Complex a, Complex b) => Float2.Dot(a, b); + public static float Dot(params Complex[] vals) + { + List floats = new(); + foreach (Complex c in vals) floats.Add(c); + return Float2.Dot(floats.ToArray()); + } + public static Complex Floor(Complex val) => Float2.Floor(val); + public static Complex Lerp(Complex a, Complex b, float t, bool clamp = true) => Float2.Lerp(a, b, t, clamp); + public static Complex Median(params Complex[] vals) + { + List floats = new(); + foreach (Complex c in vals) floats.Add(c); + return Float2.Median(floats.ToArray()); + } + public static Complex Max(params Complex[] vals) + { + List floats = new(); + foreach (Complex c in vals) floats.Add(c); + return Float2.Max(floats.ToArray()); + } + public static Complex Min(params Complex[] vals) + { + List floats = new(); + foreach (Complex c in vals) floats.Add(c); + return Float2.Min(floats.ToArray()); + } + public static Complex Product(params Complex[] vals) + { + List floats = new(); + foreach (Complex c in vals) floats.Add(c); + return Float2.Product(floats.ToArray()); + } + public static Complex Round(Complex val) => Float2.Round(val); + public static Complex Subtract(Complex num, params Complex[] vals) + { + List floats = new(); + foreach (Complex c in vals) floats.Add(c); + return Float2.Subtract(num, floats.ToArray()); + } + public static Complex Sum(params Complex[] vals) + { + List floats = new(); + foreach (Complex c in vals) floats.Add(c); + return Float2.Sum(floats.ToArray()); + } + + public static (float[] Us, float[] Is) SplitArray(params Complex[] vals) + { + float[] Us = new float[vals.Length], Is = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Us[i] = vals[i].u; + Is[i] = vals[i].i; + } + return (Us, Is); + } + + public int CompareTo(Complex other) => Magnitude.CompareTo(other.Magnitude); + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Complex)) return base.Equals(obj); + return Equals((Complex)obj); + } + public bool Equals(Complex other) => u == other.u && i == other.i; + public override int GetHashCode() => u.GetHashCode() ^ i.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(string? provider) => + u.ToString(provider) + (i >= 0 ? " + " : " - ") + i.ToString(provider) + "i"; + public string ToString(IFormatProvider provider) => + u.ToString(provider) + (i >= 0 ? " + " : " - ") + i.ToString(provider) + "i"; + + public object Clone() => new Complex(u, i); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return u; + yield return i; + } + + public float[] ToArray() => new[] { u, i }; + public Fill ToFill() + { + Complex @this = this; + return i => @this[i]; + } + public List ToList() => new() { u, i }; + + public Vector2d ToVector() => ((Float2)this).ToVector(); + + public static Complex operator +(Complex a, Complex b) => new(a.u + b.u, a.i + b.i); + public static Complex operator -(Complex c) => new(-c.u, -c.i); + public static Complex operator -(Complex a, Complex b) => new(a.u - b.u, a.i - b.i); + public static Complex operator *(Complex a, Complex b) => new(a.u * b.u - a.i * b.i, a.u * b.i + a.i * b.u); + public static Complex operator *(Complex a, float b) => new(a.u * b, a.i * b); + public static Complex operator *(Complex a, Matrix b) => (Complex)((Matrix)a * b); + public static Complex operator /(Complex a, Complex b) + { + float c = b.u * b.u + b.i * b.i; + return new((a.u * b.u + a.i * b.i) / c, (a.i * b.u - a.u * b.i) / c); + } + public static Complex operator /(Complex a, float b) => new(a.u / b, a.i / b); + public static Complex operator /(Complex a, Matrix b) => (Complex)((Matrix)a / b); + public static bool operator ==(Complex a, Complex b) => a.Equals(b); + public static bool operator !=(Complex a, Complex b) => !a.Equals(b); + public static bool operator >(Complex a, Complex b) => a.CompareTo(b) > 0; + public static bool operator <(Complex a, Complex b) => a.CompareTo(b) < 0; + public static bool operator >=(Complex a, Complex b) => a == b || a > b; + public static bool operator <=(Complex a, Complex b) => a == b || a < b; + + public static explicit operator Complex(Quaternion val) => new(val.u, val.i); + public static implicit operator Complex(Float2 val) => new(val.x, val.y); + public static explicit operator Complex(Float3 val) => new(val.x, val.y); + public static explicit operator Complex(Float4 val) => new(val.x, val.y); + public static implicit operator Complex(Int2 val) => new(val.x, val.y); + public static explicit operator Complex(Int3 val) => new(val.x, val.y); + public static explicit operator Complex(Int4 val) => new(val.x, val.y); + public static explicit operator Complex(Matrix m) => new(m[0, 0], m[1, 0]); + public static explicit operator Complex(Vector2d val) => val.ToXYZ(); + public static explicit operator Complex(Vert val) => new(val.position.x, val.position.y); + public static implicit operator Complex(Fill fill) => new(fill); + public static implicit operator Complex(Fill fill) => new(fill); +} diff --git a/Nerd_STF/Mathematics/NumberSystems/Quaternion.cs b/Nerd_STF/Mathematics/NumberSystems/Quaternion.cs new file mode 100644 index 0000000..7ef32ec --- /dev/null +++ b/Nerd_STF/Mathematics/NumberSystems/Quaternion.cs @@ -0,0 +1,309 @@ +namespace Nerd_STF.Mathematics.NumberSystems; + +public struct Quaternion : ICloneable, IComparable, IEquatable, IGroup +{ + public static Quaternion Back => new(0, 0, -1, 0); + public static Quaternion Down => new(0, -1, 0, 0); + public static Quaternion Far => new(0, 0, 0, 1); + public static Quaternion Forward => new(0, 0, 1, 0); + public static Quaternion Left => new(-1, 0, 0, 0); + public static Quaternion Near => new(0, 0, 0, -1); + public static Quaternion Right => new(1, 0, 0, 0); + public static Quaternion Up => new(0, 1, 0, 0); + + public static Quaternion One => new(1, 1, 1, 1); + public static Quaternion Zero => new(0, 0, 0, 0); + + public Quaternion Conjugate => new(u, -i, -j, -k); + public float Magnitude => Mathf.Sqrt(u * u + i * i + j * j + k * k); + public Quaternion Normalized => this * Mathf.InverseSqrt(u * u + i * i + j * j + k * k); + + public Float3 IJK => new(i, j, k); + + public float u, i, j, k; + + public Quaternion(float all) : this(all, all, all, all) { } + public Quaternion(float i, float j, float k) : this(0, i, j, k) { } + public Quaternion(Float3 ijk) : this(0, ijk.x, ijk.y, ijk.z) { } + public Quaternion(float u, Float3 ijk) : this(u, ijk.x, ijk.y, ijk.z) { } + public Quaternion(float u, float i, float j, float k) + { + this.u = u; + this.i = i; + this.j = j; + this.k = k; + } + public Quaternion(Fill fill) : this(fill(0), fill(1), fill(2)) { } + public Quaternion(Fill fill) : this(fill(0), fill(1), fill(2)) { } + + public float this[int index] + { + get => index switch + { + 0 => u, + 1 => i, + 2 => j, + 3 => k, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + set + { + switch (index) + { + case 0: + u = value; + break; + + case 1: + i = value; + break; + + case 2: + j = value; + break; + + case 3: + k = value; + break; + + default: throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public static Quaternion Absolute(Quaternion val) => Float4.Absolute(val); + public static Quaternion Average(params Quaternion[] vals) + { + List floats = new(); + foreach (Quaternion q in vals) floats.Add(q); + return Float4.Average(floats.ToArray()); + } + public static Quaternion Ceiling(Quaternion val) => Float4.Ceiling(val); + public static Quaternion Clamp(Quaternion val, Quaternion min, Quaternion max) => Float4.Clamp(val, min, max); + public static Quaternion ClampMagnitude(Quaternion val, float minMag, float maxMag) => + Float4.ClampMagnitude(val, minMag, maxMag); + public static Quaternion Divide(Quaternion num, params Quaternion[] vals) + { + List floats = new(); + foreach (Quaternion q in vals) floats.Add(q); + return Float4.Divide(num, floats.ToArray()); + } + public static float Dot(Quaternion a, Quaternion b) => a.u * b.u + a.i * b.i + a.j * b.j + a.k * b.k; + public static float Dot(params Quaternion[] vals) + { + List floats = new(); + foreach (Quaternion q in vals) floats.Add(q); + return Float4.Dot(floats.ToArray()); + } + public static Quaternion Floor(Quaternion val) => Float4.Floor(val); + public static Quaternion Lerp(Quaternion a, Quaternion b, float t, bool clamp = true) => Float4.Lerp(a, b, t, clamp); + public static Quaternion Median(params Quaternion[] vals) + { + List floats = new(); + foreach (Quaternion q in vals) floats.Add(q); + return Float4.Median(floats.ToArray()); + } + public static Quaternion Max(params Quaternion[] vals) + { + List floats = new(); + foreach (Quaternion q in vals) floats.Add(q); + return Float4.Max(floats.ToArray()); + } + public static Quaternion Min(params Quaternion[] vals) + { + List floats = new(); + foreach (Quaternion q in vals) floats.Add(q); + return Float4.Min(floats.ToArray()); + } + public static Quaternion Product(params Quaternion[] vals) + { + List floats = new(); + foreach (Quaternion q in vals) floats.Add(q); + return Float4.Product(floats.ToArray()); + } + public static Quaternion Round(Quaternion val) => Float4.Round(val); + public static Quaternion Subtract(Quaternion num, params Quaternion[] vals) + { + List floats = new(); + foreach (Quaternion q in vals) floats.Add(q); + return Float4.Subtract(num, floats.ToArray()); + } + public static Quaternion Sum(params Quaternion[] vals) + { + List floats = new(); + foreach (Quaternion q in vals) floats.Add(q); + return Float4.Sum(floats.ToArray()); + } + + public static Quaternion FromAngles(Angle yaw, Angle pitch, Angle? roll = null) + { + roll ??= Angle.Zero; + float cosYaw = Mathf.Cos(yaw), cosPitch = Mathf.Cos(pitch), cosRoll = Mathf.Cos(roll.Value), + sinYaw = Mathf.Sin(yaw), sinPitch = Mathf.Sin(pitch), sinRoll = Mathf.Sin(roll.Value); + + float cosYawCosPitch = cosYaw * cosPitch, + cosYawSinPitch = cosYaw * sinPitch, + sinYawCosPitch = sinYaw * cosPitch, + sinYawSinPitch = sinYaw * sinPitch; + + return new(cosYawCosPitch * cosRoll + sinYawSinPitch * sinRoll, + cosYawCosPitch * sinRoll + sinYawSinPitch * cosRoll, + cosYawSinPitch * cosRoll + sinYawCosPitch * sinRoll, + sinYawCosPitch * cosRoll + cosYawSinPitch * sinRoll); + } + public static Quaternion FromAngles(Float3 vals, Angle.Type valType) => + FromAngles(new(vals.x, valType), new(vals.y, valType), new(vals.z, valType)); + public static Quaternion FromVector(Vector3d vec) => FromAngles(vec.yaw, vec.pitch); + + public static (float[] Us, float[] Is, float[] Js, float[] Ks) SplitArray(params Quaternion[] vals) + { + float[] Us = new float[vals.Length], Is = new float[vals.Length], Js = new float[vals.Length], + Ks = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Us[i] = vals[i].u; + Is[i] = vals[i].i; + Js[i] = vals[i].j; + Ks[i] = vals[i].k; + } + return (Us, Is, Js, Ks); + } + + public int CompareTo(Quaternion other) => Magnitude.CompareTo(other.Magnitude); + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Quaternion)) return base.Equals(obj); + return Equals((Quaternion)obj); + } + public bool Equals(Quaternion other) => u == other.u && i == other.i && j == other.j && k == other.k; + public override int GetHashCode() => u.GetHashCode() ^ i.GetHashCode() ^ j.GetHashCode() ^ k.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(string? provider) => u.ToString(provider) + + (i >= 0 ? " + " : " - ") + i.ToString(provider) + "i" + + (j >= 0 ? " + " : " - ") + j.ToString(provider) + "j" + + (k >= 0 ? " + " : " - ") + k.ToString(provider) + "k"; + public string ToString(IFormatProvider provider) => u.ToString(provider) + + (i >= 0 ? " + " : " - ") + i.ToString(provider) + "i" + + (j >= 0 ? " + " : " - ") + j.ToString(provider) + "j" + + (k >= 0 ? " + " : " - ") + k.ToString(provider) + "k"; + + public object Clone() => new Quaternion(u, i, j, k); + + public Angle GetAngle() => new(2 * Mathf.ArcCos(u), Angle.Type.Radians); + public Float3 GetAxis() + { + Float3 axis = IJK; + float mag = Magnitude; + + if (mag < 0) return Float3.Zero; + return axis / mag; + } + public (Angle yaw, Angle pitch, Angle roll) ToAngles() + { + Quaternion doubled = this; + doubled.u *= u; + doubled.i *= i; + doubled.j *= j; + doubled.k *= k; + + Matrix3x3 rotMatrix = new(new[,] + { + { doubled.u + doubled.i - doubled.j - doubled.k, 0, 0 }, + { 2 * (i * j + u * k), 0, 0 }, + { 2 * (i * k + u * j), 2 * (j * k + u * i), doubled.u - doubled.i - doubled.j + doubled.k } + }); + + Angle yaw, pitch, roll; + + float r3c1Abs = Mathf.Absolute(rotMatrix.r3c1); + if (r3c1Abs >= 1) + { + rotMatrix.r1c2 = 2 * (i * j - u * k); + rotMatrix.r1c3 = 2 * (i * k + u * j); + + yaw = new(Mathf.ArcTan2(-rotMatrix.r1c2, -rotMatrix.r3c1 * rotMatrix.r1c3), Angle.Type.Radians); + pitch = new(-Constants.HalfPi * rotMatrix.r3c1 / r3c1Abs, Angle.Type.Radians); + roll = Angle.Zero; + } + else + { + yaw = new(Mathf.ArcTan2(rotMatrix.r2c1, rotMatrix.r1c1), Angle.Type.Radians); + pitch = new(Mathf.ArcSin(-rotMatrix.r3c1), Angle.Type.Radians); + roll = new(Mathf.ArcTan2(rotMatrix.r3c2, rotMatrix.r3c3), Angle.Type.Radians); + } + + return (yaw, pitch, roll); + } + public Vector3d ToVector() + { + (Angle yaw, Angle pitch, _) = ToAngles(); + return new(yaw, pitch); + } + + public Quaternion Rotate(Quaternion other) => other * this * other.Conjugate; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return u; + yield return i; + yield return j; + yield return k; + } + + public float[] ToArray() => new[] { u, i, j, k }; + public Fill ToFill() + { + Quaternion @this = this; + return i => @this[i]; + } + public List ToList() => new() { u, i, j, k }; + + public static Quaternion operator +(Quaternion a, Quaternion b) => new(a.u + b.u, a.i + b.i, a.j + b.j, a.k + b.k); + public static Quaternion operator -(Quaternion q) => new(q.u, q.i, q.j, q.k); + public static Quaternion operator -(Quaternion a, Quaternion b) => new(a.u - b.u, a.i - b.i, a.j - b.j, a.k - b.k); + public static Quaternion operator *(Quaternion x, Quaternion y) + { + float a = x.u, b = x.i, c = x.j, d = x.k, e = y.u, f = y.i, g = y.j, h = y.k, + u = a * e - b * f - c * g - d * h, + i = a * f + b * e + c * h - d * g, + j = a * g + c * e + b * h - d * f, + k = a * h + b * g + d * e - c * f; + return new(u, i, j, k); + } + public static Quaternion operator *(Quaternion a, float b) => new(a.u * b, a.i * b, a.j * b, a.k * b); + public static Quaternion operator *(Quaternion a, Matrix b) => (Quaternion)((Matrix)a * b); + public static Quaternion operator *(Quaternion a, Float3 b) => a * new Quaternion(b); + public static Quaternion operator /(Quaternion x, Quaternion y) + { + float a = x.u, b = x.i, c = x.j, d = x.k, e = y.u, f = y.i, g = y.j, h = y.k, + u = a * e + b * f + c * g + d * h, + i = b * e + c * h + d * g - a * f, + j = c * e + d * f - a * g - b * h, + k = c * f + d * e - a * h - b * g, + q = e * e + f * f + g * g + h * h; + return new(u / q, i / q, j / q, k / q); + } + public static Quaternion operator /(Quaternion a, float b) => new(a.u / b, a.i / b, a.j / b, a.k / b); + public static Quaternion operator /(Quaternion a, Matrix b) => (Quaternion)((Matrix)a / b); + public static Quaternion operator /(Quaternion a, Float3 b) => a / new Quaternion(b); + public static bool operator ==(Quaternion a, Quaternion b) => a.Equals(b); + public static bool operator !=(Quaternion a, Quaternion b) => !a.Equals(b); + public static bool operator >(Quaternion a, Quaternion b) => a.CompareTo(b) > 0; + public static bool operator <(Quaternion a, Quaternion b) => a.CompareTo(b) < 0; + public static bool operator >=(Quaternion a, Quaternion b) => a == b || a > b; + public static bool operator <=(Quaternion a, Quaternion b) => a == b || a < b; + + public static implicit operator Quaternion(Complex val) => new(val.u, val.i, 0, 0); + public static implicit operator Quaternion(Int2 val) => new(val); + public static implicit operator Quaternion(Int3 val) => new(val); + public static implicit operator Quaternion(Int4 val) => new(val.x, val.y, val.z, val.w); + public static explicit operator Quaternion(Float2 val) => new(val); + public static explicit operator Quaternion(Float3 val) => new(val); + public static implicit operator Quaternion(Float4 val) => new(val.x, val.y, val.z, val.w); + public static explicit operator Quaternion(Matrix m) => new(m[0, 0], m[1, 0], m[2, 0], m[3, 0]); + public static explicit operator Quaternion(Vector2d val) => (Quaternion)val.ToXYZ(); + public static implicit operator Quaternion(Vert val) => new(val); + public static implicit operator Quaternion(Fill fill) => new(fill); + public static implicit operator Quaternion(Fill fill) => new(fill); +} diff --git a/Nerd_STF/Mathematics/Constants.cs b/Nerd_STF/Mathematics/Samples/Constants.cs similarity index 99% rename from Nerd_STF/Mathematics/Constants.cs rename to Nerd_STF/Mathematics/Samples/Constants.cs index 84ddd58..096f5d4 100644 --- a/Nerd_STF/Mathematics/Constants.cs +++ b/Nerd_STF/Mathematics/Samples/Constants.cs @@ -1,4 +1,4 @@ -namespace Nerd_STF.Mathematics; +namespace Nerd_STF.Mathematics.Samples; public static class Constants { diff --git a/Nerd_STF/Mathematics/Samples/Equations.cs b/Nerd_STF/Mathematics/Samples/Equations.cs new file mode 100644 index 0000000..1ded66e --- /dev/null +++ b/Nerd_STF/Mathematics/Samples/Equations.cs @@ -0,0 +1,26 @@ +namespace Nerd_STF.Mathematics.Samples; + +public static class Equations +{ + public static readonly Fill SgnFill = i => i % 2 == 0 ? 1 : -1; + + public static readonly Equation CosWave = x => Mathf.Cos(x); + public static readonly Equation SinWave = x => Mathf.Sin(x); + public static readonly Equation SawWave = x => x % 1; + public static readonly Equation SquareWave = x => x % 2 < 1 ? 1 : 0; + + public static Equation Scale(Equation equ, float value, ScaleType type = ScaleType.Both) => type switch + { + ScaleType.X => x => equ(value / x), + ScaleType.Y => x => x * equ(value), + ScaleType.Both => x => x * equ(value / x), + _ => throw new ArgumentException("Unknown scale type " + type) + }; + + public enum ScaleType + { + X = 1, + Y = 2, + Both = X | Y + } +} diff --git a/Nerd_STF/Miscellaneous/GlobalUsings.cs b/Nerd_STF/Miscellaneous/GlobalUsings.cs index 45bcc57..ed144bb 100644 --- a/Nerd_STF/Miscellaneous/GlobalUsings.cs +++ b/Nerd_STF/Miscellaneous/GlobalUsings.cs @@ -10,5 +10,9 @@ global using System.Threading.Tasks; global using Nerd_STF; global using Nerd_STF.Graphics; global using Nerd_STF.Exceptions; +global using Nerd_STF.Extensions; global using Nerd_STF.Mathematics; +global using Nerd_STF.Mathematics.Algebra; global using Nerd_STF.Mathematics.Geometry; +global using Nerd_STF.Mathematics.NumberSystems; +global using Nerd_STF.Mathematics.Samples; diff --git a/Nerd_STF/Modifier.cs b/Nerd_STF/Modifier.cs new file mode 100644 index 0000000..8e7753d --- /dev/null +++ b/Nerd_STF/Modifier.cs @@ -0,0 +1,5 @@ +namespace Nerd_STF; + +public delegate float Modifier(int index, float value); +public delegate T Modifier(int index, T value); +public delegate VT Modifier(IT index, VT value); diff --git a/Nerd_STF/Modifier2D.cs b/Nerd_STF/Modifier2D.cs new file mode 100644 index 0000000..ddac9b9 --- /dev/null +++ b/Nerd_STF/Modifier2D.cs @@ -0,0 +1,5 @@ +namespace Nerd_STF; + +public delegate float Modifier2D(Int2 index, float value); +public delegate T Modifier2D(Int2 index, T value); +public delegate VT Modifier2D(IT x, IT y, VT value); diff --git a/Nerd_STF/bin/Release/net6.0/ref/Nerd_STF.dll b/Nerd_STF/bin/Release/net6.0/ref/Nerd_STF.dll index 89f2b0d0e1ecd793365ad91d508b7efe3e03dead..3020460b25091546c55797c750ae0cee78803ab6 100644 GIT binary patch literal 111616 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

5u!*<7$cEsl8wa|rm_wl@zB+R>}Mkmx= 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

jma|i9Sp87SV&EheeNoDSx^*iPG&5`+(?s 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 literal 77312 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

I>q7i-LaHFb`t-ifq|h4u?4WZVrJc8?-8Rr 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