Compare commits

...

47 Commits
v2.1.2 ... main

Author SHA1 Message Date
909ccb2934
Merge pull request #90 from That-One-Nerd/v2.4
Released version 2.4.1.
2023-07-16 20:31:27 -04:00
e020641478
Merge pull request #89 from That-One-Nerd/canary
Ready to release v2.4.1
2023-07-16 20:19:17 -04:00
56eaf31c7b forgot to add this 2023-07-16 20:16:07 -04:00
e550688634 Final description update to the changelog. 2023-07-16 19:55:15 -04:00
fc223ec70e Made a crap row-echelon solver. 2023-07-14 21:34:38 -04:00
5ff5cc7be8 Some changes to the matrix types have been made. 2023-07-14 21:08:50 -04:00
63e70f7124 Added row operators to matrixes. Row-echelon coming soon. 2023-07-10 17:10:40 -04:00
be59b70e00 Merge remote-tracking branch 'origin/main' into canary 2023-07-10 16:36:12 -04:00
97a0b2fee5 Fixed some blunders and finished #31. 2023-07-10 16:34:07 -04:00
2c2e7e3a99 Added conversion between number systems and their System.Numerics counterparts. (#32) 2023-07-10 16:04:04 -04:00
eeb70604d5 Added setters to the float and int groups. 2023-07-10 07:36:54 -04:00
990f7663b9 Added IPresets0d. Not huge at all. 2023-07-10 07:26:09 -04:00
a245607042
Merge pull request #88 from That-One-Nerd/canary
Releasing Nerd_STF version 2.4.0.
2023-07-10 00:25:18 -04:00
34349c5e14 Finalized v2.4 2023-07-10 00:24:56 -04:00
0915754f5f Made a new logo and completed the changelog. 2023-07-10 00:14:52 -04:00
8c76f533b9 Added taylor series generation. 2023-07-09 23:38:31 -04:00
d1ba73ae74 Merge branch 'canary' of https://github.com/That-One-Nerd/Nerd_STF into canary 2023-07-08 07:29:37 -04:00
564d4caa1a Removed a message my friend put in that I forgot to remove :) 2023-07-08 07:29:16 -04:00
2047676a0e Added string sections. 2023-07-07 14:57:56 -04:00
c05bd6007d Lots of changes. Among other things, CORDIC has been implemented. 2023-07-06 23:15:08 -04:00
51ad1974d5 Added a new ND array. Maybe a list will come soon. 2023-06-07 12:53:24 -04:00
f6ff0aac6c Added a new prime checking method. 2023-06-07 07:22:03 -04:00
ed9c897dbf not actually gonna do big integer
it already exists, so why even continue to make it?
2023-06-01 10:03:45 -04:00
12ae678e86 some adding to the big integer (DOESNT WORK) 2023-04-24 07:15:26 -04:00
af4ed70add meant to commit this a while ago
really havent done much in a couple weeks. time to step up.
2023-04-23 17:21:04 -04:00
516e70709d made the rational struct immutable
ill add a mutable equivalent in 2.4.1
2023-04-02 17:40:25 -04:00
80b78c4bdb some small biginteger progress.
more will come in the future. i want to make a small tweak to the rational type first.
2023-04-02 17:16:11 -04:00
5509cce466 Merge remote-tracking branch 'origin/main' into canary 2023-04-02 12:30:16 -04:00
85dad1f7f5 the beginning of 2.4 2023-04-02 12:27:42 -04:00
6056496aa6
Merge pull request #18 from That-One-Nerd/v2.3
Woo new update!
2023-03-09 16:54:24 -05:00
6d44cbe438 Released v2.3.2 2023-03-09 16:44:23 -05:00
b0915c1ff9
Merge pull request #17 from That-One-Nerd/v2.3
v2.3.1
2022-11-12 11:27:19 -05:00
fe83380366 the exact changelog looks weird on the website. 2022-11-12 11:24:34 -05:00
ed967797e6 Released v2.3.1 2022-11-12 11:21:34 -05:00
01b318582e
Merge pull request #16 from That-One-Nerd/v2.3
Removed the .sln file from the git.
2022-11-02 07:35:58 -04:00
e895bbb5b2 Removed the .sln file from the git.
It was causing cli build errors because the "Testing" project doesn't exist.
Now it works first try.
2022-11-02 07:34:12 -04:00
13063f3098
Merge pull request #15 from That-One-Nerd/v2.3
update main branch
2022-10-31 12:13:40 -04:00
efcf2b94e9 The logo is now included in the build files. 2022-10-31 12:12:36 -04:00
9a1821cb10
Merge pull request #14 from That-One-Nerd/main
Released v2.3.1.52
2022-10-31 11:56:15 -04:00
deff917a60
No longer in the gitignore 2022-10-31 11:50:49 -04:00
07cb6a2369 Released alpha v2.3.1.52 2022-10-31 11:39:51 -04:00
061c0d4df3
Merge pull request #13 from That-One-Nerd/main
Releasing alpha v2.3.1.39
2022-09-30 17:09:00 -04:00
d6234f0004 Releasing alpha v2.3.1.39 2022-09-30 17:08:09 -04:00
e06a49c634 Version 2.3.0 has been released.
More to come in the rest of 2.3!
2022-08-02 12:31:54 -04:00
494d3b2581
Merge pull request #12 from That-One-Nerd/v2.2
bruh cant believe i forgot this
2022-07-11 09:40:52 -04:00
85c112e0d8 Version 2.2.0 is out 2022-06-10 17:22:45 -04:00
c388df3d3a
Merge pull request #11 from That-One-Nerd/main
Moving
2022-05-02 13:20:23 -04:00
132 changed files with 8922 additions and 949 deletions

20
.gitignore vendored
View File

@ -1,16 +1,22 @@
# Visual Studio stuff
*.sln
*.csproj
# Useless Visual Studio stuff
.vs/
/Nerd_STF/.vs/
/Nerd_STF/Nerd_STF.csproj.user
*.sln
# Build Stuff
/Nerd_STF/obj
/Nerd_STF/bin/Debug
/Nerd_STF/bin/Release/net6.0/Nerd_STF.deps.json
/Nerd_STF/bin/Release/net6.0/Nerd_STF.dll
/Nerd_STF/bin/Release/net6.0/Nerd_STF.pdb
/Nerd_STF/bin
*.dll
*.pdb
# Testing project
/Testing
# Nuget
/Nerd_STF/LICENSE
*.nupkg
*.snupkg
# Personal
/Nerd_STF/TODO.md

View File

@ -1,14 +1,118 @@
# Nerd_STF v2.1.2
# Nerd_STF v2.4.1
This update just replaces instances of `double` with `float` instead.
Hey everyone! This is one of the larger small updates, and I'm pretty proud of what I got done in a week.
I know, this isn't the update you wanted. More stuff coming soon.
Along with adding setters to parts like `Float3.XY` and fixing a few bugs, almost all improvements in this update are related to matricies. First of all, I've added a bunch of new items to the `IMatrix` interface. Now, any deriving matrix has more requirements that fit the regular `Matrix` type. I don't know why one would use the `IMatrix` interface rather than a specific matrix type, but now the options are more sophisticated.
I've added some new stuff to all the matrix types, including row operations. You can now scale a row, add a row to another, and swap two rows. If I become aware of any more commonly-used row operations, I'll add then in a `2.4.2` update. But I think I've got all the good ones. There is also a mutable version of each operation which, rather than returning a new matrix with changes made, instead applies the changes to itself.
Did you know I made two seperate blunders in the `Cofactor()` method? For the `Matrix2x2` version of the `Cofactor()` method, I had the diagonal elements swapped. Whoops. For the `Matrix` version of the `Cofactor()` method, matricies with even column count would break because of the alternating sign pattern I was using. Now, as far as I know, that bug is fixed.
The last thing I did was add the ability to turn a matrix into its equivalent row-echelon form. This is applicable only to the `Matrix` type (the dynamic one), and works with some levels of success. It's a little weird and tends to give results with lots of negative zeroes, but overall it's fine, I think. As far as I know there aren't any obvious bugs. We'll see though.
Anyway, that's everything in this update. Again, pretty small, but meaningful nonetheless. Unless I haven't screwed anything up, the next update I work on will be `2.5`, so I'll see you then!
Here's the full changelog:
```
* Nerd_STF
= Replace all instances of `double` with `float`
* Mathematics
= Renamed `Double2` to `Float2`
= Renamed `Double3` to `Float3`
= Renamed `Double4` to `Float4`
* Abstract
* IMatrix
+ AddRow(int, int, float)
+ AddRowMutable(int, int, float)
+ Cofactor()
+ GetColumn(int)
+ GetRow(int)
+ ScaleRow(int, float)
+ ScaleRowMutable(int, float)
+ SetColumn(int, float[])
+ SetRow(int, float[])
+ Size
+ SwapRows(int, int)
+ SwapRowsMutable(int, int)
+ this[int, int]
+ this[Index, Index]
* Algebra
* Matrix
+ AddRow(int, int, float)
+ AddRowMutable(int, int, float)
+ ScaleRow(int, float)
+ ScaleRowMutable(int, float)
+ SwapRows(int, int)
+ SwapRowsMutable(int, int)
= Fixed a blunder in `SignGrid(Int2)` with signs being incorrectly placed on matrixes with even column count.
* Matrix2x2
+ AddRow(int, int, float)
+ AddRowMutable(int, int, float)
+ GetColumn(int)
+ GetRow(int)
+ ScaleRow(int, float)
+ ScaleRowMutable(int, float)
+ SetColumn(int, float[])
+ SetRow(int, float[])
+ Size
+ SwapRows(int, int)
+ SwapRowsMutable(int, int)
= Fixed a blunder in `Cofactor()` with the position of elements.
* Matrix3x3
+ AddRow(int, int, float)
+ AddRowMutable(int, int, float)
+ GetColumn(int)
+ GetRow(int)
+ ScaleRow(int, float)
+ ScaleRowMutable(int, float)
+ SetColumn(int, float[])
+ SetRow(int, float[])
+ Size
+ SwapRows(int, int)
+ SwapRowsMutable(int, int)
* Matrix4x4
+ AddRow(int, int, float)
+ AddRowMutable(int, int, float)
+ GetColumn(int)
+ GetRow(int)
+ ScaleRow(int, float)
+ ScaleRowMutable(int, float)
+ SetColumn(int, float[])
+ SetRow(int, float[])
+ Size
+ SwapRows(int, int)
+ SwapRowsMutable(int, int)
* NumberSystems
* Complex
+ operator Complex(SystemComplex)
+ operator SystemComplex(Complex)
* Quaternion
+ operator Quaternion(SystemQuaternion)
+ operator SystemQuaternion(Quaternion)
* Float3
= Added a setter to `XY`
= Added a setter to `XZ`
= Added a setter to `YZ`
* Float4
= Added a setter to `XW`
= Added a setter to `XY`
= Added a setter to `XZ`
= Added a setter to `YW`
= Added a setter to `YZ`
= Added a setter to `ZW`
= Added a setter to `XYW`
= Added a setter to `XYZ`
= Added a setter to `XZW`
= Added a setter to `YZW`
* Int3
= Added a setter to `XY`
= Added a setter to `XZ`
= Added a setter to `YZ`
* Int4
= Added a setter to `XW`
= Added a setter to `XY`
= Added a setter to `XZ`
= Added a setter to `YW`
= Added a setter to `YZ`
= Added a setter to `ZW`
= Added a setter to `XYW`
= Added a setter to `XYZ`
= Added a setter to `XZW`
= Added a setter to `YZW`
```

BIN
Extras/Banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
Extras/Logo Square.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

4
Nerd_STF/.editorconfig Normal file
View File

@ -0,0 +1,4 @@
[*.cs]
# CA1050: Declare types in namespaces
dotnet_diagnostic.CA1050.severity = warning

View File

@ -0,0 +1,18 @@
namespace Nerd_STF.Exceptions;
[Serializable]
public class BadMethodException : Nerd_STFException
{
public MethodInfo? MethodInfo;
public BadMethodException() : base("The method or delegate provided is invalid for this operation.") { }
public BadMethodException(string message) : base(message) { }
public BadMethodException(Exception inner) : base("The method or delegate provided is invalid for this operation.", inner) { }
public BadMethodException(MethodInfo method) : this() => MethodInfo = method;
public BadMethodException(MethodInfo method, Exception inner) : this(inner) => MethodInfo = method;
public BadMethodException(string message, Exception inner) : base(message, inner) { }
public BadMethodException(string message, MethodInfo method) : this(message) => MethodInfo = method;
public BadMethodException(string message, MethodInfo method, Exception inner) : this(message, inner) => MethodInfo = method;
protected BadMethodException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}

View File

