Compare commits

..

62 Commits

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

22
.gitignore vendored Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
Extras/Logo Square.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

34
LICENSE
View File

@ -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
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:
Copyright (c) 2022 That_One_Nerd
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
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
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

4
Nerd_STF/.editorconfig Normal file
View File

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

View File

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

View File

@ -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) { }
}

View 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) { }
}

View File

@ -0,0 +1,10 @@
namespace Nerd_STF.Exceptions;
[Serializable]
public class InvalidSizeException : Nerd_STFException
{
public InvalidSizeException() : base("Argument size is invalid.") { }
public InvalidSizeException(string message) : base(message) { }
public InvalidSizeException(string message, Exception inner) : base(message, inner) { }
protected InvalidSizeException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}

View File

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

View File

@ -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) { }
}

View File

@ -0,0 +1,16 @@
namespace Nerd_STF.Exceptions;
[Serializable]
public class NoInverseException : Nerd_STFException
{
public Matrix? Matrix;
public NoInverseException() : base("This matrix does not have an inverse.") { }
public NoInverseException(string message) : base(message) { }
public NoInverseException(string message, Exception inner) : base(message, inner) { }
public NoInverseException(Matrix? matrix) : this() => Matrix = matrix;
public NoInverseException(Matrix? matrix, string message) : this(message) => Matrix = matrix;
public NoInverseException(Matrix? matrix, string message, Exception inner) : this(message, inner) =>
Matrix = matrix;
protected NoInverseException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

13
Nerd_STF/FileType.cs Normal file
View File

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

3
Nerd_STF/Fill.cs Normal file
View File

@ -0,0 +1,3 @@
namespace Nerd_STF;
public delegate T Fill<T>(int index);

3
Nerd_STF/Fill2D.cs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

8
Nerd_STF/IGroup.cs Normal file
View 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
View 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
View 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
View 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
View 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,441 @@
namespace Nerd_STF.Mathematics.Algebra;
public class Matrix : IMatrix<Matrix, Matrix>
{
public static Matrix Identity(Int2 size)
{
if (size.x != size.y) throw new InvalidSizeException("Can only create an identity matrix of a square matrix." +
" You may want to use " + nameof(IdentityIsh) + " instead.");
Matrix m = Zero(size);
for (int i = 0; i < size.x; i++) m[i, i] = 1;
return m;
}
public static Matrix IdentityIsh(Int2 size)
{
Matrix m = Zero(size);
int max = Mathf.Min(size.x, size.y);
for (int i = 0; i < max; i++) m[i, i] = 1;
return m;
}
public static Matrix One(Int2 size) => new(size, 1);
public static Matrix SignGrid(Int2 size) => new(size, delegate (int x)
{
float sgnValue = Fills.SignFill(x);
if (size.y % 2 == 0 && x / size.y % 2 == 1) sgnValue *= -1;
return sgnValue;
});
public static Matrix Zero(Int2 size) => new(size);
public bool HasMinors => Size.x > 1 && Size.y > 1;
public bool IsSquare => Size.x == Size.y;
public Int2 Size { get; private init; }
private readonly float[,] array;
public Matrix() : this(Int2.Zero) { }
public Matrix(Int2 size, float all = 0)
{
Size = size;
array = new float[size.x, size.y];
if (all == 0) return;
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = all;
}
public Matrix(Int2 size, float[] vals)
{
Size = size;
array = new float[size.x, size.y];
if (vals.Length < size.x * size.y)
throw new InvalidSizeException("Array must contain enough values to fill the matrix.");
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals[c + r * size.y];
}
public Matrix(Int2 size, int[] vals)
{
Size = size;
array = new float[size.x, size.y];
if (vals.Length < size.x * size.y)
throw new InvalidSizeException("Array must contain enough values to fill the matrix.");
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals[c + r * size.y];
}
public Matrix(Int2 size, Fill<float> vals)
{
Size = size;
array = new float[size.x, size.y];
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals(c + r * size.y);
}
public Matrix(Int2 size, Fill<int> vals)
{
Size = size;
array = new float[size.x, size.y];
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals(c + r * size.y);
}
public Matrix(Int2 size, float[,] vals)
{
Size = size;
array = new float[size.x, size.y];
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals[r, c];
}
public Matrix(Int2 size, int[,] vals)
{
Size = size;
array = new float[size.x, size.y];
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals[r, c];
}
public Matrix(Int2 size, Fill2d<float> vals)
{
Size = size;
array = new float[size.x, size.y];
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals(r, c);
}
public Matrix(Int2 size, Fill2d<int> vals)
{
Size = size;
array = new float[size.x, size.y];
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals(r, c);
}
public float this[int r, int c]
{
get => array[r, c];
set => array[r, c] = value;
}
public float this[Int2 index]
{
get => this[index.x, index.y];
set => this[index.x, index.y] = value;
}
public float this[Index r, Index c]
{
get
{
int row = r.IsFromEnd ? Size.x - r.Value : r.Value,
col = c.IsFromEnd ? Size.y - c.Value : c.Value;
return array[row, col];
}
set
{
int row = r.IsFromEnd ? Size.x - r.Value : r.Value,
col = c.IsFromEnd ? Size.y - c.Value : c.Value;
array[row, col] = value;
}
}
public Matrix this[Range rs, Range cs]
{
get
{
int rowStart = rs.Start.IsFromEnd ? Size.x - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? Size.x - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? Size.y - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? Size.y - cs.End.Value : cs.End.Value;
Matrix newMatrix = new((rowEnd - rowStart - 1, colEnd - colStart - 1));
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
newMatrix[r, c] = array[r, c];
return newMatrix;
}
set
{
int rowStart = rs.Start.IsFromEnd ? Size.x - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? Size.x - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? Size.y - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? Size.y - cs.End.Value : cs.End.Value;
if (value.Size != (rowEnd - rowStart - 1, colEnd - colStart - 1))
throw new InvalidSizeException("Matrix has invalid size.");
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
array[r, c] = value[r, c];
}
}
public static Matrix Absolute(Matrix val) => new(val.Size, (r, c) => Mathf.Absolute(val[r, c]));
public static Matrix Ceiling(Matrix val) => new(val.Size, (r, c) => Mathf.Ceiling(val[r, c]));
public static Matrix Clamp(Matrix val, Matrix min, Matrix max) =>
new(val.Size, (r, c) => Mathf.Clamp(val[r, c], min[r, c], max[r, c]));
public static Matrix Divide(Matrix num, params Matrix[] vals)
{
foreach (Matrix m in vals) num /= m;
return num;
}
public static Matrix Floor(Matrix val) => new(val.Size, (r, c) => Mathf.Floor(val[r, c]));
public static Matrix Lerp(Matrix a, Matrix b, float t, bool clamp = true) =>
new(a.Size, (r, c) => Mathf.Lerp(a[r, c], b[r, c], t, clamp));
public static Matrix Product(params Matrix[] vals)
{
if (vals.Length < 1) throw new InvalidSizeException("Array must contain at least one matrix.");
if (!CheckSize(vals)) throw new InvalidSizeException("All matricies must be the same size.");
Matrix val = Identity(vals[0].Size);
foreach (Matrix m in vals) val *= m;
return val;
}
public static Matrix Round(Matrix val) => new(val.Size, (r, c) => Mathf.Round(val[r, c]));
public static Matrix Subtract(Matrix num, params Matrix[] vals)
{
foreach (Matrix m in vals) num -= m;
return num;
}
public static Matrix Sum(params Matrix[] vals)
{
if (!CheckSize(vals)) throw new InvalidSizeException("All matricies must be the same size.");
Matrix val = Zero(vals[0].Size);
foreach (Matrix m in vals) val += m;
return val;
}
public void Apply(Modifier2d modifier)
{
for (int r = 0; r < Size.x; r++) for (int c = 0; c < Size.y; c++)
array[r, c] = modifier(new(r, c), array[r, c]);
}
public float[] GetColumn(int column)
{
float[] vals = new float[Size.x];
for (int i = 0; i < Size.x; i++) vals[i] = array[i, column];
return vals;
}
public float[] GetRow(int row)
{
float[] vals = new float[Size.y];
for (int i = 0; i < Size.y; i++) vals[i] = array[row, i];
return vals;
}
public void SetColumn(int column, float[] vals)
{
if (vals.Length < Size.x)
throw new InvalidSizeException("Array must contain enough values to fill the column.");
for (int i = 0; i < Size.x; i++) array[i, column] = vals[i];
}
public void SetRow(int row, float[] vals)
{
if (vals.Length < Size.y)
throw new InvalidSizeException("Array must contain enough values to fill the row.");
for (int i = 0; i < Size.y; i++) array[row, i] = vals[i];
}
public Matrix Adjugate() => Cofactor().Transpose();
public Matrix Cofactor()
{
Matrix dets = new(Size);
Matrix[,] minors = Minors();
for (int r = 0; r < Size.y; r++) for (int c = 0; c < Size.x; c++) dets[c, r] = minors[c, r].Determinant();
return dets ^ SignGrid(Size);
}
public float Determinant()
{
if (!IsSquare) throw new InvalidSizeException("Matrix must be square to calculate determinant.");
if (Size.x <= 0) return 0;
if (Size.x == 1) return array[0, 0];
Matrix[] minors = Minors().GetRow(0, Size.x);
float det = 0;
for (int i = 0; i < minors.Length; i++) det += array[0, i] * minors[i].Determinant() * (i % 2 == 0 ? 1 : -1);
return det;
}
public Matrix? Inverse()
{
float d = Determinant();
if (d == 0) return null;
return Adjugate() / d;
}
public Matrix[,] Minors()
{
if (!HasMinors) return new Matrix[0,0];
Matrix[,] minors = new Matrix[Size.x, Size.y];
for (int r = 0; r < Size.x; r++) for (int c = 0; c < Size.y; c++) minors[r, c] = MinorOf(new(r, c));
return minors;
}
public Matrix Transpose()
{
Matrix @this = this;
return new(Size, (r, c) => @this[c, r]);
}
public Matrix MinorOf(Int2 index)
{
Matrix @this = this;
return new(@this.Size - Int2.One, delegate (int r, int c)
{
if (r >= index.x) r++;
if (c >= index.y) c++;
return @this[r, c];
});
}
public Matrix AddRow(int rowToChange, int referenceRow, float factor = 1)
{
Matrix @this = this;
return new(Size, delegate (int r, int c)
{
if (r == rowToChange) return @this[r, c] += factor * @this[referenceRow, c];
else return @this[r, c];
});
}
public void AddRowMutable(int rowToChange, int referenceRow, float factor)
{
for (int c = 0; c < Size.y; c++) this[rowToChange, c] += this[referenceRow, c] * factor;
}
public Matrix ScaleRow(int rowIndex, float factor)
{
Matrix @this = this;
return new(Size, delegate (int r, int c)
{
if (r == rowIndex) return @this[r, c] * factor;
else return @this[r, c];
});
}
public void ScaleRowMutable(int rowIndex, float factor)
{
for (int c = 0; c < Size.y; c++) this[rowIndex, c] *= factor;
}
public Matrix SwapRows(int rowA, int rowB)
{
Matrix @this = this;
return new(Size, delegate (int r, int c)
{
if (r == rowA) return @this[rowB, c];
else if (r == rowB) return @this[rowA, c];
else return @this[r, c];
});
}
public void SwapRowsMutable(int rowA, int rowB)
{
float[] dataA = GetRow(rowA), dataB = GetRow(rowB);
SetRow(rowA, dataB);
SetRow(rowB, dataA);
}
public Matrix SolveRowEchelon()
{
Matrix result = (Matrix)MemberwiseClone();
// Scale the first row so the first element of that row is 1.
result.ScaleRowMutable(0, 1 / result[0, 0]);
// For each row afterwards, subtract the required amount from all rows before it and normalize.
for (int r1 = 1; r1 < result.Size.x; r1++)
{
int min = Mathf.Min(r1, result.Size.y);
for (int r2 = 0; r2 < min; r2++)
{
result.AddRowMutable(r1, r2, -result[r1, r2]);
}
result.ScaleRowMutable(r1, 1 / result[r1, r1]);
}
return result;
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (obj == null) return base.Equals(obj);
Type t = obj.GetType();
if (t == typeof(Matrix)) return Equals((Matrix)obj);
else if (t == typeof(Matrix2x2)) return Equals((Matrix)obj);
else if (t == typeof(Matrix3x3)) return Equals((Matrix)obj);
else if (t == typeof(Matrix4x4)) return Equals((Matrix)obj);
return base.Equals(obj);
}
public bool Equals(Matrix? other)
{
if (other is null) return false;
return array.Equals(other.array);
}
public override int GetHashCode() => array.GetHashCode();
public override string ToString()
{
string res = "";
for (int r = 0; r < Size.x; r++)
{
for (int c = 0; c < Size.y; c++) res += array[r, c] + " ";
res += "\n";
}
return res;
}
public object Clone() => new Matrix(Size, array);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
{
for (int r = 0; r < Size.y; r++) for (int c = 0; c < Size.x; c++) yield return array[c, r];
}
public float[] ToArray() => array.Flatten(new(Size.y, Size.x));
public float[,] ToArray2D() => array;
public Fill<float> ToFill() => ToFillExtension.ToFill(this);
public Fill2d<float> ToFill2D()
{
Matrix @this = this;
return (x, y) => @this[x, y];
}
public List<float> ToList() => ToArray().ToList();
public static Matrix operator +(Matrix a, Matrix b) => new(a.Size, (r, c) => a[r, c] + b[r, c]);
public static Matrix? operator -(Matrix m) => m.Inverse();
public static Matrix operator -(Matrix a, Matrix b) => new(a.Size, (r, c) => a[r, c] - b[r, c]);
public static Matrix operator *(Matrix a, float b) => new(a.Size, (r, c) => a[r, c] * b);
public static Matrix operator *(Matrix a, Matrix b)
{
if (a.Size.y != b.Size.x) throw new InvalidSizeException("Incompatible Dimensions.");
return new(new(a.Size.x, b.Size.y), (r, c) => Mathf.Dot(a.GetRow(r), b.GetColumn(c)));
}
public static Complex operator *(Matrix a, Complex b) => (Complex)(a * (Matrix)b);
public static Quaternion operator *(Matrix a, Quaternion b) => (Quaternion)(a * (Matrix)b);
public static Float2 operator *(Matrix a, Float2 b) => (Float2)(a * (Matrix)b);
public static Float3 operator *(Matrix a, Float3 b) => (Float3)(a * (Matrix)b);
public static Float4 operator *(Matrix a, Float4 b) => (Float4)(a * (Matrix)b);
public static Vector2d operator *(Matrix a, Vector2d b) => (Vector2d)(a * (Matrix)b);
public static Vector3d operator *(Matrix a, Vector3d b) => (Vector3d)(a * (Matrix)b);
public static Matrix operator /(Matrix a, float b) => new(a.Size, (r, c) => a[r, c] / b);
public static Matrix operator /(Matrix a, Matrix b)
{
Matrix? bInv = b.Inverse();
if (bInv is null) throw new NoInverseException(b);
return a * bInv;
}
public static Complex operator /(Matrix a, Complex b) => (Complex)(a / (Matrix)b);
public static Quaternion operator /(Matrix a, Quaternion b) => (Quaternion)(a / (Matrix)b);
public static Float2 operator /(Matrix a, Float2 b) => (Float2)(a / (Matrix)b);
public static Float3 operator /(Matrix a, Float3 b) => (Float3)(a / (Matrix)b);
public static Float4 operator /(Matrix a, Float4 b) => (Float4)(a / (Matrix)b);
public static Vector2d operator /(Matrix a, Vector2d b) => (Vector2d)(a / (Matrix)b);
public static Vector3d operator /(Matrix a, Vector3d b) => (Vector3d)(a / (Matrix)b);
// Single number multiplication
public static Matrix operator ^(Matrix a, Matrix b) => new(a.Size, (r, c) => a[r, c] * b[r, c]);
public static bool operator ==(Matrix a, Matrix b) => a.Equals(b);
public static bool operator !=(Matrix a, Matrix b) => !a.Equals(b);
public static explicit operator Matrix(Complex c) => (Matrix)(Float2)c;
public static explicit operator Matrix(Quaternion c) => (Matrix)(Float4)c;
public static explicit operator Matrix(Float2 f) => new(new(2, 1), i => f[i]);
public static explicit operator Matrix(Float3 f) => new(new(3, 1), i => f[i]);
public static explicit operator Matrix(Float4 f) => new(new(4, 1), i => f[i]);
public static implicit operator Matrix(Matrix2x2 m) => new(new(2, 2), m.ToFill2D());
public static implicit operator Matrix(Matrix3x3 m) => new(new(3, 3), m.ToFill2D());
public static implicit operator Matrix(Matrix4x4 m) => new(new(4, 4), m.ToFill2D());
public static explicit operator Matrix(Vector2d v) => (Matrix)v.ToXYZ();
public static explicit operator Matrix(Vector3d v) => (Matrix)v.ToXYZ();
private static bool CheckSize(params Matrix[] vals)
{
if (vals.Length <= 1) return true;
Int2 size = vals[0].Size;
for (int i = 1; i < vals.Length; i++) if (size != vals[i].Size) return false;
return true;
}
}

View File

@ -0,0 +1,372 @@
namespace Nerd_STF.Mathematics.Algebra;
public record class Matrix2x2 : IStaticMatrix<Matrix2x2>
{
public static Matrix2x2 Identity => new(new[,]
{
{ 1, 0 },
{ 0, 1 }
});
public static Matrix2x2 One => new(1);
public static Matrix2x2 SignGrid => new(new[,]
{
{ +1, -1 },
{ -1, +1 }
});
public static Matrix2x2 Zero => new(0);
public Float2 Column1
{
get => new(r1c1, r2c1);
set
{
r1c1 = value.x;
r2c1 = value.y;
}
}
public Float2 Column2
{
get => new(r1c2, r2c2);
set
{
r1c2 = value.x;
r2c2 = value.y;
}
}
public Float2 Row1
{
get => new(r1c1, r1c2);
set
{
r1c1 = value.x;
r1c2 = value.y;
}
}
public Float2 Row2
{
get => new(r2c1, r2c2);
set
{
r2c1 = value.x;
r2c2 = value.y;
}
}
public Int2 Size => (2, 2);
public float r1c1, r2c1, r1c2, r2c2;
public Matrix2x2(float all) : this(all, all, all, all) { }
public Matrix2x2(float r1c1, float r1c2, float r2c1, float r2c2)
{
this.r1c1 = r1c1;
this.r1c2 = r1c2;
this.r2c1 = r2c1;
this.r2c2 = r2c2;
}
public Matrix2x2(float[] nums) : this(nums[0], nums[1], nums[2], nums[3]) { }
public Matrix2x2(int[] nums) : this(nums[0], nums[1], nums[2], nums[3]) { }
public Matrix2x2(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public Matrix2x2(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public Matrix2x2(float[,] nums) : this(nums[0, 0], nums[0, 1], nums[1, 0], nums[1, 1]) { }
public Matrix2x2(int[,] nums) : this(nums[0, 0], nums[0, 1], nums[1, 0], nums[1, 1]) { }
public Matrix2x2(Fill2d<float> fill) : this(fill(0, 0), fill(0, 1), fill(1, 0), fill(1, 1)) { }
public Matrix2x2(Fill2d<int> fill) : this(fill(0, 0), fill(0, 1), fill(1, 0), fill(1, 1)) { }
public Matrix2x2(Float2 r1, Float2 r2) : this(r1.x, r1.y, r2.x, r2.y) { }
public Matrix2x2(Fill<Float2> fill) : this(fill(0), fill(1)) { }
public Matrix2x2(Fill<Int2> fill) : this((IEnumerable<int>)fill(0), fill(1)) { }
public Matrix2x2(IEnumerable<float> r1, IEnumerable<float> r2) : this(r1.ToFill(), r2.ToFill()) { }
public Matrix2x2(IEnumerable<int> r1, IEnumerable<int> r2) : this(r1.ToFill(), r2.ToFill()) { }
public Matrix2x2(Fill<float> r1, Fill<float> r2) : this(r1(0), r1(1), r2(0), r2(1)) { }
public Matrix2x2(Fill<int> r1, Fill<int> r2) : this(r1(0), r1(1), r2(0), r2(1)) { }
public float this[int r, int c]
{
get => ToArray2D()[r, c];
set
{
// Maybe this could be improved?
// It's definitely better than it was before. Trust me.
switch ("r" + (r + 1) + "c" + (c + 1))
{
case "r1c1":
r1c1 = value;
break;
case "r2c1":
r2c1 = value;
break;
case "r1c2":
r1c2 = value;
break;
case "r2c2":
r2c2 = value;
break;
default:
string @params = "";
if (r < 0 || r > 1) @params += r;
if (c < 0 || c > 1) @params += string.IsNullOrEmpty(@params) ? c : " and " + c;
throw new IndexOutOfRangeException(@params);
}
}
}
public float this[Int2 index]
{
get => this[index.x, index.y];
set => this[index.x, index.y] = value;
}
public float this[Index r, Index c]
{
get
{
int row = r.IsFromEnd ? 2 - r.Value : r.Value,
col = c.IsFromEnd ? 2 - c.Value : c.Value;
return this[row, col];
}
set
{
int row = r.IsFromEnd ? 2 - r.Value : r.Value,
col = c.IsFromEnd ? 2 - c.Value : c.Value;
this[row, col] = value;
}
}
public float[,] this[Range rs, Range cs]
{
get
{
int rowStart = rs.Start.IsFromEnd ? 2 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 2 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 2 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 2 - cs.End.Value : cs.End.Value;
float[,] vals = new float[rowEnd - rowStart - 1, colEnd - colStart - 1];
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
vals[r, c] = this[r, c];
return vals;
}
set
{
int rowStart = rs.Start.IsFromEnd ? 2 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 2 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 2 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 2 - cs.End.Value : cs.End.Value;
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
this[r, c] = value[r, c];
}
}
public static Matrix2x2 Absolute(Matrix2x2 val) => new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r1c2),
Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r2c2));
public static Matrix2x2 Average(params Matrix2x2[] vals) => Sum(vals) / vals.Length;
public static Matrix2x2 Ceiling(Matrix2x2 val) => new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r1c2),
Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r2c2));
public static Matrix2x2 Clamp(Matrix2x2 val, Matrix2x2 min, Matrix2x2 max) =>
new(Mathf.Clamp(val.r1c1, min.r1c1, max.r1c1), Mathf.Clamp(val.r1c2, min.r1c2, max.r1c2),
Mathf.Clamp(val.r2c1, min.r2c1, max.r2c1), Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2));
public static Matrix2x2 Divide(Matrix2x2 num, params Matrix2x2[] vals)
{
foreach (Matrix2x2 m in vals) num /= m;
return num;
}
public static Matrix2x2 Floor(Matrix2x2 val) => new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r1c2),
Mathf.Floor(val.r2c1), Mathf.Floor(val.r2c2));
public static Matrix2x2 Lerp(Matrix2x2 a, Matrix2x2 b, float t, bool clamp = true) =>
new(Mathf.Lerp(a.r1c1, b.r1c1, t, clamp), Mathf.Lerp(a.r1c2, b.r1c2, t, clamp),
Mathf.Lerp(a.r2c1, b.r2c1, t, clamp), Mathf.Lerp(a.r2c2, b.r2c2, t, clamp));
public static Matrix2x2 Median(params Matrix2x2[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
Matrix2x2 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static Matrix2x2 Product(params Matrix2x2[] vals)
{
if (vals.Length < 1) return Zero;
Matrix2x2 val = Identity;
foreach (Matrix2x2 m in vals) val *= m;
return val;
}
public static Matrix2x2 Round(Matrix2x2 val) => new(Mathf.Round(val.r1c1), Mathf.Round(val.r1c2),
Mathf.Round(val.r2c1), Mathf.Round(val.r2c2));
public static Matrix2x2 Subtract(Matrix2x2 num, params Matrix2x2[] vals)
{
foreach (Matrix2x2 m in vals) num -= m;
return num;
}
public static Matrix2x2 Sum(params Matrix2x2[] vals)
{
Matrix2x2 val = Zero;
foreach (Matrix2x2 m in vals) val += m;
return val;
}
public static (float[] r1c1s, float[] r1c2s, float[] r2c1s, float[] r2c2s) SplitArray(params Matrix2x2[] vals)
{
float[] r1c1s = new float[vals.Length], r1c2s = new float[vals.Length], r2c1s = new float[vals.Length],
r2c2s = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
r1c1s[i] = vals[i].r1c1;
r1c2s[i] = vals[i].r1c2;
r2c1s[i] = vals[i].r2c1;
r2c2s[i] = vals[i].r2c2;
}
return (r1c1s, r1c2s, r2c1s, r2c2s);
}
public Matrix2x2 Adjugate() => Cofactor().Transpose();
public Matrix2x2 Cofactor()
{
Matrix2x2 swapped = new(new[,]
{
{ r2c2, r2c1 },
{ r1c2, r1c1 }
});
return swapped ^ SignGrid;
}
public float Determinant() => r1c1 * r2c2 - r1c2 * r2c1;
public Matrix2x2? Inverse()
{
float d = Determinant();
if (d == 0) return null;
return Transpose().Adjugate() / d;
}
public Matrix2x2 Transpose() => new(new[,]
{
{ r1c1, r2c1 },
{ r1c2, r2c2 }
});
public float[] GetColumn(int column) => new[] { this[0, column], this[1, column] };
public float[] GetRow(int row) => new[] { this[row, 0], this[row, 1] };
public void SetColumn(int column, float[] vals)
{
if (vals.Length < 2)
throw new InvalidSizeException("Array must contain enough values to fill the column.");
this[0, column] = vals[0];
this[1, column] = vals[1];
}
public void SetRow(int row, float[] vals)
{
if (vals.Length < 2)
throw new InvalidSizeException("Array must contain enough values to fill the row.");
this[row, 0] = vals[0];
this[row, 1] = vals[1];
}
public Matrix2x2 AddRow(int rowToChange, int referenceRow, float factor = 1)
{
Matrix2x2 @this = this;
return new(delegate (int r, int c)
{
if (r == rowToChange) return @this[r, c] += factor * @this[referenceRow, c];
else return @this[r, c];
});
}
public void AddRowMutable(int rowToChange, int referenceRow, float factor)
{
this[rowToChange, 0] += this[referenceRow, 0] * factor;
this[rowToChange, 1] += this[referenceRow, 1] * factor;
}
public Matrix2x2 ScaleRow(int rowIndex, float factor)
{
Matrix2x2 @this = this;
return new(delegate (int r, int c)
{
if (r == rowIndex) return @this[r, c] * factor;
else return @this[r, c];
});
}
public void ScaleRowMutable(int rowIndex, float factor)
{
this[rowIndex, 0] *= factor;
this[rowIndex, 1] *= factor;
}
public Matrix2x2 SwapRows(int rowA, int rowB)
{
Matrix2x2 @this = this;
return new(delegate (int r, int c)
{
if (r == rowA) return @this[rowB, c];
else if (r == rowB) return @this[rowA, c];
else return @this[r, c];
});
}
public void SwapRowsMutable(int rowA, int rowB)
{
float[] dataA = GetRow(rowA), dataB = GetRow(rowB);
SetRow(rowA, dataB);
SetRow(rowB, dataA);
}
public virtual bool Equals(Matrix2x2? other)
{
if (other is null) return false;
return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r2c1 == other.r2c1 && r2c2 == other.r2c2;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => r1c1 + " " + r1c2 + "\n" + r2c1 + " " + r2c2;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
{
yield return r1c1;
yield return r1c2;
yield return r2c1;
yield return r2c2;
}
public float[] ToArray() => new[] { r1c1, r1c2, r2c1, r2c2 };
public float[,] ToArray2D() => new[,]
{
{ r1c1, r1c2 },
{ r2c1, r2c2 }
};
public Fill<float> ToFill() => ToFillExtension.ToFill(this);
public Fill2d<float> ToFill2D()
{
Matrix2x2 @this = this;
return (x, y) => @this[x, y];
}
public List<float> ToList() => new() { r1c1, r1c2, r2c1, r2c2 };
public static Matrix2x2 operator +(Matrix2x2 a, Matrix2x2 b) => new(a.r1c1 + b.r1c1, a.r1c2 + b.r1c2,
a.r2c1 + b.r2c1, a.r2c2 + b.r2c2);
public static Matrix2x2? operator -(Matrix2x2 m) => m.Inverse();
public static Matrix2x2 operator -(Matrix2x2 a, Matrix2x2 b) => new(a.r1c1 - b.r1c1, a.r1c2 - b.r1c2,
a.r2c1 - b.r2c1, a.r2c2 - b.r2c2);
public static Matrix2x2 operator *(Matrix2x2 a, float b) => new(a.r1c1 * b, a.r1c2 * b, a.r2c1 * b, a.r2c2 * b);
public static Matrix2x2 operator *(Matrix2x2 a, Matrix2x2 b) => new(new[,]
{
{ Float2.Dot(a.Row1, b.Column1), Float2.Dot(a.Row1, b.Column2) },
{ Float2.Dot(a.Row2, b.Column1), Float2.Dot(a.Row2, b.Column2) },
});
public static Float2 operator *(Matrix2x2 a, Float2 b) => (Matrix)a * b;
public static Matrix2x2 operator /(Matrix2x2 a, float b) => new(a.r1c1 / b, a.r1c2 / b, a.r2c1 / b, a.r2c2 / b);
public static Matrix2x2 operator /(Matrix2x2 a, Matrix2x2 b)
{
Matrix2x2? bInv = b.Inverse();
if (bInv is null) throw new NoInverseException(b);
return a * bInv;
}
public static Float2 operator /(Matrix2x2 a, Float2 b) => (Matrix)a / b;
public static Matrix2x2 operator ^(Matrix2x2 a, Matrix2x2 b) => // Single number multiplication.
new(a.r1c1 * b.r1c1, a.r1c2 * b.r1c2, a.r2c1 * b.r2c1, a.r2c2 * b.r2c2);
public static explicit operator Matrix2x2(Matrix m)
{
Matrix2x2 res = Zero, identity = Identity;
for (int r = 0; r < 2; r++) for (int c = 0; c < 2; c++)
res[c, r] = m.Size.x > r && m.Size.y > c ? m[r, c] : identity[r, c];
return res;
}
public static explicit operator Matrix2x2(Matrix3x3 m) => new(m.r1c1, m.r2c1, m.r1c2, m.r2c2);
public static explicit operator Matrix2x2(Matrix4x4 m) => new(m.r1c1, m.r2c1, m.r1c2, m.r2c2);
}

View File

@ -0,0 +1,511 @@
namespace Nerd_STF.Mathematics.Algebra;
public record class Matrix3x3 : IStaticMatrix<Matrix3x3>
{
public static Matrix3x3 Identity => new(new[,]
{
{ 1, 0, 0 },
{ 0, 1, 0 },
{ 0, 0, 1 }
});
public static Matrix3x3 One => new(1);
public static Matrix3x3 SignGrid => new(new[,]
{
{ +1, -1, +1 },
{ -1, +1, -1 },
{ +1, -1, +1 }
});
public static Matrix3x3 Zero => new(0);
public Float3 Column1
{
get => new(r1c1, r2c1, r3c1);
set
{
r1c1 = value.x;
r2c1 = value.y;
r3c1 = value.z;
}
}
public Float3 Column2
{
get => new(r1c2, r2c2, r3c2);
set
{
r1c2 = value.x;
r2c2 = value.y;
r3c2 = value.z;
}
}
public Float3 Column3
{
get => new(r1c3, r2c3, r3c3);
set
{
r1c3 = value.x;
r2c3 = value.y;
r3c3 = value.z;
}
}
public Float3 Row1
{
get => new(r1c1, r1c2, r1c3);
set
{
r1c1 = value.x;
r1c2 = value.y;
r1c3 = value.z;
}
}
public Float3 Row2
{
get => new(r2c1, r2c2, r2c3);
set
{
r2c1 = value.x;
r2c2 = value.y;
r2c3 = value.z;
}
}
public Float3 Row3
{
get => new(r3c1, r3c2, r3c3);
set
{
r3c1 = value.x;
r3c2 = value.y;
r3c3 = value.z;
}
}
public Int2 Size => (3, 3);
public float r1c1, r2c1, r3c1, r1c2, r2c2, r3c2, r1c3, r2c3, r3c3;
public Matrix3x3(float all) : this(all, all, all, all, all, all, all, all, all) { }
public Matrix3x3(float r1c1, float r1c2, float r1c3, float r2c1,
float r2c2, float r2c3, float r3c1, float r3c2, float r3c3)
{
this.r1c1 = r1c1;
this.r1c2 = r1c2;
this.r1c3 = r1c3;
this.r2c1 = r2c1;
this.r2c2 = r2c2;
this.r2c3 = r2c3;
this.r3c1 = r3c1;
this.r3c2 = r3c2;
this.r3c3 = r3c3;
}
public Matrix3x3(float[] nums) : this(nums[0], nums[1], nums[2],
nums[3], nums[4], nums[5], nums[6], nums[7], nums[8]) { }
public Matrix3x3(int[] nums) : this(nums[0], nums[1], nums[2],
nums[3], nums[4], nums[5], nums[6], nums[7], nums[8]) { }
public Matrix3x3(Fill<float> fill) : this(fill(0), fill(1), fill(2),
fill(3), fill(4), fill(5), fill(6), fill(7), fill(8)) { }
public Matrix3x3(Fill<int> fill) : this(fill(0), fill(1), fill(2),
fill(3), fill(4), fill(5), fill(6), fill(7), fill(8)) { }
public Matrix3x3(float[,] nums) : this(nums[0, 0], nums[0, 1], nums[0, 2],
nums[1, 0], nums[1, 1], nums[1, 2], nums[2, 0], nums[2, 1], nums[2, 2]) { }
public Matrix3x3(int[,] nums) : this(nums[0, 0], nums[0, 1], nums[0, 2],
nums[1, 0], nums[1, 1], nums[1, 2], nums[2, 0], nums[2, 1], nums[2, 2]) { }
public Matrix3x3(Fill2d<float> fill) : this(fill(0, 0), fill(0, 1), fill(0, 2),
fill(1, 0), fill(1, 1), fill(1, 2), fill(2, 0), fill(2, 1), fill(2, 2)) { }
public Matrix3x3(Fill2d<int> fill) : this(fill(0, 0), fill(0, 1), fill(0, 2),
fill(1, 0), fill(1, 1), fill(1, 2), fill(2, 0), fill(2, 1), fill(2, 2)) { }
public Matrix3x3(Float3 r1, Float3 r2, Float3 r3) : this(r1.x, r1.y, r1.z, r2.x, r2.y, r2.z, r3.x, r3.y, r3.z) { }
public Matrix3x3(Fill<Float3> fill) : this(fill(0), fill(1), fill(2)) { }
public Matrix3x3(Fill<Int3> fill) : this((IEnumerable<int>)fill(0), fill(1), fill(2)) { }
public Matrix3x3(IEnumerable<float> r1, IEnumerable<float> r2, IEnumerable<float> r3)
: this(r1.ToFill(), r2.ToFill(), r3.ToFill()) { }
public Matrix3x3(IEnumerable<int> r1, IEnumerable<int> r2, IEnumerable<int> r3)
: this(r1.ToFill(), r2.ToFill(), r3.ToFill()) { }
public Matrix3x3(Fill<float> r1, Fill<float> r2, Fill<float> r3)
: this(r1(0), r1(1), r1(2), r2(0), r2(1), r2(2), r3(0), r3(1), r3(2)) { }
public Matrix3x3(Fill<int> r1, Fill<int> r2, Fill<int> r3)
: this(r1(0), r1(1), r1(2), r2(0), r2(1), r2(2), r3(0), r3(1), r3(2)) { }
public float this[int r, int c]
{
get => ToArray2D()[r, c];
set
{
// Maybe this could be improved?
// It's definitely better than it was before. Trust me.
switch ("r" + (r + 1) + "c" + (c + 1))
{
case "r1c1":
r1c1 = value;
break;
case "r2c1":
r2c1 = value;
break;
case "r3c1":
r3c1 = value;
break;
case "r1c2":
r1c2 = value;
break;
case "r2c2":
r2c2 = value;
break;
case "r3c2":
r3c2 = value;
break;
case "r1c3":
r1c3 = value;
break;
case "r2c3":
r2c3 = value;
break;
case "r3c3":
r3c3 = value;
break;
default:
string @params = "";
if (r < 0 || r > 2) @params += r;
if (c < 0 || c > 2) @params += string.IsNullOrEmpty(@params) ? c : " and " + c;
throw new IndexOutOfRangeException(@params);
}
}
}
public float this[Int2 index]
{
get => this[index.x, index.y];
set => this[index.x, index.y] = value;
}
public float this[Index r, Index c]
{
get
{
int row = r.IsFromEnd ? 3 - r.Value : r.Value,
col = c.IsFromEnd ? 3 - c.Value : c.Value;
return this[row, col];
}
set
{
int row = r.IsFromEnd ? 3 - r.Value : r.Value,
col = c.IsFromEnd ? 3 - c.Value : c.Value;
this[row, col] = value;
}
}
public float[,] this[Range rs, Range cs]
{
get
{
int rowStart = rs.Start.IsFromEnd ? 3 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 3 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 3 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 3 - cs.End.Value : cs.End.Value;
float[,] vals = new float[rowEnd - rowStart - 1, colEnd - colStart - 1];
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
vals[r, c] = this[r, c];
return vals;
}
set
{
int rowStart = rs.Start.IsFromEnd ? 3 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 3 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 3 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 3 - cs.End.Value : cs.End.Value;
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
this[r, c] = value[r, c];
}
}
public static Matrix3x3 Absolute(Matrix3x3 val) =>
new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r1c2), Mathf.Absolute(val.r1c3),
Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r2c2), Mathf.Absolute(val.r2c3),
Mathf.Absolute(val.r3c1), Mathf.Absolute(val.r3c2), Mathf.Absolute(val.r3c3));
public static Matrix3x3 Average(params Matrix3x3[] vals) => Sum(vals) / vals.Length;
public static Matrix3x3 Ceiling(Matrix3x3 val) =>
new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r1c2), Mathf.Ceiling(val.r1c3),
Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r2c2), Mathf.Ceiling(val.r2c3),
Mathf.Ceiling(val.r3c1), Mathf.Ceiling(val.r3c2), Mathf.Ceiling(val.r3c3));
public static Matrix3x3 Clamp(Matrix3x3 val, Matrix3x3 min, Matrix3x3 max) =>
new(Mathf.Clamp(val.r1c1, min.r1c1, max.r1c1), Mathf.Clamp(val.r1c2, min.r1c2, max.r1c2),
Mathf.Clamp(val.r1c3, min.r1c3, max.r1c3), Mathf.Clamp(val.r2c1, min.r2c1, max.r2c1),
Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2), Mathf.Clamp(val.r2c3, min.r2c3, max.r2c3),
Mathf.Clamp(val.r3c1, min.r3c1, max.r3c1), Mathf.Clamp(val.r3c2, min.r3c2, max.r3c2),
Mathf.Clamp(val.r3c3, min.r3c3, max.r3c3));
public static Matrix3x3 Divide(Matrix3x3 num, params Matrix3x3[] vals)
{
foreach (Matrix3x3 m in vals) num /= m;
return num;
}
public static Matrix3x3 Floor(Matrix3x3 val) =>
new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r1c2), Mathf.Floor(val.r1c3),
Mathf.Floor(val.r2c1), Mathf.Floor(val.r2c2), Mathf.Floor(val.r2c3),
Mathf.Floor(val.r3c1), Mathf.Floor(val.r3c2), Mathf.Floor(val.r3c3));
public static Matrix3x3 Lerp(Matrix3x3 a, Matrix3x3 b, float t, bool clamp = true) =>
new(Mathf.Lerp(a.r1c1, b.r1c1, t, clamp), Mathf.Lerp(a.r1c2, b.r1c2, t, clamp),
Mathf.Lerp(a.r1c3, b.r1c3, t, clamp), Mathf.Lerp(a.r2c1, b.r2c1, t, clamp),
Mathf.Lerp(a.r2c2, b.r2c2, t, clamp), Mathf.Lerp(a.r2c3, b.r2c3, t, clamp),
Mathf.Lerp(a.r3c1, b.r3c1, t, clamp), Mathf.Lerp(a.r3c2, b.r3c2, t, clamp),
Mathf.Lerp(a.r3c3, b.r3c3, t, clamp));
public static Matrix3x3 Median(params Matrix3x3[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
Matrix3x3 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static Matrix3x3 Product(params Matrix3x3[] vals)
{
if (vals.Length < 1) return Zero;
Matrix3x3 val = Identity;
foreach (Matrix3x3 m in vals) val *= m;
return val;
}
public static Matrix3x3 Round(Matrix3x3 val) =>
new(Mathf.Round(val.r1c1), Mathf.Round(val.r1c2), Mathf.Round(val.r1c3),
Mathf.Round(val.r2c1), Mathf.Round(val.r2c2), Mathf.Round(val.r2c3),
Mathf.Round(val.r3c1), Mathf.Round(val.r3c2), Mathf.Round(val.r3c3));
public static Matrix3x3 Subtract(Matrix3x3 num, params Matrix3x3[] vals)
{
foreach (Matrix3x3 m in vals) num -= m;
return num;
}
public static Matrix3x3 Sum(params Matrix3x3[] vals)
{
Matrix3x3 val = Zero;
foreach (Matrix3x3 m in vals) val += m;
return val;
}
public static (float[] r1c1s, float[] r1c2s, float[] r1c3s, float[] r2c1s, float[] r2c2s,
float[] r2c3s, float[] r3c1s, float[] r3c2s, float[] r3c3s) SplitArray(params Matrix3x3[] vals)
{
float[] r1c1s = new float[vals.Length], r1c2s = new float[vals.Length], r1c3s = new float[vals.Length],
r2c1s = new float[vals.Length], r2c2s = new float[vals.Length], r2c3s = new float[vals.Length],
r3c1s = new float[vals.Length], r3c2s = new float[vals.Length], r3c3s = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
r1c1s[i] = vals[i].r1c1;
r1c2s[i] = vals[i].r1c2;
r1c3s[i] = vals[i].r1c3;
r2c1s[i] = vals[i].r2c1;
r2c2s[i] = vals[i].r2c2;
r2c3s[i] = vals[i].r2c3;
r3c1s[i] = vals[i].r3c1;
r3c2s[i] = vals[i].r3c2;
r3c3s[i] = vals[i].r3c3;
}
return (r1c1s, r1c2s, r1c3s, r2c1s, r2c2s, r2c3s, r3c1s, r3c2s, r3c3s);
}
public Matrix3x3 Adjugate() => Cofactor().Transpose();
public Matrix3x3 Cofactor()
{
Matrix3x3 dets = Zero;
Matrix2x2[,] minors = Minors();
for (int r = 0; r < 3; r++) for (int c = 0; c < 3; c++) dets[r, c] = minors[r, c].Determinant();
return dets ^ SignGrid;
}
public float Determinant()
{
Matrix2x2[,] minors = Minors();
return (r1c1 * minors[0, 0].Determinant()) - (r1c2 * minors[0, 1].Determinant())
+ (r1c3 * minors[0, 2].Determinant());
}
public Matrix3x3? Inverse()
{
float d = Determinant();
if (d == 0) return null;
return Adjugate() / d;
}
public Matrix2x2[,] Minors() => new Matrix2x2[,]
{
{ new(r2c2, r2c3, r3c2, r3c3), new(r2c1, r2c3, r3c1, r3c3), new(r2c1, r2c2, r3c1, r3c2) },
{ new(r1c2, r1c3, r3c2, r3c3), new(r1c1, r1c3, r3c1, r3c3), new(r1c1, r1c2, r3c1, r3c2) },
{ new(r1c2, r1c3, r2c2, r2c3), new(r1c1, r1c3, r2c1, r2c3), new(r1c1, r1c2, r2c1, r2c2) }
};
public Matrix3x3 Transpose() => new(new[,]
{
{ r1c1, r2c1, r3c1 },
{ r1c2, r2c2, r3c2 },
{ r1c3, r2c3, r3c3 }
});
public float[] GetColumn(int column) => new[] { this[0, column], this[1, column], this[2, column] };
public float[] GetRow(int row) => new[] { this[row, 0], this[row, 1], this[row, 2] };
public void SetColumn(int column, float[] vals)
{
if (vals.Length < 3)
throw new InvalidSizeException("Array must contain enough values to fill the column.");
this[0, column] = vals[0];
this[1, column] = vals[1];
this[2, column] = vals[2];
}
public void SetRow(int row, float[] vals)
{
if (vals.Length < 3)
throw new InvalidSizeException("Array must contain enough values to fill the row.");
this[row, 0] = vals[0];
this[row, 1] = vals[1];
this[row, 2] = vals[2];
}
public Matrix3x3 AddRow(int rowToChange, int referenceRow, float factor = 1)
{
Matrix3x3 @this = this;
return new(delegate (int r, int c)
{
if (r == rowToChange) return @this[r, c] += factor * @this[referenceRow, c];
else return @this[r, c];
});
}
public void AddRowMutable(int rowToChange, int referenceRow, float factor)
{
this[rowToChange, 0] += this[referenceRow, 0] * factor;
this[rowToChange, 1] += this[referenceRow, 1] * factor;
this[rowToChange, 2] += this[referenceRow, 2] * factor;
}
public Matrix3x3 ScaleRow(int rowIndex, float factor)
{
Matrix3x3 @this = this;
return new(delegate (int r, int c)
{
if (r == rowIndex) return @this[r, c] * factor;
else return @this[r, c];
});
}
public void ScaleRowMutable(int rowIndex, float factor)
{
this[rowIndex, 0] *= factor;
this[rowIndex, 1] *= factor;
this[rowIndex, 2] *= factor;
}
public Matrix3x3 SwapRows(int rowA, int rowB)
{
Matrix3x3 @this = this;
return new(delegate (int r, int c)
{
if (r == rowA) return @this[rowB, c];
else if (r == rowB) return @this[rowA, c];
else return @this[r, c];
});
}
public void SwapRowsMutable(int rowA, int rowB)
{
float[] dataA = GetRow(rowA), dataB = GetRow(rowB);
SetRow(rowA, dataB);
SetRow(rowB, dataA);
}
public virtual bool Equals(Matrix3x3? other)
{
if (other is null) return false;
return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r1c3 == other.r1c3 &&
r2c1 == other.r2c1 && r2c2 == other.r2c2 && r2c3 == other.r2c3 &&
r3c1 == other.r3c1 && r3c2 == other.r3c2 && r3c3 == other.r3c3;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() =>
r1c1 + " " + r1c2 + " " + r1c3 + "\n" +
r2c1 + " " + r2c2 + " " + r2c3 + "\n" +
r3c1 + " " + r3c2 + " " + r3c3;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
{
yield return r1c1;
yield return r1c2;
yield return r1c3;
yield return r2c1;
yield return r2c2;
yield return r2c3;
yield return r3c1;
yield return r3c2;
yield return r3c3;
}
public float[] ToArray() => new[] { r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3 };
public float[,] ToArray2D() => new[,]
{
{ r1c1, r1c2, r1c3 },
{ r2c1, r2c2, r2c3 },
{ r3c1, r3c2, r3c3 }
};
public Fill<float> ToFill() => ToFillExtension.ToFill(this);
public Fill2d<float> ToFill2D()
{
Matrix3x3 @this = this;
return (x, y) => @this[x, y];
}
public List<float> ToList() => new() { r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3 };
public static Matrix3x3 operator +(Matrix3x3 a, Matrix3x3 b) =>
new(a.r1c1 + b.r1c1, a.r1c2 + b.r1c2, a.r1c3 + b.r1c3,
a.r2c1 + b.r2c1, a.r2c2 + b.r2c2, a.r2c3 + b.r2c3,
a.r3c1 + b.r3c1, a.r3c2 + b.r3c2, a.r3c3 + b.r3c3);
public static Matrix3x3? operator -(Matrix3x3 m) => m.Inverse();
public static Matrix3x3 operator -(Matrix3x3 a, Matrix3x3 b) =>
new(a.r1c1 - b.r1c1, a.r1c2 - b.r1c2, a.r1c3 - b.r1c3,
a.r2c1 - b.r2c1, a.r2c2 - b.r2c2, a.r2c3 - b.r2c3,
a.r3c1 - b.r3c1, a.r3c2 - b.r3c2, a.r3c3 - b.r3c3);
public static Matrix3x3 operator *(Matrix3x3 a, float b) =>
new(a.r1c1 * b, a.r1c2 * b, a.r1c3 * b,
a.r2c1 * b, a.r2c2 * b, a.r2c3 * b,
a.r3c1 * b, a.r3c2 * b, a.r3c3 * b);
public static Matrix3x3 operator *(Matrix3x3 a, Matrix3x3 b) => new(new[,]
{
{ Float3.Dot(a.Row1, b.Column1), Float3.Dot(a.Row1, b.Column2), Float3.Dot(a.Row1, b.Column3) },
{ Float3.Dot(a.Row2, b.Column1), Float3.Dot(a.Row2, b.Column2), Float3.Dot(a.Row2, b.Column3) },
{ Float3.Dot(a.Row3, b.Column1), Float3.Dot(a.Row3, b.Column2), Float3.Dot(a.Row3, b.Column3) },
});
public static Float3 operator *(Matrix3x3 a, Float3 b) => (Matrix)a * b;
public static Matrix3x3 operator /(Matrix3x3 a, float b) =>
new(a.r1c1 / b, a.r1c2 / b, a.r1c3 / b,
a.r2c1 / b, a.r2c2 / b, a.r2c3 / b,
a.r3c1 / b, a.r3c2 / b, a.r3c3 / b);
public static Matrix3x3 operator /(Matrix3x3 a, Matrix3x3 b)
{
Matrix3x3? bInv = b.Inverse();
if (bInv is null) throw new NoInverseException(b);
return a * bInv;
}
public static Float3 operator /(Matrix3x3 a, Float3 b) => (Matrix)a / b;
public static Matrix3x3 operator ^(Matrix3x3 a, Matrix3x3 b) => // Single number multiplication
new(a.r1c1 * b.r1c1, a.r1c2 * b.r1c2, a.r1c3 * b.r1c3,
a.r2c1 * b.r2c1, a.r2c2 * b.r2c2, a.r2c3 * b.r2c3,
a.r3c1 * b.r3c1, a.r3c2 * b.r3c2, a.r3c3 * b.r3c3);
public static explicit operator Matrix3x3(Matrix m)
{
Matrix3x3 res = Zero, identity = Identity;
for (int r = 0; r < 3; r++) for (int c = 0; c < 3; c++)
res[c, r] = m.Size.x > r && m.Size.y > c ? m[r, c] : identity[r, c];
return res;
}
public static implicit operator Matrix3x3(Matrix2x2 m)
{
Matrix3x3 identity = Identity;
return new(new[,]
{
{ m.r1c1, m.r1c2, identity.r1c3 },
{ m.r2c1, m.r2c2, identity.r2c3 },
{ identity.r3c1, identity.r3c2, identity.r3c3 }
});
}
public static explicit operator Matrix3x3(Matrix4x4 m) => new(new[,]
{
{ m.r1c1, m.r1c2, m.r1c3 },
{ m.r2c1, m.r2c2, m.r2c3 },
{ m.r3c1, m.r3c2, m.r3c3 }
});
}

View File

@ -0,0 +1,672 @@
using System.Data.Common;
namespace Nerd_STF.Mathematics.Algebra;
public record class Matrix4x4 : IStaticMatrix<Matrix4x4>
{
public static Matrix4x4 Identity => new(new[,]
{
{ 1, 0, 0, 0 },
{ 0, 1, 0, 0 },
{ 0, 0, 1, 0 },
{ 0, 0, 0, 1 },
});
public static Matrix4x4 One => new(1);
public static Matrix4x4 SignGrid => new(new[,]
{
{ +1, -1, +1, -1 },
{ -1, +1, -1, +1 },
{ +1, -1, +1, -1 },
{ -1, +1, -1, +1 }
});
public static Matrix4x4 Zero => new(0);
public Float4 Column1
{
get => new(r1c1, r2c1, r3c1, r4c1);
set
{
r1c1 = value.x;
r2c1 = value.y;
r3c1 = value.z;
r4c1 = value.w;
}
}
public Float4 Column2
{
get => new(r1c2, r2c2, r3c2, r4c2);
set
{
r1c2 = value.x;
r2c2 = value.y;
r3c2 = value.z;
r4c2 = value.w;
}
}
public Float4 Column3
{
get => new(r1c3, r2c3, r3c3, r4c3);
set
{
r1c3 = value.x;
r2c3 = value.y;
r3c3 = value.z;
r4c3 = value.w;
}
}
public Float4 Column4
{
get => new(r1c4, r2c4, r3c4, r4c4);
set
{
r1c4 = value.x;
r2c4 = value.y;
r3c4 = value.z;
r4c4 = value.w;
}
}
public Float4 Row1
{
get => new(r1c1, r1c2, r1c3, r1c4);
set
{
r1c1 = value.x;
r1c2 = value.y;
r1c3 = value.z;
r1c4 = value.w;
}
}
public Float4 Row2
{
get => new(r2c1, r2c2, r2c3, r2c4);
set
{
r2c1 = value.x;
r2c2 = value.y;
r2c3 = value.z;
r2c4 = value.w;
}
}
public Float4 Row3
{
get => new(r3c1, r3c2, r3c3, r3c4);
set
{
r3c1 = value.x;
r3c2 = value.y;
r3c3 = value.z;
r3c4 = value.w;
}
}
public Float4 Row4
{
get => new(r4c1, r4c2, r4c3, r4c4);
set
{
r4c1 = value.x;
r4c2 = value.y;
r4c3 = value.z;
r4c4 = value.w;
}
}
public Int2 Size => (4, 4);
public float r1c1, r2c1, r3c1, r4c1, r1c2, r2c2, r3c2, r4c2, r1c3, r2c3, r3c3, r4c3, r1c4, r2c4, r3c4, r4c4;
public Matrix4x4(float all) : this(all, all, all, all, all,
all, all, all, all, all, all, all, all, all, all, all) { }
public Matrix4x4(float r1c1, float r1c2, float r1c3, float r1c4, float r2c1, float r2c2, float r2c3,
float r2c4, float r3c1, float r3c2, float r3c3, float r3c4, float r4c1, float r4c2, float r4c3, float r4c4)
{
this.r1c1 = r1c1;
this.r2c1 = r2c1;
this.r3c1 = r3c1;
this.r4c1 = r4c1;
this.r1c2 = r1c2;
this.r2c2 = r2c2;
this.r3c2 = r3c2;
this.r4c2 = r4c2;
this.r1c3 = r1c3;
this.r2c3 = r2c3;
this.r3c3 = r3c3;
this.r4c3 = r4c3;
this.r1c4 = r1c4;
this.r2c4 = r2c4;
this.r3c4 = r3c4;
this.r4c4 = r4c4;
}
public Matrix4x4(float[] nums) : this(nums[0], nums[1], nums[2], nums[3], nums[4], nums[5], nums[6],
nums[7], nums[8], nums[9], nums[10], nums[11], nums[12], nums[13], nums[14], nums[15]) { }
public Matrix4x4(int[] nums) : this(nums[0], nums[1], nums[2], nums[3], nums[4], nums[5], nums[6],
nums[7], nums[8], nums[9], nums[10], nums[11], nums[12], nums[13], nums[14], nums[15]) { }
public Matrix4x4(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4), fill(5), fill(6),
fill(7), fill(8), fill(9), fill(10), fill(11), fill(12), fill(13), fill(14), fill(15)) { }
public Matrix4x4(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4), fill(5), fill(6),
fill(7), fill(8), fill(9), fill(10), fill(11), fill(12), fill(13), fill(14), fill(15)) { }
public Matrix4x4(float[,] nums) : this(nums[0, 0], nums[0, 1], nums[0, 2], nums[0, 3], nums[1, 0],
nums[1, 1], nums[1, 2], nums[1, 3], nums[2, 0], nums[2, 1], nums[2, 2], nums[2, 3], nums[3, 0],
nums[3, 1], nums[3, 2], nums[3, 3]) { }
public Matrix4x4(int[,] nums) : this(nums[0, 0], nums[0, 1], nums[0, 2], nums[0, 3], nums[1, 0],
nums[1, 1], nums[1, 2], nums[1, 3], nums[2, 0], nums[2, 1], nums[2, 2], nums[2, 3], nums[3, 0],
nums[3, 1], nums[3, 2], nums[3, 3]) { }
public Matrix4x4(Fill2d<float> fill) : this(fill(0, 0), fill(0, 1), fill(0, 2), fill(0, 3), fill(1, 0),
fill(1, 1), fill(1, 2), fill(1, 3), fill(2, 0), fill(2, 1), fill(2, 2), fill(2, 3), fill(3, 0),
fill(3, 1), fill(3, 2), fill(3, 3)) { }
public Matrix4x4(Fill2d<int> fill) : this(fill(0, 0), fill(0, 1), fill(0, 2), fill(0, 3), fill(1, 0),
fill(1, 1), fill(1, 2), fill(1, 3), fill(2, 0), fill(2, 1), fill(2, 2), fill(2, 3), fill(3, 0),
fill(3, 1), fill(3, 2), fill(3, 3)) { }
public Matrix4x4(Float4 r1, Float4 r2, Float4 r3, Float4 r4) : this(r1.x, r1.y, r1.z,
r1.w, r2.x, r2.y, r2.z, r2.w, r3.x, r3.y, r3.z, r3.w, r4.x, r4.y, r4.z, r4.w) { }
public Matrix4x4(Fill<Float4> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public Matrix4x4(Fill<Int4> fill) : this((IEnumerable<int>)fill(0), fill(1), fill(2), fill(3)) { }
public Matrix4x4(IEnumerable<float> r1, IEnumerable<float> r2, IEnumerable<float> r3, IEnumerable<float> r4)
: this(r1.ToFill(), r2.ToFill(), r3.ToFill(), r4.ToFill()) { }
public Matrix4x4(IEnumerable<int> r1, IEnumerable<int> r2, IEnumerable<int> r3, IEnumerable<int> r4)
: this(r1.ToFill(), r2.ToFill(), r3.ToFill(), r4.ToFill()) { }
public Matrix4x4(Fill<float> r1, Fill<float> r2, Fill<float> r3, Fill<float> r4) : this(r1(0), r1(1),
r1(2), r1(3), r2(0), r2(1), r2(2), r2(3), r3(0), r3(1), r3(2), r3(3), r4(0), r4(1), r4(2), r4(3)) { }
public Matrix4x4(Fill<int> r1, Fill<int> r2, Fill<int> r3, Fill<int> r4) : this(r1(0), r1(1),
r1(2), r1(3), r2(0), r2(1), r2(2), r2(3), r3(0), r3(1), r3(2), r3(3), r4(0), r4(1), r4(2), r4(3)) { }
public float this[int r, int c]
{
get => ToArray2D()[r, c];
set
{
// Maybe this could be improved?
// It's definitely better than it was before. Trust me.
switch ("r" + (r + 1) + "c" + (c + 1))
{
case "r1c1":
r1c1 = value;
break;
case "r2c1":
r2c1 = value;
break;
case "r3c1":
r3c1 = value;
break;
case "r4c1":
r4c1 = value;
break;
case "r1c2":
r1c2 = value;
break;
case "r2c2":
r2c2 = value;
break;
case "r3c2":
r3c2 = value;
break;
case "r4c2":
r4c2 = value;
break;
case "r1c3":
r1c3 = value;
break;
case "r2c3":
r2c3 = value;
break;
case "r3c3":
r3c3 = value;
break;
case "r4c3":
r4c3 = value;
break;
case "r1c4":
r1c4 = value;
break;
case "r2c4":
r2c4 = value;
break;
case "r3c4":
r3c4 = value;
break;
case "r4c4":
r4c4 = value;
break;
default:
string @params = "";
if (r < 0 || r > 2) @params += r;
if (c < 0 || c > 2) @params += string.IsNullOrEmpty(@params) ? c : " and " + c;
throw new IndexOutOfRangeException(@params);
}
}
}
public float this[Int2 index]
{
get => this[index.x, index.y];
set => this[index.x, index.y] = value;
}
public float this[Index r, Index c]
{
get
{
int row = r.IsFromEnd ? 4 - r.Value : r.Value,
col = c.IsFromEnd ? 4 - c.Value : c.Value;
return this[row, col];
}
set
{
int row = r.IsFromEnd ? 4 - r.Value : r.Value,
col = c.IsFromEnd ? 4 - c.Value : c.Value;
this[row, col] = value;
}
}
public float[,] this[Range rs, Range cs]
{
get
{
int rowStart = rs.Start.IsFromEnd ? 4 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 4 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 4 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 4 - cs.End.Value : cs.End.Value;
float[,] vals = new float[rowEnd - rowStart - 1, colEnd - colStart - 1];
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
vals[r, c] = this[r, c];
return vals;
}
set
{
int rowStart = rs.Start.IsFromEnd ? 4 - rs.Start.Value : rs.Start.Value,
rowEnd = rs.End.IsFromEnd ? 4 - rs.End.Value : rs.End.Value,
colStart = cs.Start.IsFromEnd ? 4 - cs.Start.Value : cs.Start.Value,
colEnd = cs.End.IsFromEnd ? 4 - cs.End.Value : cs.End.Value;
for (int r = rowStart; r < rowEnd; r++)
for (int c = colStart; c < colEnd; c++)
this[r, c] = value[r, c];
}
}
public static Matrix4x4 Absolute(Matrix4x4 val) =>
new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r1c2), Mathf.Absolute(val.r1c3), Mathf.Absolute(val.r1c4),
Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r2c2), Mathf.Absolute(val.r2c3), Mathf.Absolute(val.r2c4),
Mathf.Absolute(val.r3c1), Mathf.Absolute(val.r3c2), Mathf.Absolute(val.r3c3), Mathf.Absolute(val.r3c4),
Mathf.Absolute(val.r4c1), Mathf.Absolute(val.r4c2), Mathf.Absolute(val.r4c3), Mathf.Absolute(val.r4c4));
public static Matrix4x4 Average(params Matrix4x4[] vals) => Sum(vals) / vals.Length;
public static Matrix4x4 Ceiling(Matrix4x4 val) =>
new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r1c2), Mathf.Ceiling(val.r1c3), Mathf.Ceiling(val.r1c4),
Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r2c2), Mathf.Ceiling(val.r2c3), Mathf.Ceiling(val.r2c4),
Mathf.Ceiling(val.r3c1), Mathf.Ceiling(val.r3c2), Mathf.Ceiling(val.r3c3), Mathf.Ceiling(val.r3c4),
Mathf.Ceiling(val.r4c1), Mathf.Ceiling(val.r4c2), Mathf.Ceiling(val.r4c3), Mathf.Ceiling(val.r4c4));
public static Matrix4x4 Clamp(Matrix4x4 val, Matrix4x4 min, Matrix4x4 max) =>
new(Mathf.Clamp(val.r1c1, min.r1c1, max.r1c1), Mathf.Clamp(val.r1c2, min.r1c2, max.r1c2),
Mathf.Clamp(val.r1c3, min.r1c3, max.r1c3), Mathf.Clamp(val.r1c4, min.r1c4, max.r1c4),
Mathf.Clamp(val.r2c1, min.r2c1, max.r2c1), Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2),
Mathf.Clamp(val.r2c3, min.r2c3, max.r2c3), Mathf.Clamp(val.r2c4, min.r2c4, max.r2c4),
Mathf.Clamp(val.r3c1, min.r3c1, max.r3c1), Mathf.Clamp(val.r3c2, min.r3c2, max.r3c2),
Mathf.Clamp(val.r3c3, min.r3c3, max.r3c3), Mathf.Clamp(val.r3c4, min.r3c4, max.r3c4),
Mathf.Clamp(val.r4c1, min.r4c1, max.r4c1), Mathf.Clamp(val.r4c2, min.r4c2, max.r4c2),
Mathf.Clamp(val.r4c3, min.r4c3, max.r4c3), Mathf.Clamp(val.r4c4, min.r4c4, max.r4c4));
public static Matrix4x4 Divide(Matrix4x4 num, params Matrix4x4[] vals)
{
foreach (Matrix4x4 m in vals) num /= m;
return num;
}
public static Matrix4x4 Floor(Matrix4x4 val) =>
new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r1c2), Mathf.Floor(val.r1c3), Mathf.Floor(val.r1c4),
Mathf.Floor(val.r2c1), Mathf.Floor(val.r2c2), Mathf.Floor(val.r2c3), Mathf.Floor(val.r2c4),
Mathf.Floor(val.r3c1), Mathf.Floor(val.r3c2), Mathf.Floor(val.r3c3), Mathf.Floor(val.r3c4),
Mathf.Floor(val.r4c1), Mathf.Floor(val.r4c2), Mathf.Floor(val.r4c3), Mathf.Floor(val.r4c4));
public static Matrix4x4 Lerp(Matrix4x4 a, Matrix4x4 b, float t, bool clamp = true) =>
new(Mathf.Lerp(a.r1c1, b.r1c1, t, clamp), Mathf.Lerp(a.r1c2, b.r1c2, t, clamp),
Mathf.Lerp(a.r1c3, b.r1c3, t, clamp), Mathf.Lerp(a.r1c4, b.r1c4, t, clamp),
Mathf.Lerp(a.r2c1, b.r2c1, t, clamp), Mathf.Lerp(a.r2c2, b.r2c2, t, clamp),
Mathf.Lerp(a.r2c3, b.r2c3, t, clamp), Mathf.Lerp(a.r2c4, b.r2c4, t, clamp),
Mathf.Lerp(a.r3c1, b.r3c1, t, clamp), Mathf.Lerp(a.r3c2, b.r3c2, t, clamp),
Mathf.Lerp(a.r3c3, b.r3c3, t, clamp), Mathf.Lerp(a.r3c4, b.r3c4, t, clamp),
Mathf.Lerp(a.r4c1, b.r4c1, t, clamp), Mathf.Lerp(a.r4c2, b.r4c2, t, clamp),
Mathf.Lerp(a.r4c3, b.r4c3, t, clamp), Mathf.Lerp(a.r4c4, b.r4c4, t, clamp));
public static Matrix4x4 Median(params Matrix4x4[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
Matrix4x4 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static Matrix4x4 Product(params Matrix4x4[] vals)
{
if (vals.Length < 1) return Zero;
Matrix4x4 val = Identity;
foreach (Matrix4x4 m in vals) val *= m;
return val;
}
public static Matrix4x4 Round(Matrix4x4 val) =>
new(Mathf.Round(val.r1c1), Mathf.Round(val.r1c2), Mathf.Round(val.r1c3), Mathf.Round(val.r1c4),
Mathf.Round(val.r2c1), Mathf.Round(val.r2c2), Mathf.Round(val.r2c3), Mathf.Round(val.r2c4),
Mathf.Round(val.r3c1), Mathf.Round(val.r3c2), Mathf.Round(val.r3c3), Mathf.Round(val.r3c4),
Mathf.Round(val.r4c1), Mathf.Round(val.r4c2), Mathf.Round(val.r4c3), Mathf.Round(val.r4c4));
public static Matrix4x4 Subtract(Matrix4x4 num, params Matrix4x4[] vals)
{
foreach (Matrix4x4 m in vals) num -= m;
return num;
}
public static Matrix4x4 Sum(params Matrix4x4[] vals)
{
Matrix4x4 val = Zero;
foreach (Matrix4x4 m in vals) val += m;
return val;
}
public static (float[] r1c1s, float[] r1c2, float[] r1c3, float[] r1c4, float[] r2c1, float[] r2c2s,
float[] r2c3, float[] r2c4, float[] r3c1, float[] r3c2, float[] r3c3s, float[] r3c4, float[] r4c1,
float[] r4c2, float[] r4c3, float[] r4c4s) SplitArray(params Matrix4x4[] vals)
{
float[] r1c1s = new float[vals.Length], r1c2s = new float[vals.Length], r1c3s = new float[vals.Length],
r1c4s = new float[vals.Length], r2c1s = new float[vals.Length], r2c2s = new float[vals.Length],
r2c3s = new float[vals.Length], r2c4s = new float[vals.Length], r3c1s = new float[vals.Length],
r3c2s = new float[vals.Length], r3c3s = new float[vals.Length], r3c4s = new float[vals.Length],
r4c1s = new float[vals.Length], r4c2s = new float[vals.Length], r4c3s = new float[vals.Length],
r4c4s = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
r1c1s[i] = vals[i].r1c1;
r1c2s[i] = vals[i].r1c2;
r1c3s[i] = vals[i].r1c3;
r1c4s[i] = vals[i].r1c4;
r2c1s[i] = vals[i].r2c1;
r2c2s[i] = vals[i].r2c2;
r2c3s[i] = vals[i].r2c3;
r2c4s[i] = vals[i].r2c4;
r3c1s[i] = vals[i].r3c1;
r3c2s[i] = vals[i].r3c2;
r3c3s[i] = vals[i].r3c3;
r3c4s[i] = vals[i].r3c4;
r4c1s[i] = vals[i].r4c1;
r4c2s[i] = vals[i].r4c2;
r4c3s[i] = vals[i].r4c3;
r4c4s[i] = vals[i].r4c4;
}
return (r1c1s, r1c2s, r1c3s, r1c4s, r2c1s, r2c2s, r2c3s, r2c4s,
r3c1s, r3c2s, r3c3s, r3c4s, r4c1s, r4c2s, r4c3s, r4c4s);
}
public Matrix4x4 Adjugate() => Cofactor().Transpose();
public Matrix4x4 Cofactor()
{
Matrix4x4 dets = Zero;
Matrix3x3[,] minors = Minors();
for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) dets[r, c] = minors[r, c].Determinant();
return dets ^ SignGrid;
}
public float Determinant()
{
Matrix3x3[,] minors = Minors();
return (r1c1 * minors[0, 0].Determinant()) - (r1c2 * minors[0, 1].Determinant()) +
(r1c3 * minors[0, 2].Determinant()) - (r1c4 * minors[0, 3].Determinant());
}
public Matrix4x4? Inverse()
{
float d = Determinant();
if (d == 0) return null;
return Adjugate() / d;
}
public Matrix3x3[,] Minors() => new Matrix3x3[,]
{
{
new(r2c2, r2c3, r2c4, r3c2, r3c3, r3c4, r4c2, r4c3, r4c4),
new(r2c1, r2c3, r2c4, r3c1, r3c3, r3c4, r4c1, r4c3, r4c4),
new(r2c1, r2c2, r2c4, r3c1, r3c2, r3c4, r4c1, r4c2, r4c4),
new(r2c1, r2c2, r2c3, r3c1, r3c2, r3c3, r4c1, r4c2, r4c3)
},
{
new(r1c2, r1c3, r1c4, r3c2, r3c3, r3c4, r4c2, r4c3, r4c4),
new(r1c1, r1c3, r1c4, r3c1, r3c3, r3c4, r4c1, r4c3, r4c4),
new(r1c1, r1c2, r1c4, r3c1, r3c2, r3c4, r4c1, r4c2, r4c4),
new(r1c1, r1c2, r1c3, r3c1, r3c2, r3c3, r4c1, r4c2, r4c3)
},
{
new(r1c2, r1c3, r1c4, r2c2, r2c3, r2c4, r4c2, r4c3, r4c4),
new(r1c1, r1c3, r1c4, r2c1, r2c3, r2c4, r4c1, r4c3, r4c4),
new(r1c1, r1c2, r1c4, r2c1, r2c2, r2c4, r4c1, r4c2, r4c4),
new(r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r4c1, r4c2, r4c3)
},
{
new(r1c2, r1c3, r1c4, r2c2, r2c3, r2c4, r3c2, r3c3, r3c4),
new(r1c1, r1c3, r1c4, r2c1, r2c3, r2c4, r3c1, r3c3, r3c4),
new(r1c1, r1c2, r1c4, r2c1, r2c2, r2c4, r3c1, r3c2, r3c4),
new(r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3)
}
};
public Matrix4x4 Transpose() => new(new[,]
{
{ r1c1, r2c1, r3c1, r4c1 },
{ r1c2, r2c2, r3c2, r4c2 },
{ r1c3, r2c3, r3c3, r4c3 },
{ r1c4, r2c4, r3c4, r4c4 }
});
public float[] GetColumn(int column) =>
new[] { this[0, column], this[1, column], this[2, column], this[3, column] };
public float[] GetRow(int row) =>
new[] { this[row, 0], this[row, 1], this[row, 2], this[row, 3] };
public void SetColumn(int column, float[] vals)
{
if (vals.Length < 4)
throw new InvalidSizeException("Array must contain enough values to fill the column.");
this[0, column] = vals[0];
this[1, column] = vals[1];
this[2, column] = vals[2];
this[3, column] = vals[3];
}
public void SetRow(int row, float[] vals)
{
if (vals.Length < 4)
throw new InvalidSizeException("Array must contain enough values to fill the row.");
this[row, 0] = vals[0];
this[row, 1] = vals[1];
this[row, 2] = vals[2];
this[row, 3] = vals[3];
}
public Matrix4x4 AddRow(int rowToChange, int referenceRow, float factor = 1)
{
Matrix4x4 @this = this;
return new(delegate (int r, int c)
{
if (r == rowToChange) return @this[r, c] += factor * @this[referenceRow, c];
else return @this[r, c];
});
}
public void AddRowMutable(int rowToChange, int referenceRow, float factor)
{
this[rowToChange, 0] += this[referenceRow, 0] * factor;
this[rowToChange, 1] += this[referenceRow, 1] * factor;
this[rowToChange, 2] += this[referenceRow, 2] * factor;
this[rowToChange, 3] += this[referenceRow, 3] * factor;
}
public Matrix4x4 ScaleRow(int rowIndex, float factor)
{
Matrix4x4 @this = this;
return new(delegate (int r, int c)
{
if (r == rowIndex) return @this[r, c] * factor;
else return @this[r, c];
});
}
public void ScaleRowMutable(int rowIndex, float factor)
{
this[rowIndex, 0] *= factor;
this[rowIndex, 1] *= factor;
this[rowIndex, 2] *= factor;
this[rowIndex, 3] *= factor;
}
public Matrix4x4 SwapRows(int rowA, int rowB)
{
Matrix4x4 @this = this;
return new(delegate (int r, int c)
{
if (r == rowA) return @this[rowB, c];
else if (r == rowB) return @this[rowA, c];
else return @this[r, c];
});
}
public void SwapRowsMutable(int rowA, int rowB)
{
float[] dataA = GetRow(rowA), dataB = GetRow(rowB);
SetRow(rowA, dataB);
SetRow(rowB, dataA);
}
public virtual bool Equals(Matrix4x4? other)
{
if (other is null) return false;
return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r1c3 == other.r1c3 && r1c4 == other.r1c4 &&
r2c1 == other.r2c1 && r2c2 == other.r2c2 && r2c3 == other.r2c3 && r2c4 == other.r2c4 &&
r3c1 == other.r3c1 && r3c2 == other.r3c2 && r3c3 == other.r3c3 && r3c4 == other.r3c4 &&
r4c1 == other.r4c1 && r4c2 == other.r4c2 && r4c3 == other.r4c3 && r4c4 == other.r4c4;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() =>
r1c1 + " " + r1c2 + " " + r1c3 + " " + r1c4 + "\n" +
r2c1 + " " + r2c2 + " " + r2c3 + " " + r2c4 + "\n" +
r3c1 + " " + r3c2 + " " + r3c3 + " " + r3c4 + "\n" +
r4c1 + " " + r4c2 + " " + r4c3 + " " + r4c4;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator()
{
yield return r1c1;
yield return r1c2;
yield return r1c3;
yield return r1c4;
yield return r2c1;
yield return r2c2;
yield return r2c3;
yield return r2c4;
yield return r3c1;
yield return r3c2;
yield return r3c3;
yield return r3c4;
yield return r4c1;
yield return r4c2;
yield return r4c3;
yield return r4c4;
}
public float[] ToArray() => new[]
{
r1c1, r2c1, r3c1, r4c1,
r1c2, r2c2, r3c2, r4c2,
r1c3, r2c3, r3c3, r4c3,
r1c4, r2c4, r3c4, r4c4
};
public float[,] ToArray2D() => new[,]
{
{ r1c1, r1c2, r1c3, r1c4 },
{ r2c1, r2c2, r2c3, r2c4 },
{ r3c1, r3c2, r3c3, r3c4 },
{ r4c1, r4c2, r4c3, r4c4 }
};
public Fill<float> ToFill() => ToFillExtension.ToFill(this);
public Fill2d<float> ToFill2D()
{
Matrix4x4 @this = this;
return (x, y) => @this[x, y];
}
public List<float> ToList() => new()
{
r1c1, r2c1, r3c1, r4c1,
r1c2, r2c2, r3c2, r4c2,
r1c3, r2c3, r3c3, r4c3,
r1c4, r2c4, r3c4, r4c4
};
public static Matrix4x4 operator +(Matrix4x4 a, Matrix4x4 b) =>
new(a.r1c1 + b.r1c1, a.r1c2 + b.r1c2, a.r1c3 + b.r1c3, a.r1c4 + b.r1c4,
a.r2c1 + b.r2c1, a.r2c2 + b.r2c2, a.r2c3 + b.r2c3, a.r2c4 + b.r2c4,
a.r3c1 + b.r3c1, a.r3c2 + b.r3c2, a.r3c3 + b.r3c3, a.r3c4 + b.r3c4,
a.r4c1 + b.r4c1, a.r4c2 + b.r4c2, a.r4c3 + b.r4c3, a.r4c4 + b.r4c4);
public static Matrix4x4? operator -(Matrix4x4 m) => m.Inverse();
public static Matrix4x4 operator -(Matrix4x4 a, Matrix4x4 b) =>
new(a.r1c1 - b.r1c1, a.r1c2 - b.r1c2, a.r1c3 - b.r1c3, a.r1c4 - b.r1c4,
a.r2c1 - b.r2c1, a.r2c2 - b.r2c2, a.r2c3 - b.r2c3, a.r2c4 - b.r2c4,
a.r3c1 - b.r3c1, a.r3c2 - b.r3c2, a.r3c3 - b.r3c3, a.r3c4 - b.r3c4,
a.r4c1 - b.r4c1, a.r4c2 - b.r4c2, a.r4c3 - b.r4c3, a.r4c4 - b.r4c4);
public static Matrix4x4 operator *(Matrix4x4 a, float b) =>
new(a.r1c1 * b, a.r1c2 * b, a.r1c3 * b, a.r1c4 * b,
a.r2c1 * b, a.r2c2 * b, a.r2c3 * b, a.r2c4 * b,
a.r3c1 * b, a.r3c2 * b, a.r3c3 * b, a.r3c4 * b,
a.r4c1 * b, a.r4c2 * b, a.r4c3 * b, a.r4c4 * b);
public static Matrix4x4 operator *(Matrix4x4 a, Matrix4x4 b) => new(new[,]
{
{ Float4.Dot(a.Row1, b.Column1), Float4.Dot(a.Row1, b.Column2),
Float4.Dot(a.Row1, b.Column3), Float4.Dot(a.Row1, b.Column4) },
{ Float4.Dot(a.Row2, b.Column1), Float4.Dot(a.Row2, b.Column2),
Float4.Dot(a.Row2, b.Column3), Float4.Dot(a.Row2, b.Column4) },
{ Float4.Dot(a.Row3, b.Column1), Float4.Dot(a.Row3, b.Column2),
Float4.Dot(a.Row3, b.Column3), Float4.Dot(a.Row3, b.Column4) },
{ Float4.Dot(a.Row4, b.Column1), Float4.Dot(a.Row4, b.Column2),
Float4.Dot(a.Row4, b.Column3), Float4.Dot(a.Row4, b.Column4) }
});
public static Float4 operator *(Matrix4x4 a, Float4 b) => (Matrix)a * b;
public static Matrix4x4 operator /(Matrix4x4 a, float b) =>
new(a.r1c1 / b, a.r1c2 / b, a.r1c3 / b, a.r1c4 / b,
a.r2c1 / b, a.r2c2 / b, a.r2c3 / b, a.r2c4 / b,
a.r3c1 / b, a.r3c2 / b, a.r3c3 / b, a.r3c4 / b,
a.r4c1 / b, a.r4c2 / b, a.r4c3 / b, a.r4c4 / b);
public static Matrix4x4 operator /(Matrix4x4 a, Matrix4x4 b)
{
Matrix4x4? bInv = b.Inverse();
if (bInv is null) throw new NoInverseException(b);
return a * bInv;
}
public static Float4 operator /(Matrix4x4 a, Float4 b) => (Matrix)a / b;
public static Matrix4x4 operator ^(Matrix4x4 a, Matrix4x4 b) => // Single number multiplication
new(a.r1c1 * b.r1c1, a.r1c2 * b.r1c2, a.r1c3 * b.r1c3, a.r1c4 * b.r1c4,
a.r2c1 * b.r2c1, a.r2c2 * b.r2c2, a.r2c3 * b.r2c3, a.r2c4 * b.r2c4,
a.r3c1 * b.r3c1, a.r3c2 * b.r3c2, a.r3c3 * b.r3c3, a.r3c4 * b.r3c4,
a.r4c1 * b.r4c1, a.r4c2 * b.r4c2, a.r4c3 * b.r4c3, a.r4c4 * b.r4c4);
public static explicit operator Matrix4x4(Matrix m)
{
Matrix4x4 res = Zero, identity = Identity;
for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++)
res[c, r] = m.Size.x > r && m.Size.y > c ? m[r, c] : identity[r, c];
return res;
}
public static implicit operator Matrix4x4(Matrix2x2 m)
{
Matrix4x4 identity = Identity;
return new(new[,]
{
{ m.r1c1, m.r1c2, identity.r1c3, identity.r1c4 },
{ m.r2c1, m.r2c2, identity.r2c3, identity.r2c4 },
{ identity.r3c1, identity.r3c2, identity.r3c3, identity.r3c4 },
{ identity.r4c1, identity.r4c2, identity.r4c3, identity.r4c4 }
});
}
public static implicit operator Matrix4x4(Matrix3x3 m)
{
Matrix4x4 identity = Identity;
return new(new[,]
{
{ m.r1c1, m.r1c2, m.r1c3, identity.r1c4 },
{ m.r2c1, m.r2c2, m.r2c3, identity.r2c4 },
{ m.r3c1, m.r3c2, m.r3c3, identity.r3c4 },
{ identity.r4c1, identity.r4c2, identity.r4c3, identity.r4c4 }
});
}
}

