Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 909ccb2934 | |||
| e020641478 | |||
| 56eaf31c7b | |||
| e550688634 | |||
| fc223ec70e | |||
| 5ff5cc7be8 | |||
| 63e70f7124 | |||
| be59b70e00 | |||
| 97a0b2fee5 | |||
| 2c2e7e3a99 | |||
| eeb70604d5 | |||
| 990f7663b9 | |||
| a245607042 | |||
| 34349c5e14 | |||
| 0915754f5f | |||
| 8c76f533b9 | |||
| d1ba73ae74 | |||
| 564d4caa1a | |||
| 2047676a0e | |||
| c05bd6007d | |||
| 51ad1974d5 | |||
| f6ff0aac6c | |||
| ed9c897dbf | |||
| 12ae678e86 | |||
| af4ed70add | |||
| 516e70709d | |||
| 80b78c4bdb | |||
| 5509cce466 | |||
| 85dad1f7f5 | |||
| 6056496aa6 | |||
| 6d44cbe438 | |||
| b0915c1ff9 | |||
| fe83380366 | |||
| ed967797e6 | |||
| 01b318582e | |||
| e895bbb5b2 | |||
| 13063f3098 | |||
| efcf2b94e9 | |||
| 9a1821cb10 | |||
| deff917a60 | |||
| 07cb6a2369 | |||
| 061c0d4df3 | |||
| d6234f0004 | |||
| e06a49c634 | |||
| 494d3b2581 | |||
| 85c112e0d8 | |||
| c388df3d3a | |||
| 6233ba65f9 | |||
| 55d40a1fa5 | |||
| da7f3e23c2 | |||
| 395998e1a7 | |||
| 97c189c296 | |||
| 47ca4eb726 | |||
| 0dfcddf63d | |||
| 4be41701cd | |||
| 1a769b4404 | |||
| 1d6fe9ff2f | |||
| 5650733357 | |||
| 8e3a27f87d | |||
| b322a2d811 | |||
| 0f63750e79 | |||
| dd4d0befd3 |
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Useless Visual Studio stuff
|
||||||
|
.vs/
|
||||||
|
/Nerd_STF/.vs/
|
||||||
|
/Nerd_STF/Nerd_STF.csproj.user
|
||||||
|
*.sln
|
||||||
|
|
||||||
|
# Build Stuff
|
||||||
|
/Nerd_STF/obj
|
||||||
|
/Nerd_STF/bin
|
||||||
|
*.dll
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Testing project
|
||||||
|
/Testing
|
||||||
|
|
||||||
|
# Nuget
|
||||||
|
/Nerd_STF/LICENSE
|
||||||
|
*.nupkg
|
||||||
|
*.snupkg
|
||||||
|
|
||||||
|
# Personal
|
||||||
|
/Nerd_STF/TODO.md
|
||||||
118
Changelog.md
Normal file
118
Changelog.md
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# Nerd_STF v2.4.1
|
||||||
|
|
||||||
|
Hey everyone! This is one of the larger small updates, and I'm pretty proud of what I got done in a week.
|
||||||
|
|
||||||
|
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
|
||||||
|
* Mathematics
|
||||||
|
* 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
BIN
Extras/Banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 836 KiB |
BIN
Extras/Logo Square High Res.png
Normal file
BIN
Extras/Logo Square High Res.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 316 KiB |
BIN
Extras/Logo Square.png
Normal file
BIN
Extras/Logo Square.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
34
LICENSE
34
LICENSE
@ -1,23 +1,21 @@
|
|||||||
Boost Software License - Version 1.0 - August 17th, 2003
|
MIT License
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person or organization
|
Copyright (c) 2022 That_One_Nerd
|
||||||
obtaining a copy of the software and accompanying documentation covered by
|
|
||||||
this license (the "Software") to use, reproduce, display, distribute,
|
|
||||||
execute, and transmit the Software, and to prepare derivative works of the
|
|
||||||
Software, and to permit third-parties to whom the Software is furnished to
|
|
||||||
do so, all subject to the following:
|
|
||||||
|
|
||||||
The copyright notices in the Software and this entire statement, including
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
the above license grant, this restriction and the following disclaimer,
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
must be included in all copies of the Software, in whole or in part, and
|
in the Software without restriction, including without limitation the rights
|
||||||
all derivative works of the Software, unless such copies or derivative
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
works are solely in the form of machine-executable object code generated by
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
a source language processor.
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
DEALINGS IN THE SOFTWARE.
|
SOFTWARE.
|
||||||
|
|||||||
BIN
Nerd_STF.dll
BIN
Nerd_STF.dll
Binary file not shown.
4
Nerd_STF/.editorconfig
Normal file
4
Nerd_STF/.editorconfig
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[*.cs]
|
||||||
|
|
||||||
|
# CA1050: Declare types in namespaces
|
||||||
|
dotnet_diagnostic.CA1050.severity = warning
|
||||||
18
Nerd_STF/Exceptions/BadMethodException.cs
Normal file
18
Nerd_STF/Exceptions/BadMethodException.cs
Normal 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) { }
|
||||||
|
}
|
||||||
28
Nerd_STF/Exceptions/DifferingVertCountException.cs
Normal file
28
Nerd_STF/Exceptions/DifferingVertCountException.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
public Polygon[]? Polygons;
|
||||||
|
|
||||||
|
public DifferingVertCountException() : base("Not all polygons have the same vert count.") { }
|
||||||
|
public DifferingVertCountException(Exception inner) : base("Not all polygons have the same vert count.", inner) { }
|
||||||
|
public DifferingVertCountException(string paramName) : this() => ParamName = paramName;
|
||||||
|
public DifferingVertCountException(string paramName, Exception inner) : this(inner) => ParamName = paramName;
|
||||||
|
public DifferingVertCountException(params Polygon[] polys) : this() => Polygons = polys;
|
||||||
|
public DifferingVertCountException(Polygon[] polys, Exception inner) : this(inner) => Polygons = polys;
|
||||||
|
public DifferingVertCountException(string paramName, Polygon[] polys) : this()
|
||||||
|
{
|
||||||
|
ParamName = paramName;
|
||||||
|
Polygons = polys;
|
||||||
|
}
|
||||||
|
public DifferingVertCountException(string paramName, Polygon[] polys, Exception inner) : this(inner)
|
||||||
|
{
|
||||||
|
ParamName = paramName;
|
||||||
|
Polygons = polys;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DifferingVertCountException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||||
|
}
|
||||||
27
Nerd_STF/Exceptions/DisconnectedLinesException.cs
Normal file
27
Nerd_STF/Exceptions/DisconnectedLinesException.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
namespace Nerd_STF.Exceptions;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class DisconnectedLinesException : Nerd_STFException
|
||||||
|
{
|
||||||
|
public string? ParamName;
|
||||||
|
public Line[]? Lines;
|
||||||
|
|
||||||
|
public DisconnectedLinesException() : base("Lines are not connected.") { }
|
||||||
|
public DisconnectedLinesException(Exception inner) : base("Lines are not connected.", inner) { }
|
||||||
|
public DisconnectedLinesException(string paramName) : this() => ParamName = paramName;
|
||||||
|
public DisconnectedLinesException(string paramName, Exception inner) : this(inner) => ParamName = paramName;
|
||||||
|
public DisconnectedLinesException(params Line[] lines) : this() => Lines = lines;
|
||||||
|
public DisconnectedLinesException(Line[] lines, Exception inner) : this(inner) => Lines = lines;
|
||||||
|
public DisconnectedLinesException(string paramName, Line[] lines) : this()
|
||||||
|
{
|
||||||
|
ParamName = paramName;
|
||||||
|
Lines = lines;
|
||||||
|
}
|
||||||
|
public DisconnectedLinesException(string paramName, Line[] lines, Exception inner) : this(inner)
|
||||||
|
{
|
||||||
|
ParamName = paramName;
|
||||||
|
Lines = lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DisconnectedLinesException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||||
|
}
|
||||||
10
Nerd_STF/Exceptions/InvalidSizeException.cs
Normal file
10
Nerd_STF/Exceptions/InvalidSizeException.cs
Normal 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) { }
|
||||||
|
}
|
||||||
10
Nerd_STF/Exceptions/MathException.cs
Normal file
10
Nerd_STF/Exceptions/MathException.cs
Normal 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) { }
|
||||||
|
}
|
||||||
12
Nerd_STF/Exceptions/Nerd_STFException.cs
Normal file
12
Nerd_STF/Exceptions/Nerd_STFException.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace Nerd_STF.Exceptions;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class Nerd_STFException : Exception
|
||||||
|
{
|
||||||
|
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) { }
|
||||||
|
}
|
||||||
16
Nerd_STF/Exceptions/NoInverseException.cs
Normal file
16
Nerd_STF/Exceptions/NoInverseException.cs
Normal 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) { }
|
||||||
|
}
|
||||||
10
Nerd_STF/Exceptions/UndefinedException.cs
Normal file
10
Nerd_STF/Exceptions/UndefinedException.cs
Normal 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) { }
|
||||||
|
}
|
||||||
61
Nerd_STF/Extensions/Container2DExtension.cs
Normal file
61
Nerd_STF/Extensions/Container2DExtension.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Nerd_STF/Extensions/ConversionExtension.cs
Normal file
8
Nerd_STF/Extensions/ConversionExtension.cs
Normal 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];
|
||||||
|
}
|
||||||
305
Nerd_STF/Extensions/EquationExtension.cs
Normal file
305
Nerd_STF/Extensions/EquationExtension.cs
Normal 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)
|
||||||
|
};
|
||||||
|
}
|
||||||
101
Nerd_STF/Extensions/StringExtension.cs
Normal file
101
Nerd_STF/Extensions/StringExtension.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Nerd_STF/Extensions/ToFillExtension.cs
Normal file
6
Nerd_STF/Extensions/ToFillExtension.cs
Normal 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
13
Nerd_STF/FileType.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace Nerd_STF;
|
||||||
|
|
||||||
|
public enum FileType
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
BMP,
|
||||||
|
HEIC,
|
||||||
|
JPEG,
|
||||||
|
MTL,
|
||||||
|
PNG,
|
||||||
|
TIFF,
|
||||||
|
WEBP,
|
||||||
|
}
|
||||||
3
Nerd_STF/Fill.cs
Normal file
3
Nerd_STF/Fill.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace Nerd_STF;
|
||||||
|
|
||||||
|
public delegate T Fill<T>(int index);
|
||||||
3
Nerd_STF/Fill2D.cs
Normal file
3
Nerd_STF/Fill2D.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace Nerd_STF;
|
||||||
|
|
||||||
|
public delegate T Fill2d<T>(int indexX, int indexY);
|
||||||
29
Nerd_STF/Graphics/Abstract/IColor.cs
Normal file
29
Nerd_STF/Graphics/Abstract/IColor.cs
Normal 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);
|
||||||
|
}
|
||||||
9
Nerd_STF/Graphics/Abstract/IColorByte.cs
Normal file
9
Nerd_STF/Graphics/Abstract/IColorByte.cs
Normal 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> { }
|
||||||
4
Nerd_STF/Graphics/Abstract/IColorFloat.cs
Normal file
4
Nerd_STF/Graphics/Abstract/IColorFloat.cs
Normal 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> { }
|
||||||
17
Nerd_STF/Graphics/Abstract/IColorPresets.cs
Normal file
17
Nerd_STF/Graphics/Abstract/IColorPresets.cs
Normal 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
253
Nerd_STF/Graphics/CMYKA.cs
Normal 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);
|
||||||
|
}
|
||||||
276
Nerd_STF/Graphics/CMYKAByte.cs
Normal file
276
Nerd_STF/Graphics/CMYKAByte.cs
Normal 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);
|
||||||
|
}
|
||||||
19
Nerd_STF/Graphics/ColorChannel.cs
Normal file
19
Nerd_STF/Graphics/ColorChannel.cs
Normal 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
249
Nerd_STF/Graphics/HSVA.cs
Normal 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);
|
||||||
|
}
|
||||||
247
Nerd_STF/Graphics/HSVAByte.cs
Normal file
247
Nerd_STF/Graphics/HSVAByte.cs
Normal 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);
|
||||||
|
}
|
||||||
14
Nerd_STF/Graphics/IlluminationFlags.cs
Normal file
14
Nerd_STF/Graphics/IlluminationFlags.cs
Normal 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,
|
||||||
|
}
|
||||||
16
Nerd_STF/Graphics/IlluminationModel.cs
Normal file
16
Nerd_STF/Graphics/IlluminationModel.cs
Normal 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
149
Nerd_STF/Graphics/Image.cs
Normal 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);
|
||||||
|
}
|
||||||
191
Nerd_STF/Graphics/Material.cs
Normal file
191
Nerd_STF/Graphics/Material.cs
Normal 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
254
Nerd_STF/Graphics/RGBA.cs
Normal 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);
|
||||||
|
}
|
||||||
260
Nerd_STF/Graphics/RGBAByte.cs
Normal file
260
Nerd_STF/Graphics/RGBAByte.cs
Normal 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);
|
||||||
|
}
|
||||||
25
Nerd_STF/Graphics/TextureConfig.cs
Normal file
25
Nerd_STF/Graphics/TextureConfig.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
317
Nerd_STF/Helpers/CordicHelper.cs
Normal file
317
Nerd_STF/Helpers/CordicHelper.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
91
Nerd_STF/Helpers/MathfHelper.cs
Normal file
91
Nerd_STF/Helpers/MathfHelper.cs
Normal 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.
|
||||||
|
}
|
||||||
|
}
|
||||||
48
Nerd_STF/Helpers/RationalHelper.cs
Normal file
48
Nerd_STF/Helpers/RationalHelper.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Nerd_STF/Helpers/UnsafeHelper.cs
Normal file
13
Nerd_STF/Helpers/UnsafeHelper.cs
Normal 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;
|
||||||
|
}
|
||||||
8
Nerd_STF/IGroup.cs
Normal file
8
Nerd_STF/IGroup.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Nerd_STF;
|
||||||
|
|
||||||
|
public interface IGroup<T> : IEnumerable<T>
|
||||||
|
{
|
||||||
|
public T[] ToArray();
|
||||||
|
public Fill<T> ToFill();
|
||||||
|
public List<T> ToList();
|
||||||
|
}
|
||||||
7
Nerd_STF/IGroup2D.cs
Normal file
7
Nerd_STF/IGroup2D.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Nerd_STF;
|
||||||
|
|
||||||
|
public interface IGroup2d<T> : IGroup<T>
|
||||||
|
{
|
||||||
|
public T[,] ToArray2D();
|
||||||
|
public Fill2d<T> ToFill2D();
|
||||||
|
}
|
||||||
18
Nerd_STF/LogMessage.cs
Normal file
18
Nerd_STF/LogMessage.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace Nerd_STF;
|
||||||
|
|
||||||
|
public struct LogMessage
|
||||||
|
{
|
||||||
|
public string Message;
|
||||||
|
public LogSeverity Severity;
|
||||||
|
public DateTime Timestamp;
|
||||||
|
|
||||||
|
public LogMessage() : this("", LogSeverity.Information, null) { }
|
||||||
|
public LogMessage(string msg, LogSeverity severity, DateTime? time = null)
|
||||||
|
{
|
||||||
|
Message = msg;
|
||||||
|
Severity = severity;
|
||||||
|
Timestamp = time ?? DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => Timestamp + " " + Severity.ToString().ToUpper() + ": " + Message;
|
||||||
|
}
|
||||||
10
Nerd_STF/LogSeverity.cs
Normal file
10
Nerd_STF/LogSeverity.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace Nerd_STF;
|
||||||
|
|
||||||
|
public enum LogSeverity
|
||||||
|
{
|
||||||
|
Debug = 1,
|
||||||
|
Information = 2,
|
||||||
|
Warning = 4,
|
||||||
|
Error = 8,
|
||||||
|
Fatal = 16,
|
||||||
|
}
|
||||||
71
Nerd_STF/Logger.cs
Normal file
71
Nerd_STF/Logger.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Nerd_STF;
|
||||||
|
|
||||||
|
public class Logger
|
||||||
|
{
|
||||||
|
public event Action<LogMessage> OnMessageRecieved = DefaultLogHandler;
|
||||||
|
|
||||||
|
public LogMessage[] Cache => msgs.ToArray();
|
||||||
|
public int CacheSize = 64;
|
||||||
|
public List<LogSeverity> IncludeSeverities = new()
|
||||||
|
{
|
||||||
|
LogSeverity.Warning,
|
||||||
|
LogSeverity.Error,
|
||||||
|
LogSeverity.Fatal,
|
||||||
|
};
|
||||||
|
public Stream? LogStream;
|
||||||
|
public int WriteSize;
|
||||||
|
|
||||||
|
private readonly List<LogMessage> msgs;
|
||||||
|
private readonly List<string> writeCache;
|
||||||
|
|
||||||
|
public Logger(Stream? logStream = null, int cacheSize = 64, int writeSize = 1)
|
||||||
|
{
|
||||||
|
CacheSize = cacheSize;
|
||||||
|
LogStream = logStream;
|
||||||
|
WriteSize = writeSize;
|
||||||
|
|
||||||
|
msgs = new(CacheSize);
|
||||||
|
writeCache = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(LogMessage msg)
|
||||||
|
{
|
||||||
|
if (!IncludeSeverities.Contains(msg.Severity)) return;
|
||||||
|
|
||||||
|
msgs.Insert(0, msg);
|
||||||
|
writeCache.Add(msg.ToString());
|
||||||
|
while (msgs.Count > CacheSize) msgs.RemoveAt(CacheSize);
|
||||||
|
OnMessageRecieved(msg);
|
||||||
|
|
||||||
|
if (writeCache.Count >= WriteSize && LogStream != null)
|
||||||
|
{
|
||||||
|
string s = "";
|
||||||
|
foreach (string cache in writeCache) s += cache + "\n" + (cache.Contains('\n') ? "\n" : "");
|
||||||
|
LogStream.Write(Encoding.Default.GetBytes(s));
|
||||||
|
LogStream.Flush();
|
||||||
|
writeCache.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DefaultLogHandler(LogMessage msg)
|
||||||
|
{
|
||||||
|
ConsoleColor color = msg.Severity switch
|
||||||
|
{
|
||||||
|
LogSeverity.Debug => ConsoleColor.DarkGray,
|
||||||
|
LogSeverity.Information => ConsoleColor.White,
|
||||||
|
LogSeverity.Warning => ConsoleColor.DarkYellow,
|
||||||
|
LogSeverity.Error => ConsoleColor.Red,
|
||||||
|
LogSeverity.Fatal => ConsoleColor.DarkRed,
|
||||||
|
_ => throw new ArgumentException("Unknown log severity " + msg.Severity, nameof(msg)),
|
||||||
|
};
|
||||||
|
|
||||||
|
ConsoleColor originalCol = Console.ForegroundColor;
|
||||||
|
|
||||||
|
Console.ForegroundColor = color;
|
||||||
|
Console.WriteLine(msg.ToString());
|
||||||
|
|
||||||
|
Console.ForegroundColor = originalCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Nerd_STF/Mathematics/Abstract/IAbsolute.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IAbsolute.cs
Normal 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);
|
||||||
|
}
|
||||||
6
Nerd_STF/Mathematics/Abstract/IAverage.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IAverage.cs
Normal 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);
|
||||||
|
}
|
||||||
7
Nerd_STF/Mathematics/Abstract/ICeiling.cs
Normal file
7
Nerd_STF/Mathematics/Abstract/ICeiling.cs
Normal 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);
|
||||||
|
}
|
||||||
6
Nerd_STF/Mathematics/Abstract/IClamp.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IClamp.cs
Normal 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);
|
||||||
|
}
|
||||||
15
Nerd_STF/Mathematics/Abstract/IClampMagnitude.cs
Normal file
15
Nerd_STF/Mathematics/Abstract/IClampMagnitude.cs
Normal 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>;
|
||||||
|
}
|
||||||
6
Nerd_STF/Mathematics/Abstract/IClosestTo.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IClosestTo.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Nerd_STF.Mathematics.Abstract;
|
||||||
|
|
||||||
|
public interface IClosestTo<T> where T : IEquatable<T>
|
||||||
|
{
|
||||||
|
public T ClosestTo(T item);
|
||||||
|
}
|
||||||
6
Nerd_STF/Mathematics/Abstract/IContains.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IContains.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Nerd_STF.Mathematics.Abstract;
|
||||||
|
|
||||||
|
public interface IContains<T> where T : IEquatable<T>
|
||||||
|
{
|
||||||
|
public bool Contains(T item);
|
||||||
|
}
|
||||||
8
Nerd_STF/Mathematics/Abstract/ICross.cs
Normal file
8
Nerd_STF/Mathematics/Abstract/ICross.cs
Normal 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);
|
||||||
|
}
|
||||||
8
Nerd_STF/Mathematics/Abstract/IDivide.cs
Normal file
8
Nerd_STF/Mathematics/Abstract/IDivide.cs
Normal 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);
|
||||||
|
}
|
||||||
18
Nerd_STF/Mathematics/Abstract/IDot.cs
Normal file
18
Nerd_STF/Mathematics/Abstract/IDot.cs
Normal 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>;
|
||||||
|
}
|
||||||
6
Nerd_STF/Mathematics/Abstract/IEncapsulate.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IEncapsulate.cs
Normal 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);
|
||||||
|
}
|
||||||
9
Nerd_STF/Mathematics/Abstract/IFloor.cs
Normal file
9
Nerd_STF/Mathematics/Abstract/IFloor.cs
Normal 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);
|
||||||
|
}
|
||||||
9
Nerd_STF/Mathematics/Abstract/IFromTuple.cs
Normal file
9
Nerd_STF/Mathematics/Abstract/IFromTuple.cs
Normal 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);
|
||||||
|
}
|
||||||
3
Nerd_STF/Mathematics/Abstract/IIndexAll.cs
Normal file
3
Nerd_STF/Mathematics/Abstract/IIndexAll.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace Nerd_STF.Mathematics.Abstract;
|
||||||
|
|
||||||
|
public interface IIndexAll<TSub> : IIndexGet<TSub>, IIndexSet<TSub> { }
|
||||||
7
Nerd_STF/Mathematics/Abstract/IIndexGet.cs
Normal file
7
Nerd_STF/Mathematics/Abstract/IIndexGet.cs
Normal 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; }
|
||||||
|
}
|
||||||
3
Nerd_STF/Mathematics/Abstract/IIndexRangeAll.cs
Normal file
3
Nerd_STF/Mathematics/Abstract/IIndexRangeAll.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace Nerd_STF.Mathematics.Abstract;
|
||||||
|
|
||||||
|
public interface IIndexRangeAll<TSub> : IIndexRangeGet<TSub>, IIndexRangeSet<TSub> { }
|
||||||
6
Nerd_STF/Mathematics/Abstract/IIndexRangeGet.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IIndexRangeGet.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Nerd_STF.Mathematics.Abstract;
|
||||||
|
|
||||||
|
public interface IIndexRangeGet<TSub>
|
||||||
|
{
|
||||||
|
public TSub[] this[Range range] { get; }
|
||||||
|
}
|
||||||
6
Nerd_STF/Mathematics/Abstract/IIndexRangeSet.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IIndexRangeSet.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Nerd_STF.Mathematics.Abstract;
|
||||||
|
|
||||||
|
public interface IIndexRangeSet<TSub>
|
||||||
|
{
|
||||||
|
public TSub[] this[Range range] { set; }
|
||||||
|
}
|
||||||
7
Nerd_STF/Mathematics/Abstract/IIndexSet.cs
Normal file
7
Nerd_STF/Mathematics/Abstract/IIndexSet.cs
Normal 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; }
|
||||||
|
}
|
||||||
15
Nerd_STF/Mathematics/Abstract/ILerp.cs
Normal file
15
Nerd_STF/Mathematics/Abstract/ILerp.cs
Normal 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>;
|
||||||
|
}
|
||||||
8
Nerd_STF/Mathematics/Abstract/IMagnitude.cs
Normal file
8
Nerd_STF/Mathematics/Abstract/IMagnitude.cs
Normal 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; }
|
||||||
|
}
|
||||||
8
Nerd_STF/Mathematics/Abstract/IMathOperators.cs
Normal file
8
Nerd_STF/Mathematics/Abstract/IMathOperators.cs
Normal 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> { }
|
||||||
37
Nerd_STF/Mathematics/Abstract/IMatrix.cs
Normal file
37
Nerd_STF/Mathematics/Abstract/IMatrix.cs
Normal 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();
|
||||||
|
}
|
||||||
9
Nerd_STF/Mathematics/Abstract/IMatrixPresets.cs
Normal file
9
Nerd_STF/Mathematics/Abstract/IMatrixPresets.cs
Normal 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; }
|
||||||
|
}
|
||||||
6
Nerd_STF/Mathematics/Abstract/IMax.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IMax.cs
Normal 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);
|
||||||
|
}
|
||||||
6
Nerd_STF/Mathematics/Abstract/IMedian.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IMedian.cs
Normal 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);
|
||||||
|
}
|
||||||
6
Nerd_STF/Mathematics/Abstract/IMin.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IMin.cs
Normal 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);
|
||||||
|
}
|
||||||
6
Nerd_STF/Mathematics/Abstract/IPresets0d.cs
Normal file
6
Nerd_STF/Mathematics/Abstract/IPresets0d.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Nerd_STF.Mathematics.Abstract;
|
||||||
|
|
||||||
|
public interface IPresets0d<T> where T : IPresets0d<T>
|
||||||
|
{
|
||||||
|
public static abstract T Unit { get; }
|
||||||
|
}
|
||||||
7
Nerd_STF/Mathematics/Abstract/IPresets1D.cs
Normal file
7
Nerd_STF/Mathematics/Abstract/IPresets1D.cs
Normal 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; }
|
||||||
|
}
|
||||||
9
Nerd_STF/Mathematics/Abstract/IPresets2D.cs
Normal file
9
Nerd_STF/Mathematics/Abstract/IPresets2D.cs
Normal 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; }
|
||||||
|
}
|
||||||
7
Nerd_STF/Mathematics/Abstract/IPresets3D.cs
Normal file
7
Nerd_STF/Mathematics/Abstract/IPresets3D.cs
Normal 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; }
|
||||||
|
}
|
||||||
7
Nerd_STF/Mathematics/Abstract/IPresets4D.cs
Normal file
7
Nerd_STF/Mathematics/Abstract/IPresets4D.cs
Normal 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; }
|
||||||
|
}
|
||||||
9
Nerd_STF/Mathematics/Abstract/IProduct.cs
Normal file
9
Nerd_STF/Mathematics/Abstract/IProduct.cs
Normal 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);
|
||||||
|
}
|
||||||
9
Nerd_STF/Mathematics/Abstract/IRound.cs
Normal file
9
Nerd_STF/Mathematics/Abstract/IRound.cs
Normal 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);
|
||||||
|
}
|
||||||
9
Nerd_STF/Mathematics/Abstract/IShape2D.cs
Normal file
9
Nerd_STF/Mathematics/Abstract/IShape2D.cs
Normal 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; }
|
||||||
|
}
|
||||||
9
Nerd_STF/Mathematics/Abstract/IShape3D.cs
Normal file
9
Nerd_STF/Mathematics/Abstract/IShape3D.cs
Normal 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; }
|
||||||
|
}
|
||||||
8
Nerd_STF/Mathematics/Abstract/ISplittable.cs
Normal file
8
Nerd_STF/Mathematics/Abstract/ISplittable.cs
Normal 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);
|
||||||
|
}
|
||||||
7
Nerd_STF/Mathematics/Abstract/IStaticMatrix.cs
Normal file
7
Nerd_STF/Mathematics/Abstract/IStaticMatrix.cs
Normal 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>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
7
Nerd_STF/Mathematics/Abstract/ISubdivide.cs
Normal file
7
Nerd_STF/Mathematics/Abstract/ISubdivide.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Nerd_STF.Mathematics.Abstract;
|
||||||
|
|
||||||
|
public interface ISubdivide<T>
|
||||||
|
{
|
||||||
|
public T Subdivide();
|
||||||
|
public T Subdivide(int iterations);
|
||||||
|
}
|
||||||
8
Nerd_STF/Mathematics/Abstract/ISubtract.cs
Normal file
8
Nerd_STF/Mathematics/Abstract/ISubtract.cs
Normal 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);
|
||||||
|
}
|
||||||
9
Nerd_STF/Mathematics/Abstract/ISum.cs
Normal file
9
Nerd_STF/Mathematics/Abstract/ISum.cs
Normal 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);
|
||||||
|
}
|
||||||
19
Nerd_STF/Mathematics/Abstract/ITriangulate.cs
Normal file
19
Nerd_STF/Mathematics/Abstract/ITriangulate.cs
Normal 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();
|
||||||
|
}
|
||||||
441
Nerd_STF/Mathematics/Algebra/Matrix.cs
Normal file
441
Nerd_STF/Mathematics/Algebra/Matrix.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
372
Nerd_STF/Mathematics/Algebra/Matrix2x2.cs
Normal file
372
Nerd_STF/Mathematics/Algebra/Matrix2x2.cs
Normal 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);
|
||||||
|
}
|
||||||
511
Nerd_STF/Mathematics/Algebra/Matrix3x3.cs
Normal file
511
Nerd_STF/Mathematics/Algebra/Matrix3x3.cs
Normal 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 }
|
||||||
|
});
|
||||||
|
}
|
||||||
672
Nerd_STF/Mathematics/Algebra/Matrix4x4.cs
Normal file
672
Nerd_STF/Mathematics/Algebra/Matrix4x4.cs
Normal 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 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
142
Nerd_STF/Mathematics/Algebra/Vector2d.cs
Normal file
142
Nerd_STF/Mathematics/Algebra/Vector2d.cs
Normal 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);
|
||||||
|
}
|
||||||
208
Nerd_STF/Mathematics/Algebra/Vector3d.cs
Normal file
208
Nerd_STF/Mathematics/Algebra/Vector3d.cs
Normal 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);
|
||||||
|
}
|
||||||
143
Nerd_STF/Mathematics/Angle.cs
Normal file
143
Nerd_STF/Mathematics/Angle.cs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
namespace Nerd_STF.Mathematics;
|
||||||
|
|
||||||
|
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);
|
||||||
|
public static Angle Quarter => new(90);
|
||||||
|
public static Angle Zero => new(0);
|
||||||
|
|
||||||
|
public float Degrees
|
||||||
|
{
|
||||||
|
get => p_deg;
|
||||||
|
set => p_deg = value;
|
||||||
|
}
|
||||||
|
public float Gradians
|
||||||
|
{
|
||||||
|
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 * Constants.DegToRad;
|
||||||
|
set => p_deg = value * Constants.RadToDeg;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
Type.Degrees => value,
|
||||||
|
Type.Gradians => value * 0.9f,
|
||||||
|
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(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, 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) => Max(true, vals);
|
||||||
|
public static Angle Max(bool useBounded, params Angle[] vals)
|
||||||
|
{
|
||||||
|
if (!useBounded) return new(Mathf.Max(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.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] = 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 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(Type.Degrees);
|
||||||
|
public string ToString(Type outputType) => outputType switch
|
||||||
|
{
|
||||||
|
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 + 180);
|
||||||
|
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, 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);
|
||||||
|
public static bool operator >(Angle a, Angle b) => a.CompareTo(b) > 0;
|
||||||
|
public static bool operator <(Angle a, Angle b) => a.CompareTo(b) < 0;
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Nerd_STF/Mathematics/Calculus.cs
Normal file
56
Nerd_STF/Mathematics/Calculus.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
namespace Nerd_STF.Mathematics;
|
||||||
|
|
||||||
|
public static class Calculus
|
||||||
|
{
|
||||||
|
public const float DefaultStep = 0.001f;
|
||||||
|
|
||||||
|
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 + step) - equ(x)) / step;
|
||||||
|
|
||||||
|
public static float GetIntegral(Equation equ, float lowerBound, float upperBound, float step = DefaultStep)
|
||||||
|
{
|
||||||
|
float val = 0;
|
||||||
|
for (float x = lowerBound; x <= upperBound; x += step) val += equ(x) * step;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 < iterations; i++) val -= GetDerivativeAtPoint(equ, val, step) * rate;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Nerd_STF/Mathematics/Equation.cs
Normal file
3
Nerd_STF/Mathematics/Equation.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace Nerd_STF.Mathematics;
|
||||||
|
|
||||||
|
public delegate float Equation(float x);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user