@ -1,8 +1,7 @@
using System.Runtime.Serialization;
namespace Nerd_STF.Exceptions;
namespace Nerd_STF.Exceptions;
[Serializable]
[Obsolete("The Polygon struct is a garbage fire, and will be fixed in v2.4.0", false)]
public class DifferingVertCountException : Nerd_STFException
{
public string? ParamName;

View File

@ -1,6 +1,4 @@
using System.Runtime.Serialization;
namespace Nerd_STF.Exceptions;
namespace Nerd_STF.Exceptions;
[Serializable]
public class DisconnectedLinesException : Nerd_STFException

View File

@ -0,0 +1,10 @@
namespace Nerd_STF.Exceptions;
[Serializable]
public class InvalidSizeException : Nerd_STFException
{
public InvalidSizeException() : base("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) { }
}

View File

@ -0,0 +1,10 @@
namespace Nerd_STF.Exceptions;
[Serializable]
public class MathException : Nerd_STFException
{
public MathException() : base("A calculation error occured.") { }
public MathException(string message) : base(message) { }
public MathException(string message, Exception inner) : base(message, inner) { }
protected MathException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}

View File

@ -1,11 +1,11 @@
using System.Runtime.Serialization;
namespace Nerd_STF.Exceptions;
namespace Nerd_STF.Exceptions;
[Serializable]
public class Nerd_STFException : Exception
{
public Nerd_STFException() { }
public Nerd_STFException() : base("An unknown error occured within Nerd_STF.") { }
public Nerd_STFException(Exception inner)
: base("An unknown error occured within Nerd_STF.", inner) { }
public Nerd_STFException(string message) : base(message) { }
public Nerd_STFException(string message, Exception inner) : base(message, inner) { }
protected Nerd_STFException(SerializationInfo info, StreamingContext context) : base(info, context) { }

View File

@ -0,0 +1,16 @@
namespace Nerd_STF.Exceptions;
[Serializable]
public class NoInverseException : Nerd_STFException
{
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) { }
}

View File

@ -0,0 +1,10 @@
namespace Nerd_STF.Exceptions;
[Serializable]
public class UndefinedException : MathException
{
public UndefinedException() : this("A calculation has produced an undefined number.") { }
public UndefinedException(string message) : base(message) { }
public UndefinedException(string message, Exception inner) : base(message, inner) { }
protected UndefinedException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}

View File

@ -0,0 +1,61 @@
namespace Nerd_STF.Extensions;
public static class Container2DExtension
{
public static T[] Flatten<T>(this T[,] array, Int2? size = null)
{
size ??= GetSize(array);
T[] res = new T[size.Value.x * size.Value.y];
for (int x = 0; x < size.Value.x; x++) for (int y = 0; y < size.Value.y; y++)
res[x + y * size.Value.x] = array[y, x];
return res;
}
public static T[] GetColumn<T>(this T[,] array, int column, int length)
{
T[] res = new T[length];
for (int i = 0; i < length; i++) res[i] = array[i, column];
return res;
}
public static T[] GetRow<T>(this T[,] array, int row, int length)
{
T[] res = new T[length];
for (int i = 0; i < length; i++) res[i] = array[row, i];
return res;
}
public static Int2 GetSize<T>(this T[,] array)
{
Int2 size = Int2.Zero;
try
{
while (true)
{
_ = array[size.x, 0];
size.x++;
}
}
catch (IndexOutOfRangeException) { }
try
{
while (true)
{
_ = array[0, size.y];
size.y++;
}
}
catch (IndexOutOfRangeException) { }
return size;
}
public static T[,] SwapDimensions<T>(this T[,] array, Int2? size = null)
{
size ??= GetSize(array);
T[,] vals = new T[size.Value.y, size.Value.x];
for (int x = 0; x < size.Value.y; x++) for (int y = 0; y < size.Value.x; y++) vals[x, y] = array[y, x];
return vals;
}
}

View File

@ -0,0 +1,8 @@
namespace Nerd_STF.Extensions;
public static class ConversionExtension
{
public static Fill<T> ToFill<T>(this T[] arr) => i => arr[i];
public static Fill<T> ToFill<T>(this T[,] arr, Int2? size) => arr.Flatten(size).ToFill();
public static Fill2d<T> ToFill2D<T>(this T[,] arr) => (x, y) => arr[x, y];
}

View File

@ -0,0 +1,305 @@
namespace Nerd_STF.Extensions;
public static class EquationExtension
{
private static readonly List<Type> ValidNumberTypes = new()
{
typeof(byte),
typeof(sbyte),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal)
};
public static Equation Absolute(this Equation equ) => x => Mathf.Absolute(equ(x));
public static Equation AbsoluteMod(this Equation equ, float mod) => x => Mathf.AbsoluteMod(equ(x), mod);
public static Equation ArcCos(this Equation equ) => x => Mathf.ArcCos(equ(x)).Radians;
public static Equation ArcCot(this Equation equ) => x => Mathf.ArcCot(equ(x)).Radians;
public static Equation ArcCsc(this Equation equ) => x => Mathf.ArcCsc(equ(x)).Radians;
public static Equation ArcSec(this Equation equ) => x => Mathf.ArcSec(equ(x)).Radians;
public static Equation ArcSin(this Equation equ) => x => Mathf.ArcSin(equ(x)).Radians;
public static Equation ArcTan(this Equation equ) => x => Mathf.ArcTan(equ(x)).Radians;
public static Equation ArcCosh(this Equation equ) => x => Mathf.ArcCosh(equ(x));
public static Equation ArcCoth(this Equation equ) => x => Mathf.ArcCoth(equ(x));
public static Equation ArcCsch(this Equation equ) => x => Mathf.ArcCsch(equ(x));
public static Equation ArcSech(this Equation equ) => x => Mathf.ArcSech(equ(x));
public static Equation ArcSinh(this Equation equ) => x => Mathf.ArcSinh(equ(x));
public static Equation ArcTanh(this Equation equ) => x => Mathf.ArcTanh(equ(x));
public static float Average(this Equation equ, float min, float max, float step = Calculus.DefaultStep) =>
Mathf.Average(equ, min, max, step);
public static Equation Average(this Equation equ, Equation min, Equation max, float step = Calculus.DefaultStep) =>
x => Mathf.Average(equ, min(x), max(x), step);
public static Equation Binomial(this Equation equ, int total, float successRate) =>
x => Mathf.Binomial((int)equ(x), total, successRate);
public static Equation Binomial(this Equation equ, Equation total, Equation successRate) =>
x => Mathf.Binomial((int)equ(x), (int)total(x), successRate(x));
public static Equation Cbrt(this Equation equ) => x => Mathf.Cbrt(equ(x));
public static Equation Ceiling(this Equation equ) => x => Mathf.Ceiling(equ(x));
public static Equation Clamp(this Equation equ, float min, float max) => x => Mathf.Clamp(equ(x), min, max);
public static Equation Clamp(this Equation equ, Equation min, Equation max) =>
x => Mathf.Clamp(equ(x), min(x), max(x));
public static Equation Combinations(this Equation equ, int size) =>
x => Mathf.Combinations(size, (int)equ(x));
public static Equation Combinations(this Equation equ, Equation size) =>
x => Mathf.Combinations((int)size(x), (int)equ(x));
public static Equation Cos(this Equation equ) => x => Mathf.Cos(equ(x));
public static Equation Cot(this Equation equ) => x => Mathf.Cot(equ(x));
public static Equation Csc(this Equation equ) => x => Mathf.Csc(equ(x));
public static Equation Cosh(this Equation equ) => x => Mathf.Cosh(equ(x));
public static Equation Coth(this Equation equ) => x => Mathf.Coth(equ(x));
public static Equation Csch(this Equation equ) => x => Mathf.Csch(equ(x));
public static Equation Divide(this Equation equ, params float[] dividends) =>
x => Mathf.Divide(equ(x), dividends);
public static Equation Divide(this Equation equ, params Equation[] dividends) => delegate (float x)
{
float[] dividendsAtValue = new float[dividends.Length];
for (int i = 0; i < dividends.Length; i++) dividendsAtValue[i] = dividends[i](x);
return Mathf.Divide(equ(x), dividendsAtValue);
};
public static Equation Factorial(this Equation equ) => x => Mathf.Factorial((int)equ(x));
public static Equation Floor(this Equation equ) => x => Mathf.Floor(equ(x));
public static Equation GetDerivative(this Equation equ, float step = Calculus.DefaultStep) =>
Calculus.GetDerivative(equ, step);
public static float GetDerivativeAtPoint(this Equation equ, float x, float step = Calculus.DefaultStep) =>
Calculus.GetDerivativeAtPoint(equ, x, step);
public static float GetIntegral(this Equation equ, float lowerBound, float upperBound,
float step = Calculus.DefaultStep) => Calculus.GetIntegral(equ, lowerBound, upperBound, step);
public static Equation GetDynamicIntegral(this Equation equ, Equation lowerBound,
Equation upperBound, float step = Calculus.DefaultStep) => Calculus.GetDynamicIntegral(equ, lowerBound, upperBound, step);
public static Equation GetTaylorSeries(this Equation equ, float referenceX, int iterations = 4, float step = 0.01f) =>
Calculus.GetTaylorSeries(equ, referenceX, iterations, step);
public static Dictionary<float, float> GetValues(this Equation equ, float min, float max,
float step = Calculus.DefaultStep) => Mathf.GetValues(equ, min, max, step);
public static float GradientDescent(this Equation equ, float initial, float rate, int iterations = 1000,
float step = Calculus.DefaultStep) => Calculus.GradientDescent(equ, initial, rate, iterations, step);
public static Equation InverseSqrt(this Equation equ) => x => Mathf.InverseSqrt(equ(x));
public static Equation Log(this Equation equ, float @base) => x => Mathf.Log(@base, equ(x));
public static float Max(this Equation equ, float min, float max, float step = Calculus.DefaultStep) =>
Mathf.Max(equ, min, max, step);
public static float Min(this Equation equ, float min, float max, float step = Calculus.DefaultStep) =>
Mathf.Min(equ, min, max, step);
public static Equation Permutations(this Equation equ, int size) =>
x => Mathf.Permutations(size, (int)equ(x));
public static Equation Permutations(this Equation equ, Equation size) =>
x => Mathf.Permutations((int)size(x), (int)equ(x));
public static Equation Power(this Equation equ, float pow) => x => Mathf.Power(equ(x), pow);
public static Equation Power(this Equation equ, Equation pow) => x => Mathf.Power(equ(x), pow(x));
public static Equation Product(this Equation equ, params float[] vals) => delegate (float x)
{
float[] valsAtValue = new float[vals.Length + 1];
valsAtValue[0] = equ(x);
for (int i = 0; i < vals.Length; i++) valsAtValue[i + 1] = vals[i];
return Mathf.Product(valsAtValue);
};
public static Equation Product(this Equation equ, params Equation[] vals) => delegate (float x)
{
float[] valsAtValue = new float[vals.Length + 1];
valsAtValue[0] = equ(x);
for (int i = 0; i < vals.Length; i++) valsAtValue[i + 1] = vals[i](x);
return Mathf.Product(valsAtValue);
};
public static Equation Root(this Equation equ, float index) => x => Mathf.Root(equ(x), index);
public static Equation Root(this Equation equ, Equation index) => x => Mathf.Root(equ(x), index(x));
public static Equation Round(this Equation equ) => x => Mathf.Round(equ(x));
public static Equation Sec(this Equation equ) => x => Mathf.Sec(equ(x));
public static Equation Sin(this Equation equ) => x => Mathf.Sin(equ(x));
public static Equation Sech(this Equation equ) => x => Mathf.Sech(equ(x));
public static Equation Sinh(this Equation equ) => x => Mathf.Sinh(equ(x));
public static float SolveBisection(this Equation equ, float initialA, float initialB, float tolerance = 1e-5f,
int maxIterations = 1000) =>
Mathf.SolveBisection(equ, initialA, initialB, tolerance, maxIterations);
public static float SolveEquation(this Equation equ, float initial, float tolerance = 1e-5f,
float step = Calculus.DefaultStep, int maxIterations = 1000) =>
Mathf.SolveEquation(equ, initial, tolerance, step, maxIterations);
public static float SolveNewton(this Equation equ, float initial, float tolerance = 1e-5f,
float step = Calculus.DefaultStep, int maxIterations = 1000) =>
Mathf.SolveNewton(equ, initial, tolerance, step, maxIterations);
public static Equation Sqrt(this Equation equ) => x => Mathf.Sqrt(equ(x));
public static Equation Subtract(this Equation equ, params float[] vals) =>
x => Mathf.Subtract(equ(x), vals);
public static Equation Subtract(this Equation equ, params Equation[] vals) => delegate (float x)
{
float[] valsAtValue = new float[vals.Length];
for (int i = 0; i < vals.Length; i++) valsAtValue[i] = vals[i](x);
return Mathf.Subtract(equ(x), valsAtValue);
};
public static Equation Sum(this Equation equ, params float[] vals) => delegate (float x)
{
float[] valsAtValue = new float[vals.Length + 1];
valsAtValue[0] = equ(x);
for (int i = 0; i < vals.Length; i++) valsAtValue[i + 1] = vals[i];
return Mathf.Sum(valsAtValue);
};
public static Equation Sum(this Equation equ, params Equation[] vals) => delegate (float x)
{
float[] valsAtValue = new float[vals.Length + 1];
valsAtValue[0] = equ(x);
for (int i = 0; i < vals.Length; i++) valsAtValue[i + 1] = vals[i](x);
return Mathf.Sum(valsAtValue);
};
public static Equation Tan(this Equation equ) => x => Mathf.Tan(equ(x));
public static Equation Tanh(this Equation equ) => x => Mathf.Tanh(equ(x));
public static Equation ZScore(this Equation equ, params float[] vals) => x => Mathf.ZScore(equ(x), vals);
public static Equation ZScore(this Equation equ, params Equation[] vals) => delegate (float x)
{
float[] valsAtValue = new float[vals.Length];
for (int i = 0; i < vals.Length; i++) valsAtValue[i] = vals[i](x);
return Mathf.ZScore(equ(x), valsAtValue);
};
public static Equation ZScore(this Equation equ, float mean, float stdev) =>
x => Mathf.ZScore(equ(x), mean, stdev);
public static Equation ZScore(this Equation equ, Equation mean, Equation stdev) =>
x => Mathf.ZScore(equ(x), mean(x), stdev(x));
public static Equation InvokeMethod(this Equation equ, MethodInfo method, params object?[]? args)
{
// Determine if this method is a valid method. This exception will be thrown if this method
// shouldn't be invoked this way. Might be able to be handled a bit better, but it works.
Exception throwIfBad = new BadMethodException("This method cannot be invoked in the context of an " +
nameof(Equation), method);
// Basic method property check.
if (method.IsAbstract || method.IsConstructor || method.IsGenericMethod || !method.IsPublic)
throw throwIfBad;
// Check if a valid number of arguments is provided and the first one takes a number.
ParameterInfo[] paramTypes = method.GetParameters();
int requiredParams = 0;
while (requiredParams < paramTypes.Length && !paramTypes[requiredParams].IsOptional) requiredParams++;
args ??= Array.Empty<object>();
if (args.Length + 1 < requiredParams || args.Length > paramTypes.Length) throw throwIfBad;
if (paramTypes.Length < 1) throw throwIfBad;
if (!ValidNumberTypes.Contains(paramTypes[0].ParameterType)) throw throwIfBad;
// Check if the return type is also a number.
if (!ValidNumberTypes.Contains(method.ReturnType)) throw throwIfBad;
// This is a good method. Generate the arguments required using the equation and invoke it.
// The first item in this list will be the float value of the equation.
List<object?> invokeArgs = new() { 0 };
invokeArgs.AddRange(args);
return delegate (float x)
{
// Invoke the method (with some casting of course).
invokeArgs[0] = Convert.ChangeType(equ(x), method.ReturnType);
object? result = method.Invoke(null, invokeArgs.ToArray());
if (result is null) throw new UndefinedException($"Invoked method \"{method.Name}\" returned null " +
"for this input.");
return (float)Convert.ChangeType(result, typeof(float));
};
}
public static Equation InvokeMathMethod(this Equation equ, string name, params object?[]? args)
{
// Check a couple math classes to see if the method is found. If at least one is found,
// compare the parameters and return type to what is expected. If more than one perfect
// match exists, the first one will be selected.
args ??= Array.Empty<object>();
Type[] toCheck = { typeof(Mathf), typeof(Math) }; // This is the order methods should be searched in.
foreach (Type t in toCheck)
{
// Basic property and return checks.
List<MethodInfo> possibleMethods = (from m in t.GetMethods()
let basicCheck = !m.IsAbstract && !m.IsConstructor &&
!m.IsGenericMethod && m.IsPublic
let nameCheck = m.Name == name
let returnCheck = ValidNumberTypes.Contains(m.ReturnType)
where basicCheck && nameCheck && returnCheck
select m).ToList();
if (possibleMethods.Count < 1) continue;
foreach (MethodInfo m in possibleMethods)
{
// Check if a valid number of arguments is provided and the first one takes a number.
ParameterInfo[] paramTypes = m.GetParameters();
int requiredParams = 0;
while (requiredParams < paramTypes.Length && !paramTypes[requiredParams].IsOptional) requiredParams++;
args ??= Array.Empty<object>();
if (args.Length + 1 < requiredParams || args.Length > paramTypes.Length) continue;
if (paramTypes.Length < 1) continue;
if (!ValidNumberTypes.Contains(paramTypes[0].ParameterType)) continue;
// This is a good method. Generate the arguments required using the equation and invoke it.
// The first item in this list will be the float value of the equation.
List<object?> invokeArgs = new() { 0 };
invokeArgs.AddRange(args);
return delegate (float x)
{
// Invoke the method (with some casting of course).
invokeArgs[0] = Convert.ChangeType(equ(x), m.ReturnType);
object? result = m.Invoke(null, invokeArgs.ToArray());
if (result is null) throw new UndefinedException($"Invoked method \"{m.Name}\" returned " +
"null for this input.");
return (float)Convert.ChangeType(result, typeof(float));
};
}
}
throw new BadMethodException("No method that fits this criteria found in the math types.");
}
public static Equation Scale(this Equation equ, float value, ScaleType type = ScaleType.Both) => type switch
{
ScaleType.X => x => equ(x / value),
ScaleType.Y => x => value * equ(x),
ScaleType.Both => x => value * equ(x / value),
_ => throw new ArgumentException("Unknown scale type " + type)
};
}

View File

@ -0,0 +1,101 @@
namespace Nerd_STF.Extensions;
public static class StringExtension
{
public static string? GetSection(this string str, string prefix, bool includeFix = true, int startIndex = 0,
int? endIndex = null)
{
endIndex ??= str.Length;
int start = str.IndexOf(prefix, startIndex);
if (start == -1 || start > endIndex.Value) return null;
int end = str.IndexOf(prefix, start + prefix.Length);
if (end == -1) end = str.Length;
else if (end > endIndex.Value) end = endIndex.Value;
if (includeFix)
{
start += prefix.Length;
if (start > end) return null;
}
return str[start..end];
}
public static string? GetSection(this string str, string prefix, string suffix, bool includeFix = true,
int startIndex = 0, int? endIndex = null)
{
endIndex ??= str.Length;
int start = str.IndexOf(prefix, startIndex);
if (start == -1 || start > endIndex.Value) return null;
int end = str.IndexOf(suffix, start + prefix.Length);
if (end == -1) return null;
else if (end > endIndex.Value) end = endIndex.Value;
if (includeFix) start += prefix.Length;
else end += suffix.Length;
if (start > end) return null;
return str[start..end];
}
public static string[] GetSections(this string str, string prefix, bool includeFix = true, int startIndex = 0,
int? endIndex = null)
{
endIndex ??= str.Length;
List<string> sections = new();
for (int i = startIndex; i < endIndex && i < str.Length; )
{
int start = str.IndexOf(prefix, startIndex);
if (start == -1 || start > endIndex.Value) break;
int end = str.IndexOf(prefix, start + prefix.Length);
if (end == -1) end = str.Length;
else if (end > endIndex.Value) end = endIndex.Value;
if (includeFix)
{
start += prefix.Length;
if (start > end) break;
}
sections.Add(str[start..end]);
i = end;
}
return sections.ToArray();
}
public static string[] GetSections(this string str, string prefix, string suffix, bool includeFix = true, int startIndex = 0,
int? endIndex = null)
{
endIndex ??= str.Length;
List<string> sections = new();
for (int i = startIndex; i < endIndex && i < str.Length; )
{
endIndex ??= str.Length;
int start = str.IndexOf(prefix, i);
if (start == -1 || start > endIndex.Value) break;
int end = str.IndexOf(suffix, start + prefix.Length);
if (end == -1) break;
else if (end > endIndex.Value) end = endIndex.Value;
if (includeFix) start += prefix.Length;
else end += suffix.Length;
if (start > end) break;
i = end;
sections.Add(str[start..end]);
}
return sections.ToArray();
}
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Extensions;
public static class ToFillExtension
{
public static Fill<T> ToFill<T>(this IEnumerable<T> group) => i => group.ElementAt(i);
}

13
Nerd_STF/FileType.cs Normal file
View File

@ -0,0 +1,13 @@
namespace Nerd_STF;
public enum FileType
{
None = 0,
BMP,
HEIC,
JPEG,
MTL,
PNG,
TIFF,
WEBP,
}

3
Nerd_STF/Fill2D.cs Normal file
View File

@ -0,0 +1,3 @@
namespace Nerd_STF;
public delegate T Fill2d<T>(int indexX, int indexY);

View File

@ -0,0 +1,29 @@
using System.Numerics;
namespace Nerd_STF.Graphics.Abstract;
public interface IColor : IEquatable<IColor>
{
public CMYKA ToCMYKA();
public HSVA ToHSVA();
public RGBA ToRGBA();
public CMYKAByte ToCMYKAByte();
public HSVAByte ToHSVAByte();
public RGBAByte ToRGBAByte();
}
public interface IColor<T> : IColor where T : IColor<T>
{
public static abstract bool operator ==(T a, CMYKA b);
public static abstract bool operator !=(T a, CMYKA b);
public static abstract bool operator ==(T a, CMYKAByte b);
public static abstract bool operator !=(T a, CMYKAByte b);
public static abstract bool operator ==(T a, HSVA b);
public static abstract bool operator !=(T a, HSVA b);
public static abstract bool operator ==(T a, HSVAByte b);
public static abstract bool operator !=(T a, HSVAByte b);
public static abstract bool operator ==(T a, RGBA b);
public static abstract bool operator !=(T a, RGBA b);
public static abstract bool operator ==(T a, RGBAByte b);
public static abstract bool operator !=(T a, RGBAByte b);
}

View File

@ -0,0 +1,9 @@
namespace Nerd_STF.Graphics.Abstract;
public interface IColorByte : IColor, IGroup<byte>
{
public int[] ToArrayInt();
public Fill<int> ToFillInt();
public List<int> ToListInt();
}
public interface IColorByte<T> : IColor<T>, IColorByte where T : struct, IColorByte<T> { }

View File

@ -0,0 +1,4 @@
namespace Nerd_STF.Graphics.Abstract;
public interface IColorFloat : IColor, IGroup<float> { }
public interface IColorFloat<T> : IColor<T>, IColorFloat where T : struct, IColorFloat<T> { }

View File

@ -0,0 +1,17 @@
namespace Nerd_STF.Graphics.Abstract;
public interface IColorPresets<T> where T : IColorPresets<T>
{
public static abstract T Black { get; }
public static abstract T Blue { get; }
public static abstract T Clear { get; }
public static abstract T Cyan { get; }
public static abstract T Gray { get; }
public static abstract T Green { get; }
public static abstract T Magenta { get; }
public static abstract T Orange { get; }
public static abstract T Purple { get; }
public static abstract T Red { get; }
public static abstract T White { get; }
public static abstract T Yellow { get; }
}

253
Nerd_STF/Graphics/CMYKA.cs Normal file
View File

@ -0,0 +1,253 @@
namespace Nerd_STF.Graphics;
public record struct CMYKA : IAverage<CMYKA>, IClamp<CMYKA>, IColorFloat<CMYKA>, IColorPresets<CMYKA>,
IEquatable<CMYKA>, IIndexAll<float>, IIndexRangeAll<float>, ILerp<CMYKA, float>, IMedian<CMYKA>,
ISplittable<CMYKA, (float[] Cs, float[] Ms, float[] Ys, float[] Ks, float[] As)>
{
public static CMYKA Black => new(0, 0, 0, 1);
public static CMYKA Blue => new(1, 1, 0, 0);
public static CMYKA Clear => new(0, 0, 0, 0, 0);
public static CMYKA Cyan => new(1, 0, 0, 0);
public static CMYKA Gray => new(0, 0, 0, 0.5f);
public static CMYKA Green => new(1, 0, 1, 0);
public static CMYKA Magenta => new(0, 1, 0, 0);
public static CMYKA Orange => new(0, 0.5f, 1, 0);
public static CMYKA Purple => new(0.5f, 1, 0, 0);
public static CMYKA Red => new(0, 1, 1, 0);
public static CMYKA White => new(0, 0, 0, 0);
public static CMYKA Yellow => new(0, 0, 1, 0);
public float C
{
get => p_c;
set => p_c = Mathf.Clamp(value, 0, 1);
}
public float M
{
get => p_m;
set => p_m = Mathf.Clamp(value, 0, 1);
}
public float Y
{
get => p_y;
set => p_y = Mathf.Clamp(value, 0, 1);
}
public float K
{
get => p_k;
set => p_k = Mathf.Clamp(value, 0, 1);
}
public float A
{
get => p_a;
set => p_a = Mathf.Clamp(value, 0, 1);
}
public bool HasCyan => p_c > 0;
public bool HasMagenta => p_m > 0;
public bool HasYellow => p_y > 0;
public bool HasBlack => p_k > 0;
public bool IsOpaque => p_a == 1;
public bool IsVisible => p_a != 0;
private float p_c, p_m, p_y, p_k, p_a;
public CMYKA() : this(0, 0, 0, 0, 1) { }
public CMYKA(float all) : this(all, all, all, all, all) { }
public CMYKA(float all, float a) : this(all, all, all, all, a) { }
public CMYKA(float c, float m, float y, float k) : this(c, m, y, k, 1) { }
public CMYKA(float c, float m, float y, float k, float a)
{
p_c = Mathf.Clamp(c, 0, 1);
p_m = Mathf.Clamp(m, 0, 1);
p_y = Mathf.Clamp(y, 0, 1);
p_k = Mathf.Clamp(k, 0, 1);
p_a = Mathf.Clamp(a, 0, 1);
}
public CMYKA(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4)) { }
public float this[int index]
{
get => index switch
{
0 => C,
1 => M,
2 => Y,
3 => K,
4 => A,
_ => throw new IndexOutOfRangeException(nameof(index)),
};
set
{
switch (index)
{
case 0:
C = value;
break;
case 1:
M = value;
break;
case 2:
Y = value;
break;
case 3:
K = value;
break;
case 4:
A = value;
break;
default: throw new IndexOutOfRangeException(nameof(index));
}
}
}
public float this[Index index]
{
get => this[index.IsFromEnd ? 5 - index.Value : index.Value];
set => this[index.IsFromEnd ? 5 - index.Value : index.Value] = value;
}
public float[] this[Range range]
{
get
{
int start = range.Start.IsFromEnd ? 5 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 5 - range.End.Value : range.End.Value;
List<float> res = new();
for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray();
}
set
{
int start = range.Start.IsFromEnd ? 5 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 5 - range.End.Value : range.End.Value;
for (int i = start; i < end; i++) this[i] = value[i];
}
}
public static CMYKA Average(params CMYKA[] vals)
{
CMYKA val = new(0, 0, 0, 0, 0);
for (int i = 0; i < vals.Length; i++) val += vals[i];
return val / vals.Length;
}
public static CMYKA Clamp(CMYKA val, CMYKA min, CMYKA max) =>
new(Mathf.Clamp(val.C, min.C, max.C),
Mathf.Clamp(val.M, min.M, max.M),
Mathf.Clamp(val.Y, min.Y, max.Y),
Mathf.Clamp(val.K, min.K, max.K),
Mathf.Clamp(val.A, min.A, max.A));
public static CMYKA Lerp(CMYKA a, CMYKA b, float t, bool clamp = true) =>
new(Mathf.Lerp(a.C, b.C, t, clamp), Mathf.Lerp(a.M, b.M, t, clamp), Mathf.Lerp(a.Y, b.Y, t, clamp),
Mathf.Lerp(a.K, b.K, t, clamp), Mathf.Lerp(a.A, b.A, t, clamp));
public static CMYKA LerpSquared(CMYKA a, CMYKA b, float t, bool clamp = true)
{
CMYKA val = Lerp(a * a, b * b, t, clamp);
float C = Mathf.Sqrt(val.C), M = Mathf.Sqrt(val.M), Y = Mathf.Sqrt(val.Y), K = Mathf.Sqrt(val.K), A = Mathf.Sqrt(val.A);
return new(C, M, Y, K, A);
}
public static CMYKA Median(params CMYKA[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
CMYKA valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static (float[] Cs, float[] Ms, float[] Ys, float[] Ks, float[] As) SplitArray(params CMYKA[] vals)
{
float[] Cs = new float[vals.Length], Ms = new float[vals.Length],
Ys = new float[vals.Length], Ks = new float[vals.Length],
As = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Cs[i] = vals[i].C;
Ms[i] = vals[i].M;
Ys[i] = vals[i].Y;
Ks[i] = vals[i].K;
As[i] = vals[i].A;
}
return (Cs, Ms, Ys, Ks, As);
}
public bool Equals(IColor? col) => col != null && Equals(col.ToCMYKA());
public bool Equals(CMYKA col) => A == 0 && col.A == 0 || K == 1 && col.K == 1 || C == col.C && M == col.M
&& Y == col.Y && K == col.K && A == col.A;
public override int GetHashCode() => base.GetHashCode();
public RGBA ToRGBA()
{
float kInv = 1 - K, r = 1 - C, g = 1 - M, b = 1 - Y;
return new(r * kInv, g * kInv, b * kInv, A);
}
public CMYKA ToCMYKA() => this;
public HSVA ToHSVA() => ToRGBA().ToHSVA();
public RGBAByte ToRGBAByte() => ToRGBA().ToRGBAByte();
public CMYKAByte ToCMYKAByte() => new(Mathf.RoundInt(C * 255), Mathf.RoundInt(M * 255), Mathf.RoundInt(Y * 255),
Mathf.RoundInt(K * 255), Mathf.RoundInt(A * 255));
public HSVAByte ToHSVAByte() => ToRGBA().ToHSVAByte();
public float[] ToArray() => new[] { C, M, Y, K, A };
public Fill<float> ToFill()
{
CMYKA @this = this;
return i => @this[i];
}
public List<float> ToList() => new() { C, M, Y, K, A };
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
{
yield return C;
yield return M;
yield return Y;
yield return K;
yield return A;
}
private bool PrintMembers(StringBuilder builder)
{
builder.Append("C = ");
builder.Append(C);
builder.Append(", M = ");
builder.Append(M);
builder.Append(", Y = ");
builder.Append(Y);
builder.Append(", K = ");
builder.Append(K);
builder.Append(", A = ");
builder.Append(A);
return true;
}
public static CMYKA operator +(CMYKA a, CMYKA b) => new(a.C + b.C, a.M + b.M, a.Y + b.Y, a.K + b.K, a.A + b.A);
public static CMYKA operator -(CMYKA c) => new(1 - c.C, 1 - c.M, 1 - c.Y, 1 - c.K, c.A != 1 ? 1 - c.A : 1);
public static CMYKA operator -(CMYKA a, CMYKA b) => new(a.C - b.C, a.M - b.M, a.Y - b.Y, a.K - b.K, a.A - b.A);
public static CMYKA operator *(CMYKA a, CMYKA b) => new(a.C * b.C, a.M * b.M, a.Y * b.Y, a.K * b.K, a.A * b.A);
public static CMYKA operator *(CMYKA a, float b) => new(a.C * b, a.M * b, a.Y * b, a.K * b, a.A * b);
public static CMYKA operator /(CMYKA a, CMYKA b) => new(a.C / b.C, a.M / b.M, a.Y / b.Y, a.K / b.K, a.A / b.A);
public static CMYKA operator /(CMYKA a, float b) => new(a.C / b, a.M / b, a.Y / b, a.K / b, a.A / b);
public static bool operator ==(CMYKA a, HSVA b) => a.Equals(b);
public static bool operator !=(CMYKA a, HSVA b) => a.Equals(b);
public static bool operator ==(CMYKA a, RGBA b) => a.Equals(b);
public static bool operator !=(CMYKA a, RGBA b) => a.Equals(b);
public static bool operator ==(CMYKA a, CMYKAByte b) => a.Equals(b);
public static bool operator !=(CMYKA a, CMYKAByte b) => a.Equals(b);
public static bool operator ==(CMYKA a, HSVAByte b) => a.Equals(b);
public static bool operator !=(CMYKA a, HSVAByte b) => a.Equals(b);
public static bool operator ==(CMYKA a, RGBAByte b) => a.Equals(b);
public static bool operator !=(CMYKA a, RGBAByte b) => a.Equals(b);
public static explicit operator CMYKA(Float3 val) => new(val.x, val.y, val.z, 0);
public static implicit operator CMYKA(Float4 val) => new(val.x, val.y, val.z, val.w);
public static implicit operator CMYKA(HSVA val) => val.ToCMYKA();
public static implicit operator CMYKA(RGBA val) => val.ToCMYKA();
public static implicit operator CMYKA(CMYKAByte val) => val.ToCMYKA();
public static implicit operator CMYKA(HSVAByte val) => val.ToCMYKA();
public static implicit operator CMYKA(RGBAByte val) => val.ToCMYKA();
public static implicit operator CMYKA(Fill<float> val) => new(val);
}

View File

@ -0,0 +1,276 @@
namespace Nerd_STF.Graphics;
public record struct CMYKAByte : IAverage<CMYKAByte>, IClamp<CMYKAByte>, IColorByte<CMYKAByte>,
IColorPresets<CMYKAByte>, IEquatable<CMYKAByte>, IIndexAll<int>, IIndexRangeAll<int>,
ILerp<CMYKAByte, float>, IMedian<CMYKAByte>,
ISplittable<CMYKAByte, (byte[] Cs, byte[] Ms, byte[] Ys, byte[] Ks, byte[] As)>
{
public static CMYKAByte Black => new(0, 0, 0, 255);
public static CMYKAByte Blue => new(255, 255, 0, 0);
public static CMYKAByte Clear => new(0, 0, 0, 0, 0);
public static CMYKAByte Cyan => new(255, 0, 0, 0);
public static CMYKAByte Gray => new(0, 0, 0, 127);
public static CMYKAByte Green => new(255, 0, 255, 0);
public static CMYKAByte Magenta => new(0, 255, 0, 0);
public static CMYKAByte Orange => new(0, 127, 255, 0);
public static CMYKAByte Purple => new(127, 255, 0, 0);
public static CMYKAByte Red => new(0, 255, 255, 0);
public static CMYKAByte White => new(0, 0, 0, 0);
public static CMYKAByte Yellow => new(0, 0, 255, 0);
public int C
{
get => p_c;
set => p_c = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
public int M
{
get => p_m;
set => p_m = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
public int Y
{
get => p_y;
set => p_y = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
public int K
{
get => p_k;
set => p_k = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
public int A
{
get => p_a;
set => p_a = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
private byte p_c, p_m, p_y, p_k, p_a;
public bool HasCyan => C > 0;
public bool HasMagenta => M > 0;
public bool HasYellow => Y > 0;
public bool HasBlack => K > 0;
public bool IsOpaque => A == 255;
public bool IsVisible => A != 0;
public CMYKAByte() : this(0, 0, 0, 0, 255) { }
public CMYKAByte(int all) : this(all, all, all, all, all) { }
public CMYKAByte(int all, int a) : this(all, all, all, all, a) { }
public CMYKAByte(int c, int m, int y, int k) : this(c, m, y, k, 255) { }
public CMYKAByte(int c, int m, int y, int k, int a)
{
p_c = (byte)Mathf.Clamp(c, 0, 255);
p_m = (byte)Mathf.Clamp(m, 0, 255);
p_y = (byte)Mathf.Clamp(y, 0, 255);
p_k = (byte)Mathf.Clamp(k, 0, 255);
p_a = (byte)Mathf.Clamp(a, 0, 255);
}
public CMYKAByte(Fill<byte> fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4)) { }
public CMYKAByte(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4)) { }
public int this[int index]
{
get => index switch
{
0 => C,
1 => M,
2 => Y,
3 => K,
4 => A,
_ => throw new IndexOutOfRangeException(nameof(index)),
};
set
{
switch (index)
{
case 0:
C = value;
break;
case 1:
M = value;
break;
case 2:
Y = value;
break;
case 3:
K = value;
break;
case 4:
A = value;
break;
default: throw new IndexOutOfRangeException(nameof(index));
}
}
}
public int this[Index index]
{
get => this[index.IsFromEnd ? 5 - index.Value : index.Value];
set => this[index.IsFromEnd ? 5 - index.Value : index.Value] = value;
}
public int[] this[Range range]
{
get
{
int start = range.Start.IsFromEnd ? 5 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 5 - range.End.Value : range.End.Value;
List<int> res = new();
for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray();
}
set
{
int start = range.Start.IsFromEnd ? 5 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 5 - range.End.Value : range.End.Value;
for (int i = start; i < end; i++) this[i] = value[i];
}
}
public static CMYKAByte Average(params CMYKAByte[] vals)
{
CMYKAByte val = new(0, 0, 0, 0, 0);
for (int i = 0; i < vals.Length; i++) val += vals[i];
return val / vals.Length;
}
public static CMYKAByte Clamp(CMYKAByte val, CMYKAByte min, CMYKAByte max) =>
new(Mathf.Clamp(val.C, min.C, max.C),
Mathf.Clamp(val.M, min.M, max.M),
Mathf.Clamp(val.Y, min.Y, max.Y),
Mathf.Clamp(val.K, min.K, max.K),
Mathf.Clamp(val.A, min.A, max.A));
public static CMYKAByte Lerp(CMYKAByte a, CMYKAByte b, float t, bool clamp = true) =>
new(Mathf.Lerp(a.C, b.C, t, clamp), Mathf.Lerp(a.M, b.M, t, clamp), Mathf.Lerp(a.Y, b.Y, t, clamp),
Mathf.Lerp(a.K, b.K, t, clamp), Mathf.Lerp(a.A, b.A, t, clamp));
public static CMYKAByte LerpSquared(CMYKAByte a, CMYKAByte b, float t, bool clamp = true) => CMYKA.LerpSquared(a.ToCMYKA(), b.ToCMYKA(), t, clamp).ToCMYKAByte();
public static CMYKAByte Median(params CMYKAByte[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
CMYKAByte valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static (byte[] Cs, byte[] Ms, byte[] Ys, byte[] Ks, byte[] As) SplitArray(params CMYKAByte[] vals)
{
byte[] Cs = new byte[vals.Length], Ms = new byte[vals.Length],
Ys = new byte[vals.Length], Ks = new byte[vals.Length],
As = new byte[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Cs[i] = vals[i].p_c;
Ms[i] = vals[i].p_m;
Ys[i] = vals[i].p_y;
Ks[i] = vals[i].p_k;
As[i] = vals[i].p_a;
}
return (Cs, Ms, Ys, Ks, As);
}
public static (int[] Cs, int[] Ms, int[] Ys, int[] Ks, int[] As) SplitArrayInt(params CMYKAByte[] vals)
{
int[] Cs = new int[vals.Length], Ms = new int[vals.Length],
Ys = new int[vals.Length], Ks = new int[vals.Length],
As = new int[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Cs[i] = vals[i].C;
Ms[i] = vals[i].M;
Ys[i] = vals[i].Y;
Ks[i] = vals[i].K;
As[i] = vals[i].A;
}
return (Cs, Ms, Ys, Ks, As);
}
public bool Equals(CMYKAByte col) => A == 0 && col.A == 0 || K == 1 && col.K == 255 || C == col.C && M == col.M
&& Y == col.Y && K == col.K && A == col.A;
public bool Equals(IColor? col) => col != null && Equals(col.ToCMYKAByte());
public override int GetHashCode() => base.GetHashCode();
public RGBA ToRGBA() => ToCMYKA().ToRGBA();
public CMYKA ToCMYKA() => new(C / 255f, M / 255f, Y / 255f, K / 255f, A / 255f);
public HSVA ToHSVA() => ToCMYKA().ToHSVA();
public RGBAByte ToRGBAByte() => ToCMYKA().ToRGBAByte();
public CMYKAByte ToCMYKAByte() => this;
public HSVAByte ToHSVAByte() => ToCMYKA().ToHSVAByte();
public byte[] ToArray() => new[] { p_c, p_m, p_y, p_k, p_a };
public int[] ToArrayInt() => new[] { C, M, Y, K, A };
public Fill<byte> ToFill()
{
CMYKAByte @this = this;
return i => (byte)@this[i];
}
public Fill<int> ToFillInt()
{
CMYKAByte @this = this;
return i => @this[i];
}
public List<byte> ToList() => new() { p_c, p_m, p_y, p_k, p_a };
public List<int> ToListInt() => new() { C, M, Y, K, A };
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<byte> GetEnumerator()
{
yield return p_c;
yield return p_m;
yield return p_y;
yield return p_k;
yield return p_a;
}
private bool PrintMembers(StringBuilder builder)
{
builder.Append("C = ");
builder.Append(C);
builder.Append(", M = ");
builder.Append(M);
builder.Append(", Y = ");
builder.Append(Y);
builder.Append(", K = ");
builder.Append(K);
builder.Append(", A = ");
builder.Append(A);
return true;
}
public static CMYKAByte operator +(CMYKAByte a, CMYKAByte b) =>
new(a.C + b.C, a.M + b.M, a.Y + b.Y, a.K + b.K, a.A + b.A);
public static CMYKAByte operator -(CMYKAByte c) =>
new(255 - c.C, 255 - c.M, 255 - c.Y, 255 - c.K, c.A != 255 ? 255 - c.A : 255);
public static CMYKAByte operator -(CMYKAByte a, CMYKAByte b) =>
new(a.C - b.C, a.M - b.M, a.Y - b.Y, a.K - b.K, a.A - b.A);
public static CMYKAByte operator *(CMYKAByte a, CMYKAByte b) =>
new(a.C * b.C, a.M * b.M, a.Y * b.Y, a.K * b.K, a.A * b.A);
public static CMYKAByte operator *(CMYKAByte a, int b) =>
new(a.C * b, a.M * b, a.Y * b, a.K * b, a.A * b);
public static CMYKAByte operator *(CMYKAByte a, float b) => (a.ToCMYKA() * b).ToCMYKAByte();
public static CMYKAByte operator /(CMYKAByte a, CMYKAByte b) =>
new(a.C / b.C, a.M / b.M, a.Y / b.Y, a.K / b.K, a.A / b.A);
public static CMYKAByte operator /(CMYKAByte a, int b) =>
new(a.C / b, a.M / b, a.Y / b, a.K / b, a.A / b);
public static CMYKAByte operator /(CMYKAByte a, float b) => (a.ToCMYKA() / b).ToCMYKAByte();
public static bool operator ==(CMYKAByte a, CMYKA b) => a.Equals(b);
public static bool operator !=(CMYKAByte a, CMYKA b) => a.Equals(b);
public static bool operator ==(CMYKAByte a, HSVA b) => a.Equals(b);
public static bool operator !=(CMYKAByte a, HSVA b) => a.Equals(b);
public static bool operator ==(CMYKAByte a, RGBA b) => a.Equals(b);
public static bool operator !=(CMYKAByte a, RGBA b) => a.Equals(b);
public static bool operator ==(CMYKAByte a, HSVAByte b) => a.Equals(b);
public static bool operator !=(CMYKAByte a, HSVAByte b) => a.Equals(b);
public static bool operator ==(CMYKAByte a, RGBAByte b) => a.Equals(b);
public static bool operator !=(CMYKAByte a, RGBAByte b) => a.Equals(b);
public static explicit operator CMYKAByte(Int3 val) => new(val.x, val.y, val.z, 0);
public static implicit operator CMYKAByte(Int4 val) => new(val.x, val.y, val.z, val.w);
public static implicit operator CMYKAByte(HSVA val) => val.ToCMYKAByte();
public static implicit operator CMYKAByte(RGBA val) => val.ToCMYKAByte();
public static implicit operator CMYKAByte(CMYKA val) => val.ToCMYKAByte();
public static implicit operator CMYKAByte(HSVAByte val) => val.ToCMYKAByte();
public static implicit operator CMYKAByte(RGBAByte val) => val.ToCMYKAByte();
public static implicit operator CMYKAByte(Fill<byte> val) => new(val);
public static implicit operator CMYKAByte(Fill<int> val) => new(val);
}

View File

@ -0,0 +1,19 @@
namespace Nerd_STF.Graphics;
public enum ColorChannel : byte
{
Alpha,
Black,
Blue,
Cyan,
Green,
Hue,
Luminance,
Magenta,
Matte,
Red,
Saturation,
Yellow,
Value,
ZDepth
}

249
Nerd_STF/Graphics/HSVA.cs Normal file
View File

@ -0,0 +1,249 @@
namespace Nerd_STF.Graphics;
public record struct HSVA : IAverage<HSVA>, IClamp<HSVA>, IColorFloat<HSVA>, IColorPresets<HSVA>, IEquatable<HSVA>,
IIndexAll<float>, IIndexRangeAll<float>, ILerp<HSVA, float>, IMedian<HSVA>,
ISplittable<HSVA, (Angle[] Hs, float[] Ss, float[] Vs, float[] As)>
{
public static HSVA Black => new(Angle.Zero, 0, 0);
public static HSVA Blue => new(new Angle(240), 1, 1);
public static HSVA Clear => new(Angle.Zero, 0, 0, 0);
public static HSVA Cyan => new(new Angle(180), 1, 1);
public static HSVA Gray => new(Angle.Zero, 0, 0.5f);
public static HSVA Green => new(new Angle(120), 1, 1);
public static HSVA Magenta => new(new Angle(300), 1, 1);
public static HSVA Orange => new(new Angle(30), 1, 1);
public static HSVA Purple => new(new Angle(270), 1, 1);
public static HSVA Red => new(Angle.Zero, 1, 1);
public static HSVA White => new(Angle.Zero, 0, 1);
public static HSVA Yellow => new(new Angle(60), 1, 1);
public Angle H
{
get => p_h;
set => p_h = value.Bounded;
}
public float S
{
get => p_s;
set => p_s = Mathf.Clamp(value, 0, 1);
}
public float V
{
get => p_v;
set => p_v = Mathf.Clamp(value, 0, 1);
}
public float A
{
get => p_a;
set => p_a = Mathf.Clamp(value, 0, 1);
}
private Angle p_h;
private float p_s, p_v, p_a;
public bool HasColor => p_s != 0 && p_v != 0;
public bool IsOpaque => p_a == 1;
public bool IsVisible => p_a != 0;
public HSVA() : this(Angle.Zero, 0, 0, 1) { }
public HSVA(Angle h, float s, float v) : this(h, s, v, 1) { }
public HSVA(Angle h, float s, float v, float a)
{
p_h = h.Bounded;
p_s = Mathf.Clamp(s, 0, 1);
p_v = Mathf.Clamp(v, 0, 1);
p_a = Mathf.Clamp(a, 0, 1);
}
public HSVA(Angle h, Fill<float> fill) : this(h, fill(0), fill(1), fill(2)) { }
public HSVA(Angle h, Fill<int> fill) : this(h, fill(0), fill(1), fill(2)) { }
public HSVA(float h, float s, float v) : this(new Angle(h, Angle.Type.Normalized), s, v) { }
public HSVA(float h, float s, float v, float a) : this(new Angle(h, Angle.Type.Normalized), s, v, a) { }
public HSVA(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public HSVA(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public float this[int index]
{
get => index switch
{
0 => H.Normalized,
1 => S,
2 => V,
3 => A,
_ => throw new IndexOutOfRangeException(nameof(index)),
};
set
{
switch (index)
{
case 0:
H = new(value, Angle.Type.Normalized);
break;
case 1:
S = value;
break;
case 2:
V = value;
break;
case 3:
A = value;
break;
default: throw new IndexOutOfRangeException(nameof(index));
}
}
}
public float this[Index index]
{
get => this[index.IsFromEnd ? 4 - index.Value : index.Value];
set => this[index.IsFromEnd ? 4 - index.Value : index.Value] = value;
}
public float[] this[Range range]
{
get
{
int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value;
List<float> res = new();
for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray();
}
set
{
int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value;
for (int i = start; i < end; i++) this[i] = value[i];
}
}
public static HSVA Average(params HSVA[] vals)
{
HSVA val = new(Angle.Zero, 0, 0, 0);
for (int i = 0; i < vals.Length; i++) val += vals[i];
return val / vals.Length;
}
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 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));
public static HSVA Median(params HSVA[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
HSVA valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static (Angle[] Hs, float[] Ss, float[] Vs, float[] As) SplitArray(params HSVA[] vals)
{
Angle[] Hs = new Angle[vals.Length];
float[] Ss = new float[vals.Length], Vs = new float[vals.Length],
As = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Hs[i] = vals[i].H;
Ss[i] = vals[i].S;
Vs[i] = vals[i].V;
As[i] = vals[i].A;
}
return (Hs, Ss, Vs, As);
}
public static (float[] Hs, float[] Ss, float[] Vs, float[] As) SplitArrayNormalized(params HSVA[] vals)
{
float[] Hs = new float[vals.Length], Ss = new float[vals.Length],
Vs = new float[vals.Length], As = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Hs[i] = vals[i].H.Normalized;
Ss[i] = vals[i].S;
Vs[i] = vals[i].V;
As[i] = vals[i].A;
}
return (Hs, Ss, Vs, As);
}
public bool Equals(IColor? col) => col != null && Equals(col.ToHSVA());
public bool Equals(HSVA col) => S == 0 && col.S == 0 || V == 0 && col.V == 0 || A == 0 && col.A == 0
|| H == col.H && S == col.S && V == col.V && A == col.A;
public override int GetHashCode() => base.GetHashCode();
public RGBA ToRGBA()
{
float d = H.Degrees, c = V * S, x = c * (1 - Mathf.Absolute(d / 60 % 2 - 1)), m = V - c;
(float r, float g, float b) vals = (0, 0, 0);
if (d < 60) vals = (c, x, 0);
else if (d < 120) vals = (x, c, 0);
else if (d < 180) vals = (0, c, x);
else if (d < 240) vals = (0, x, c);
else if (d < 300) vals = (x, 0, c);
else if (d < 360) vals = (c, 0, x);
return new(vals.r + m, vals.g + m, vals.b + m, A);
}
public CMYKA ToCMYKA() => ToRGBA().ToCMYKA();
public HSVA ToHSVA() => this;
public RGBAByte ToRGBAByte() => ToRGBA().ToRGBAByte();
public CMYKAByte ToCMYKAByte() => ToRGBA().ToCMYKAByte();
public HSVAByte ToHSVAByte() => new(Mathf.RoundInt(H.Normalized * 255), Mathf.RoundInt(S * 255),
Mathf.RoundInt(V * 255), Mathf.RoundInt(A * 255));
public float[] ToArray() => new[] { H.Normalized, S, V, A };
public Fill<float> ToFill()
{
HSVA @this = this;
return i => @this[i];
}
public List<float> ToList() => new() { H.Normalized, S, V, A };
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
{
yield return H.Normalized;
yield return S;
yield return V;
yield return A;
}
private bool PrintMembers(StringBuilder builder)
{
builder.Append("H = ");
builder.Append(H);
builder.Append(", S = ");
builder.Append(S);
builder.Append(", V = ");
builder.Append(V);
builder.Append(", A = ");
builder.Append(A);
return true;
}
public static HSVA operator +(HSVA a, HSVA b) => new(a.H + b.H, a.S + b.S, a.V + b.V, a.A + b.A);
public static HSVA operator -(HSVA c) => new(1 - c.H.Normalized, 1 - c.S, 1 - c.V, c.A != 1 ? 1 - c.A : 1);
public static HSVA operator -(HSVA a, HSVA b) => new(a.H - b.H, a.S - b.S, a.V - b.V, a.A - b.A);
public static HSVA operator *(HSVA a, float b) => new(a.H * b, a.S * b, a.V * b, a.A * b);
public static HSVA operator /(HSVA a, float b) => new(a.H / b, a.S / b, a.V / b, a.A / b);
public static bool operator ==(HSVA a, CMYKA b) => a.Equals(b);
public static bool operator !=(HSVA a, CMYKA b) => a.Equals(b);
public static bool operator ==(HSVA a, RGBA b) => a.Equals(b);
public static bool operator !=(HSVA a, RGBA b) => a.Equals(b);
public static bool operator ==(HSVA a, CMYKAByte b) => a.Equals(b);
public static bool operator !=(HSVA a, CMYKAByte b) => a.Equals(b);
public static bool operator ==(HSVA a, HSVAByte b) => a.Equals(b);
public static bool operator !=(HSVA a, HSVAByte b) => a.Equals(b);
public static bool operator ==(HSVA a, RGBAByte b) => a.Equals(b);
public static bool operator !=(HSVA a, RGBAByte b) => a.Equals(b);
public static explicit operator HSVA(Float3 val) => new(val.x, val.y, val.z);
public static explicit operator HSVA(Float4 val) => new(val.x, val.y, val.z, val.w);
public static implicit operator HSVA(CMYKA val) => val.ToHSVA();
public static implicit operator HSVA(RGBA val) => val.ToHSVA();
public static implicit operator HSVA(CMYKAByte val) => val.ToHSVA();
public static implicit operator HSVA(HSVAByte val) => val.ToHSVA();
public static implicit operator HSVA(RGBAByte val) => val.ToHSVA();
public static implicit operator HSVA(Fill<float> val) => new(val);
}

View File

@ -0,0 +1,247 @@
namespace Nerd_STF.Graphics;
public record struct HSVAByte : IAverage<HSVAByte>, IClamp<HSVAByte>, IColorByte<HSVAByte>, IColorPresets<HSVAByte>,
IEquatable<HSVAByte>, IIndexAll<int>, IIndexRangeAll<int>, ILerp<HSVAByte, float>, IMedian<HSVAByte>,
ISplittable<HSVAByte, (byte[] Hs, byte[] Ss, byte[] Vs, byte[] As)>
{
public static HSVAByte Black => new(Angle.Zero, 0, 0);
public static HSVAByte Blue => new(new Angle(240), 255, 255);
public static HSVAByte Clear => new(Angle.Zero, 0, 0, 0);
public static HSVAByte Cyan => new(new Angle(180), 255, 255);
public static HSVAByte Gray => new(Angle.Zero, 0, 127);
public static HSVAByte Green => new(new Angle(120), 255, 255);
public static HSVAByte Magenta => new(new Angle(300), 255, 255);
public static HSVAByte Orange => new(new Angle(30), 255, 255);
public static HSVAByte Purple => new(new Angle(270), 255, 255);
public static HSVAByte Red => new(Angle.Zero, 255, 255);
public static HSVAByte White => new(Angle.Zero, 0, 255);
public static HSVAByte Yellow => new(new Angle(60), 255, 255);
public int H
{
get => p_h;
set => p_h = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
public int S
{
get => p_s;
set => p_s = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
public int V
{
get => p_v;
set => p_v = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
public int A
{
get => p_a;
set => p_a = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
private byte p_h, p_s, p_v, p_a;
public bool HasColor => S != 0 && V != 0;
public bool IsOpaque => A == 255;
public bool IsVisible => A == 0;
public HSVAByte() : this(0, 0, 0, 255) { }
public HSVAByte(int all) : this(all, all, all, all) { }
public HSVAByte(int all, int a) : this(all, all, all, a) { }
public HSVAByte(int h, int s, int v) : this(h, s, v, 255) { }
public HSVAByte(int h, int s, int v, int a)
{
p_h = (byte)Mathf.Clamp(h, 0, 255);
p_s = (byte)Mathf.Clamp(s, 0, 255);
p_v = (byte)Mathf.Clamp(v, 0, 255);
p_a = (byte)Mathf.Clamp(a, 0, 255);
}
public HSVAByte(Angle h, int s, int v) : this(h, s, v, 255) { }
public HSVAByte(Angle h, int s, int v, int a) : this(Mathf.RoundInt(h.Normalized * 255), s, v, a) { }
public HSVAByte(Fill<byte> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public HSVAByte(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public int this[int index]
{
get => index switch
{
0 => H,
1 => S,
2 => V,
3 => A,
_ => throw new IndexOutOfRangeException(nameof(index)),
};
set
{
switch (index)
{
case 0:
H = value;
break;
case 1:
S = value;
break;
case 2:
V = value;
break;
case 3:
A = value;
break;
default: throw new IndexOutOfRangeException(nameof(index));
}
}
}
public int this[Index index]
{
get => this[index.IsFromEnd ? 4 - index.Value : index.Value];
set => this[index.IsFromEnd ? 4 - index.Value : index.Value] = value;
}
public int[] this[Range range]
{
get
{
int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value;
List<int> res = new();
for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray();
}
set
{
int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value;
for (int i = start; i < end; i++) this[i] = value[i];
}
}
public static HSVAByte Average(params HSVAByte[] vals)
{
HSVAByte val = new(0, 0, 0, 0);
for (int i = 0; i < vals.Length; i++) val += vals[i];
return val / vals.Length;
}
public static HSVAByte Clamp(HSVAByte val, HSVAByte min, HSVAByte max) =>
new(Mathf.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 HSVAByte Lerp(HSVAByte a, HSVAByte b, float t, bool clamp = true) =>
new(Mathf.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));
public static HSVAByte Median(params HSVAByte[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
HSVAByte valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static (byte[] Hs, byte[] Ss, byte[] Vs, byte[] As) SplitArray(params HSVAByte[] vals)
{
byte[] Hs = new byte[vals.Length], Ss = new byte[vals.Length],
Vs = new byte[vals.Length], As = new byte[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Hs[i] = vals[i].p_h;
Ss[i] = vals[i].p_s;
Vs[i] = vals[i].p_v;
As[i] = vals[i].p_a;
}
return (Hs, Ss, Vs, As);
}
public static (int[] Hs, int[] Ss, int[] Vs, int[] As) SplitArrayInt(params HSVAByte[] vals)
{
int[] Hs = new int[vals.Length], Ss = new int[vals.Length],
Vs = new int[vals.Length], As = new int[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Hs[i] = vals[i].H;
Ss[i] = vals[i].S;
Vs[i] = vals[i].V;
As[i] = vals[i].A;
}
return (Hs, Ss, Vs, As);
}
public bool Equals(IColor? col) => col != null && Equals(col.ToHSVAByte());
public bool Equals(HSVAByte col) => S == 0 && col.S == 0 || V == 0 && col.V == 0 || A == 0 && col.A == 0
|| H == col.H && S == col.S && V == col.V && A == col.A;
public override int GetHashCode() => base.GetHashCode();
public RGBA ToRGBA() => ToHSVA().ToRGBA();
public CMYKA ToCMYKA() => ToHSVA().ToCMYKA();
public HSVA ToHSVA() => new(H / 255f, S / 255f, V / 255f, A / 255f);
public RGBAByte ToRGBAByte() => ToHSVA().ToRGBAByte();
public CMYKAByte ToCMYKAByte() => ToHSVA().ToCMYKAByte();
public HSVAByte ToHSVAByte() => this;
public byte[] ToArray() => new[] { p_h, p_s, p_v, p_a };
public int[] ToArrayInt() => new[] { H, S, V, A };
public Fill<byte> ToFill()
{
HSVAByte @this = this;
return i => (byte)@this[i];
}
public Fill<int> ToFillInt()
{
HSVAByte @this = this;
return i => @this[i];
}
public List<byte> ToList() => new() { p_h, p_s, p_v, p_a };
public List<int> ToListInt() => new() { H, S, V, A };
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<byte> GetEnumerator()
{
yield return p_h;
yield return p_s;
yield return p_v;
yield return p_a;
}
private bool PrintMembers(StringBuilder builder)
{
builder.Append("H = ");
builder.Append(H);
builder.Append(", S = ");
builder.Append(S);
builder.Append(", V = ");
builder.Append(V);
builder.Append(", A = ");
builder.Append(A);
return true;
}
public static HSVAByte operator +(HSVAByte a, HSVAByte b) => new(a.H + b.H, a.S + b.S, a.V + b.V, a.A + b.A);
public static HSVAByte operator -(HSVAByte c) => new(255 - c.H, 255 - c.S, 255 - c.V, c.A != 255 ? 255 - c.A : 255);
public static HSVAByte operator -(HSVAByte a, HSVAByte b) => new(a.H - b.H, a.S - b.S, a.V - b.V, a.A - b.A);
public static HSVAByte operator *(HSVAByte a, HSVAByte b) => new(a.H * b.H, a.S * b.S, a.V * b.V, a.A * b.A);
public static HSVAByte operator *(HSVAByte a, int b) => new(a.H * b, a.S * b, a.V * b, a.A * b);
public static HSVAByte operator *(HSVAByte a, float b) => (a.ToHSVA() * b).ToHSVAByte();
public static HSVAByte operator /(HSVAByte a, HSVAByte b) => new(a.H / b.H, a.S / b.S, a.V / b.V, a.A / b.A);
public static HSVAByte operator /(HSVAByte a, int b) => new(a.H / b, a.S / b, a.V / b, a.A / b);
public static HSVAByte operator /(HSVAByte a, float b) => (a.ToHSVA() * b).ToHSVAByte();
public static bool operator ==(HSVAByte a, CMYKA b) => a.Equals(b);
public static bool operator !=(HSVAByte a, CMYKA b) => a.Equals(b);
public static bool operator ==(HSVAByte a, HSVA b) => a.Equals(b);
public static bool operator !=(HSVAByte a, HSVA b) => a.Equals(b);
public static bool operator ==(HSVAByte a, RGBA b) => a.Equals(b);
public static bool operator !=(HSVAByte a, RGBA b) => a.Equals(b);
public static bool operator ==(HSVAByte a, CMYKAByte b) => a.Equals(b);
public static bool operator !=(HSVAByte a, CMYKAByte b) => a.Equals(b);
public static bool operator ==(HSVAByte a, RGBAByte b) => a.Equals(b);
public static bool operator !=(HSVAByte a, RGBAByte b) => a.Equals(b);
public static implicit operator HSVAByte(Int3 val) => new(val.x, val.y, val.z);
public static implicit operator HSVAByte(Int4 val) => new(val.x, val.y, val.z, val.w);
public static implicit operator HSVAByte(CMYKA val) => val.ToHSVAByte();
public static implicit operator HSVAByte(HSVA val) => val.ToHSVAByte();
public static implicit operator HSVAByte(RGBA val) => val.ToHSVAByte();
public static implicit operator HSVAByte(CMYKAByte val) => val.ToHSVAByte();
public static implicit operator HSVAByte(RGBAByte val) => val.ToHSVAByte();
public static implicit operator HSVAByte(Fill<byte> val) => new(val);
public static implicit operator HSVAByte(Fill<int> val) => new(val);
}

View File

@ -0,0 +1,14 @@
namespace Nerd_STF.Graphics;
[Flags]
public enum IlluminationFlags : byte
{
Color = 1,
Ambient = 2,
Highlight = 4,
Reflection = 8,
Raytrace = 16,
Fresnel = 32,
Refraction = 64,
Glass = 128,
}

View File

@ -0,0 +1,16 @@
namespace Nerd_STF.Graphics;
public enum IlluminationModel : ushort
{
Mode0 = IlluminationFlags.Color,
Mode1 = IlluminationFlags.Color | IlluminationFlags.Ambient,
Mode2 = IlluminationFlags.Highlight,
Mode3 = IlluminationFlags.Reflection | IlluminationFlags.Raytrace,
Mode4 = IlluminationFlags.Glass | IlluminationFlags.Raytrace,
Mode5 = IlluminationFlags.Fresnel | IlluminationFlags.Raytrace,
Mode6 = IlluminationFlags.Refraction | IlluminationFlags.Raytrace,
Mode7 = IlluminationFlags.Refraction | IlluminationFlags.Fresnel | IlluminationFlags.Raytrace,
Mode8 = IlluminationFlags.Reflection,
Mode9 = IlluminationFlags.Glass,
Mode10 = 256,
}

149
Nerd_STF/Graphics/Image.cs Normal file
View File

@ -0,0 +1,149 @@
namespace Nerd_STF.Graphics;
public class Image : ICloneable, IEnumerable<IColor>, IEquatable<Image>
{
public IColor[,] Pixels { get; private set; }
public Int2 Size { get; private set; }
public Image(int width, int height)
{
Pixels = new IColor[width, height];
Size = new(width, height);
for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) Pixels[x, y] = RGBA.Clear;
}
public Image(int width, int height, IColor[] cols)
{
Pixels = new IColor[width, height];
Size = new(width, height);
for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) Pixels[x, y] = cols[y * width + x];
}
public Image(int width, int height, IColor[,] cols)
{
Pixels = new IColor[width, height];
Size = new(width, height);
for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) Pixels[x, y] = cols[y, x];
}
public Image(int width, int height, Fill<IColor> fill)
{
Pixels = new IColor[width, height];
Size = new(width, height);
for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) Pixels[x, y] = fill(y * width + x);
}
public Image(int width, int height, Fill2d<IColor> fill)
{
Pixels = new IColor[width, height];
Size = new(width, height);
for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) Pixels[x, y] = fill(y, x);
}
public Image(Int2 size) : this(size.x, size.y) { }
public Image(Int2 size, IColor[] cols) : this(size.x, size.y, cols) { }
public Image(Int2 size, IColor[,] cols) : this(size.x, size.y, cols) { }
public Image(Int2 size, Fill<IColor> fill) : this(size.x, size.y, fill) { }
public Image(Int2 size, Fill2d<IColor> fill) : this(size.x, size.y, fill) { }
public IColor this[int indexX, int indexY]
{
get => Pixels[indexX, indexY];
set => Pixels[indexX, indexY] = value;
}
public IColor this[Int2 index]
{
get => Pixels[index.x, index.y];
set => Pixels[index.x, index.y] = value;
}
public static Image FromSingleColor(int width, int height, IColor col) => new(width, height, (x, y) => col);
public static Image FromSingleColor(Int2 size, IColor col) => new(size, (x, y) => col);
public static Image FromSingleColor(IColor col) => new(1, 1, (x, y) => col);
public object Clone() => new Image(Size, Pixels);
public bool Equals(Image? other) => other is not null && Pixels == other.Pixels;
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (obj == null) return base.Equals(obj);
if (obj.GetType() == typeof(Image)) return Equals((Image)obj);
return base.Equals(obj);
}
public override int GetHashCode() => Pixels.GetHashCode();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<IColor> GetEnumerator()
{
for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++) yield return Pixels[x, y];
}
public void ModifyBrightness(float value, bool set = false)
{
for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++)
{
RGBA col = Pixels[x, y].ToRGBA();
col.R = (set ? 0 : col.R) + value;
col.G = (set ? 0 : col.G) + value;
col.B = (set ? 0 : col.B) + value;
Pixels[x, y] = col;
}
}
public void ModifyContrast(float value)
{
float factor = 259 * (255 + value) / (255 * (259 - value));
for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++)
{
RGBA col = Pixels[x, y].ToRGBA();
col.R = factor * (col.R - 0.5f) + 0.5f;
col.G = factor * (col.G - 0.5f) + 0.5f;
col.B = factor * (col.B - 0.5f) + 0.5f;
Pixels[x, y] = col;
}
}
public void ModifyHue(Angle value, bool set = false)
{
for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++)
{
HSVA col = Pixels[x, y].ToHSVA();
col.H = (set ? Angle.Zero : col.H) + value;
Pixels[x, y] = col;
}
}
public void ModifySaturation(float value, bool set = false)
{
for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++)
{
HSVA col = Pixels[x, y].ToHSVA();
col.S = (set ? 1 : col.S) * value;
Pixels[x, y] = col;
}
}
public void Paint(Int2 min, Int2 max, IColor col)
{
for (int y = min.y; y <= max.y; y++) for (int x = min.x; x <= max.x; x++) Pixels[x, y] = col;
}
public void Resize(Int2 resolution) => Scale(resolution / (Float2)Size);
public void Scale(float factor) => Scale(Float2.One * factor);
public void Scale(Float2 factor)
{
Int2 newSize = (Int2)(Size * factor);
Image img = new(newSize);
for (int y = 0; y < newSize.y; y++) for (int x = 0; x < newSize.x; x++)
{
Float2 f = new((float)x / newSize.x, (float)y / newSize.y);
RGBA col = Pixels[Mathf.Floor(f.x * Size.x), Mathf.Floor(f.y * Size.y)].ToRGBA();
img[x, y] = col;
}
Pixels = img.Pixels;
Size = img.Size;
}
public IColor[] ToArray()
{
IColor[] vals = new IColor[Pixels.Length];
for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++) vals[y * Size.x + x] = Pixels[x, y];
return vals;
}
public static bool operator ==(Image a, Image b) => a.Equals(b);
public static bool operator !=(Image a, Image b) => !a.Equals(b);
}

View File

@ -0,0 +1,191 @@
using Nerd_STF.Graphics.Abstract;
namespace Nerd_STF.Graphics;
public struct Material : ICloneable, IEquatable<Material>
{
public float Alpha;
public float Anisotropy;
public float AnisotropyRoughness;
public IColorFloat AmbientColor;
public float ClearcoatRoughness;
public float ClearcoatThickness;
public IColorFloat DiffuseColor;
public IColorFloat Emissive;
public IlluminationModel IllumModel;
public float Metallic;
public float OpticalDensity;
public float Roughness;
public float Sheen;
public IColorFloat SpecularColor;
public float SpecularExponent;
public (Image Image, TextureConfig Config) AlphaTexture;
public (Image Image, TextureConfig Config) AmbientTexture;
public (Image Image, TextureConfig Config) DiffuseTexture;
public (Image Image, TextureConfig Config) DisplacementTexture;
public (Image Image, TextureConfig Config) EmissiveTexture;
public (Image Image, TextureConfig Config) MetallicTexture;
public (Image Image, TextureConfig Config) NormalTexture;
public (Image Image, TextureConfig Config) RoughnessTexture;
public (Image Image, TextureConfig Config) SheenTexture;
public (Image Image, TextureConfig Config) SpecularTexture;
public (Image Image, TextureConfig Config) SpecularHighlightTexture;
public (Image Image, TextureConfig Config) StencilTexture;
public Material()
{
Alpha = 1;
Anisotropy = 0;
AnisotropyRoughness = 0;
AmbientColor = RGBA.White;
ClearcoatRoughness = 0;
ClearcoatThickness = 0;
DiffuseColor = RGBA.White;
Emissive = RGBA.Clear;
IllumModel = IlluminationModel.Mode0;
Metallic = 0;
OpticalDensity = 1.45f;
Roughness = 1;
Sheen = 0;
SpecularColor = RGBA.White;
SpecularExponent = 10;
AlphaTexture = (Image.FromSingleColor(RGBA.White), new());
AmbientTexture = (Image.FromSingleColor(RGBA.White), new());
DiffuseTexture = (Image.FromSingleColor(RGBA.White), new());
DisplacementTexture = (Image.FromSingleColor(RGBA.White), new());
EmissiveTexture = (Image.FromSingleColor(RGBA.Clear), new());
MetallicTexture = (Image.FromSingleColor(RGBA.Black), new());
NormalTexture = (Image.FromSingleColor(new RGBA(0.5f, 0.5f, 1)), new());
RoughnessTexture = (Image.FromSingleColor(RGBA.White), new());
SheenTexture = (Image.FromSingleColor(RGBA.Black), new());
SpecularTexture = (Image.FromSingleColor(RGBA.White), new());
SpecularHighlightTexture = (Image.FromSingleColor(RGBA.White), new());
StencilTexture = (Image.FromSingleColor(RGBA.White), new());
}
public Material(Fill<object> fill)
{
Alpha = (float)fill(0);
Anisotropy = (float)fill(1);
AnisotropyRoughness = (float)fill(2);
AmbientColor = (IColorFloat)fill(3);
ClearcoatRoughness = (float)fill(4);
ClearcoatThickness = (float)fill(5);
DiffuseColor = (IColorFloat)fill(6);
Emissive = (IColorFloat)fill(7);
IllumModel = (IlluminationModel)fill(8);
Metallic = (float)fill(9);
OpticalDensity = (float)fill(10);
Roughness = (float)fill(11);
Sheen = (float)fill(12);
SpecularColor = (IColorFloat)fill(13);
SpecularExponent = (float)fill(14);
AlphaTexture = ((Image, TextureConfig))fill(15);
AmbientTexture = ((Image, TextureConfig))fill(16);
DiffuseTexture = ((Image, TextureConfig))fill(17);
DisplacementTexture = ((Image, TextureConfig))fill(18);
EmissiveTexture = ((Image, TextureConfig))fill(19);
MetallicTexture = ((Image, TextureConfig))fill(20);
NormalTexture = ((Image, TextureConfig))fill(21);
RoughnessTexture = ((Image, TextureConfig))fill(22);
SheenTexture = ((Image, TextureConfig))fill(23);
SpecularTexture = ((Image, TextureConfig))fill(24);
SpecularHighlightTexture = ((Image, TextureConfig))fill(25);
StencilTexture = ((Image, TextureConfig))fill(26);
}
public Material(IlluminationModel illum, Fill<float> floats, Fill<IColorFloat> colors, Fill<(Image, TextureConfig)> images)
{
Alpha = floats(0);
Anisotropy = floats(1);
AnisotropyRoughness = floats(2);
AmbientColor = colors(0);
ClearcoatRoughness = floats(3);
ClearcoatThickness = floats(4);
DiffuseColor = colors(1);
Emissive = colors(2);
IllumModel = illum;
Metallic = floats(5);
OpticalDensity = floats(6);
Roughness = floats(7);
Sheen = floats(8);
SpecularColor = colors(3);
SpecularExponent = floats(9);
AlphaTexture = images(0);
AmbientTexture = images(1);
DiffuseTexture = images(2);
DisplacementTexture = images(3);
EmissiveTexture = images(4);
MetallicTexture = images(5);
NormalTexture = images(6);
RoughnessTexture = images(7);
SheenTexture = images(8);
SpecularTexture = images(9);
SpecularHighlightTexture = images(10);
StencilTexture = images(11);
}
public object Clone() => new Material()
{
Alpha = Alpha,
AmbientColor = AmbientColor,
Anisotropy = Anisotropy,
AnisotropyRoughness = AnisotropyRoughness,
ClearcoatRoughness = ClearcoatRoughness,
ClearcoatThickness = ClearcoatThickness,
DiffuseColor = DiffuseColor,
Emissive = Emissive,
IllumModel = IllumModel,
Metallic = Metallic,
OpticalDensity = OpticalDensity,
Roughness = Roughness,
Sheen = Sheen,
SpecularColor = SpecularColor,
SpecularExponent = SpecularExponent,
AlphaTexture = AlphaTexture,
AmbientTexture = AmbientTexture,
DiffuseTexture = DiffuseTexture,
DisplacementTexture = DisplacementTexture,
EmissiveTexture = EmissiveTexture,
MetallicTexture = MetallicTexture,
NormalTexture = NormalTexture,
RoughnessTexture = RoughnessTexture,
SheenTexture = SheenTexture,
SpecularTexture = SpecularTexture,
SpecularHighlightTexture = SpecularHighlightTexture,
StencilTexture = StencilTexture
};
public bool Equals(Material other) => Alpha.Equals(other.Alpha) && AmbientColor.Equals(other.AmbientColor) &&
Anisotropy.Equals(other.Anisotropy) && AnisotropyRoughness.Equals(other.AnisotropyRoughness) &&
ClearcoatRoughness.Equals(other.ClearcoatRoughness) && ClearcoatThickness.Equals(other.ClearcoatThickness) &&
DiffuseColor.Equals(other.DiffuseColor) && Emissive.Equals(other.Emissive) &&
IllumModel.Equals(other.IllumModel) && Metallic.Equals(other.Metallic) &&
OpticalDensity.Equals(other.OpticalDensity) && Roughness.Equals(other.Roughness) && Sheen.Equals(other.Sheen) &&
SpecularColor.Equals(other.SpecularColor) && SpecularExponent.Equals(other.SpecularExponent) &&
AlphaTexture.Equals(other.AlphaTexture) && AmbientTexture.Equals(other.AmbientTexture) &&
DiffuseTexture.Equals(other.DiffuseTexture) && DisplacementTexture.Equals(other.DisplacementTexture) &&
EmissiveTexture.Equals(other.EmissiveTexture) && MetallicTexture.Equals(other.MetallicTexture) &&
NormalTexture.Equals(other.NormalTexture) && RoughnessTexture.Equals(other.RoughnessTexture) &&
SheenTexture.Equals(other.SheenTexture) && SpecularTexture.Equals(other.SheenTexture) &&
SpecularHighlightTexture.Equals(other.SpecularHighlightTexture) && StencilTexture.Equals(other.StencilTexture);
public override bool Equals([NotNullWhen(true)] object? obj)
{
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() ^
DiffuseColor.GetHashCode() ^ Emissive.GetHashCode() ^ IllumModel.GetHashCode() ^ Metallic.GetHashCode() ^
OpticalDensity.GetHashCode() ^ Roughness.GetHashCode() ^ Sheen.GetHashCode() ^ SpecularColor.GetHashCode() ^
SpecularExponent.GetHashCode() ^ AlphaTexture.GetHashCode() ^ AmbientTexture.GetHashCode() ^
DiffuseTexture.GetHashCode() ^ DisplacementTexture.GetHashCode() ^ Emissive.GetHashCode() ^
Metallic.GetHashCode() ^ NormalTexture.GetHashCode() ^ RoughnessTexture.GetHashCode() ^
SheenTexture.GetHashCode() ^ SpecularTexture.GetHashCode() ^ SpecularHighlightTexture.GetHashCode() ^
StencilTexture.GetHashCode();
public static bool operator ==(Material a, Material b) => a.Equals(b);
public static bool operator !=(Material a, Material b) => !a.Equals(b);
}

254
Nerd_STF/Graphics/RGBA.cs Normal file
View File

@ -0,0 +1,254 @@
namespace Nerd_STF.Graphics;
public record struct RGBA : IAverage<RGBA>, IClamp<RGBA>, IColorFloat<RGBA>, IEquatable<RGBA>, IIndexAll<float>,
IIndexRangeAll<float>, ILerp<RGBA, float>, IMedian<RGBA>,
ISplittable<RGBA, (float[] Rs, float[] Gs, float[] Bs, float[] As)>
{
public static RGBA Black => new(0, 0, 0);
public static RGBA Blue => new(0, 0, 1);
public static RGBA Clear => new(0, 0, 0, 0);
public static RGBA Cyan => new(0, 1, 1);
public static RGBA Gray => new(0.5f, 0.5f, 0.5f);
public static RGBA Green => new(0, 1, 0);
public static RGBA Magenta => new(1, 0, 1);
public static RGBA Orange => new(1, 0.5f, 0);
public static RGBA Purple => new(0.5f, 0, 1);
public static RGBA Red => new(1, 0, 0);
public static RGBA White => new(1, 1, 1);
public static RGBA Yellow => new(1, 1, 0);
public float R
{
get => p_r;
set => p_r = Mathf.Clamp(value, 0, 1);
}
public float G
{
get => p_g;
set => p_g = Mathf.Clamp(value, 0, 1);
}
public float B
{
get => p_b;
set => p_b = Mathf.Clamp(value, 0, 1);
}
public float A
{
get => p_a;
set => p_a = Mathf.Clamp(value, 0, 1);
}
public bool HasBlue => p_b > 0;
public bool HasGreen => p_g > 0;
public bool HasRed => p_r > 0;
public bool IsOpaque => p_a == 1;
public bool IsVisible => p_a != 0;
private float p_r, p_g, p_b, p_a;
public RGBA() : this(0, 0, 0, 1) { }
public RGBA(float all) : this(all, all, all, all) { }
public RGBA(float all, float a) : this(all, all, all, a) { }
public RGBA(float r, float g, float b) : this(r, g, b, 1) { }
public RGBA(float r, float g, float b, float a)
{
p_r = Mathf.Clamp(r, 0, 1);
p_g = Mathf.Clamp(g, 0, 1);
p_b = Mathf.Clamp(b, 0, 1);
p_a = Mathf.Clamp(a, 0, 1);
}
public RGBA(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public float this[int index]
{
get => index switch
{
0 => R,
1 => G,
2 => B,
3 => A,
_ => throw new IndexOutOfRangeException(nameof(index)),
};
set
{
switch (index)
{
case 0:
R = value;
break;
case 1:
G = value;
break;
case 2:
B = value;
break;
case 3:
A = value;
break;
default: throw new IndexOutOfRangeException(nameof(index));
}
}
}
public float this[Index index]
{
get => this[index.IsFromEnd ? 4 - index.Value : index.Value];
set => this[index.IsFromEnd ? 4 - index.Value : index.Value] = value;
}
public float[] this[Range range]
{
get
{
int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value;
List<float> res = new();
for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray();
}
set
{
int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value;
for (int i = start; i < end; i++) this[i] = value[i];
}
}
public static RGBA Average(params RGBA[] vals)
{
RGBA val = new(0, 0, 0, 0);
for (int i = 0; i < vals.Length; i++) val += vals[i];
return val / vals.Length;
}
public static RGBA Clamp(RGBA val, RGBA min, RGBA max) =>
new(Mathf.Clamp(val.R, min.R, max.R),
Mathf.Clamp(val.G, min.G, max.G),
Mathf.Clamp(val.B, min.B, max.B),
Mathf.Clamp(val.A, min.A, max.A));
public static RGBA Lerp(RGBA a, RGBA b, float t, bool clamp = true) =>
new(Mathf.Lerp(a.R, b.R, t, clamp), Mathf.Lerp(a.G, b.G, t, clamp), Mathf.Lerp(a.B, b.B, t, clamp),
Mathf.Lerp(a.A, b.A, t, clamp));
public static RGBA LerpSquared(RGBA a, RGBA b, float t, bool clamp = true)
{
RGBA val = Lerp(a * a, b * b, t, clamp);
float R = Mathf.Sqrt(val.R), G = Mathf.Sqrt(val.G), B = Mathf.Sqrt(val.B), A = Mathf.Sqrt(val.A);
return new(R, G, B, A);
}
public static RGBA Median(params RGBA[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
RGBA valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static (float[] Rs, float[] Gs, float[] Bs, float[] As) SplitArray(params RGBA[] vals)
{
float[] Rs = new float[vals.Length], Gs = new float[vals.Length],
Bs = new float[vals.Length], As = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Rs[i] = vals[i].R;
Gs[i] = vals[i].G;
Bs[i] = vals[i].B;
As[i] = vals[i].A;
}
return (Rs, Gs, Bs, As);
}
public bool Equals(IColor? col) => col != null && Equals(col.ToRGBA());
public bool Equals(RGBA col) => A == 0 && col.A == 0 || R == col.R && G == col.G && B == col.B && A == col.A;
public override int GetHashCode() => base.GetHashCode();
public RGBA ToRGBA() => this;
public CMYKA ToCMYKA()
{
float v = Mathf.Max(R, G, B), k = 1 - v;
if (v == 1) return new(0, 0, 0, 1, A);
float kInv = 1 / v, c = 1 - R - k, m = 1 - G - k, y = 1 - B - k;
return new(c * kInv, m * kInv, y * kInv, k, A);
}
public HSVA ToHSVA()
{
float cMax = Mathf.Max(R, G, B), cMin = Mathf.Min(R, G, B), delta = cMax - cMin;
Angle hue = Angle.Zero;
if (delta != 0)
{
float val = 0;
if (cMax == R) val = (G - B) / delta % 6;
else if (cMax == G) val = (B - R) / delta + 2;
else if (cMax == B) val = (R - G) / delta + 4;
hue = new(val * 60);
}
float sat = cMax == 0 ? 0 : delta / cMax;
return new(hue, sat, cMax, A);
}
public RGBAByte ToRGBAByte() => new(Mathf.RoundInt(R * 255), Mathf.RoundInt(G * 255), Mathf.RoundInt(B * 255),
Mathf.RoundInt(A * 255));
public CMYKAByte ToCMYKAByte() => ToCMYKA().ToCMYKAByte();
public HSVAByte ToHSVAByte() => ToHSVA().ToHSVAByte();
public float[] ToArray() => new[] { R, G, B, A };
public Fill<float> ToFill()
{
RGBA @this = this;
return i => @this[i];
}
public List<float> ToList() => new() { R, G, B, A };
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
{
yield return R;
yield return G;
yield return B;
yield return A;
}
public Vector3d ToVector() => ((Float3)this).ToVector();
private bool PrintMembers(StringBuilder builder)
{
builder.Append("R = ");
builder.Append(R);
builder.Append(", G = ");
builder.Append(G);
builder.Append(", B = ");
builder.Append(B);
builder.Append(", A = ");
builder.Append(A);
return true;
}
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);
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 a, float b) => new(a.R * b, a.G * b, a.B * b, a.A * b);
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 a, float b) => new(a.R / b, a.G / b, a.B / b, a.A / b);
public static bool operator ==(RGBA a, CMYKA b) => a.Equals(b);
public static bool operator !=(RGBA a, CMYKA b) => a.Equals(b);
public static bool operator ==(RGBA a, HSVA b) => a.Equals(b);
public static bool operator !=(RGBA a, HSVA b) => a.Equals(b);
public static bool operator ==(RGBA a, CMYKAByte b) => a.Equals(b);
public static bool operator !=(RGBA a, CMYKAByte b) => a.Equals(b);
public static bool operator ==(RGBA a, HSVAByte b) => a.Equals(b);
public static bool operator !=(RGBA a, HSVAByte b) => a.Equals(b);
public static bool operator ==(RGBA a, RGBAByte b) => a.Equals(b);
public static bool operator !=(RGBA a, RGBAByte b) => a.Equals(b);
public static implicit operator RGBA(Float3 val) => new(val.x, val.y, val.z);
public static implicit operator RGBA(Float4 val) => new(val.x, val.y, val.z, val.w);
public static implicit operator RGBA(CMYKA val) => val.ToRGBA();
public static implicit operator RGBA(HSVA val) => val.ToRGBA();
public static implicit operator RGBA(CMYKAByte val) => val.ToRGBA();
public static implicit operator RGBA(HSVAByte val) => val.ToRGBA();
public static implicit operator RGBA(RGBAByte val) => val.ToRGBA();
public static implicit operator RGBA(Fill<float> val) => new(val);
}

View File

@ -0,0 +1,260 @@
namespace Nerd_STF.Graphics;
public record struct RGBAByte : IAverage<RGBAByte>, IClamp<RGBAByte>, IColorByte<RGBAByte>, IColorPresets<RGBAByte>,
IEquatable<RGBAByte>, IIndexAll<int>, IIndexRangeAll<int>, ILerp<RGBAByte, float>, IMedian<RGBAByte>,
ISplittable<RGBAByte, (byte[] Rs, byte[] Gs, byte[] Bs, byte[] As)>
{
public static RGBAByte Black => new(0, 0, 0);
public static RGBAByte Blue => new(0, 0, 255);
public static RGBAByte Clear => new(0, 0, 0, 0);
public static RGBAByte Cyan => new(0, 255, 255);
public static RGBAByte Gray => new(127, 127, 127);
public static RGBAByte Green => new(0, 255, 0);
public static RGBAByte Magenta => new(255, 0, 255);
public static RGBAByte Orange => new(255, 127, 0);
public static RGBAByte Purple => new(127, 0, 255);
public static RGBAByte Red => new(255, 0, 0);
public static RGBAByte White => new(255, 255, 255);
public static RGBAByte Yellow => new(255, 255, 0);
public int R
{
get => p_r;
set => p_r = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
public int G
{
get => p_g;
set => p_g = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
public int B
{
get => p_b;
set => p_b = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
public int A
{
get => p_a;
set => p_a = (byte)Mathf.Clamp(value, byte.MinValue, byte.MaxValue);
}
private byte p_r, p_g, p_b, p_a;
public bool HasBlue => B > 0;
public bool HasGreen => G > 0;
public bool HasRed => R > 0;
public bool IsOpaque => A == 255;
public bool IsVisible => A != 0;
public RGBAByte() : this(0, 0, 0, 255) { }
public RGBAByte(int all) : this(all, all, all, all) { }
public RGBAByte(int all, int a) : this(all, all, all, a) { }
public RGBAByte(int r, int g, int b) : this(r, g, b, 255) { }
public RGBAByte(int r, int g, int b, int a)
{
R = (byte)Mathf.Clamp(r, 0, 255);
G = (byte)Mathf.Clamp(g, 0, 255);
B = (byte)Mathf.Clamp(b, 0, 255);
A = (byte)Mathf.Clamp(a, 0, 255);
}
public RGBAByte(Fill<byte> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public RGBAByte(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public int this[int index]
{
get => index switch
{
0 => R,
1 => G,
2 => B,
3 => A,
_ => throw new IndexOutOfRangeException(nameof(index)),
};
set
{
switch (index)
{
case 0:
R = value;
break;
case 1:
G = value;
break;
case 2:
B = value;
break;
case 3:
A = value;
break;
default: throw new IndexOutOfRangeException(nameof(index));
}
}
}
public int this[Index index]
{
get => this[index.IsFromEnd ? 4 - index.Value : index.Value];
set => this[index.IsFromEnd ? 4 - index.Value : index.Value] = value;
}
public int[] this[Range range]
{
get
{
int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value;
List<int> res = new();
for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray();
}
set
{
int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value;
for (int i = start; i < end; i++) this[i] = value[i];
}
}
public static RGBAByte Average(params RGBAByte[] vals)
{
RGBAByte val = new(0, 0, 0, 0);
for (int i = 0; i < vals.Length; i++) val += vals[i];
return val / vals.Length;
}
public static RGBAByte Clamp(RGBAByte val, RGBAByte min, RGBAByte max) =>
new(Mathf.Clamp(val.R, min.R, max.R),
Mathf.Clamp(val.G, min.G, max.G),
Mathf.Clamp(val.B, min.B, max.B),
Mathf.Clamp(val.A, min.A, max.A));
public static RGBAByte Lerp(RGBAByte a, RGBAByte b, float t, bool clamp = true) =>
new(Mathf.Lerp(a.R, b.R, t, clamp), Mathf.Lerp(a.G, b.G, t, clamp), Mathf.Lerp(a.B, b.B, t, clamp),
Mathf.Lerp(a.A, b.A, t, clamp));
public static RGBAByte LerpSquared(RGBAByte a, RGBAByte b, byte t, bool clamp = true) =>
RGBA.LerpSquared(a.ToRGBA(), b.ToRGBA(), t, clamp).ToRGBAByte();
public static RGBAByte Median(params RGBAByte[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
RGBAByte valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static RGBAByte Max(params RGBAByte[] vals)
{
(int[] Rs, int[] Gs, int[] Bs, int[] As) = SplitArrayInt(vals);
return new(Mathf.Max(Rs), Mathf.Max(Gs), Mathf.Max(Bs), Mathf.Max(As));
}
public static RGBAByte Min(params RGBAByte[] vals)
{
(int[] Rs, int[] Gs, int[] Bs, int[] As) = SplitArrayInt(vals);
return new(Mathf.Min(Rs), Mathf.Min(Gs), Mathf.Min(Bs), Mathf.Min(As));
}
public static (byte[] Rs, byte[] Gs, byte[] Bs, byte[] As) SplitArray(params RGBAByte[] vals)
{
byte[] Rs = new byte[vals.Length], Gs = new byte[vals.Length],
Bs = new byte[vals.Length], As = new byte[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Rs[i] = vals[i].p_r;
Gs[i] = vals[i].p_g;
Bs[i] = vals[i].p_b;
As[i] = vals[i].p_a;
}
return (Rs, Gs, Bs, As);
}
public static (int[] Rs, int[] Gs, int[] Bs, int[] As) SplitArrayInt(params RGBAByte[] vals)
{
int[] Rs = new int[vals.Length], Gs = new int[vals.Length],
Bs = new int[vals.Length], As = new int[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Rs[i] = vals[i].R;
Gs[i] = vals[i].G;
Bs[i] = vals[i].B;
As[i] = vals[i].A;
}
return (Rs, Gs, Bs, As);
}
public bool Equals(IColor? col) => col != null && Equals(col.ToRGBAByte());
public bool Equals(RGBAByte col) => A == 0 && col.A == 0 || R == col.R && G == col.G && B == col.B && A == col.A;
public override int GetHashCode() => base.GetHashCode();
public RGBA ToRGBA() => new(R / 255f, G / 255f, B / 255f, A / 255f);
public CMYKA ToCMYKA() => ToRGBA().ToCMYKA();
public HSVA ToHSVA() => ToRGBA().ToHSVA();
public RGBAByte ToRGBAByte() => this;
public CMYKAByte ToCMYKAByte() => ToRGBA().ToCMYKAByte();
public HSVAByte ToHSVAByte() => ToRGBA().ToHSVAByte();
public byte[] ToArray() => new[] { p_r, p_g, p_b, p_a };
public int[] ToArrayInt() => new[] { R, G, B, A };
public Fill<byte> ToFill()
{
RGBAByte @this = this;
return i => (byte)@this[i];
}
public Fill<int> ToFillInt()
{
RGBAByte @this = this;
return i => @this[i];
}
public List<byte> ToList() => new() { p_r, p_g, p_b, p_a };
public List<int> ToListInt() => new() { R, G, B, A };
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<byte> GetEnumerator()
{
yield return p_r;
yield return p_g;
yield return p_b;
yield return p_a;
}
public Vector3d ToVector() => ((RGBA)this).ToVector();
private bool PrintMembers(StringBuilder builder)
{
builder.Append("R = ");
builder.Append(R);
builder.Append(", G = ");
builder.Append(G);
builder.Append(", B = ");
builder.Append(B);
builder.Append(", A = ");
builder.Append(A);
return true;
}
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);
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 a, int b) => new(a.R * b, a.G * b, a.B * b, a.A * b);
public static RGBAByte operator *(RGBAByte a, float b) => (a.ToRGBA() * b).ToRGBAByte();
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 a, int b) => new(a.R / b, a.G / b, a.B / b, a.A / b);
public static RGBAByte operator /(RGBAByte a, float b) => (a.ToRGBA() / b).ToRGBAByte();
public static bool operator ==(RGBAByte a, CMYKA b) => a.Equals(b);
public static bool operator !=(RGBAByte a, CMYKA b) => a.Equals(b);
public static bool operator ==(RGBAByte a, HSVA b) => a.Equals(b);
public static bool operator !=(RGBAByte a, HSVA b) => a.Equals(b);
public static bool operator ==(RGBAByte a, RGBA b) => a.Equals(b);
public static bool operator !=(RGBAByte a, RGBA b) => a.Equals(b);
public static bool operator ==(RGBAByte a, CMYKAByte b) => a.Equals(b);
public static bool operator !=(RGBAByte a, CMYKAByte b) => a.Equals(b);
public static bool operator ==(RGBAByte a, HSVAByte b) => a.Equals(b);
public static bool operator !=(RGBAByte a, HSVAByte b) => a.Equals(b);
public static implicit operator RGBAByte(Int3 val) => new(val.x, val.y, val.z);
public static implicit operator RGBAByte(Int4 val) => new(val.x, val.y, val.z, val.w);
public static implicit operator RGBAByte(CMYKA val) => val.ToRGBAByte();
public static implicit operator RGBAByte(HSVA val) => val.ToRGBAByte();
public static implicit operator RGBAByte(RGBA val) => val.ToRGBAByte();
public static implicit operator RGBAByte(CMYKAByte val) => val.ToRGBAByte();
public static implicit operator RGBAByte(HSVAByte val) => val.ToRGBAByte();
public static implicit operator RGBAByte(Fill<byte> val) => new(val);
public static implicit operator RGBAByte(Fill<int> val) => new(val);
}

View File

@ -0,0 +1,25 @@
namespace Nerd_STF.Graphics;
public struct TextureConfig
{
public (bool U, bool V) BlendUV;
public float Boost;
public ColorChannel Channel;
public bool Clamp;
public float NormalStrength;
public Float3 Offset;
public Float3 Scale;
public Float3 Turbulance;
public TextureConfig()
{
BlendUV = (true, true);
Boost = 0;
Channel = ColorChannel.Red;
Clamp = false;
NormalStrength = 1;
Offset = Float3.Zero;
Scale = Float3.One;
Turbulance = Float3.Zero;
}
}

View File

@ -0,0 +1,317 @@
namespace Nerd_STF.Helpers;
// TODO: Make this internal
// Putting this in here for future reference:
// CORDIC is basically just splitting up an
// operation into more smaller operations.
// For example, turning sin(5rad) into
// sin(4rad + 1rad), which can be turned into
// the formula cos(4rad)cos(1rad) - sin(4rad)sin(1rad),
// to which we know the values of each part.
// Then we just do this iteratively on a bunch
// of powers of 2.
public static class CordicHelper
{
private static readonly float[] p_cosTable =
{
0.540302305868f, // cos(2^0)
0.87758256189f, // cos(2^-1)
0.968912421711f, // cos(2^-2)
0.992197667229f, // cos(2^-3)
0.9980475107f, // cos(2^-4)
0.999511758485f, // cos(2^-5)
0.999877932171f, // cos(2^-6)
0.999969482577f, // cos(2^-7)
0.999992370615f, // cos(2^-8)
0.999998092652f, // cos(2^-9)
0.999999523163f, // cos(2^-10)
0.999999880791f, // cos(2^-11)
0.999999970198f, // cos(2^-12)
0.999999992549f, // cos(2^-13)
0.999999998137f, // cos(2^-14)
0.999999999534f // cos(2^-15)
};
private static readonly float[] p_sinTable =
{
0.841470984808f, // sin(2^0)
0.479425538604f, // sin(2^-1)
0.247403959255f, // sin(2^-2)
0.124674733385f, // sin(2^-3)
0.0624593178424f, // sin(2^-4)
0.0312449139853f, // sin(2^-5)
0.0156243642249f, // sin(2^-6)
0.00781242052738f, // sin(2^-7)
0.0039062400659f, // sin(2^-8)
0.00195312375824f, // sin(2^-9)
0.00097656234478f, // sin(2^-10)
0.000488281230597f, // sin(2^-11)
0.000244140622575f, // sin(2^-12)
0.000122070312197f, // sin(2^-13)
0.0000610351562121f, // sin(2^-14)
0.0000305175781203f // sin(2^-15)
};
private static readonly float[] p_coshTable =
{
1.54308063482f, // cosh(2^0)
1.12762596521f, // cosh(2^-1)
1.03141309988f, // cosh(2^-2)
1.00782267783f, // cosh(2^-3)
1.00195376087f, // cosh(2^-4)
1.00048832099f, // cosh(2^-5)
1.0001220728f, // cosh(2^-6)
1.00003051773f, // cosh(2^-7)
1.0000076294f, // cosh(2^-8)
1.00000190735f, // cosh(2^-9)
1.00000047684f, // cosh(2^-10)
1.00000011921f, // cosh(2^-11)
1.0000000298f, // cosh(2^-12)
1.00000000745f, // cosh(2^-13)
1.00000000186f, // cosh(2^-14)
1.00000000047f, // cosh(2^-15)
};
private static readonly float[] p_sinhTable =
{
1.17520119364f, // sinh(2^0)
0.521095305494f, // sinh(2^-1)
0.252612316808f, // sinh(2^-2)
0.125325775241f, // sinh(2^-3)
0.0625406980522f, // sinh(2^-4)
0.0312550865114f, // sinh(2^-5)
0.0156256357906f, // sinh(2^-6)
0.0078125794731f, // sinh(2^-7)
0.00390625993412f, // sinh(2^-8)
0.00195312624176f, // sinh(2^-9)
0.00097656265522f, // sinh(2^-10)
0.000488281269403f, // sinh(2^-11)
0.000244140627425f, // sinh(2^-12)
0.000122070312803f, // sinh(2^-13)
0.0000610351562879f, // sinh(2^-14)
0.0000305175781297f, // sinh(2^-15)
};
private static readonly Dictionary<(float bas, int depth), float[]> p_expTables;
static CordicHelper()
{
p_expTables = new();
}
// This was originally intended to replace the Mathf.Cos
// and Mathf.Sin functions, but it ended up being considerably
// slower. In the future if it gets optimized, I might then
// choose to replace it.
// REMEMBER: When implementing, remember to use Mathf.AbsoluteMod,
// because that's what I was intending when I wrote this.
public static (float sin, float cos) CalculateTrig(float x, int iterations)
{
float approximateX = 0,
approximateCos = 1,
approximateSin = 0;
// Iterate continuously until it gets better.
for (int i = 0; i < iterations; i++)
{
// We need to find the biggest step that'll move us
// closer to the real X (without overshooting).
float diffX = x - approximateX;
// This is assuming that cosTable and sinTable
// have the same length.
for (int j = 0; j < p_cosTable.Length; j++)
{
// The amount the difference will shrink.
float incX = FastGenExp2((sbyte)-j);
if (diffX >= incX)
{
// Because here we go big to small, the first one that triggers
// this if statement should also be the biggest one that can.
// Get the sin and cos values for this power of two.
float valCos = p_cosTable[j],
valSin = p_sinTable[j];
// Do the products.
float newCos = approximateCos * valCos - approximateSin * valSin,
newSin = approximateCos * valSin + approximateSin * valCos;
// Apply differences
approximateX += incX;
approximateCos = newCos;
approximateSin = newSin;
break;
}
}
}
// Sin and cos should be pretty accurate by now,
// so we can return them.
return (approximateSin, approximateCos);
}
public static (float sinh, float cosh) CalculateHyperTrig(float x, int iterations)
{
float approximateX = 0,
approximateCosh = 1,
approximateSinh = 0;
// Iterate continuously until it gets better.
for (int i = 0; i < iterations; i++)
{
// We need to find the biggest step that'll move us
// closer to the real X (without overshooting).
float diffX = x - approximateX;
// This is assuming that cosTable and sinTable
// have the same length.
for (int j = 0; j < p_coshTable.Length; j++)
{
// The amount the difference will shrink.
float incX = FastGenExp2((sbyte)-j);
if (diffX >= incX)
{
// Because here we go big to small, the first one that triggers
// this if statement should also be the biggest one that can.
// Get the sin and cos values for this power of two.
float valCosh = p_coshTable[j],
valSinh = p_sinhTable[j];
// Do the products.
float newCosh = approximateCosh * valCosh + approximateSinh * valSinh,
newSinh = approximateCosh * valSinh + approximateSinh * valCosh;
// Apply differences
approximateX += incX;
approximateCosh = newCosh;
approximateSinh = newSinh;
break;
}
}
}
// Sin and cos should be pretty accurate by now,
// so we can return them.
return (approximateSinh, approximateCosh);
}
public static float ExpAnyBase(float bas, float pow, int tableDepth, int iterations)
{
// We need to auto-generate a table of values for this number the user enters.
float[] table;
if (p_expTables.ContainsKey((bas, tableDepth)))
{
// Table was already generated, so we can reuse it.
table = p_expTables[(bas, tableDepth)];
}
else
{
// Calculate a table for the CORDIC system by
// applying sequential square roots.
table = new float[tableDepth];
table[0] = bas;
for (int i = 1; i < tableDepth; i++) table[i] = Mathf.Sqrt(table[i - 1]);
p_expTables.Add((bas, tableDepth), table);
}
// Now we can perform the CORDIC method.
float approximateX = 0, approximateVal = 1;
// Iterate continuously until it gets better.
for (int i = 0; i < iterations; i++)
{
// We need to find the biggest step that'll move us
// closer to the real X (without overshooting).
float diffX = pow - approximateX;
for (int j = 0; j < tableDepth; j++)
{
// The amount the difference will shrink.
float incX = FastGenExp2((sbyte)-j);
if (diffX >= incX)
{
// Because here we go big to small, the first one that triggers
// this if statement should also be the biggest one that can.
// Get the power value for this power of two.
float val = table[j];
// Apply our value.
approximateX += incX;
approximateVal *= val;
break;
}
}
}
// Value should be pretty accurate by now,
// so we can return it.
return approximateVal;
}
public static float LogAnyBase(float bas, float val, int tableDepth, int iterations)
{
// We need to auto-generate a table of values for this number the user enters.
// However, we can use the already existing exponent tables and just swap the
// indexes and the values.
float[] table;
if (p_expTables.ContainsKey((bas, tableDepth)))
{
// Table was already generated, so we can reuse it.
table = p_expTables[(bas, tableDepth)];
}
else
{
// Calculate a table for the CORDIC system by
// applying sequential square roots.
table = new float[tableDepth];
table[0] = bas;
for (int i = 1; i < tableDepth; i++) table[i] = Mathf.Sqrt(table[i - 1]);
p_expTables.Add((bas, tableDepth), table);
}
// Now we can perform the CORDIC method.
float approximateX = 0, approximateVal = 1;
// Iterate continuously until it gets better.
for (int i = 0; i < iterations; i++)
{
float diffY = val / approximateVal;
for (int j = 0; j < table.Length; j++)
{
// The amount the difference will shrink.
float incX = FastGenExp2((sbyte)-j);
float newVal = table[j];
if (diffY >= newVal)
{
// Because here we go big to small, the first one that triggers
// this if statement should also be the biggest one that can.
// Apply our value.
approximateX += incX;
approximateVal *= newVal;
break;
}
}
}
// Value should be pretty accurate by now,
// so we can return it.
return approximateX;
}
// An extremely fast way to generate 2 to
// the power of p. I say "generate" because I'm
// just messing with the mantissa's data and
// not doing any real math.
private static float FastGenExp2(sbyte p)
{
int data = (((p - 1) ^ 0b10000000) << 23) & ~(1 << 31);
return UnsafeHelper.SwapType<int, float>(data);
}
}

View File

@ -0,0 +1,91 @@
namespace Nerd_STF.Helpers;
internal static class MathfHelper
{
public static (int[] group, long max)[] MillerRabinWitnessNumbers =
{
(new int[] { 2, 3 }, 1_373_653),
(new int[] { 31, 73 }, 9_080_191),
(new int[] { 2, 3, 5 }, 25_326_001),
(new int[] { 2, 13, 23, 1_662_803 }, 1_122_004_669_633),
(new int[] { 2, 3, 5, 7, 11 }, 2_152_302_898_747),
(new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 }, long.MaxValue) // The real maximum is 318_665_857_834_031_151_167_461
};
public static bool IsPrimeClassic(long num)
{
for (long i = 2; i <= num / 2; i++) if (num % i == 0) return false;
return true;
}
// 0x49204655434B494E47204C4F5645204E554D42525048494C45
// For some reason (I think the witnesses are slightly off), 12 numbers under 100,000
// are misrepresented as prime. I guess one of the witnesses becomes a liar for them.
// Mostly works though.
//
// TODO: In 2.10, the Mathf.PowerMod(int, int, int) method needs to be reworked to
// have a better O(n).
public static bool IsPrimeMillerRabin(long num)
{
// Negatives are composite, zero and one are composite, two and three are prime.
if (num <= 3) return num > 1;
long unchanged = num;
// Find the number's proper witness group.
int[]? witnessGroup = null;
for (int i = 0; i < MillerRabinWitnessNumbers.Length; i++)
{
if (num <= MillerRabinWitnessNumbers[i].max)
{
witnessGroup = MillerRabinWitnessNumbers[i].group;
break;
}
}
if (witnessGroup is null) throw new MathException($"The number {num} is out of range of the available witness " +
$"numbers. Use the {nameof(PrimeCheckMethod.Classic)} method instead."); // This should never happen.
// Prep the number for court.
num -= 1; // For clarity.
// Seperate out powers of two.
int m = 0;
while (num % 2 == 0)
{
m++;
num /= 2;
}
long d = num; // The rest.
// Our number is rewritten as 2^m * d + 1
foreach (int a in witnessGroup)
{
// Calculate a^d = 1 mod n
// If true, the number *may* be prime (test all star numbers to be sure)
// If false, the number is *definitely* composite.
bool thinks = false;
for (int m2 = 0; m2 < m; m2++)
{
// Add any amount of multiples of two as given, but not as many as the original breakdown.
int additional = 1;
for (int m3 = 0; m3 < m2; m3++) additional *= 2;
long result = Mathf.PowerMod(a, additional * d, unchanged);
if (Mathf.AbsoluteMod(result + 1, unchanged) == 0 || Mathf.AbsoluteMod(result - 1, unchanged) == 0)
{
thinks = true;
break;
}
}
if (!thinks) return false; // Definitely not prime.
else continue; // For clarity. A claim that the number is prime is not trustworthy until we've checked all witnesses.
}
return true; // Probably prime.
}
}

View File

@ -0,0 +1,48 @@
namespace Nerd_STF.Helpers;
internal static class RationalHelper
{
public static Rational SimplifyAuto(float value)
{
string valueStr = value.ToString();
int pointIndex = valueStr.IndexOf(".");
if (pointIndex < 0) return new((int)value, 1);
int raise = valueStr.Substring(pointIndex + 1).Length;
int den = Mathf.Power(10, raise);
return new((int)(value * den), den);
}
public static Rational SimplifyFarey(float value, float tolerance, int maxIters)
{
float remainder = value % 1;
if (remainder == 0) return new((int)value, 1);
int additional = (int)(value - remainder);
Rational min = Rational.Zero, max = Rational.One;
Rational result;
float resultValue;
int iters = 0;
do
{
result = new(min.numerator + max.numerator, min.denominator + max.denominator, false);
resultValue = result.GetValue();
if (remainder == resultValue) break;
else if (remainder > resultValue) min = result;
else if (remainder < resultValue) max = result;
iters++;
if (maxIters != -1 && iters > maxIters) break;
}
while (Mathf.Absolute(resultValue - value) > tolerance);
if (additional != 0)
result = new(result.numerator + additional * result.denominator, result.denominator);
return result;
}
}

View File

@ -0,0 +1,13 @@
namespace Nerd_STF.Helpers;
// These are all the unsafe functions I couldn't make safe. I don't want too much
// unsafe code, so this is where I put all of it that I require.
internal static unsafe class UnsafeHelper
{
// Forcefully change the type of an object
// without changing the data of the object.
public static NT SwapType<CT, NT>(CT obj)
where CT : unmanaged
where NT : unmanaged
=> *(NT*)&obj;
}

View File

@ -1,6 +0,0 @@
namespace Nerd_STF;
public interface IClosest<T> where T : IEquatable<T>
{
public T ClosestTo(T item);
}

View File

@ -1,6 +0,0 @@
namespace Nerd_STF;
public interface IContainer<T> where T : IEquatable<T>
{
public bool Contains(T item);
}

View File

@ -1,6 +0,0 @@
namespace Nerd_STF;
public interface IEncapsulator<T, TE> : IContainer<TE> where T : IEquatable<T> where TE : IEquatable<TE>
{
public T Encapsulate(TE val);
}

View File

@ -3,5 +3,6 @@
public interface IGroup<T> : IEnumerable<T>
{
public T[] ToArray();
public Fill<T> ToFill();
public List<T> ToList();
}

7
Nerd_STF/IGroup2D.cs Normal file
View File

@ -0,0 +1,7 @@
namespace Nerd_STF;
public interface IGroup2d<T> : IGroup<T>
{
public T[,] ToArray2D();
public Fill2d<T> ToFill2D();
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IAbsolute<T> where T : IAbsolute<T>
{
public static abstract T Absolute(T val);
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IAverage<T> where T : IAverage<T>
{
public static abstract T Average(params T[] vals);
}

View File

@ -0,0 +1,7 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface ICeiling<TSelf> : ICeiling<TSelf, TSelf> where TSelf : ICeiling<TSelf> { }
public interface ICeiling<TSelf, TRound> where TSelf : ICeiling<TSelf, TRound>
{
public static abstract TRound Ceiling(TSelf val);
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IClamp<T> where T : IClamp<T>
{
public static abstract T Clamp(T val, T min, T max);
}

View File

@ -0,0 +1,15 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface IClampMagnitude<TSelf, TNumber>
where TSelf : IClampMagnitude<TSelf, TNumber>
where TNumber : INumber<TNumber>
{
public static abstract TSelf ClampMagnitude(TSelf val, TNumber minMag, TNumber maxMag);
}
public interface IClampMagnitude<TSelf> where TSelf : IClampMagnitude<TSelf>
{
public static abstract TSelf ClampMagnitude<TNumber>(TSelf val, TNumber minMag, TNumber maxMag)
where TNumber : INumber<TNumber>;
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IClosestTo<T> where T : IEquatable<T>
{
public T ClosestTo(T item);
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IContains<T> where T : IEquatable<T>
{
public bool Contains(T item);
}

View File

@ -0,0 +1,8 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface ICross<TSelf> : ICross<TSelf, TSelf>
where TSelf : ICross<TSelf> { }
public interface ICross<TSelf, TOut> where TSelf : ICross<TSelf, TOut>
{
public static abstract TOut Cross(TSelf a, TSelf b, bool normalized = false);
}

View File

@ -0,0 +1,8 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface IDivide<T> : IDivisionOperators<T, T, T> where T : IDivide<T>
{
public static abstract T Divide(T num, params T[] vals);
}

View File

@ -0,0 +1,18 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface IDot<TSelf, TNumber>
where TSelf : IDot<TSelf, TNumber>
where TNumber : INumber<TNumber>
{
public static abstract TNumber Dot(TSelf a, TSelf b);
public static abstract TNumber Dot(TSelf[] vals);
}
public interface IDot<TSelf> where TSelf : IDot<TSelf>
{
public static abstract TNumber Dot<TNumber>(TSelf a, TSelf b)
where TNumber : INumber<TNumber>;
public static abstract TNumber Dot<TNumber>(TSelf[] vals)
where TNumber : INumber<TNumber>;
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IEncapsulate<T, TE> : IContains<TE> where T : IEquatable<T> where TE : IEquatable<TE>
{
public T Encapsulate(TE val);
}

View File

@ -0,0 +1,9 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IFloor<TSelf> : IFloor<TSelf, TSelf>
where TSelf : IFloor<TSelf> { }
public interface IFloor<TSelf, TRound>
where TSelf : IFloor<TSelf, TRound>
{
public static abstract TRound Floor(TSelf val);
}

View File

@ -0,0 +1,9 @@
using System.Runtime.CompilerServices;
namespace Nerd_STF.Mathematics.Abstract;
public interface IFromTuple<TSelf, TTuple> where TSelf : IFromTuple<TSelf, TTuple>
where TTuple : ITuple
{
public static abstract implicit operator TSelf(TTuple val);
}

View File

@ -0,0 +1,3 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IIndexAll<TSub> : IIndexGet<TSub>, IIndexSet<TSub> { }

View File

@ -0,0 +1,7 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IIndexGet<TSub>
{
public TSub this[int index] { get; }
public TSub this[Index index] { get; }
}

View File

@ -0,0 +1,3 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IIndexRangeAll<TSub> : IIndexRangeGet<TSub>, IIndexRangeSet<TSub> { }

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IIndexRangeGet<TSub>
{
public TSub[] this[Range range] { get; }
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IIndexRangeSet<TSub>
{
public TSub[] this[Range range] { set; }
}

View File

@ -0,0 +1,7 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IIndexSet<TSub>
{
public TSub this[int index] { set; }
public TSub this[Index index] { set; }
}

View File

@ -0,0 +1,15 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface ILerp<TSelf, TNumber>
where TSelf : ILerp<TSelf, TNumber>
where TNumber : INumber<TNumber>
{
public static abstract TSelf Lerp(TSelf a, TSelf b, TNumber t, bool clamp = true);
}
public interface ILerp<TSelf> where TSelf : ILerp<TSelf>
{
public static abstract TSelf Lerp<TNumber>(TSelf a, TSelf b, TNumber t, bool clamp = true)
where TNumber : INumber<TNumber>;
}

View File

@ -0,0 +1,8 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface IMagnitude<TNumber> where TNumber : INumber<TNumber>
{
public TNumber Magnitude { get; }
}

View File

@ -0,0 +1,8 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface IMathOperators<TSelf> : IAdditionOperators<TSelf, TSelf, TSelf>,
ISubtractionOperators<TSelf, TSelf, TSelf>, IMultiplyOperators<TSelf, TSelf, TSelf>,
IDivisionOperators<TSelf, TSelf, TSelf>
where TSelf : IMathOperators<TSelf> { }

View File

@ -0,0 +1,37 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IMatrix<T> : IAbsolute<T>, ICeiling<T>, IClamp<T>, IDivide<T>,
IEquatable<T>, IFloor<T>, IGroup2d<float>, ILerp<T, float>, IProduct<T>, IRound<T>,
ISubtract<T>, ISum<T>
where T : IMatrix<T>
{
public Int2 Size { get; }
public float this[int r, int c] { get; set; }
public float this[Index r, Index c] { get; set; }
public T Adjugate();
public T Cofactor();
public float Determinant();
public T? Inverse();
public T Transpose();
public float[] GetColumn(int column);
public float[] GetRow(int row);
public void SetColumn(int column, float[] value);
public void SetRow(int row, float[] values);
public T AddRow(int rowToChange, int referenceRow, float factor = 1);
public void AddRowMutable(int rowToChange, int referenceRow, float factor = 1);
public T ScaleRow(int rowIndex, float value);
public void ScaleRowMutable(int rowIndex, float value);
public T SwapRows(int rowA, int rowB);
public void SwapRowsMutable(int rowA, int rowB);
}
public interface IMatrix<This, TMinor> : IMatrix<This> where This : IMatrix<This, TMinor>
where TMinor : IMatrix<TMinor>
{
public TMinor[,] Minors();
}

View File

@ -0,0 +1,9 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IMatrixPresets<T> where T : IMatrix<T>, IMatrixPresets<T>
{
public static abstract T Identity { get; }
public static abstract T One { get; }
public static abstract T SignGrid { get; }
public static abstract T Zero { get; }
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IMax<T> where T : IMax<T>, IComparable<T>
{
public static virtual T Max(params T[] vals) => Mathf.Max(vals);
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IMedian<T> where T : IMedian<T>
{
public static virtual T Median(params T[] vals) => Mathf.Median(vals);
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IMin<T> where T : IMin<T>, IComparable<T>
{
public static virtual T Min(params T[] vals) => Mathf.Min(vals);
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IPresets0d<T> where T : IPresets0d<T>
{
public static abstract T Unit { get; }
}

View File

@ -0,0 +1,7 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IPresets1d<T> where T : IPresets1d<T>
{
public static abstract T One { get; }
public static abstract T Zero { get; }
}

View File

@ -0,0 +1,9 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IPresets2d<T> : IPresets1d<T> where T : IPresets2d<T>
{
public static abstract T Down { get; }
public static abstract T Left { get; }
public static abstract T Right { get; }
public static abstract T Up { get; }
}

View File

@ -0,0 +1,7 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IPresets3d<T> : IPresets2d<T> where T : IPresets3d<T>
{
public static abstract T Back { get; }
public static abstract T Forward { get; }
}

View File

@ -0,0 +1,7 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IPresets4d<T> : IPresets2d<T>, IPresets3d<T> where T : IPresets4d<T>
{
public static abstract T HighW { get; }
public static abstract T LowW { get; }
}

View File

@ -0,0 +1,9 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface IProduct<T> : IMultiplyOperators<T, T, T>
where T : IProduct<T>
{
public static abstract T Product(params T[] vals);
}

View File

@ -0,0 +1,9 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IRound<TSelf> : IRound<TSelf, TSelf>
where TSelf : IRound<TSelf> { }
public interface IRound<TSelf, TRound>
where TSelf : IRound<TSelf, TRound>
{
public static abstract TRound Round(TSelf val);
}

View File

@ -0,0 +1,9 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface IShape2d<TNumber> where TNumber : INumber<TNumber>
{
public TNumber Area { get; }
public TNumber Perimeter { get; }
}

View File

@ -0,0 +1,9 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface IShape3d<TNumber> where TNumber : INumber<TNumber>
{
public TNumber SurfaceArea { get; }
public TNumber Volume { get; }
}

View File

@ -0,0 +1,8 @@
using System.Runtime.CompilerServices;
namespace Nerd_STF.Mathematics.Abstract;
public interface ISplittable<TSelf, TTuple> where TTuple : ITuple
{
public static abstract TTuple SplitArray(params TSelf[] vals);
}

View File

@ -0,0 +1,7 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IStaticMatrix<T> : IAverage<T>, IMatrix<T>, IMedian<T>,
IMatrixPresets<T> where T : IStaticMatrix<T>
{
}

View File

@ -0,0 +1,7 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface ISubdivide<T>
{
public T Subdivide();
public T Subdivide(int iterations);
}

View File

@ -0,0 +1,8 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface ISubtract<T> : ISubtractionOperators<T, T, T> where T : ISubtract<T>
{
public static abstract T Subtract(T num, params T[] vals);
}

View File

@ -0,0 +1,9 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface ISum<T> : IAdditionOperators<T, T, T>
where T : ISum<T>
{
public static abstract T Sum(params T[] vals);
}

View File

@ -0,0 +1,19 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface ITriangulate
{
public static Triangle[] TriangulateAll(params ITriangulate[] triangulatables)
{
List<Triangle> res = new();
foreach (ITriangulate triangulatable in triangulatables) res.AddRange(triangulatable.Triangulate());
return res.ToArray();
}
public static Triangle[] TriangulateAll<T>(params T[] triangulatables) where T : ITriangulate
{
List<Triangle> res = new();
foreach (ITriangulate triangulatable in triangulatables) res.AddRange(triangulatable.Triangulate());
return res.ToArray();
}
public Triangle[] Triangulate();
}

View File

@ -0,0 +1,441 @@
namespace Nerd_STF.Mathematics.Algebra;
public class Matrix : IMatrix<Matrix, Matrix>
{
public static Matrix Identity(Int2 size)
{
if (size.x != size.y) throw new InvalidSizeException("Can only create an identity matrix of a square matrix." +
" You may want to use " + nameof(IdentityIsh) + " instead.");
Matrix m = Zero(size);
for (int i = 0; i < size.x; i++) m[i, i] = 1;
return m;
}
public static Matrix IdentityIsh(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, delegate (int x)
{
float sgnValue = Fills.SignFill(x);
if (size.y % 2 == 0 && x / size.y % 2 == 1) sgnValue *= -1;
return sgnValue;
});
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.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = 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.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = 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.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals[c + r * size.y];
}
public Matrix(Int2 size, Fill<float> vals)
{
Size = size;
array = new float[size.x, size.y];
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals(c + r * size.y);
}
public Matrix(Int2 size, Fill<int> vals)
{
Size = size;
array = new float[size.x, size.y];
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = 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.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals[r, c];
}
public Matrix(Int2 size, int[,] vals)
{
Size = size;
array = new float[size.x, size.y];
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals[r, c];
}
public Matrix(Int2 size, Fill2d<float> vals)
{
Size = size;
array = new float[size.x, size.y];
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals(r, c);
}
public Matrix(Int2 size, Fill2d<int> vals)
{
Size = size;
array = new float[size.x, size.y];
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals(r, c);
}
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 float this[Index r, Index c]
{
get
{
int row = r.IsFromEnd ? Size.x - r.Value : r.Value,
col = c.IsFromEnd ? Size.y - c.Value : c.Value;
return array[row, col];
}
set
{
int row = r.IsFromEnd ? Size.x - r.Value : r.Value,
col = c.IsFromEnd ? Size.y - c.Value : c.Value;
array[row, col] = value;
}
}
public Matrix this[Range rs, Range cs]
{
get
{
int rowStart = rs.Start.IsFromEnd ? Size.x - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? Size.x - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? Size.y - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? Size.y - cs.End.Value : cs.End.Value;
Matrix newMatrix = new((rowEnd - rowStart - 1, colEnd - colStart - 1));
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
newMatrix[r, c] = array[r, c];
return newMatrix;
}
set
{
int rowStart = rs.Start.IsFromEnd ? Size.x - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? Size.x - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? Size.y - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? Size.y - cs.End.Value : cs.End.Value;
if (value.Size != (rowEnd - rowStart - 1, colEnd - colStart - 1))
throw new InvalidSizeException("Matrix has invalid size.");
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
array[r, c] = value[r, c];
}
}
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.x; r++) for (int c = 0; c < Size.y; c++)
array[r, c] = modifier(new(r, c), array[r, c]);
}
public float[] GetColumn(int column)
{
float[] vals = new float[Size.x];
for (int i = 0; i < Size.x; i++) vals[i] = array[i, column];
return vals;
}
public float[] GetRow(int row)
{
float[] vals = new float[Size.y];
for (int i = 0; i < Size.y; i++) vals[i] = array[row, i];
return vals;
}
public void SetColumn(int column, float[] vals)
{
if (vals.Length < Size.x)
throw new InvalidSizeException("Array must contain enough values to fill the column.");
for (int i = 0; i < Size.x; i++) array[i, column] = vals[i];
}
public void SetRow(int row, float[] vals)
{
if (vals.Length < Size.y)
throw new InvalidSizeException("Array must contain enough values to fill the row.");
for (int i = 0; i < Size.y; i++) array[row, i] = vals[i];
}
public Matrix Adjugate() => Cofactor().Transpose();
public Matrix Cofactor()
{
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) return 0;
if (Size.x == 1) return array[0, 0];
Matrix[] minors = Minors().GetRow(0, Size.x);
float det = 0;
for (int i = 0; i < minors.Length; i++) det += array[0, i] * minors[i].Determinant() * (i % 2 == 0 ? 1 : -1);
return det;
}
public Matrix? Inverse()
{
float d = Determinant();
if (d == 0) return null;
return Adjugate() / d;
}
public Matrix[,] Minors()
{
if (!HasMinors) return new Matrix[0,0];
Matrix[,] minors = new Matrix[Size.x, Size.y];
for (int r = 0; r < Size.x; r++) for (int c = 0; c < Size.y; c++) minors[r, c] = MinorOf(new(r, c));
return minors;
}
public Matrix Transpose()
{
Matrix @this = this;
return new(Size, (r, c) => @this[c, r]);
}
public Matrix MinorOf(Int2 index)
{
Matrix @this = this;
return new(@this.Size - Int2.One, delegate (int r, int c)
{
if (r >= index.x) r++;
if (c >= index.y) c++;
return @this[r, c];
});
}
public Matrix AddRow(int rowToChange, int referenceRow, float factor = 1)
{
Matrix @this = this;
return new(Size, delegate (int r, int c)
{
if (r == rowToChange) return @this[r, c] += factor * @this[referenceRow, c];
else return @this[r, c];
});
}
public void AddRowMutable(int rowToChange, int referenceRow, float factor)
{
for (int c = 0; c < Size.y; c++) this[rowToChange, c] += this[referenceRow, c] * factor;
}
public Matrix ScaleRow(int rowIndex, float factor)
{
Matrix @this = this;
return new(Size, delegate (int r, int c)
{
if (r == rowIndex) return @this[r, c] * factor;
else return @this[r, c];
});
}
public void ScaleRowMutable(int rowIndex, float factor)
{
for (int c = 0; c < Size.y; c++) this[rowIndex, c] *= factor;
}
public Matrix SwapRows(int rowA, int rowB)
{
Matrix @this = this;
return new(Size, delegate (int r, int c)
{
if (r == rowA) return @this[rowB, c];
else if (r == rowB) return @this[rowA, c];
else return @this[r, c];
});
}
public void SwapRowsMutable(int rowA, int rowB)
{
float[] dataA = GetRow(rowA), dataB = GetRow(rowB);
SetRow(rowA, dataB);
SetRow(rowB, dataA);
}
public Matrix SolveRowEchelon()
{
Matrix result = (Matrix)MemberwiseClone();
// Scale the first row so the first element of that row is 1.
result.ScaleRowMutable(0, 1 / result[0, 0]);
// For each row afterwards, subtract the required amount from all rows before it and normalize.
for (int r1 = 1; r1 < result.Size.x; r1++)
{
int min = Mathf.Min(r1, result.Size.y);
for (int r2 = 0; r2 < min; r2++)
{
result.AddRowMutable(r1, r2, -result[r1, r2]);
}
result.ScaleRowMutable(r1, 1 / result[r1, r1]);
}
return result;
}
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)
{
if (other is null) return false;
return array.Equals(other.array);
}
public override int GetHashCode() => array.GetHashCode();
public override string ToString()
{
string res = "";
for (int r = 0; r < Size.x; r++)
{
for (int c = 0; c < Size.y; c++) res += array[r, c] + " ";
res += "\n";
}
return res;
}
public object Clone() => new Matrix(Size, array);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> 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(new(Size.y, Size.x));
public float[,] ToArray2D() => array;
public Fill<float> ToFill() => ToFillExtension.ToFill(this);
public Fill2d<float> ToFill2D()
{
Matrix @this = this;
return (x, y) => @this[x, y];
}
public List<float> 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)
{
if (a.Size.y != b.Size.x) throw new InvalidSizeException("Incompatible Dimensions.");
return new(new(a.Size.x, b.Size.y), (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)
{
Matrix? bInv = b.Inverse();
if (bInv is null) throw new NoInverseException(b);
return a * bInv;
}
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;
}
}

View File

@ -0,0 +1,372 @@
namespace Nerd_STF.Mathematics.Algebra;
public record class Matrix2x2 : IStaticMatrix<Matrix2x2>
{
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 Int2 Size => (2, 2);
public float r1c1, r2c1, r1c2, r2c2;
public Matrix2x2(float all) : this(all, all, all, all) { }
public Matrix2x2(float r1c1, float r1c2, float r2c1, float r2c2)
{
this.r1c1 = r1c1;
this.r1c2 = r1c2;
this.r2c1 = r2c1;
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<float> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public Matrix2x2(Fill<int> 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<float> fill) : this(fill(0, 0), fill(0, 1), fill(1, 0), fill(1, 1)) { }
public Matrix2x2(Fill2d<int> fill) : this(fill(0, 0), fill(0, 1), fill(1, 0), fill(1, 1)) { }
public Matrix2x2(Float2 r1, Float2 r2) : this(r1.x, r1.y, r2.x, r2.y) { }
public Matrix2x2(Fill<Float2> fill) : this(fill(0), fill(1)) { }
public Matrix2x2(Fill<Int2> fill) : this((IEnumerable<int>)fill(0), fill(1)) { }
public Matrix2x2(IEnumerable<float> r1, IEnumerable<float> r2) : this(r1.ToFill(), r2.ToFill()) { }
public Matrix2x2(IEnumerable<int> r1, IEnumerable<int> r2) : this(r1.ToFill(), r2.ToFill()) { }
public Matrix2x2(Fill<float> r1, Fill<float> r2) : this(r1(0), r1(1), r2(0), r2(1)) { }
public Matrix2x2(Fill<int> r1, Fill<int> r2) : this(r1(0), r1(1), r2(0), r2(1)) { }
public float this[int r, int c]
{
get => ToArray2D()[r, c];
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 float this[Index r, Index c]
{
get
{
int row = r.IsFromEnd ? 2 - r.Value : r.Value,
col = c.IsFromEnd ? 2 - c.Value : c.Value;
return this[row, col];
}
set
{
int row = r.IsFromEnd ? 2 - r.Value : r.Value,
col = c.IsFromEnd ? 2 - c.Value : c.Value;
this[row, col] = value;
}
}
public float[,] this[Range rs, Range cs]
{
get
{
int rowStart = rs.Start.IsFromEnd ? 2 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 2 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 2 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 2 - cs.End.Value : cs.End.Value;
float[,] vals = new float[rowEnd - rowStart - 1, colEnd - colStart - 1];
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
vals[r, c] = this[r, c];
return vals;
}
set
{
int rowStart = rs.Start.IsFromEnd ? 2 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 2 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 2 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 2 - cs.End.Value : cs.End.Value;
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
this[r, c] = value[r, c];
}
}
public static Matrix2x2 Absolute(Matrix2x2 val) => new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r1c2),
Mathf.Absolute(val.r2c1), 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.r1c2),
Mathf.Ceiling(val.r2c1), 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.r1c2, min.r1c2, max.r1c2),
Mathf.Clamp(val.r2c1, min.r2c1, max.r2c1), 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.r1c2),
Mathf.Floor(val.r2c1), 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.r1c2, b.r1c2, t, clamp),
Mathf.Lerp(a.r2c1, b.r2c1, 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.r1c2),
Mathf.Round(val.r2c1), 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[] r1c2s, float[] r2c1s, float[] r2c2s) SplitArray(params Matrix2x2[] vals)
{
float[] r1c1s = new float[vals.Length], r1c2s = new float[vals.Length], r2c1s = new float[vals.Length],
r2c2s = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
r1c1s[i] = vals[i].r1c1;
r1c2s[i] = vals[i].r1c2;
r2c1s[i] = vals[i].r2c1;
r2c2s[i] = vals[i].r2c2;
}
return (r1c1s, r1c2s, r2c1s, r2c2s);
}
public Matrix2x2 Adjugate() => Cofactor().Transpose();
public Matrix2x2 Cofactor()
{
Matrix2x2 swapped = new(new[,]
{
{ r2c2, r2c1 },
{ r1c2, r1c1 }
});
return swapped ^ SignGrid;
}
public float Determinant() => r1c1 * r2c2 - r1c2 * r2c1;
public Matrix2x2? Inverse()
{
float d = Determinant();
if (d == 0) return null;
return Transpose().Adjugate() / d;
}
public Matrix2x2 Transpose() => new(new[,]
{
{ r1c1, r2c1 },
{ r1c2, r2c2 }
});
public float[] GetColumn(int column) => new[] { this[0, column], this[1, column] };
public float[] GetRow(int row) => new[] { this[row, 0], this[row, 1] };
public void SetColumn(int column, float[] vals)
{
if (vals.Length < 2)
throw new InvalidSizeException("Array must contain enough values to fill the column.");
this[0, column] = vals[0];
this[1, column] = vals[1];
}
public void SetRow(int row, float[] vals)
{
if (vals.Length < 2)
throw new InvalidSizeException("Array must contain enough values to fill the row.");
this[row, 0] = vals[0];
this[row, 1] = vals[1];
}
public Matrix2x2 AddRow(int rowToChange, int referenceRow, float factor = 1)
{
Matrix2x2 @this = this;
return new(delegate (int r, int c)
{
if (r == rowToChange) return @this[r, c] += factor * @this[referenceRow, c];
else return @this[r, c];
});
}
public void AddRowMutable(int rowToChange, int referenceRow, float factor)
{
this[rowToChange, 0] += this[referenceRow, 0] * factor;
this[rowToChange, 1] += this[referenceRow, 1] * factor;
}
public Matrix2x2 ScaleRow(int rowIndex, float factor)
{
Matrix2x2 @this = this;
return new(delegate (int r, int c)
{
if (r == rowIndex) return @this[r, c] * factor;
else return @this[r, c];
});
}
public void ScaleRowMutable(int rowIndex, float factor)
{
this[rowIndex, 0] *= factor;
this[rowIndex, 1] *= factor;
}
public Matrix2x2 SwapRows(int rowA, int rowB)
{
Matrix2x2 @this = this;
return new(delegate (int r, int c)
{
if (r == rowA) return @this[rowB, c];
else if (r == rowB) return @this[rowA, c];
else return @this[r, c];
});
}
public void SwapRowsMutable(int rowA, int rowB)
{
float[] dataA = GetRow(rowA), dataB = GetRow(rowB);
SetRow(rowA, dataB);
SetRow(rowB, dataA);
}
public virtual bool Equals(Matrix2x2? other)
{
if (other is null) return false;
return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r2c1 == other.r2c1 && r2c2 == other.r2c2;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => r1c1 + " " + r1c2 + "\n" + r2c1 + " " + r2c2;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
{
yield return r1c1;
yield return r1c2;
yield return r2c1;
yield return r2c2;
}
public float[] ToArray() => new[] { r1c1, r1c2, r2c1, r2c2 };
public float[,] ToArray2D() => new[,]
{
{ r1c1, r1c2 },
{ r2c1, r2c2 }
};
public Fill<float> ToFill() => ToFillExtension.ToFill(this);
public Fill2d<float> ToFill2D()
{
Matrix2x2 @this = this;
return (x, y) => @this[x, y];
}
public List<float> ToList() => new() { r1c1, r1c2, r2c1, r2c2 };
public static Matrix2x2 operator +(Matrix2x2 a, Matrix2x2 b) => new(a.r1c1 + b.r1c1, a.r1c2 + b.r1c2,
a.r2c1 + b.r2c1, 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.r1c2 - b.r1c2,
a.r2c1 - b.r2c1, a.r2c2 - b.r2c2);
public static Matrix2x2 operator *(Matrix2x2 a, float b) => new(a.r1c1 * b, a.r1c2 * b, a.r2c1 * 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 Float2 operator *(Matrix2x2 a, Float2 b) => (Matrix)a * b;
public static Matrix2x2 operator /(Matrix2x2 a, float b) => new(a.r1c1 / b, a.r1c2 / b, a.r2c1 / b, a.r2c2 / b);
public static Matrix2x2 operator /(Matrix2x2 a, Matrix2x2 b)
{
Matrix2x2? bInv = b.Inverse();
if (bInv is null) throw new NoInverseException(b);
return a * bInv;
}
public static Float2 operator /(Matrix2x2 a, Float2 b) => (Matrix)a / b;
public static Matrix2x2 operator ^(Matrix2x2 a, Matrix2x2 b) => // Single number multiplication.
new(a.r1c1 * b.r1c1, a.r1c2 * b.r1c2, a.r2c1 * b.r2c1, a.r2c2 * b.r2c2);
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 > r && m.Size.y > c ? 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);
}

View File

@ -0,0 +1,511 @@
namespace Nerd_STF.Mathematics.Algebra;
public record class Matrix3x3 : IStaticMatrix<Matrix3x3>
{
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 Int2 Size => (3, 3);
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 r1c2, float r1c3, float r2c1,
float r2c2, float r2c3, float r3c1, float r3c2, float r3c3)
{
this.r1c1 = r1c1;
this.r1c2 = r1c2;
this.r1c3 = r1c3;
this.r2c1 = r2c1;
this.r2c2 = r2c2;
this.r2c3 = r2c3;
this.r3c1 = r3c1;
this.r3c2 = r3c2;
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<float> fill) : this(fill(0), fill(1), fill(2),
fill(3), fill(4), fill(5), fill(6), fill(7), fill(8)) { }
public Matrix3x3(Fill<int> 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<float> 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<int> 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 r1, Float3 r2, Float3 r3) : this(r1.x, r1.y, r1.z, r2.x, r2.y, r2.z, r3.x, r3.y, r3.z) { }
public Matrix3x3(Fill<Float3> fill) : this(fill(0), fill(1), fill(2)) { }
public Matrix3x3(Fill<Int3> fill) : this((IEnumerable<int>)fill(0), fill(1), fill(2)) { }
public Matrix3x3(IEnumerable<float> r1, IEnumerable<float> r2, IEnumerable<float> r3)
: this(r1.ToFill(), r2.ToFill(), r3.ToFill()) { }
public Matrix3x3(IEnumerable<int> r1, IEnumerable<int> r2, IEnumerable<int> r3)
: this(r1.ToFill(), r2.ToFill(), r3.ToFill()) { }
public Matrix3x3(Fill<float> r1, Fill<float> r2, Fill<float> r3)
: this(r1(0), r1(1), r1(2), r2(0), r2(1), r2(2), r3(0), r3(1), r3(2)) { }
public Matrix3x3(Fill<int> r1, Fill<int> r2, Fill<int> r3)
: this(r1(0), r1(1), r1(2), r2(0), r2(1), r2(2), r3(0), r3(1), r3(2)) { }
public float this[int r, int c]
{
get => ToArray2D()[r, c];
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 float this[Index r, Index c]
{
get
{
int row = r.IsFromEnd ? 3 - r.Value : r.Value,
col = c.IsFromEnd ? 3 - c.Value : c.Value;
return this[row, col];
}
set
{
int row = r.IsFromEnd ? 3 - r.Value : r.Value,
col = c.IsFromEnd ? 3 - c.Value : c.Value;
this[row, col] = value;
}
}
public float[,] this[Range rs, Range cs]
{
get
{
int rowStart = rs.Start.IsFromEnd ? 3 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 3 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 3 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 3 - cs.End.Value : cs.End.Value;
float[,] vals = new float[rowEnd - rowStart - 1, colEnd - colStart - 1];
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
vals[r, c] = this[r, c];
return vals;
}
set
{
int rowStart = rs.Start.IsFromEnd ? 3 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 3 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 3 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 3 - cs.End.Value : cs.End.Value;
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
this[r, c] = value[r, c];
}
}
public static Matrix3x3 Absolute(Matrix3x3 val) =>
new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r1c2), Mathf.Absolute(val.r1c3),
Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r2c2), Mathf.Absolute(val.r2c3),
Mathf.Absolute(val.r3c1), Mathf.Absolute(val.r3c2), 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.r1c2), Mathf.Ceiling(val.r1c3),
Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r2c2), Mathf.Ceiling(val.r2c3),
Mathf.Ceiling(val.r3c1), Mathf.Ceiling(val.r3c2), 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.r1c2, min.r1c2, max.r1c2),
Mathf.Clamp(val.r1c3, min.r1c3, max.r1c3), Mathf.Clamp(val.r2c1, min.r2c1, max.r2c1),
Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2), Mathf.Clamp(val.r2c3, min.r2c3, max.r2c3),
Mathf.Clamp(val.r3c1, min.r3c1, max.r3c1), Mathf.Clamp(val.r3c2, min.r3c2, max.r3c2),
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.r1c2), Mathf.Floor(val.r1c3),
Mathf.Floor(val.r2c1), Mathf.Floor(val.r2c2), Mathf.Floor(val.r2c3),
Mathf.Floor(val.r3c1), Mathf.Floor(val.r3c2), 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.r1c2, b.r1c2, t, clamp),
Mathf.Lerp(a.r1c3, b.r1c3, t, clamp), Mathf.Lerp(a.r2c1, b.r2c1, t, clamp),
Mathf.Lerp(a.r2c2, b.r2c2, t, clamp), Mathf.Lerp(a.r2c3, b.r2c3, t, clamp),
Mathf.Lerp(a.r3c1, b.r3c1, t, clamp), Mathf.Lerp(a.r3c2, b.r3c2, 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.r1c2), Mathf.Round(val.r1c3),
Mathf.Round(val.r2c1), Mathf.Round(val.r2c2), Mathf.Round(val.r2c3),
Mathf.Round(val.r3c1), Mathf.Round(val.r3c2), 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[] r1c2s, float[] r1c3s, float[] r2c1s, float[] r2c2s,
float[] r2c3s, float[] r3c1s, float[] r3c2s, float[] r3c3s) SplitArray(params Matrix3x3[] vals)
{
float[] r1c1s = new float[vals.Length], r1c2s = new float[vals.Length], r1c3s = new float[vals.Length],
r2c1s = new float[vals.Length], r2c2s = new float[vals.Length], r2c3s = new float[vals.Length],
r3c1s = new float[vals.Length], r3c2s = new float[vals.Length], r3c3s = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
r1c1s[i] = vals[i].r1c1;
r1c2s[i] = vals[i].r1c2;
r1c3s[i] = vals[i].r1c3;
r2c1s[i] = vals[i].r2c1;
r2c2s[i] = vals[i].r2c2;
r2c3s[i] = vals[i].r2c3;
r3c1s[i] = vals[i].r3c1;
r3c2s[i] = vals[i].r3c2;
r3c3s[i] = vals[i].r3c3;
}
return (r1c1s, r1c2s, r1c3s, r2c1s, r2c2s, r2c3s, r3c1s, r3c2s, r3c3s);
}
public Matrix3x3 Adjugate() => Cofactor().Transpose();
public Matrix3x3 Cofactor()
{
Matrix3x3 dets = Zero;
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[0, 1].Determinant())
+ (r1c3 * minors[0, 2].Determinant());
}
public Matrix3x3? Inverse()
{
float d = Determinant();
if (d == 0) return null;
return Adjugate() / d;
}
public Matrix2x2[,] Minors() => new Matrix2x2[,]
{
{ new(r2c2, r2c3, r3c2, r3c3), new(r2c1, r2c3, r3c1, r3c3), new(r2c1, r2c2, r3c1, r3c2) },
{ new(r1c2, r1c3, r3c2, r3c3), new(r1c1, r1c3, r3c1, r3c3), new(r1c1, r1c2, r3c1, r3c2) },
{ new(r1c2, r1c3, r2c2, r2c3), new(r1c1, r1c3, r2c1, r2c3), new(r1c1, r1c2, r2c1, r2c2) }
};
public Matrix3x3 Transpose() => new(new[,]
{
{ r1c1, r2c1, r3c1 },
{ r1c2, r2c2, r3c2 },
{ r1c3, r2c3, r3c3 }
});
public float[] GetColumn(int column) => new[] { this[0, column], this[1, column], this[2, column] };
public float[] GetRow(int row) => new[] { this[row, 0], this[row, 1], this[row, 2] };
public void SetColumn(int column, float[] vals)
{
if (vals.Length < 3)
throw new InvalidSizeException("Array must contain enough values to fill the column.");
this[0, column] = vals[0];
this[1, column] = vals[1];
this[2, column] = vals[2];
}
public void SetRow(int row, float[] vals)
{
if (vals.Length < 3)
throw new InvalidSizeException("Array must contain enough values to fill the row.");
this[row, 0] = vals[0];
this[row, 1] = vals[1];
this[row, 2] = vals[2];
}
public Matrix3x3 AddRow(int rowToChange, int referenceRow, float factor = 1)
{
Matrix3x3 @this = this;
return new(delegate (int r, int c)
{
if (r == rowToChange) return @this[r, c] += factor * @this[referenceRow, c];
else return @this[r, c];
});
}
public void AddRowMutable(int rowToChange, int referenceRow, float factor)
{
this[rowToChange, 0] += this[referenceRow, 0] * factor;
this[rowToChange, 1] += this[referenceRow, 1] * factor;
this[rowToChange, 2] += this[referenceRow, 2] * factor;
}
public Matrix3x3 ScaleRow(int rowIndex, float factor)
{
Matrix3x3 @this = this;
return new(delegate (int r, int c)
{
if (r == rowIndex) return @this[r, c] * factor;
else return @this[r, c];
});
}
public void ScaleRowMutable(int rowIndex, float factor)
{
this[rowIndex, 0] *= factor;
this[rowIndex, 1] *= factor;
this[rowIndex, 2] *= factor;
}
public Matrix3x3 SwapRows(int rowA, int rowB)
{
Matrix3x3 @this = this;
return new(delegate (int r, int c)
{
if (r == rowA) return @this[rowB, c];
else if (r == rowB) return @this[rowA, c];
else return @this[r, c];
});
}
public void SwapRowsMutable(int rowA, int rowB)
{
float[] dataA = GetRow(rowA), dataB = GetRow(rowB);
SetRow(rowA, dataB);
SetRow(rowB, dataA);
}
public virtual bool Equals(Matrix3x3? other)
{
if (other is null) return false;
return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r1c3 == other.r1c3 &&
r2c1 == other.r2c1 && r2c2 == other.r2c2 && r2c3 == other.r2c3 &&
r3c1 == other.r3c1 && r3c2 == other.r3c2 && r3c3 == other.r3c3;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() =>
r1c1 + " " + r1c2 + " " + r1c3 + "\n" +
r2c1 + " " + r2c2 + " " + r2c3 + "\n" +
r3c1 + " " + r3c2 + " " + r3c3;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
{
yield return r1c1;
yield return r1c2;
yield return r1c3;
yield return r2c1;
yield return r2c2;
yield return r2c3;
yield return r3c1;
yield return r3c2;
yield return r3c3;
}
public float[] ToArray() => new[] { r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3 };
public float[,] ToArray2D() => new[,]
{
{ r1c1, r1c2, r1c3 },
{ r2c1, r2c2, r2c3 },
{ r3c1, r3c2, r3c3 }
};
public Fill<float> ToFill() => ToFillExtension.ToFill(this);
public Fill2d<float> ToFill2D()
{
Matrix3x3 @this = this;
return (x, y) => @this[x, y];
}
public List<float> ToList() => new() { r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3 };
public static Matrix3x3 operator +(Matrix3x3 a, Matrix3x3 b) =>
new(a.r1c1 + b.r1c1, a.r1c2 + b.r1c2, a.r1c3 + b.r1c3,
a.r2c1 + b.r2c1, a.r2c2 + b.r2c2, a.r2c3 + b.r2c3,
a.r3c1 + b.r3c1, a.r3c2 + b.r3c2, 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.r1c2 - b.r1c2, a.r1c3 - b.r1c3,
a.r2c1 - b.r2c1, a.r2c2 - b.r2c2, a.r2c3 - b.r2c3,
a.r3c1 - b.r3c1, a.r3c2 - b.r3c2, a.r3c3 - b.r3c3);
public static Matrix3x3 operator *(Matrix3x3 a, float b) =>
new(a.r1c1 * b, a.r1c2 * b, a.r1c3 * b,
a.r2c1 * b, a.r2c2 * b, a.r2c3 * b,
a.r3c1 * b, a.r3c2 * 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 Float3 operator *(Matrix3x3 a, Float3 b) => (Matrix)a * b;
public static Matrix3x3 operator /(Matrix3x3 a, float b) =>
new(a.r1c1 / b, a.r1c2 / b, a.r1c3 / b,
a.r2c1 / b, a.r2c2 / b, a.r2c3 / b,
a.r3c1 / b, a.r3c2 / b, a.r3c3 / b);
public static Matrix3x3 operator /(Matrix3x3 a, Matrix3x3 b)
{
Matrix3x3? bInv = b.Inverse();
if (bInv is null) throw new NoInverseException(b);
return a * bInv;
}
public static Float3 operator /(Matrix3x3 a, Float3 b) => (Matrix)a / b;
public static Matrix3x3 operator ^(Matrix3x3 a, Matrix3x3 b) => // Single number multiplication
new(a.r1c1 * b.r1c1, a.r1c2 * b.r1c2, a.r1c3 * b.r1c3,
a.r2c1 * b.r2c1, a.r2c2 * b.r2c2, a.r2c3 * b.r2c3,
a.r3c1 * b.r3c1, a.r3c2 * b.r3c2, a.r3c3 * b.r3c3);
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 > r && m.Size.y > c ? 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 }
});
}