View File

@ -0,0 +1,142 @@
namespace Nerd_STF.Mathematics.Algebra;
public record struct Vector2d : IAbsolute<Vector2d>, IAverage<Vector2d>,
IClampMagnitude<Vector2d, float>, IComparable<Vector2d>, ICross<Vector2d, Vector3d>,
IDot<Vector2d, float>, IEquatable<Vector2d>, IFromTuple<Vector2d, (Angle angle, float mag)>,
ILerp<Vector2d, float>, IMax<Vector2d>, IMagnitude<float>, IMedian<Vector2d>, IMin<Vector2d>,
IPresets2d<Vector2d>, ISplittable<Vector2d, (Angle[] rots, float[] mags)>, ISubtract<Vector2d>,
ISum<Vector2d>
{
public static Vector2d Down => new(Angle.Down);
public static Vector2d Left => new(Angle.Left);
public static Vector2d Right => new(Angle.Right);
public static Vector2d Up => new(Angle.Up);
public static Vector2d One => new(Angle.Zero);
public static Vector2d Zero => new(Angle.Zero, 0);
public float Magnitude
{
get => magnitude;
set => magnitude = value;
}
public Vector2d Inverse => new(-theta, magnitude);
public Vector2d Normalized => new(theta, 1);
public Angle theta;
public float magnitude;
public Vector2d(Angle theta, float mag = 1)
{
this.theta = theta;
magnitude = mag;
}
public Vector2d(float theta, Angle.Type rotType, float mag = 1) : this(new(theta, rotType), mag) { }
public static Vector2d Absolute(Vector2d val) => new(Angle.Absolute(val.theta), Mathf.Absolute(val.magnitude));
public static Vector2d Average(params Vector2d[] vals)
{
(Angle[] thetas, float[] Mags) = SplitArray(vals);
return new(Angle.Average(thetas), Mathf.Average(Mags));
}
public static Vector2d Ceiling(Vector2d val, Angle.Type angleRound = Angle.Type.Degrees) =>
new(Angle.Ceiling(val.theta, angleRound), Mathf.Ceiling(val.magnitude));
public static Vector2d ClampMagnitude(Vector2d val, float minMag, float maxMag)
{
if (maxMag < minMag) throw new ArgumentOutOfRangeException(nameof(maxMag),
nameof(maxMag) + " must be greater than or equal to " + nameof(minMag));
float mag = Mathf.Clamp(val.magnitude, minMag, maxMag);
return new(val.theta, mag);
}
public static Vector3d Cross(Vector2d a, Vector2d b, bool normalized = false) =>
Float2.Cross(a.ToXYZ(), b.ToXYZ(), normalized).ToVector();
public static float Dot(Vector2d a, Vector2d b) => Float2.Dot(a.ToXYZ(), b.ToXYZ());
public static float Dot(params Vector2d[] vals)
{
Float2[] floats = new Float2[vals.Length];
for (int i = 0; i < vals.Length; i++) floats[i] = vals[i].ToXYZ();
return Float2.Dot(floats);
}
public static Vector2d Floor(Vector2d val, Angle.Type angleRound = Angle.Type.Degrees) =>
new(Angle.Floor(val.theta, angleRound), Mathf.Floor(val.magnitude));
public static Vector2d Lerp(Vector2d a, Vector2d b, float t, bool clamp = true) =>
new(Angle.Lerp(a.theta, b.theta, t, clamp), Mathf.Lerp(a.magnitude, b.magnitude, t, clamp));
public static Vector2d Median(params Vector2d[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
Vector2d valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static Vector2d Max(params Vector2d[] vals)
{
if (vals.Length < 1) return Zero;
Vector2d val = vals[0];
foreach (Vector2d f in vals) val = f > val ? f : val;
return val;
}
public static Vector2d Min(params Vector2d[] vals)
{
if (vals.Length < 1) return Zero;
Vector2d val = vals[0];
foreach (Vector2d f in vals) val = f < val ? f : val;
return val;
}
public static Vector2d Round(Vector2d val, Angle.Type angleRound = Angle.Type.Degrees) =>
new(Angle.Round(val.theta, angleRound), Mathf.Round(val.magnitude));
public static Vector2d Subtract(Vector2d num, params Vector2d[] vals)
{
foreach (Vector2d v in vals) num -= v;
return num;
}
public static Vector2d Sum(params Vector2d[] vals)
{
if (vals.Length < 1) return Zero;
Vector2d val = One;
foreach (Vector2d v in vals) val += v;
return val;
}
public static (Angle[] rots, float[] mags) SplitArray(params Vector2d[] vals)
{
Angle[] rots = new Angle[vals.Length];
float[] mags = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
rots[i] = vals[i].theta;
mags[i] = vals[i].magnitude;
}
return (rots, mags);
}
public int CompareTo(Vector2d other) => magnitude.CompareTo(other.magnitude);
public bool Equals(Vector2d other) => theta == other.theta && magnitude == other.magnitude;
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => ToString(Angle.Type.Degrees);
public string ToString(Angle.Type outputType) =>
nameof(Vector2d) + " { Mag = " + magnitude + ", Rot = " + theta.ToString(outputType) + " }";
public Float2 ToXYZ() => new Float2(Mathf.Cos(theta), Mathf.Sin(theta)) * magnitude;
public static Vector2d operator +(Vector2d a, Vector2d b) => new(a.theta + b.theta, a.magnitude + b.magnitude);
public static Vector2d operator -(Vector2d v) => v.Inverse;
public static Vector2d operator -(Vector2d a, Vector2d b) => new(a.theta - b.theta, a.magnitude - b.magnitude);
public static Vector2d operator *(Vector2d a, float b) => new(a.theta, a.magnitude * b);
public static Vector2d operator *(Vector2d a, Matrix b) => (Vector2d)((Matrix)a * b);
public static Vector2d operator /(Vector2d a, float b) => new(a.theta, a.magnitude / b);
public static Vector2d operator /(Vector2d a, Matrix b) => (Vector2d)((Matrix)a / b);
public static bool operator >(Vector2d a, Vector2d b) => a.CompareTo(b) > 0;
public static bool operator <(Vector2d a, Vector2d b) => a.CompareTo(b) < 0;
public static bool operator >=(Vector2d a, Vector2d b) => a == b || a > b;
public static bool operator <=(Vector2d a, Vector2d b) => a == b || a < b;
public static explicit operator Vector2d(Complex val) => val.ToVector();
public static explicit operator Vector2d(Float2 val) => val.ToVector();
public static explicit operator Vector2d(Float3 val) => (Vector2d)val.ToVector();
public static explicit operator Vector2d(Int2 val) => val.ToVector();
public static explicit operator Vector2d(Int3 val) => (Vector2d)val.ToVector();
public static explicit operator Vector2d(Matrix m) => ((Float2)m).ToVector();
public static explicit operator Vector2d(Vert val) => (Vector2d)val.ToVector();
public static explicit operator Vector2d(Vector3d val) => new(val.yaw, val.magnitude);
public static implicit operator Vector2d((Angle angle, float mag) val) => new(val.angle, val.mag);
}

View File

@ -0,0 +1,208 @@
namespace Nerd_STF.Mathematics.Algebra;
public record struct Vector3d : IAbsolute<Vector3d>, IAverage<Vector3d>, IClampMagnitude<Vector3d, float>,
IComparable<Vector3d>, ICross<Vector3d>, IDot<Vector3d, float>, IEquatable<Vector3d>,
IFromTuple<Vector3d, (Angle yaw, Angle pitch, float mag)>, IIndexAll<Angle>, IIndexRangeAll<Angle>,
ILerp<Vector3d, float>, IMagnitude<float>, IMax<Vector3d>, IMedian<Vector3d>, IMin<Vector3d>,
IPresets3d<Vector3d>, ISubtract<Vector3d>, ISum<Vector3d>
{
public static Vector3d Back => new(Angle.Zero, Angle.Up);
public static Vector3d Down => new(Angle.Down, Angle.Zero);
public static Vector3d Forward => new(Angle.Zero, Angle.Down);
public static Vector3d Left => new(Angle.Left, Angle.Zero);
public static Vector3d Right => new(Angle.Right, Angle.Zero);
public static Vector3d Up => new(Angle.Up, Angle.Zero);
public static Vector3d One => new(Angle.Zero);
public static Vector3d Zero => new(Angle.Zero, 0);
public float Magnitude
{
get => magnitude;
set => magnitude = value;
}
public Vector3d Inverse => new(-yaw, -pitch, magnitude);
public Vector3d Normalized => new(yaw, pitch, 1);
public Angle yaw, pitch;
public float magnitude;
public Vector3d(Angle allRot, float mag = 1) : this(allRot, allRot, mag) { }
public Vector3d(float allRot, Angle.Type rotType, float mag = 1) : this(allRot, allRot, rotType, mag) { }
public Vector3d(Angle yaw, Angle pitch, float mag = 1)
{
this.yaw = yaw;
this.pitch = pitch;
magnitude = mag;
}
public Vector3d(float yaw, float pitch, Angle.Type rotType, float mag = 1)
: this(new Angle(yaw, rotType), new(pitch, rotType), mag) { }
public Vector3d(Float2 rots, Angle.Type rotType, float mag = 1) : this(rots.x, rots.y, rotType, mag) { }
public Vector3d(Fill<Angle> fill, float mag = 1) : this(fill(0), fill(1), mag) { }
public Vector3d(Fill<float> fill, Angle.Type rotType, float mag = 1) : this(fill(0), fill(1), rotType, mag) { }
public Angle this[int index]
{
get => index switch
{
0 => yaw,
1 => pitch,
_ => throw new IndexOutOfRangeException(nameof(index)),
};
set
{
switch (index)
{
case 0:
yaw = value;
break;
case 1:
pitch = value;
break;
default: throw new IndexOutOfRangeException(nameof(index));
}
}
}
public Angle this[Index index]
{
get => this[index.IsFromEnd ? 2 - index.Value : index.Value];
set => this[index.IsFromEnd ? 2 - index.Value : index.Value] = value;
}
public Angle[] this[Range range]
{
get
{
int start = range.Start.IsFromEnd ? 2 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 2 - range.End.Value : range.End.Value;
List<Angle> res = new();
for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray();
}
set
{
int start = range.Start.IsFromEnd ? 2 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 2 - range.End.Value : range.End.Value;
for (int i = start; i < end; i++) this[i] = value[i];
}
}
public static Vector3d Absolute(Vector3d val) => new(Angle.Absolute(val.yaw), Angle.Absolute(val.pitch),
Mathf.Absolute(val.magnitude));
public static Vector3d Average(params Vector3d[] vals)
{
(Angle[] Thetas, Angle[] Phis, float[] Mags) = SplitArray(vals);
return new(Angle.Average(Thetas), Angle.Average(Phis), Mathf.Average(Mags));
}
public static Vector3d Ceiling(Vector3d val, Angle.Type angleRound = Angle.Type.Degrees) =>
new(Angle.Ceiling(val.yaw, angleRound), Angle.Ceiling(val.pitch, angleRound),
Mathf.Ceiling(val.magnitude));
public static Vector3d ClampMagnitude(Vector3d val, float minMag, float maxMag)
{
if (maxMag < minMag) throw new ArgumentOutOfRangeException(nameof(maxMag),
nameof(maxMag) + " must be greater than or equal to " + nameof(minMag));
float mag = Mathf.Clamp(val.magnitude, minMag, maxMag);
return new(val.yaw, val.pitch, mag);
}
public static Vector3d Cross(Vector3d a, Vector3d b, bool normalized = false) =>
Float3.Cross(a.ToXYZ(), b.ToXYZ(), normalized).ToVector();
public static float Dot(Vector3d a, Vector3d b) => Float3.Dot(a.ToXYZ(), b.ToXYZ());
public static float Dot(params Vector3d[] vals)
{
Float3[] floats = new Float3[vals.Length];
for (int i = 0; i < vals.Length; i++) floats[i] = vals[i].ToXYZ();
return Float3.Dot(floats);
}
public static Vector3d Floor(Vector3d val, Angle.Type angleRound = Angle.Type.Degrees) =>
new(Angle.Floor(val.yaw, angleRound), Angle.Floor(val.pitch, angleRound), Mathf.Floor(val.magnitude));
public static Vector3d Lerp(Vector3d a, Vector3d b, float t, bool clamp = true) =>
new(Angle.Lerp(a.yaw, b.yaw, t, clamp), Angle.Lerp(a.pitch, b.pitch, t, clamp),
Mathf.Lerp(a.magnitude, b.magnitude, t, clamp));
public static Vector3d Median(params Vector3d[] vals)
{
float index = Mathf.Average(0, vals.Length - 1);
Vector3d valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB);
}
public static Vector3d Max(params Vector3d[] vals)
{
if (vals.Length < 1) return Zero;
Vector3d val = vals[0];
foreach (Vector3d f in vals) val = f > val ? f : val;
return val;
}
public static Vector3d Min(params Vector3d[] vals)
{
if (vals.Length < 1) return Zero;
Vector3d val = vals[0];
foreach (Vector3d f in vals) val = f < val ? f : val;
return val;
}
public static Vector3d Round(Vector3d val, Angle.Type angleRound = Angle.Type.Degrees) =>
new(Angle.Round(val.yaw, angleRound), Angle.Round(val.pitch, angleRound), Mathf.Round(val.magnitude));
public static Vector3d Subtract(Vector3d num, params Vector3d[] vals)
{
foreach (Vector3d v in vals) num -= v;
return num;
}
public static Vector3d Sum(params Vector3d[] vals)
{
if (vals.Length < 1) return Zero;
Vector3d val = One;
foreach (Vector3d v in vals) val += v;
return val;
}
public static (Angle[] yaws, Angle[] pitches, float[] mags) SplitArray(params Vector3d[] vals)
{
Angle[] yaws = new Angle[vals.Length], pitches = new Angle[vals.Length];
float[] mags = new float[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
yaws[i] = vals[i].yaw;
pitches[i] = vals[i].pitch;
mags[i] = vals[i].magnitude;
}
return (yaws, pitches, mags);
}
public int CompareTo(Vector3d other) => magnitude.CompareTo(other.magnitude);
public bool Equals(Vector3d other) => yaw == other.yaw && pitch == other.pitch
&& magnitude == other.magnitude;
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => ToString(Angle.Type.Degrees);
public string ToString(Angle.Type outputType) =>
nameof(Vector3d) + " { Mag = " + magnitude + ", Yaw = " + yaw.ToString(outputType) +
", Pitch = " + pitch.ToString(outputType) + " }";
public Float3 ToXYZ() => new Float3(Mathf.Sin(pitch) * Mathf.Sin(yaw),
Mathf.Cos(yaw),
Mathf.Cos(pitch) * Mathf.Sin(yaw)) * magnitude;
public static Vector3d operator +(Vector3d a, Vector3d b) => new(a.yaw + b.yaw, a.pitch + b.pitch,
a.magnitude + b.magnitude);
public static Vector3d operator -(Vector3d v) => v.Inverse;
public static Vector3d operator -(Vector3d a, Vector3d b) => new(a.yaw - b.yaw, a.pitch - b.pitch,
a.magnitude - b.magnitude);
public static Vector3d operator *(Vector3d a, float b) => new(a.yaw, a.pitch, a.magnitude * b);
public static Vector3d operator *(Vector3d a, Matrix b) => (Vector3d)((Matrix)a * b);
public static Vector3d operator /(Vector3d a, float b) => new(a.yaw, a.pitch, a.magnitude / b);
public static Vector3d operator /(Vector3d a, Matrix b) => (Vector3d)((Matrix)a / b);
public static bool operator >(Vector3d a, Vector3d b) => a.CompareTo(b) > 0;
public static bool operator <(Vector3d a, Vector3d b) => a.CompareTo(b) < 0;
public static bool operator >=(Vector3d a, Vector3d b) => a == b || a > b;
public static bool operator <=(Vector3d a, Vector3d b) => a == b || a < b;
public static explicit operator Vector3d(Complex val) => val.ToVector();
public static explicit operator Vector3d(Float2 val) => val.ToVector();
public static explicit operator Vector3d(Float3 val) => val.ToVector();
public static explicit operator Vector3d(Int2 val) => val.ToVector();
public static explicit operator Vector3d(Int3 val) => val.ToVector();
public static explicit operator Vector3d(Matrix m) => ((Float3)m).ToVector();
public static explicit operator Vector3d(Vert val) => val.ToVector();
public static implicit operator Vector3d(Vector2d v) => new(v.theta, Angle.Zero, v.magnitude);
public static implicit operator Vector3d((Angle yaw, Angle pitch, float mag) val) =>
new(val.yaw, val.pitch, val.mag);
}

View File

@ -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,
}
}

View 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;
}
}

View 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