View File

@ -0,0 +1,672 @@
using System.Data.Common;
namespace Nerd_STF.Mathematics.Algebra;
public record class Matrix4x4 : IStaticMatrix<Matrix4x4>
{
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, r1c4);
set
{
r1c1 = value.x;
r1c2 = value.y;
r1c3 = value.z;
r1c4 = value.w;
}
}
public Float4 Row2
{
get => new(r2c1, r2c2, r2c3, r2c4);
set
{
r2c1 = value.x;
r2c2 = value.y;
r2c3 = value.z;
r2c4 = value.w;
}
}
public Float4 Row3
{
get => new(r3c1, r3c2, r3c3, r3c4);
set
{
r3c1 = value.x;
r3c2 = value.y;
r3c3 = value.z;
r3c4 = value.w;
}
}
public Float4 Row4
{
get => new(r4c1, r4c2, r4c3, r4c4);
set
{
r4c1 = value.x;
r4c2 = value.y;
r4c3 = value.z;
r4c4 = value.w;
}
}
public Int2 Size => (4, 4);
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 r1c2, float r1c3, float r1c4, float r2c1, float r2c2, float r2c3,
float r2c4, float r3c1, float r3c2, float r3c3, float r3c4, float r4c1, float r4c2, float r4c3, 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<float> 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<int> 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<float> 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<int> 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 r1, Float4 r2, Float4 r3, Float4 r4) : this(r1.x, r1.y, r1.z,
r1.w, r2.x, r2.y, r2.z, r2.w, r3.x, r3.y, r3.z, r3.w, r4.x, r4.y, r4.z, r4.w) { }
public Matrix4x4(Fill<Float4> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public Matrix4x4(Fill<Int4> fill) : this((IEnumerable<int>)fill(0), fill(1), fill(2), fill(3)) { }
public Matrix4x4(IEnumerable<float> r1, IEnumerable<float> r2, IEnumerable<float> r3, IEnumerable<float> r4)
: this(r1.ToFill(), r2.ToFill(), r3.ToFill(), r4.ToFill()) { }
public Matrix4x4(IEnumerable<int> r1, IEnumerable<int> r2, IEnumerable<int> r3, IEnumerable<int> r4)
: this(r1.ToFill(), r2.ToFill(), r3.ToFill(), r4.ToFill()) { }
public Matrix4x4(Fill<float> r1, Fill<float> r2, Fill<float> r3, Fill<float> r4) : this(r1(0), r1(1),
r1(2), r1(3), r2(0), r2(1), r2(2), r2(3), r3(0), r3(1), r3(2), r3(3), r4(0), r4(1), r4(2), r4(3)) { }
public Matrix4x4(Fill<int> r1, Fill<int> r2, Fill<int> r3, Fill<int> r4) : this(r1(0), r1(1),
r1(2), r1(3), r2(0), r2(1), r2(2), r2(3), r3(0), r3(1), r3(2), r3(3), r4(0), r4(1), r4(2), r4(3)) { }
public float this[int r, int c]
{
get => ToArray2D()[r, c];
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 float this[Index r, Index c]
{
get
{
int row = r.IsFromEnd ? 4 - r.Value : r.Value,
col = c.IsFromEnd ? 4 - c.Value : c.Value;
return this[row, col];
}
set
{
int row = r.IsFromEnd ? 4 - r.Value : r.Value,
col = c.IsFromEnd ? 4 - c.Value : c.Value;
this[row, col] = value;
}
}
public float[,] this[Range rs, Range cs]
{
get
{
int rowStart = rs.Start.IsFromEnd ? 4 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 4 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 4 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 4 - cs.End.Value : cs.End.Value;
float[,] vals = new float[rowEnd - rowStart - 1, colEnd - colStart - 1];
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
vals[r, c] = this[r, c];
return vals;
}
set
{
int rowStart = rs.Start.IsFromEnd ? 4 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 4 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 4 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 4 - cs.End.Value : cs.End.Value;
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
this[r, c] = value[r, c];
}
}
public static Matrix4x4 Absolute(Matrix4x4 val) =>
new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r1c2), Mathf.Absolute(val.r1c3), Mathf.Absolute(val.r1c4),
Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r2c2), Mathf.Absolute(val.r2c3), Mathf.Absolute(val.r2c4),
Mathf.Absolute(val.r3c1), Mathf.Absolute(val.r3c2), Mathf.Absolute(val.r3c3), Mathf.Absolute(val.r3c4),
Mathf.Absolute(val.r4c1), Mathf.Absolute(val.r4c2), Mathf.Absolute(val.r4c3), 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.r1c2), Mathf.Ceiling(val.r1c3), Mathf.Ceiling(val.r1c4),
Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r2c2), Mathf.Ceiling(val.r2c3), Mathf.Ceiling(val.r2c4),
Mathf.Ceiling(val.r3c1), Mathf.Ceiling(val.r3c2), Mathf.Ceiling(val.r3c3), Mathf.Ceiling(val.r3c4),
Mathf.Ceiling(val.r4c1), Mathf.Ceiling(val.r4c2), Mathf.Ceiling(val.r4c3), 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.r1c2, min.r1c2, max.r1c2),
Mathf.Clamp(val.r1c3, min.r1c3, max.r1c3), Mathf.Clamp(val.r1c4, min.r1c4, max.r1c4),
Mathf.Clamp(val.r2c1, min.r2c1, max.r2c1), Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2),
Mathf.Clamp(val.r2c3, min.r2c3, max.r2c3), Mathf.Clamp(val.r2c4, min.r2c4, max.r2c4),
Mathf.Clamp(val.r3c1, min.r3c1, max.r3c1), Mathf.Clamp(val.r3c2, min.r3c2, max.r3c2),
Mathf.Clamp(val.r3c3, min.r3c3, max.r3c3), Mathf.Clamp(val.r3c4, min.r3c4, max.r3c4),
Mathf.Clamp(val.r4c1, min.r4c1, max.r4c1), Mathf.Clamp(val.r4c2, min.r4c2, max.r4c2),
Mathf.Clamp(val.r4c3, min.r4c3, max.r4c3), 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.r1c2), Mathf.Floor(val.r1c3), Mathf.Floor(val.r1c4),
Mathf.Floor(val.r2c1), Mathf.Floor(val.r2c2), Mathf.Floor(val.r2c3), Mathf.Floor(val.r2c4),
Mathf.Floor(val.r3c1), Mathf.Floor(val.r3c2), Mathf.Floor(val.r3c3), Mathf.Floor(val.r3c4),
Mathf.Floor(val.r4c1), Mathf.Floor(val.r4c2), Mathf.Floor(val.r4c3), 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.r1c2, b.r1c2, t, clamp),
Mathf.Lerp(a.r1c3, b.r1c3, t, clamp), Mathf.Lerp(a.r1c4, b.r1c4, t, clamp),
Mathf.Lerp(a.r2c1, b.r2c1, t, clamp), Mathf.Lerp(a.r2c2, b.r2c2, t, clamp),
Mathf.Lerp(a.r2c3, b.r2c3, t, clamp), Mathf.Lerp(a.r2c4, b.r2c4, t, clamp),
Mathf.Lerp(a.r3c1, b.r3c1, t, clamp), Mathf.Lerp(a.r3c2, b.r3c2, t, clamp),
Mathf.Lerp(a.r3c3, b.r3c3, t, clamp), Mathf.Lerp(a.r3c4, b.r3c4, t, clamp),
Mathf.Lerp(a.r4c1, b.r4c1, t, clamp), Mathf.Lerp(a.r4c2, b.r4c2, t, clamp),
Mathf.Lerp(a.r4c3, b.r4c3, 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.r1c2), Mathf.Round(val.r1c3), Mathf.Round(val.r1c4),
Mathf.Round(val.r2c1), Mathf.Round(val.r2c2), Mathf.Round(val.r2c3), Mathf.Round(val.r2c4),
Mathf.Round(val.r3c1), Mathf.Round(val.r3c2), Mathf.Round(val.r3c3), Mathf.Round(val.r3c4),
Mathf.Round(val.r4c1), Mathf.Round(val.r4c2), Mathf.Round(val.r4c3), 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[] r1c2, float[] r1c3, float[] r1c4, float[] r2c1, float[] r2c2s,
float[] r2c3, float[] r2c4, float[] r3c1, float[] r3c2, float[] r3c3s, float[] r3c4, float[] r4c1,
float[] r4c2, float[] r4c3, float[] r4c4s) SplitArray(params Matrix4x4[] vals)
{
float[] r1c1s = new float[vals.Length], r1c2s = new float[vals.Length], r1c3s = new float[vals.Length],
r1c4s = new float[vals.Length], r2c1s = new float[vals.Length], r2c2s = new float[vals.Length],
r2c3s = new float[vals.Length], r2c4s = new float[vals.Length], r3c1s = new float[vals.Length],
r3c2s = new float[vals.Length], r3c3s = new float[vals.Length], r3c4s = new float[vals.Length],
r4c1s = new float[vals.Length], r4c2s = new float[vals.Length], r4c3s = new float[vals.Length],
r4c4s = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
r1c1s[i] = vals[i].r1c1;
r1c2s[i] = vals[i].r1c2;
r1c3s[i] = vals[i].r1c3;
r1c4s[i] = vals[i].r1c4;
r2c1s[i] = vals[i].r2c1;
r2c2s[i] = vals[i].r2c2;
r2c3s[i] = vals[i].r2c3;
r2c4s[i] = vals[i].r2c4;
r3c1s[i] = vals[i].r3c1;
r3c2s[i] = vals[i].r3c2;
r3c3s[i] = vals[i].r3c3;
r3c4s[i] = vals[i].r3c4;
r4c1s[i] = vals[i].r4c1;
r4c2s[i] = vals[i].r4c2;
r4c3s[i] = vals[i].r4c3;
r4c4s[i] = vals[i].r4c4;
}
return (r1c1s, r1c2s, r1c3s, r1c4s, r2c1s, r2c2s, r2c3s, r2c4s,
r3c1s, r3c2s, r3c3s, r3c4s, r4c1s, r4c2s, r4c3s, r4c4s);
}
public Matrix4x4 Adjugate() => Cofactor().Transpose();
public Matrix4x4 Cofactor()
{
Matrix4x4 dets = Zero;
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[0, 1].Determinant()) +
(r1c3 * minors[0, 2].Determinant()) - (r1c4 * minors[0, 3].Determinant());
}
public Matrix4x4? Inverse()
{
float d = Determinant();
if (d == 0) return null;
return Adjugate() / d;
}
public Matrix3x3[,] Minors() => new Matrix3x3[,]
{
{
new(r2c2, r2c3, r2c4, r3c2, r3c3, r3c4, r4c2, r4c3, r4c4),
new(r2c1, r2c3, r2c4, r3c1, r3c3, r3c4, r4c1, r4c3, r4c4),
new(r2c1, r2c2, r2c4, r3c1, r3c2, r3c4, r4c1, r4c2, r4c4),
new(r2c1, r2c2, r2c3, r3c1, r3c2, r3c3, r4c1, r4c2, r4c3)
},
{
new(r1c2, r1c3, r1c4, r3c2, r3c3, r3c4, r4c2, r4c3, r4c4),
new(r1c1, r1c3, r1c4, r3c1, r3c3, r3c4, r4c1, r4c3, r4c4),
new(r1c1, r1c2, r1c4, r3c1, r3c2, r3c4, r4c1, r4c2, r4c4),
new(r1c1, r1c2, r1c3, r3c1, r3c2, r3c3, r4c1, r4c2, r4c3)
},
{
new(r1c2, r1c3, r1c4, r2c2, r2c3, r2c4, r4c2, r4c3, r4c4),
new(r1c1, r1c3, r1c4, r2c1, r2c3, r2c4, r4c1, r4c3, r4c4),
new(r1c1, r1c2, r1c4, r2c1, r2c2, r2c4, r4c1, r4c2, r4c4),
new(r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r4c1, r4c2, r4c3)
},
{
new(r1c2, r1c3, r1c4, r2c2, r2c3, r2c4, r3c2, r3c3, r3c4),
new(r1c1, r1c3, r1c4, r2c1, r2c3, r2c4, r3c1, r3c3, r3c4),
new(r1c1, r1c2, r1c4, r2c1, r2c2, r2c4, r3c1, r3c2, r3c4),
new(r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3)
}
};
public Matrix4x4 Transpose() => new(new[,]
{
{ r1c1, r2c1, r3c1, r4c1 },
{ r1c2, r2c2, r3c2, r4c2 },
{ r1c3, r2c3, r3c3, r4c3 },
{ r1c4, r2c4, r3c4, r4c4 }
});
public float[] GetColumn(int column) =>
new[] { this[0, column], this[1, column], this[2, column], this[3, column] };
public float[] GetRow(int row) =>
new[] { this[row, 0], this[row, 1], this[row, 2], this[row, 3] };
public void SetColumn(int column, float[] vals)
{
if (vals.Length < 4)
throw new InvalidSizeException("Array must contain enough values to fill the column.");
this[0, column] = vals[0];
this[1, column] = vals[1];
this[2, column] = vals[2];
this[3, column] = vals[3];
}
public void SetRow(int row, float[] vals)
{
if (vals.Length < 4)
throw new InvalidSizeException("Array must contain enough values to fill the row.");
this[row, 0] = vals[0];
this[row, 1] = vals[1];
this[row, 2] = vals[2];
this[row, 3] = vals[3];
}
public Matrix4x4 AddRow(int rowToChange, int referenceRow, float factor = 1)
{
Matrix4x4 @this = this;
return new(delegate (int r, int c)
{
if (r == rowToChange) return @this[r, c] += factor * @this[referenceRow, c];
else return @this[r, c];
});
}
public void AddRowMutable(int rowToChange, int referenceRow, float factor)
{
this[rowToChange, 0] += this[referenceRow, 0] * factor;
this[rowToChange, 1] += this[referenceRow, 1] * factor;
this[rowToChange, 2] += this[referenceRow, 2] * factor;
this[rowToChange, 3] += this[referenceRow, 3] * factor;
}
public Matrix4x4 ScaleRow(int rowIndex, float factor)
{
Matrix4x4 @this = this;
return new(delegate (int r, int c)
{
if (r == rowIndex) return @this[r, c] * factor;
else return @this[r, c];
});
}
public void ScaleRowMutable(int rowIndex, float factor)
{
this[rowIndex, 0] *= factor;
this[rowIndex, 1] *= factor;
this[rowIndex, 2] *= factor;
this[rowIndex, 3] *= factor;
}
public Matrix4x4 SwapRows(int rowA, int rowB)
{
Matrix4x4 @this = this;
return new(delegate (int r, int c)
{
if (r == rowA) return @this[rowB, c];
else if (r == rowB) return @this[rowA, c];
else return @this[r, c];
});
}
public void SwapRowsMutable(int rowA, int rowB)
{
float[] dataA = GetRow(rowA), dataB = GetRow(rowB);
SetRow(rowA, dataB);
SetRow(rowB, dataA);
}
public virtual bool Equals(Matrix4x4? other)
{
if (other is null) return false;
return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r1c3 == other.r1c3 && r1c4 == other.r1c4 &&
r2c1 == other.r2c1 && r2c2 == other.r2c2 && r2c3 == other.r2c3 && r2c4 == other.r2c4 &&
r3c1 == other.r3c1 && r3c2 == other.r3c2 && r3c3 == other.r3c3 && r3c4 == other.r3c4 &&
r4c1 == other.r4c1 && r4c2 == other.r4c2 && r4c3 == other.r4c3 && r4c4 == other.r4c4;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() =>
r1c1 + " " + r1c2 + " " + r1c3 + " " + r1c4 + "\n" +
r2c1 + " " + r2c2 + " " + r2c3 + " " + r2c4 + "\n" +
r3c1 + " " + r3c2 + " " + r3c3 + " " + r3c4 + "\n" +
r4c1 + " " + r4c2 + " " + r4c3 + " " + r4c4;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
{
yield return r1c1;
yield return r1c2;
yield return r1c3;
yield return r1c4;
yield return r2c1;
yield return r2c2;
yield return r2c3;
yield return r2c4;
yield return r3c1;
yield return r3c2;
yield return r3c3;
yield return r3c4;
yield return r4c1;
yield return r4c2;
yield return r4c3;
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 Fill<float> ToFill() => ToFillExtension.ToFill(this);
public Fill2d<float> ToFill2D()
{
Matrix4x4 @this = this;
return (x, y) => @this[x, y];
}
public List<float> 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.r1c2 + b.r1c2, a.r1c3 + b.r1c3, a.r1c4 + b.r1c4,
a.r2c1 + b.r2c1, a.r2c2 + b.r2c2, a.r2c3 + b.r2c3, a.r2c4 + b.r2c4,
a.r3c1 + b.r3c1, a.r3c2 + b.r3c2, a.r3c3 + b.r3c3, a.r3c4 + b.r3c4,
a.r4c1 + b.r4c1, a.r4c2 + b.r4c2, a.r4c3 + b.r4c3, 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.r1c2 - b.r1c2, a.r1c3 - b.r1c3, a.r1c4 - b.r1c4,
a.r2c1 - b.r2c1, a.r2c2 - b.r2c2, a.r2c3 - b.r2c3, a.r2c4 - b.r2c4,
a.r3c1 - b.r3c1, a.r3c2 - b.r3c2, a.r3c3 - b.r3c3, a.r3c4 - b.r3c4,
a.r4c1 - b.r4c1, a.r4c2 - b.r4c2, a.r4c3 - b.r4c3, a.r4c4 - b.r4c4);
public static Matrix4x4 operator *(Matrix4x4 a, float b) =>
new(a.r1c1 * b, a.r1c2 * b, a.r1c3 * b, a.r1c4 * b,
a.r2c1 * b, a.r2c2 * b, a.r2c3 * b, a.r2c4 * b,
a.r3c1 * b, a.r3c2 * b, a.r3c3 * b, a.r3c4 * b,
a.r4c1 * b, a.r4c2 * b, a.r4c3 * 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 Float4 operator *(Matrix4x4 a, Float4 b) => (Matrix)a * b;
public static Matrix4x4 operator /(Matrix4x4 a, float b) =>
new(a.r1c1 / b, a.r1c2 / b, a.r1c3 / b, a.r1c4 / b,
a.r2c1 / b, a.r2c2 / b, a.r2c3 / b, a.r2c4 / b,
a.r3c1 / b, a.r3c2 / b, a.r3c3 / b, a.r3c4 / b,
a.r4c1 / b, a.r4c2 / b, a.r4c3 / b, a.r4c4 / b);
public static Matrix4x4 operator /(Matrix4x4 a, Matrix4x4 b)
{
Matrix4x4? bInv = b.Inverse();
if (bInv is null) throw new NoInverseException(b);
return a * bInv;
}
public static Float4 operator /(Matrix4x4 a, Float4 b) => (Matrix)a / b;
public static Matrix4x4 operator ^(Matrix4x4 a, Matrix4x4 b) => // Single number multiplication
new(a.r1c1 * b.r1c1, a.r1c2 * b.r1c2, a.r1c3 * b.r1c3, a.r1c4 * b.r1c4,
a.r2c1 * b.r2c1, a.r2c2 * b.r2c2, a.r2c3 * b.r2c3, a.r2c4 * b.r2c4,
a.r3c1 * b.r3c1, a.r3c2 * b.r3c2, a.r3c3 * b.r3c3, a.r3c4 * b.r3c4,
a.r4c1 * b.r4c1, a.r4c2 * b.r4c2, a.r4c3 * b.r4c3, a.r4c4 * b.r4c4);
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 > r && m.Size.y > c ? 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 }
});
}
}

View File

@ -0,0 +1,142 @@
namespace Nerd_STF.Mathematics.Algebra;
public record struct Vector2d : IAbsolute<Vector2d>, IAverage<Vector2d>,
IClampMagnitude<Vector2d, float>, IComparable<Vector2d>, ICross<Vector2d, Vector3d>,
IDot<Vector2d, float>, IEquatable<Vector2d>, IFromTuple<Vector2d, (Angle angle, float mag)>,
ILerp<Vector2d, float>, IMax<Vector2d>, IMagnitude<float>, IMedian<Vector2d>, IMin<Vector2d>,
IPresets2d<Vector2d>, ISplittable<Vector2d, (Angle[] rots, float[] mags)>, ISubtract<Vector2d>,
ISum<Vector2d>
{
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 float Magnitude
{
get => magnitude;
set => magnitude = value;
}
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 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 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 bool Equals(Vector2d other) => theta == other.theta && magnitude == other.magnitude;
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => ToString(Angle.Type.Degrees);
public string ToString(Angle.Type outputType) =>
nameof(Vector2d) + " { Mag = " + magnitude + ", Rot = " + theta.ToString(outputType) + " }";
public Float2 ToXYZ() => new Float2(Mathf.Cos(theta), Mathf.Sin(theta)) * magnitude;
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, float b) => new(a.theta, a.magnitude * b);
public static Vector2d operator *(Vector2d a, Matrix b) => (Vector2d)((Matrix)a * b);
public static Vector2d operator /(Vector2d a, float b) => new(a.theta, a.magnitude / b);
public static Vector2d operator /(Vector2d a, Matrix b) => (Vector2d)((Matrix)a / 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);
public static implicit operator Vector2d((Angle angle, float mag) val) => new(val.angle, val.mag);
}

View File

@ -0,0 +1,208 @@
namespace Nerd_STF.Mathematics.Algebra;
public record struct Vector3d : IAbsolute<Vector3d>, IAverage<Vector3d>, IClampMagnitude<Vector3d, float>,
IComparable<Vector3d>, ICross<Vector3d>, IDot<Vector3d, float>, IEquatable<Vector3d>,
IFromTuple<Vector3d, (Angle yaw, Angle pitch, float mag)>, IIndexAll<Angle>, IIndexRangeAll<Angle>,
ILerp<Vector3d, float>, IMagnitude<float>, IMax<Vector3d>, IMedian<Vector3d>, IMin<Vector3d>,
IPresets3d<Vector3d>, ISubtract<Vector3d>, ISum<Vector3d>
{
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 float Magnitude
{
get => magnitude;
set => magnitude = value;
}
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<Angle> fill, float mag = 1) : this(fill(0), fill(1), mag) { }
public Vector3d(Fill<float> 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 Angle this[Index index]
{
get => this[index.IsFromEnd ? 2 - index.Value : index.Value];
set => this[index.IsFromEnd ? 2 - index.Value : index.Value] = value;
}
public Angle[] this[Range range]
{
get
{
int start = range.Start.IsFromEnd ? 2 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 2 - range.End.Value : range.End.Value;
List<Angle> res = new();
for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray();
}
set
{
int start = range.Start.IsFromEnd ? 2 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 2 - range.End.Value : range.End.Value;
for (int i = start; i < end; i++) this[i] = value[i];
}
}
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 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 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[] yaws, Angle[] pitches, float[] mags) SplitArray(params Vector3d[] vals)
{
Angle[] yaws = new Angle[vals.Length], pitches = new Angle[vals.Length];
float[] mags = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
yaws[i] = vals[i].yaw;
pitches[i] = vals[i].pitch;
mags[i] = vals[i].magnitude;
}
return (yaws, pitches, mags);
}
public int CompareTo(Vector3d other) => magnitude.CompareTo(other.magnitude);
public bool Equals(Vector3d other) => yaw == other.yaw && pitch == other.pitch
&& magnitude == other.magnitude;
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => ToString(Angle.Type.Degrees);
public string ToString(Angle.Type outputType) =>
nameof(Vector3d) + " { Mag = " + magnitude + ", Yaw = " + yaw.ToString(outputType) +
", Pitch = " + pitch.ToString(outputType) + " }";
public Float3 ToXYZ() => new Float3(Mathf.Sin(pitch) * Mathf.Sin(yaw),
Mathf.Cos(yaw),
Mathf.Cos(pitch) * Mathf.Sin(yaw)) * 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, float b) => new(a.yaw, a.pitch, a.magnitude * b);
public static Vector3d operator *(Vector3d a, Matrix b) => (Vector3d)((Matrix)a * b);
public static Vector3d operator /(Vector3d a, float b) => new(a.yaw, a.pitch, a.magnitude / b);
public static Vector3d operator /(Vector3d a, Matrix b) => (Vector3d)((Matrix)a / 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);
public static implicit operator Vector3d((Angle yaw, Angle pitch, float mag) val) =>
new(val.yaw, val.pitch, val.mag);
}

View File

@ -1,7 +1,14 @@
namespace Nerd_STF.Mathematics;
public struct Angle : ICloneable, IComparable<Angle>, IEquatable<Angle>
public struct Angle : IAbsolute<Angle>, IAverage<Angle>, IClamp<Angle>, ICloneable,
IComparable<Angle>, IEquatable<Angle>, ILerp<Angle, float>, IMax<Angle>, IMedian<Angle>,
IMin<Angle>, IPresets2d<Angle>
{
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);
@ -18,87 +25,104 @@ public struct Angle : ICloneable, IComparable<Angle>, IEquatable<Angle>
get => p_deg * 1.11111111111f; // Reciprocal of 9/10 as a constant (10/9)
set => p_deg = value * 0.9f;
}
public float Normalized
{
get => p_deg / 360;
set => p_deg = value * 360;
}
public float Radians
{
get => p_deg * Mathf.DegToRad;
set => p_deg = value * Mathf.RadToDeg;
get => p_deg * Constants.DegToRad;
set => p_deg = value * Constants.RadToDeg;
}
public Angle Bounded => new(p_deg % 360);
public Angle Bounded => new(Mathf.AbsoluteMod(p_deg, 360));
public Angle Complimentary => Quarter - this;
public Angle Supplementary => Half - this;
public Angle Reflected => new Angle(-p_deg).Bounded;
private float p_deg;
public Angle(float value, Type valueType = Type.Degrees)
{
p_deg = valueType switch
public Angle(float value, Type valueType = Type.Degrees) => p_deg = valueType switch
{
Type.Degrees => value,
Type.Gradians => value * 0.9f,
Type.Radians => value * Mathf.RadToDeg,
Type.Normalized => value * 360,
Type.Radians => value * Constants.RadToDeg,
_ => throw new ArgumentException("Unknown type.", nameof(valueType)),
};
}
public static Angle Absolute(Angle val) => new(Mathf.Absolute(val.p_deg));
public static Angle Average(params Angle[] vals) => new(Mathf.Average(ToDoubles(Type.Degrees, vals)));
public static Angle Ceiling(Angle val) => new(Mathf.Ceiling(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)), 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) => new(Mathf.Ceiling(val.p_deg));
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(ToDoubles(Type.Degrees, vals)));
public static Angle Median(params Angle[] vals) => new(Mathf.Median(ToDoubles(Type.Degrees, vals)));
public static Angle Min(params Angle[] vals) => new(Mathf.Min(ToDoubles(Type.Degrees, vals)));
public static Angle Max(params Angle[] vals) => Max(true, vals);
public static Angle Max(bool useBounded, params Angle[] vals)
{
if (!useBounded) return new(Mathf.Max(SplitArray(Type.Degrees, vals)));
public static float[] ToDoubles(Type outputType, params Angle[] vals)
Angle[] boundeds = new Angle[vals.Length];
for (int i = 0; i < vals.Length; i++) boundeds[i] = vals[i].Bounded;
return new(Mathf.Max(SplitArray(Type.Degrees, boundeds)));
}
public static Angle Median(params Angle[] vals) => new(Mathf.Median(SplitArray(Type.Degrees, vals)));
public static Angle Min(params Angle[] vals) => Min(true, vals);
public static Angle Min(bool useBounded, params Angle[] vals)
{
if (!useBounded) return new(Mathf.Min(SplitArray(Type.Degrees, vals)));
Angle[] boundeds = new Angle[vals.Length];
for (int i = 0; i < vals.Length; i++) boundeds[i] = vals[i].Bounded;
return new(Mathf.Min(SplitArray(Type.Degrees, boundeds)));
}
public static Angle Round(Angle val, Type type = Type.Degrees) =>
new(Mathf.Round(val.ValueFromType(type)), type);
public static float[] SplitArray(Type outputType, params Angle[] vals)
{
float[] res = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
res[i] = outputType switch
{
Type.Degrees => vals[i].Degrees,
Type.Gradians => vals[i].Gradians,
Type.Radians => vals[i].Radians,
_ => throw new ArgumentException("Unknown type.", nameof(outputType)),
};
}
for (int i = 0; i < vals.Length; i++) res[i] = vals[i].ValueFromType(outputType);
return res;
}
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;
public override int GetHashCode() => Degrees.GetHashCode() ^ Gradians.GetHashCode() ^ Radians.GetHashCode();
public override string ToString() => ToString((string?)null);
public string ToString(Type outputType) => ToString((string?)null, outputType);
public string ToString(string? provider, Type outputType = Type.Degrees) => outputType switch
public override string ToString() => ToString(Type.Degrees);
public string ToString(Type outputType) => outputType switch
{
Type.Degrees => p_deg.ToString(provider),
Type.Gradians => Gradians.ToString(provider),
Type.Radians => Radians.ToString(provider),
_ => throw new ArgumentException("Unknown type.", nameof(outputType)),
};
public string ToString(IFormatProvider provider, Type outputType = Type.Degrees) => outputType switch
{
Type.Degrees => p_deg.ToString(provider),
Type.Gradians => Gradians.ToString(provider),
Type.Radians => Radians.ToString(provider),
Type.Degrees => p_deg + "°",
Type.Gradians => Gradians + "grad",
Type.Normalized => Normalized + "%",
Type.Radians => Radians + "rad",
_ => throw new ArgumentException("Unknown type.", nameof(outputType)),
};
public object Clone() => new Angle(p_deg);
public float ValueFromType(Type type) => type switch
{
Type.Degrees => Degrees,
Type.Gradians => Gradians,
Type.Normalized => Normalized,
Type.Radians => Radians,
_ => throw new ArgumentException("Unknown type.", nameof(type))
};
public static Angle operator +(Angle a, Angle b) => new(a.p_deg + b.p_deg);
public static Angle operator -(Angle a) => new(-a.p_deg);
public static Angle operator -(Angle a) => new(a.p_deg + 180);
public static Angle operator -(Angle a, Angle b) => new(a.p_deg - b.p_deg);
public static Angle operator *(Angle a, Angle b) => new(a.p_deg * b.p_deg);
public static Angle operator *(Angle a, float b) => new(a.p_deg * b);
public static Angle operator /(Angle a, Angle b) => new(a.p_deg / b.p_deg);
public static Angle operator /(Angle a, float b) => new(a.p_deg / b);
public static bool operator ==(Angle a, Angle b) => a.Equals(b);
public static bool operator !=(Angle a, Angle b) => !a.Equals(b);
@ -107,10 +131,13 @@ public struct Angle : ICloneable, IComparable<Angle>, IEquatable<Angle>
public static bool operator >=(Angle a, Angle b) => a == b || a > b;
public static bool operator <=(Angle a, Angle b) => a == b || a < b;
public static implicit operator Angle((float val, Type type) obj) => new(obj.val, obj.type);
public enum Type
{
Degrees,
Gradians,
Normalized,
Radians,
}
}

View File

@ -4,18 +4,10 @@ public static class Calculus
{
public const float DefaultStep = 0.001f;
public static Equation GetDerivative(Equation equ, float min, float max, float step = DefaultStep)
{
Dictionary<float, float> vals = new();
for (float x = min; x <= max; x += step)
{
float val1 = equ(x), val2 = equ(x + step), change = (val2 - val1) / step;
vals.Add(x, change);
}
return Mathf.MakeEquation(vals);
}
public static Equation GetDerivative(Equation equ, float step = DefaultStep) =>
x => GetDerivativeAtPoint(equ, x, step);
public static float GetDerivativeAtPoint(Equation equ, float x, float step = DefaultStep) =>
(equ(x + DefaultStep) - equ(x)) / step;
(equ(x + step) - equ(x)) / step;
public static float GetIntegral(Equation equ, float lowerBound, float upperBound, float step = DefaultStep)
{
@ -24,11 +16,41 @@ public static class Calculus
return val;
}
public static float GradientDescent(Equation equ, float initial, float rate, float stepCount = 1000,
public static Equation GetDynamicIntegral(Equation equ, Equation lowerBound, Equation upperBound,
float step = DefaultStep) => x => GetIntegral(equ, lowerBound(x), upperBound(x), step);
public static Equation GetTaylorSeries(Equation equ, float referenceX, int iterations = 4, float step = 0.01f)
{
Equation activeDerivative = equ;
float[] coefficients = new float[iterations];
int fact = 1;
for (int i = 0; i < iterations; i++)
{
coefficients[i] = activeDerivative(referenceX) / fact;
activeDerivative = GetDerivative(activeDerivative, step);
fact *= i + 1;
}
return delegate (float x)
{
float xVal = 1, result = 0;
for (int i = 0; i < coefficients.Length; i++)
{
result += coefficients[i] * xVal;
xVal *= x;
}
return result;
};
}
// Unfortunately, I cannot test this function, as I have literally no idea how it works and
// I can't find any tools online (and couldn't make my own) to compare my results.
// Something to know, though I didn't feel like it deserved its own [Obsolete] attribute.
public static float GradientDescent(Equation equ, float initial, float rate, int iterations = 1000,
float step = DefaultStep)
{
float val = initial;
for (int i = 0; i < stepCount; i++) val -= GetDerivativeAtPoint(equ, val, step) * rate;
for (int i = 0; i < iterations; i++) val -= GetDerivativeAtPoint(equ, val, step) * rate;
return val;
}
}

View File

@ -0,0 +1,3 @@
namespace Nerd_STF.Mathematics;
public delegate Complex Equation2d(Complex input);

View File

@ -1,6 +1,12 @@
namespace Nerd_STF.Mathematics;
public struct Float2 : ICloneable, IComparable<Float2>, IEquatable<Float2>, IGroup<float>
public record struct Float2 : IAbsolute<Float2>, IAverage<Float2>, ICeiling<Float2, Int2>,
IClamp<Float2>, IClampMagnitude<Float2, float>, IComparable<Float2>,
ICross<Float2, Float3>, IDivide<Float2>, IDot<Float2, float>, IEquatable<Float2>,
IFloor<Float2, Int2>, IFromTuple<Float2, (float x, float y)>, IGroup<float>,
ILerp<Float2, float>, IMathOperators<Float2>, IMax<Float2>, IMedian<Float2>, IMin<Float2>,
IIndexAll<float>, IIndexRangeAll<float>, IPresets2d<Float2>, IProduct<Float2>, IRound<Float2, Int2>,
ISplittable<Float2, (float[] Xs, float[] Ys)>, ISubtract<Float2>, ISum<Float2>
{
public static Float2 Down => new(0, -1);
public static Float2 Left => new(-1, 0);
@ -11,19 +17,18 @@ public struct Float2 : ICloneable, IComparable<Float2>, IEquatable<Float2>, 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;
public Float2(float all) : this(all, all) { }
public Float2(Fill<float> fill) : this(fill(0), fill(1)) { }
public Float2(Fill<int> fill) : this(fill(0), fill(1)) { }
public Float2(float x, float y)
{
this.x = x;
this.y = y;
}
public Float2(Fill<float> fill) : this(fill(0), fill(1)) { }
public Float2(Fill<int> fill) : this(fill(0), fill(1)) { }
public float this[int index]
{
get => index switch
@ -48,11 +53,33 @@ public struct Float2 : ICloneable, IComparable<Float2>, IEquatable<Float2>, IGro
}
}
}
public float this[Index index]
{
get => this[index.IsFromEnd ? 2 - index.Value : index.Value];
set => this[index.IsFromEnd ? 2 - index.Value : index.Value] = value;
}
public float[] this[Range range]
{
get
{
int start = range.Start.IsFromEnd ? 2 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 2 - range.End.Value : range.End.Value;
List<float> res = new();
for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray();
}
set
{
int start = range.Start.IsFromEnd ? 2 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 2 - range.End.Value : range.End.Value;
for (int i = start; i < end; i++) this[i] = value[i];
}
}
public static Float2 Absolute(Float2 val) =>
new(Mathf.Absolute(val.x), Mathf.Absolute(val.y));
public static Float2 Average(params Float2[] vals) => Sum(vals) / vals.Length;
public static Float2 Ceiling(Float2 val) =>
public static Int2 Ceiling(Float2 val) =>
new(Mathf.Ceiling(val.x), Mathf.Ceiling(val.y));
public static Float2 Clamp(Float2 val, Float2 min, Float2 max) =>
new(Mathf.Clamp(val.x, min.x, max.x),
@ -70,81 +97,74 @@ public struct Float2 : ICloneable, IComparable<Float2>, IEquatable<Float2>, IGro
}
public static Float3 Cross(Float2 a, Float2 b, bool normalized = false) =>
Float3.Cross(a, b, normalized);
public static Float2 Divide(Float2 num, params Float2[] vals)
{
foreach (Float2 d in vals) num /= d;
return num;
}
public static Float2 Divide(Float2 num, params Float2[] vals) => num / Product(vals);
public static float Dot(Float2 a, Float2 b) => a.x * b.x + a.y * b.y;
public static float Dot(params Float2[] vals)
{
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;
}
public static Float2 Floor(Float2 val) =>
public static Int2 Floor(Float2 val) =>
new(Mathf.Floor(val.x), Mathf.Floor(val.y));
public static Float2 Lerp(Float2 a, Float2 b, float t, bool clamp = true) =>
new(Mathf.Lerp(a.x, b.x, t, clamp), Mathf.Lerp(a.y, b.y, t, clamp));
public static Float2 Median(params Float2[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
float index = (vals.Length - 1) * 0.5f;
Float2 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
return (valA + valB) * 0.5f;
}
public static Float2 Max(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.Magnitude > val.Magnitude ? 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.Magnitude < val.Magnitude ? f : val;
return val;
}
public static Float2 Multiply(params Float2[] vals)
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 Subtract(Float2 num, params Float2[] vals)
{
foreach (Float2 d in vals) num -= d;
return num;
}
public static Int2 Round(Float2 val) =>
new(Mathf.RoundInt(val.x), Mathf.RoundInt(val.y));
public static Float2 Subtract(Float2 num, params Float2[] vals) => num - Sum(vals);
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;
}
public int CompareTo(Float2 other) => Magnitude.CompareTo(other.Magnitude);
public override bool Equals([NotNullWhen(true)] object? obj)
public static (float[] Xs, float[] Ys) SplitArray(params Float2[] vals)
{
if (obj == null || obj.GetType() != typeof(Float2)) return false;
return Equals((Float2)obj);
float[] Xs = new float[vals.Length], Ys = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Xs[i] = vals[i].x;
Ys[i] = vals[i].y;
}
return (Xs, Ys);
}
public bool Equals(Float2 other) => x == other.x && y == other.y;
public override int GetHashCode() => x.GetHashCode() ^ y.GetHashCode();
public override string ToString() => ToString((string?)null);
public string ToString(string? provider) =>
"X: " + x.ToString(provider) + " Y: " + y.ToString(provider);
public string ToString(IFormatProvider provider) =>
"X: " + x.ToString(provider) + " Y: " + y.ToString(provider);
public object Clone() => new Float2(x, y);
public int CompareTo(Float2 other) => Magnitude.CompareTo(other.Magnitude);
public bool Equals(Float2 other) => x == other.x && y == other.y;
public override int GetHashCode() => base.GetHashCode();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
@ -154,28 +174,46 @@ public struct Float2 : ICloneable, IComparable<Float2>, IEquatable<Float2>, IGro
}
public float[] ToArray() => new[] { x, y };
public Fill<float> ToFill()
{
Float2 @this = this;
return i => @this[i];
}
public List<float> ToList() => new() { x, y };
public Vector2d ToVector() => new(Mathf.ArcTan(y / x), Magnitude);
private bool PrintMembers(StringBuilder builder)
{
builder.Append("x = ");
builder.Append(x);
builder.Append(", y = ");
builder.Append(y);
return true;
}
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 Quaternion operator *(Float2 a, Quaternion b) => (Quaternion)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 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;
public static bool operator <(Float2 a, Float2 b) => a.CompareTo(b) < 0;
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 Float2 operator /(Float2 a, Matrix b) => (Float2)((Matrix)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<float> fill) => new(fill);
public static implicit operator Float2(Fill<int> fill) => new(fill);
public static implicit operator Float2((float x, float y) val) => new(val.x, val.y);
}

View File

@ -1,6 +1,14 @@
namespace Nerd_STF.Mathematics;
using System;
public struct Float3 : ICloneable, IComparable<Float3>, IEquatable<Float3>, IGroup<float>
namespace Nerd_STF.Mathematics;
public record struct Float3 : IAbsolute<Float3>, IAverage<Float3>,
ICeiling<Float3, Int3>, IClamp<Float3>, IClampMagnitude<Float3, float>, IComparable<Float3>,
ICross<Float3>, IDivide<Float3>, IDot<Float3, float>, IEquatable<Float3>,
IFloor<Float3, Int3>, IFromTuple<Float3, (float x, float y, float z)>, IGroup<float>,
IIndexAll<float>, IIndexRangeAll<float>, ILerp<Float3, float>, IMathOperators<Float3>, IMax<Float3>,
IMedian<Float3>, IMin<Float3>, IPresets3d<Float3>, IProduct<Float3>, IRound<Float3, Int3>,
ISplittable<Float3, (float[] Xs, float[] Ys, float[] Zs)>, ISubtract<Float3>, ISum<Float3>
{
public static Float3 Back => new(0, 0, -1);
public static Float3 Down => new(0, -1, 0);
@ -13,11 +21,35 @@ public struct Float3 : ICloneable, IComparable<Float3>, IEquatable<Float3>, 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);
public Float2 YZ => new(y, z);
public Float2 XY
{
get => (x, y);
set
{
x = value.x;
y = value.y;
}
}
public Float2 XZ
{
get => (x, z);
set
{
x = value.x;
z = value.y;
}
}
public Float2 YZ
{
get => (y, z);
set
{
y = value.x;
z = value.y;
}
}
public float x, y, z;
@ -61,11 +93,33 @@ public struct Float3 : ICloneable, IComparable<Float3>, IEquatable<Float3>, IGro
}
}
}
public float this[Index index]
{
get => this[index.IsFromEnd ? 3 - index.Value : index.Value];
set => this[index.IsFromEnd ? 3 - index.Value : index.Value] = value;
}
public float[] this[Range range]
{
get
{
int start = range.Start.IsFromEnd ? 3 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 3 - range.End.Value : range.End.Value;
List<float> res = new();
for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray();
}
set
{
int start = range.Start.IsFromEnd ? 3 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 3 - range.End.Value : range.End.Value;
for (int i = start; i < end; i++) this[i] = value[i];
}
}
public static Float3 Absolute(Float3 val) =>
new(Mathf.Absolute(val.x), Mathf.Absolute(val.y), Mathf.Absolute(val.z));
public static Float3 Average(params Float3[] vals) => Sum(vals) / vals.Length;
public static Float3 Ceiling(Float3 val) =>
public static Int3 Ceiling(Float3 val) =>
new(Mathf.Ceiling(val.x), Mathf.Ceiling(val.y), Mathf.Ceiling(val.z));
public static Float3 Clamp(Float3 val, Float3 min, Float3 max) =>
new(Mathf.Clamp(val.x, min.x, max.x),
@ -89,11 +143,7 @@ public struct Float3 : ICloneable, IComparable<Float3>, IEquatable<Float3>, IGro
a.x * b.y - b.x * a.y);
return normalized ? val.Normalized : val;
}
public static Float3 Divide(Float3 num, params Float3[] vals)
{
foreach (Float3 d in vals) num /= d;
return num;
}
public static Float3 Divide(Float3 num, params Float3[] vals) => num / Product(vals);
public static float Dot(Float3 a, Float3 b) => a.x * b.x + a.y * b.y + a.z * b.z;
public static float Dot(params Float3[] vals)
{
@ -107,42 +157,40 @@ public struct Float3 : ICloneable, IComparable<Float3>, IEquatable<Float3>, IGro
}
return x + y + z;
}
public static Float3 Floor(Float3 val) =>
public static Int3 Floor(Float3 val) =>
new(Mathf.Floor(val.x), Mathf.Floor(val.y), Mathf.Floor(val.z));
public static Float3 Lerp(Float3 a, Float3 b, float t, bool clamp = true) =>
new(Mathf.Lerp(a.x, b.x, t, clamp), Mathf.Lerp(a.y, b.y, t, clamp), Mathf.Lerp(a.z, b.z, t, clamp));
public static Float3 Median(params Float3[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
float index = (vals.Length - 1) * 0.5f;
Float3 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
return (valA + valB) * 0.5f;
}
public static Float3 Max(params Float3[] vals)
{
if (vals.Length < 1) return Zero;
Float3 val = vals[0];
foreach (Float3 d in vals) val = d > val ? d : val;
foreach (Float3 d in vals) val = d.Magnitude > val.Magnitude ? d : val;
return val;
}
public static Float3 Min(params Float3[] vals)
{
if (vals.Length < 1) return Zero;
Float3 val = vals[0];
foreach (Float3 d in vals) val = d < val ? d : val;
foreach (Float3 d in vals) val = d.Magnitude < val.Magnitude ? d : val;
return val;
}
public static Float3 Multiply(params Float3[] vals)
public static Float3 Product(params Float3[] vals)
{
if (vals.Length < 1) return Zero;
Float3 val = One;
foreach (Float3 d in vals) val *= d;
return val;
}
public static Float3 Subtract(Float3 num, params Float3[] vals)
{
foreach (Float3 d in vals) num -= d;
return num;
}
public static Int3 Round(Float3 val) =>
new(Mathf.RoundInt(val.x), Mathf.RoundInt(val.y), Mathf.RoundInt(val.z));
public static Float3 Subtract(Float3 num, params Float3[] vals) => num - Sum(vals);
public static Float3 Sum(params Float3[] vals)
{
Float3 val = Zero;
@ -150,21 +198,21 @@ public struct Float3 : ICloneable, IComparable<Float3>, IEquatable<Float3>, IGro
return val;
}
public int CompareTo(Float3 other) => Magnitude.CompareTo(other.Magnitude);
public override bool Equals([NotNullWhen(true)] object? obj)
public static (float[] Xs, float[] Ys, float[] Zs) SplitArray(params Float3[] vals)
{
if (obj == null || obj.GetType() != typeof(Float3)) return false;
return Equals((Float3)obj);
float[] Xs = new float[vals.Length], Ys = new float[vals.Length], Zs = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Xs[i] = vals[i].x;
Ys[i] = vals[i].y;
Zs[i] = vals[i].z;
}
return (Xs, Ys, Zs);
}
public bool Equals(Float3 other) => x == other.x && y == other.y && z == other.z;
public override int GetHashCode() => x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode();
public override string ToString() => ToString((string?)null);
public string ToString(string? provider) =>
"X: " + x.ToString(provider) + " Y: " + y.ToString(provider) + " Z: " + z.ToString(provider);
public string ToString(IFormatProvider provider) =>
"X: " + x.ToString(provider) + " Y: " + y.ToString(provider) + " Z: " + z.ToString(provider);
public object Clone() => new Float3(x, y, z);
public int CompareTo(Float3 other) => Magnitude.CompareTo(other.Magnitude);
public bool Equals(Float3 other) => x == other.x && y == other.y && z == other.z;
public override int GetHashCode() => base.GetHashCode();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
@ -175,28 +223,60 @@ public struct Float3 : ICloneable, IComparable<Float3>, IEquatable<Float3>, IGro
}
public float[] ToArray() => new[] { x, y, z };
public Fill<float> ToFill()
{
Float3 @this = this;
return i => @this[i];
}
public List<float> ToList() => new() { x, y, z };
public Vector3d ToVector()
{
float mag = Magnitude;
Float3 normalized = Normalized;
Angle yaw = Mathf.ArcCos(normalized.y);
Angle pitch = Mathf.ArcSin(normalized.x / Mathf.Sin(yaw));
return new(yaw, pitch, mag);
}
private bool PrintMembers(StringBuilder builder)
{
builder.Append("x = ");
builder.Append(x);
builder.Append(", y = ");
builder.Append(y);
builder.Append(", z = ");
builder.Append(z);
return true;
}
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 Quaternion operator *(Float3 a, Quaternion b) => (Quaternion)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 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;
public static bool operator <(Float3 a, Float3 b) => a.CompareTo(b) < 0;
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 Float3 operator /(Float3 a, Matrix b) => (Float3)((Matrix)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);
public static explicit operator Float3(RGBAByte val) => (Float3)val.ToRGBA();
public static explicit operator Float3(HSVAByte val) => (Float3)val.ToHSVA();
public static implicit operator Float3(Fill<float> fill) => new(fill);
public static implicit operator Float3(Fill<int> fill) => new(fill);
public static implicit operator Float3((float x, float y, float z) val) =>
new(val.x, val.y, val.z);
}

View File

@ -1,13 +1,20 @@
namespace Nerd_STF.Mathematics;
public struct Float4 : ICloneable, IComparable<Float4>, IEquatable<Float4>, IGroup<float>
public record struct Float4 : IAbsolute<Float4>,
IAverage<Float4>, ICeiling<Float4, Int4>, IClamp<Float4>, IClampMagnitude<Float4, float>,
IComparable<Float4>, IDivide<Float4>, IDot<Float4, float>, IEquatable<Float4>,
IFloor<Float4, Int4>, IFromTuple<Float4, (float x, float y, float z, float w)>,
IGroup<float>, IIndexAll<float>, IIndexRangeAll<float>, ILerp<Float4, float>, IMathOperators<Float4>,
IMax<Float4>, IMedian<Float4>, IMin<Float4>, IPresets4d<Float4>, IProduct<Float4>, IRound<Float4, Int4>,
ISplittable<Float4, (float[] Xs, float[] Ys, float[] Zs, float[] Ws)>, ISubtract<Float4>,
ISum<Float4>
{
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 HighW => new(0, 0, 0, 1);
public static Float4 Left => new(-1, 0, 0, 0);
public static Float4 LowW => new(0, 0, 0, -1);
public static Float4 Right => new(1, 0, 0, 0);
public static Float4 Up => new(0, 1, 0, 0);
@ -15,19 +22,103 @@ public struct Float4 : ICloneable, IComparable<Float4>, IEquatable<Float4>, 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);
public Float2 XW => new(x, w);
public Float2 YW => new(y, w);
public Float2 YZ => new(y, z);
public Float2 ZW => new(z, w);
public Float2 XW
{
get => (x, w);
set
{
x = value.x;
w = value.y;
}
}
public Float2 XY
{
get => (x, y);
set
{
x = value.x;
y = value.y;
}
}
public Float2 XZ
{
get => (x, z);
set
{
x = value.x;
z = value.y;
}
}
public Float2 YW
{
get => (y, w);
set
{
y = value.x;
w = value.y;
}
}
public Float2 YZ
{
get => (y, z);
set
{
y = value.x;
z = value.y;
}
}
public Float2 ZW
{
get => (z, w);
set
{
z = value.x;
w = value.y;
}
}
public Float3 XYW => new(x, y, w);
public Float3 XYZ => new(x, y, z);
public Float3 YZW => new(y, z, w);
public Float3 XZW => new(x, z, w);
public Float3 XYW
{
get => (x, y, w);
set
{
x = value.x;
y = value.y;
w = value.z;
}
}
public Float3 XYZ
{
get => (x, y, z);
set
{
x = value.x;
y = value.y;
z = value.z;
}
}
public Float3 XZW
{
get => (x, z, w);
set
{
x = value.x;
z = value.y;
w = value.z;
}
}
public Float3 YZW
{
get => (y, z, w);
set
{
y = value.x;
z = value.y;
w = value.z;
}
}
public float x, y, z, w;
@ -78,11 +169,33 @@ public struct Float4 : ICloneable, IComparable<Float4>, IEquatable<Float4>, IGro
}
}
}
public float this[Index index]
{
get => this[index.IsFromEnd ? 4 - index.Value : index.Value];
set => this[index.IsFromEnd ? 4 - index.Value : index.Value] = value;
}
public float[] this[Range range]
{
get
{
int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value;
List<float> res = new();
for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray();
}
set
{
int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value;
for (int i = start; i < end; i++) this[i] = value[i];
}
}
public static Float4 Absolute(Float4 val) =>
new(Mathf.Absolute(val.x), Mathf.Absolute(val.y), Mathf.Absolute(val.z), Mathf.Absolute(val.w));
public static Float4 Average(params Float4[] vals) => Sum(vals) / vals.Length;
public static Float4 Ceiling(Float4 val) =>
public static Int4 Ceiling(Float4 val) =>
new(Mathf.Ceiling(val.x), Mathf.Ceiling(val.y), Mathf.Ceiling(val.z), Mathf.Ceiling(val.w));
public static Float4 Clamp(Float4 val, Float4 min, Float4 max) =>
new(Mathf.Clamp(val.x, min.x, max.x),
@ -100,11 +213,7 @@ public struct Float4 : ICloneable, IComparable<Float4>, IEquatable<Float4>, IGro
else if (mag > maxMag) val *= maxMag;
return val;
}
public static Float4 Divide(Float4 num, params Float4[] vals)
{
foreach (Float4 d in vals) num /= d;
return num;
}
public static Float4 Divide(Float4 num, params Float4[] vals) => num / Product(vals);
public static float Dot(Float4 a, Float4 b) => a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
public static float Dot(params Float4[] vals)
{
@ -119,43 +228,42 @@ public struct Float4 : ICloneable, IComparable<Float4>, IEquatable<Float4>, IGro
}
return x + y + z;
}
public static Float4 Floor(Float4 val) =>
public static Int4 Floor(Float4 val) =>
new(Mathf.Floor(val.x), Mathf.Floor(val.y), Mathf.Floor(val.z), Mathf.Floor(val.w));
public static Float4 Lerp(Float4 a, Float4 b, float t, bool clamp = true) =>
new(Mathf.Lerp(a.x, b.x, t, clamp), Mathf.Lerp(a.y, b.y, t, clamp), Mathf.Lerp(a.z, b.z, t, clamp),
Mathf.Lerp(a.w, b.w, t, clamp));
public static Float4 Median(params Float4[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
float index = (vals.Length - 1) * 0.5f;
Float4 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
return (valA + valB) * 0.5f;
}
public static Float4 Max(params Float4[] vals)
{
if (vals.Length < 1) return Zero;
Float4 val = vals[0];
foreach (Float4 d in vals) val = d > val ? d : val;
foreach (Float4 d in vals) val = d.Magnitude > val.Magnitude ? d : val;
return val;
}
public static Float4 Min(params Float4[] vals)
{
if (vals.Length < 1) return Zero;
Float4 val = vals[0];
foreach (Float4 d in vals) val = d < val ? d : val;
foreach (Float4 d in vals) val = d.Magnitude < val.Magnitude ? d : val;
return val;
}
public static Float4 Multiply(params Float4[] vals)
public static Int4 Round(Float4 val) =>
new(Mathf.RoundInt(val.x), Mathf.RoundInt(val.y), Mathf.RoundInt(val.z),
Mathf.RoundInt(val.w));
public static Float4 Product(params Float4[] vals)
{
if (vals.Length < 1) return Zero;
Float4 val = One;
foreach (Float4 d in vals) val *= d;
return val;
}
public static Float4 Subtract(Float4 num, params Float4[] vals)
{
foreach (Float4 d in vals) num -= d;
return num;
}
public static Float4 Subtract(Float4 num, params Float4[] vals) => num - Sum(vals);
public static Float4 Sum(params Float4[] vals)
{
Float4 val = Zero;
@ -163,23 +271,23 @@ public struct Float4 : ICloneable, IComparable<Float4>, IEquatable<Float4>, IGro
return val;
}
public int CompareTo(Float4 other) => Magnitude.CompareTo(other.Magnitude);
public override bool Equals([NotNullWhen(true)] object? obj)
public static (float[] Xs, float[] Ys, float[] Zs, float[] Ws) SplitArray(params Float4[] vals)
{
if (obj == null || obj.GetType() != typeof(Float4)) return false;
return Equals((Float4)obj);
float[] Xs = new float[vals.Length], Ys = new float[vals.Length], Zs = new float[vals.Length],
Ws = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
Xs[i] = vals[i].x;
Ys[i] = vals[i].y;
Zs[i] = vals[i].z;
Ws[i] = vals[i].w;
}
return (Xs, Ys, Zs, Ws);
}
public bool Equals(Float4 other) => x == other.x && y == other.y && z == other.z && w == other.w;
public override int GetHashCode() => x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode();
public override string ToString() => ToString((string?)null);
public string ToString(string? provider) =>
"X: " + x.ToString(provider) + " Y: " + y.ToString(provider) + " Z: " + z.ToString(provider)
+ " W: " + w.ToString(provider);
public string ToString(IFormatProvider provider) =>
"X: " + x.ToString(provider) + " Y: " + y.ToString(provider) + " Z: " + z.ToString(provider)
+ " W: " + w.ToString(provider);
public object Clone() => new Float4(x, y, z, w);
public int CompareTo(Float4 other) => Magnitude.CompareTo(other.Magnitude);
public bool Equals(Float4 other) => x == other.x && y == other.y && z == other.z && w == other.w;
public override int GetHashCode() => base.GetHashCode();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
@ -191,28 +299,54 @@ public struct Float4 : ICloneable, IComparable<Float4>, IEquatable<Float4>, IGro
}
public float[] ToArray() => new[] { x, y, z, w };
public Fill<float> ToFill()
{
Float4 @this = this;
return i => @this[i];
}
public List<float> ToList() => new() { x, y, z, w };
private bool PrintMembers(StringBuilder builder)
{
builder.Append("x = ");
builder.Append(x);
builder.Append(", y = ");
builder.Append(y);
builder.Append(", z = ");
builder.Append(z);
builder.Append(", w = ");
builder.Append(w);
return true;
}
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 d) => new(-d.x, -d.y, -d.z, -d.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, 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 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;
public static bool operator <(Float4 a, Float4 b) => a.CompareTo(b) < 0;
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 Float4 operator /(Float4 a, Matrix b) => (Float4)((Matrix)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);
public static explicit operator Float4(HSVA val) => new(val.H.Normalized, val.S, val.V, val.A);
public static implicit operator Float4(RGBAByte val) => val.ToRGBA();
public static explicit operator Float4(CMYKAByte val) => (Float4)val.ToCMYKA();
public static implicit operator Float4(HSVAByte val) => (Float4)val.ToHSVA();
public static implicit operator Float4(Fill<float> fill) => new(fill);
public static implicit operator Float4(Fill<int> fill) => new(fill);
public static implicit operator Float4((float x, float y, float z, float w) vals) =>
new(vals.x, vals.y, vals.z, vals.w);
}

Some files were not shown because too many files have changed in this diff Show More