Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d2b18f969 | |||
| 3a7a3c320e | |||
| 664ba0fab7 | |||
| 2762dab872 | |||
| 48890c236e | |||
| 6182db049e | |||
| e070aa097f | |||
| 866326863b | |||
| 27c64c4291 | |||
| 0f9bcd2720 | |||
| 938c95fa18 | |||
| e59f253cc8 | |||
| 10e70a3574 | |||
| 36d4411d70 | |||
| fcee608322 | |||
| 0704b8eec7 | |||
| 467b5903cc |
9
.gitignore
vendored
9
.gitignore
vendored
@ -2,6 +2,9 @@
|
||||
.vs/
|
||||
/Nerd_STF/.vs/
|
||||
/Nerd_STF/Nerd_STF.csproj.user
|
||||
|
||||
# I don't want to include my test project.
|
||||
/Testing/
|
||||
*.sln
|
||||
|
||||
# Build Stuff
|
||||
@ -10,13 +13,7 @@
|
||||
*.dll
|
||||
*.pdb
|
||||
|
||||
# Testing project
|
||||
/Testing
|
||||
|
||||
# Nuget
|
||||
/Nerd_STF/LICENSE
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
|
||||
# Personal
|
||||
/Nerd_STF/TODO.md
|
||||
|
||||
155
Changelog.md
155
Changelog.md
@ -1,118 +1,53 @@
|
||||
# Nerd_STF v2.4.1
|
||||
# Nerd_STF v3.0-beta3
|
||||
|
||||
Hey everyone! This is one of the larger small updates, and I'm pretty proud of what I got done in a week.
|
||||
Nerd_STF 3.0 is approaching a stable release, but I've still got some things that I still want to add and things that I feel like are unsatisfactory. So here's another beta release. There will probably only be one or two more before a final version is ready. This is easily the most ambitious project I've done in such a small amount of time, so I think you can bear with me as I bring this part of it to completition.
|
||||
|
||||
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.
|
||||
Here's what's new:
|
||||
|
||||
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.
|
||||
## `Fill<T>` and `Fill2d<T>` are back!
|
||||
|
||||
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.
|
||||
I thought the `Fill<T>` delegate was slightly redundant, since you could probably just pass an `IEnumerable` or something instead, but I've learned that it's really easy to pass a Fill delegate in places where an IEnumerable would be annoying. I might add support for Fill stuff in the future, such as an extension list, but for now I've just re-added the delegate and made most types support it in a constructor.
|
||||
|
||||
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.
|
||||
## Slight Matrix Constructor Change
|
||||
|
||||
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!
|
||||
I think I got the meaning of the `byRows` parameter mixed up in my head, so I have swapped its meaning to what it (I think) should be. The default value has also changed, so unless you've been explicitly using it you won't notice a difference.
|
||||
|
||||
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`
|
||||
## And Best of All: Colors!
|
||||
|
||||
I have had plenty of time to think about how I could have done colors better in the previous iteration of the library, and I've come up with this, which I think is slightly better.
|
||||
|
||||
First of all, colors derive from the `IColor<TSelf>` interface similarly to how they did before, but no more `IColorFloat`. Now, every color has double-precision channels by default. To handle specific bit sizes, the `IColorFormat` interface has been created. It can of course be derived from, and I think it's pretty easy to use and understand, but hopefully there will be enough color formats already defined that you won't even need to touch it directly. At the moment, there's only one real color format created, `R8G8B8A8`, which is what it sounds like: 8 bits for each of the RGBA channels. There will be plenty more to come.
|
||||
|
||||
I have been thinking about writing a stream class that is capable of having a bit-offset. I would use it in tandom with the color formats, as many of them span multiple bytes in ways that don't always align with 8-bit bytes. It seems somewhat out of place, but I think I'll go for it anyway.
|
||||
|
||||
There's also a color palette system now. You give it a certain number of colors and it allocates room to the nearest power of two. If you give it 6 colors, it allocates room for 8. This is to always keep the size of the palette identical to its bit depth. 6 colors needs 3 bits per color, so might as well do as much as you can with those 3 bits.
|
||||
|
||||
There is also an `IndexedColor` "format," which does not store its color directly. Rather, it stores its index and a reference to the color palette it came from. I understand a true "indexed color" wouldn't store a reference to its palette to save memory, but this is mostly for ease of use. Colors are passed through methods with the `ref` keyword, so you can manipulate them directly.
|
||||
|
||||
```csharp
|
||||
void MethodA()
|
||||
{
|
||||
ColorPalette<ColorRGB> palette = new(8);
|
||||
|
||||
// palette[3] is currently set to black.
|
||||
MethodB(palette[3]);
|
||||
// palette[3] is now set to blue.
|
||||
}
|
||||
|
||||
void MethodB(IndexedColor<ColorRGB> color)
|
||||
{
|
||||
color.Color() = ColorRGB.Blue;
|
||||
|
||||
// You could also:
|
||||
ref ColorRGB val = ref color.Color();
|
||||
val = ColorRGB.Blue;
|
||||
}
|
||||
```
|
||||
|
||||
Anyway, that's all I've got for now. I'm not sure what will be next up, but here's what's left to do:
|
||||
- Complex numbers and quaternions.
|
||||
- More color types and formats.
|
||||
- Bit-offset compatible streams.
|
||||
- Fix bugs/inconveniences I've noted.
|
||||
|
||||
I think the Image type will be completely reworked and might be what version 3.1 is.
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
[*.cs]
|
||||
|
||||
# CA1050: Declare types in namespaces
|
||||
dotnet_diagnostic.CA1050.severity = warning
|
||||
@ -1,18 +0,0 @@
|
||||
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) { }
|
||||
}
|
||||
10
Nerd_STF/Exceptions/ClampOrderMismatchException.cs
Normal file
10
Nerd_STF/Exceptions/ClampOrderMismatchException.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Nerd_STF.Exceptions
|
||||
{
|
||||
public class ClampOrderMismatchException : Exception
|
||||
{
|
||||
public ClampOrderMismatchException() : base("Minimum is greater than maximum.") { }
|
||||
public ClampOrderMismatchException(string minName, string maxName) : base($"'{minName}' is greater than '{maxName}'") { }
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
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) { }
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
namespace Nerd_STF.Exceptions;
|
||||
|
||||
[Serializable]
|
||||
public class DisconnectedLinesException : Nerd_STFException
|
||||
{
|
||||
public string? ParamName;
|
||||
public Line[]? Lines;
|
||||
|
||||
public DisconnectedLinesException() : base("Lines are not connected.") { }
|
||||
public DisconnectedLinesException(Exception inner) : base("Lines are not connected.", inner) { }
|
||||
public DisconnectedLinesException(string paramName) : this() => ParamName = paramName;
|
||||
public DisconnectedLinesException(string paramName, Exception inner) : this(inner) => ParamName = paramName;
|
||||
public DisconnectedLinesException(params Line[] lines) : this() => Lines = lines;
|
||||
public DisconnectedLinesException(Line[] lines, Exception inner) : this(inner) => Lines = lines;
|
||||
public DisconnectedLinesException(string paramName, Line[] lines) : this()
|
||||
{
|
||||
ParamName = paramName;
|
||||
Lines = lines;
|
||||
}
|
||||
public DisconnectedLinesException(string paramName, Line[] lines, Exception inner) : this(inner)
|
||||
{
|
||||
ParamName = paramName;
|
||||
Lines = lines;
|
||||
}
|
||||
|
||||
protected DisconnectedLinesException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
10
Nerd_STF/Exceptions/InvalidOrderException.cs
Normal file
10
Nerd_STF/Exceptions/InvalidOrderException.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Nerd_STF.Exceptions
|
||||
{
|
||||
public class InvalidOrderException : Exception
|
||||
{
|
||||
public InvalidOrderException() : base("Invalid polynomial order.") { }
|
||||
public InvalidOrderException(string message) : base(message) { }
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
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) { }
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
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) { }
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
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) { }
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
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) { }
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
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) { }
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
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];
|
||||
}
|
||||
@ -1,305 +0,0 @@
|
||||
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)
|
||||
};
|
||||
}
|
||||
@ -1,101 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Nerd_STF.Extensions;
|
||||
|
||||
public static class ToFillExtension
|
||||
{
|
||||
public static Fill<T> ToFill<T>(this IEnumerable<T> group) => i => group.ElementAt(i);
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Nerd_STF;
|
||||
|
||||
public enum FileType
|
||||
{
|
||||
None = 0,
|
||||
BMP,
|
||||
HEIC,
|
||||
JPEG,
|
||||
MTL,
|
||||
PNG,
|
||||
TIFF,
|
||||
WEBP,
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
namespace Nerd_STF;
|
||||
|
||||
namespace Nerd_STF
|
||||
{
|
||||
public delegate T Fill<T>(int index);
|
||||
public delegate T Fill2d<T>(int x, int y);
|
||||
}
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
namespace Nerd_STF;
|
||||
|
||||
public delegate T Fill2d<T>(int indexX, int indexY);
|
||||
13
Nerd_STF/GlobalSuppressions.cs
Normal file
13
Nerd_STF/GlobalSuppressions.cs
Normal file
@ -0,0 +1,13 @@
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Style", "IDE0066:Use 'switch' expression", Justification = "Only available for C#8+.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "Only available for C#9+")]
|
||||
[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Only available for C#9+")]
|
||||
[assembly: SuppressMessage("Style", "IDE0251:Make member 'readonly'", Justification = "Only available for C#8+. Also, what does applying 'readonly' to a method even do?")]
|
||||
[assembly: SuppressMessage("Style", "IDE0057:Use range operator", Justification = "Not supported in .NET Standard 1.1 and 1.3.")]
|
||||
[assembly: SuppressMessage("Performance", "CA1846:Prefer 'AsSpan' over 'Substring'", Justification = "Not supported in .NET Standard 1.1 and 1.3.")]
|
||||
@ -1,29 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
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> { }
|
||||
@ -1,4 +0,0 @@
|
||||
namespace Nerd_STF.Graphics.Abstract;
|
||||
|
||||
public interface IColorFloat : IColor, IGroup<float> { }
|
||||
public interface IColorFloat<T> : IColor<T>, IColorFloat where T : struct, IColorFloat<T> { }
|
||||
@ -1,17 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
@ -1,253 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,276 +0,0 @@
|
||||
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);
|
||||
}
|
||||
365
Nerd_STF/Graphics/ColorCMYK.cs
Normal file
365
Nerd_STF/Graphics/ColorCMYK.cs
Normal file
@ -0,0 +1,365 @@
|
||||
using Nerd_STF.Mathematics;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
public struct ColorCMYK : IColor<ColorCMYK>,
|
||||
IEnumerable<double>
|
||||
#if CS11_OR_GREATER
|
||||
,IFromTuple<ColorCMYK, (double, double, double, double)>,
|
||||
IFromTuple<ColorCMYK, (double, double, double, double, double)>,
|
||||
ISplittable<ColorCMYK, (double[] Cs, double[] Ms, double[] Ys, double[] Ks, double[] As)>
|
||||
#endif
|
||||
{
|
||||
public static int ChannelCount => 5;
|
||||
|
||||
public static ColorCMYK Black => new ColorCMYK(0 , 0 , 0 , 1 , 1);
|
||||
public static ColorCMYK Blue => new ColorCMYK(1 , 1 , 0 , 0 , 1);
|
||||
public static ColorCMYK Clear => new ColorCMYK(0 , 0 , 0 , 0 , 0);
|
||||
public static ColorCMYK Cyan => new ColorCMYK(1 , 0 , 0 , 0 , 1);
|
||||
public static ColorCMYK Gray => new ColorCMYK(0 , 0 , 0 , 0.5, 1);
|
||||
public static ColorCMYK Green => new ColorCMYK(1 , 0 , 1 , 0 , 1);
|
||||
public static ColorCMYK Magenta => new ColorCMYK(0 , 1 , 0 , 0 , 1);
|
||||
public static ColorCMYK Orange => new ColorCMYK(0 , 0.5, 1 , 0 , 1);
|
||||
public static ColorCMYK Purple => new ColorCMYK(0.5, 1 , 0 , 0 , 1);
|
||||
public static ColorCMYK Red => new ColorCMYK(0 , 1 , 1 , 0 , 1);
|
||||
public static ColorCMYK White => new ColorCMYK(0 , 0 , 0 , 0 , 1);
|
||||
public static ColorCMYK Yellow => new ColorCMYK(0 , 0 , 1 , 0 , 1);
|
||||
|
||||
public double c, m, y, k, a;
|
||||
|
||||
public ColorCMYK(double cyan, double magenta, double yellow, double black)
|
||||
{
|
||||
c = cyan;
|
||||
m = magenta;
|
||||
y = yellow;
|
||||
k = black;
|
||||
a = 1;
|
||||
}
|
||||
public ColorCMYK(double cyan, double magenta, double yellow, double black, double alpha)
|
||||
{
|
||||
c = cyan;
|
||||
m = magenta;
|
||||
y = yellow;
|
||||
k = black;
|
||||
a = alpha;
|
||||
}
|
||||
public ColorCMYK(IEnumerable<double> nums)
|
||||
{
|
||||
c = 0;
|
||||
m = 0;
|
||||
y = 0;
|
||||
k = 0;
|
||||
a = 1;
|
||||
|
||||
int index = 0;
|
||||
foreach (double item in nums)
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index == 5) break;
|
||||
}
|
||||
}
|
||||
public ColorCMYK(Fill<double> fill)
|
||||
{
|
||||
c = fill(0);
|
||||
m = fill(1);
|
||||
y = fill(2);
|
||||
k = fill(3);
|
||||
a = fill(4);
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return c;
|
||||
case 1: return m;
|
||||
case 2: return y;
|
||||
case 3: return k;
|
||||
case 4: return a;
|
||||
default: throw new ArgumentOutOfRangeException(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 ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
public ListTuple<double> this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
double[] items = new double[key.Length];
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
{
|
||||
char c = key[i];
|
||||
switch (c)
|
||||
{
|
||||
case 'c': items[i] = c; break;
|
||||
case 'm': items[i] = m; break;
|
||||
case 'y': items[i] = y; break;
|
||||
case 'k': items[i] = k; break;
|
||||
case 'a': items[i] = a; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
return new ListTuple<double>(items);
|
||||
}
|
||||
set
|
||||
{
|
||||
IEnumerator<double> stepper = value.GetEnumerator();
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
{
|
||||
char c = key[i];
|
||||
stepper.MoveNext();
|
||||
switch (c)
|
||||
{
|
||||
case 'c': this.c = stepper.Current; break;
|
||||
case 'm': m = stepper.Current; break;
|
||||
case 'y': y = stepper.Current; break;
|
||||
case 'k': k = stepper.Current; break;
|
||||
case 'a': a = stepper.Current; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ColorCMYK Average(double gamma, IEnumerable<ColorCMYK> colors)
|
||||
{
|
||||
double avgC = 0, avgM = 0, avgY = 0, avgK = 0, avgA = 0;
|
||||
int count = 0;
|
||||
foreach (ColorCMYK color in colors)
|
||||
{
|
||||
double correctC = MathE.Pow(color.c, gamma),
|
||||
correctM = MathE.Pow(color.m, gamma),
|
||||
correctY = MathE.Pow(color.y, gamma),
|
||||
correctK = MathE.Pow(color.k, gamma);
|
||||
// Gamma doesn't apply to the alpha channel.
|
||||
|
||||
avgC += correctC;
|
||||
avgM += correctM;
|
||||
avgY += correctY;
|
||||
avgK += correctK;
|
||||
avgA += color.a;
|
||||
count++;
|
||||
}
|
||||
avgC /= count;
|
||||
avgM /= count;
|
||||
avgY /= count;
|
||||
avgK /= count;
|
||||
avgA /= count;
|
||||
double invGamma = 1 / gamma;
|
||||
return new ColorCMYK(MathE.Pow(avgC, invGamma),
|
||||
MathE.Pow(avgM, invGamma),
|
||||
MathE.Pow(avgY, invGamma),
|
||||
MathE.Pow(avgK, invGamma),
|
||||
avgA);
|
||||
}
|
||||
#if CS11_OR_GREATER
|
||||
static ColorCMYK IColor<ColorCMYK>.Average(IEnumerable<ColorCMYK> colors) => Average(1, colors);
|
||||
#endif
|
||||
public static ColorCMYK Clamp(ColorCMYK color, ColorCMYK min, ColorCMYK max) =>
|
||||
new ColorCMYK(MathE.Clamp(color.c, min.c, max.c),
|
||||
MathE.Clamp(color.m, min.m, max.m),
|
||||
MathE.Clamp(color.y, min.y, max.y),
|
||||
MathE.Clamp(color.k, min.k, max.k),
|
||||
MathE.Clamp(color.a, min.a, max.a));
|
||||
public static ColorCMYK Lerp(double gamma, ColorCMYK a, ColorCMYK b, double t, bool clamp = true)
|
||||
{
|
||||
double aCorrectedC = MathE.Pow(a.c, gamma), bCorrectedC = MathE.Pow(b.c, gamma),
|
||||
aCorrectedM = MathE.Pow(a.m, gamma), bCorrectedM = MathE.Pow(b.m, gamma),
|
||||
aCorrectedY = MathE.Pow(a.y, gamma), bCorrectedY = MathE.Pow(b.y, gamma),
|
||||
aCorrectedK = MathE.Pow(a.k, gamma), bCorrectedK = MathE.Pow(b.k, gamma);
|
||||
|
||||
double newC = MathE.Lerp(aCorrectedC, bCorrectedC, t, clamp),
|
||||
newM = MathE.Lerp(aCorrectedM, bCorrectedM, t, clamp),
|
||||
newY = MathE.Lerp(aCorrectedY, bCorrectedY, t, clamp),
|
||||
newK = MathE.Lerp(aCorrectedK, bCorrectedK, t, clamp),
|
||||
newA = MathE.Lerp(a.a, b.a, t, clamp);
|
||||
|
||||
double invGamma = 1 / gamma;
|
||||
return new ColorCMYK(MathE.Pow(newC, invGamma),
|
||||
MathE.Pow(newM, invGamma),
|
||||
MathE.Pow(newY, invGamma),
|
||||
MathE.Pow(newK, invGamma),
|
||||
newA);
|
||||
}
|
||||
#if CS11_OR_GREATER
|
||||
static ColorCMYK IInterpolable<ColorCMYK>.Lerp(ColorCMYK a, ColorCMYK b, double t, bool clamp) => Lerp(1, a, b, t, clamp);
|
||||
#endif
|
||||
public static ColorCMYK Product(IEnumerable<ColorCMYK> colors)
|
||||
{
|
||||
bool any = false;
|
||||
ColorCMYK result = new ColorCMYK(1, 1, 1, 1, 1);
|
||||
foreach (ColorCMYK color in colors)
|
||||
{
|
||||
any = true;
|
||||
result *= color;
|
||||
}
|
||||
return any ? result : Black;
|
||||
}
|
||||
public static ColorCMYK Sum(IEnumerable<ColorCMYK> colors)
|
||||
{
|
||||
bool any = false;
|
||||
ColorCMYK result = new ColorCMYK(0, 0, 0, 0);
|
||||
foreach (ColorCMYK color in colors)
|
||||
{
|
||||
any = true;
|
||||
result += color;
|
||||
}
|
||||
return any ? result : Black;
|
||||
}
|
||||
public static (double[] Cs, double[] Ms, double[] Ys, double[] Ks, double[] As) SplitArray(IEnumerable<ColorCMYK> colors)
|
||||
{
|
||||
int count = colors.Count();
|
||||
double[] Cs = new double[count], Ms = new double[count], Ys = new double[count], Ks = new double[count], As = new double[count];
|
||||
int index = 0;
|
||||
foreach (ColorCMYK c in colors)
|
||||
{
|
||||
Cs[index] = c.c;
|
||||
Ms[index] = c.m;
|
||||
Ys[index] = c.y;
|
||||
Ks[index] = c.k;
|
||||
As[index] = c.a;
|
||||
index++;
|
||||
}
|
||||
return (Cs, Ms, Ys, Ks, As);
|
||||
}
|
||||
|
||||
public Dictionary<ColorChannel, double> GetChannels() => new Dictionary<ColorChannel, double>()
|
||||
{
|
||||
{ ColorChannel.Cyan, c },
|
||||
{ ColorChannel.Magenta, m },
|
||||
{ ColorChannel.Yellow, y },
|
||||
{ ColorChannel.Key, k },
|
||||
{ ColorChannel.Alpha, a }
|
||||
};
|
||||
|
||||
public TColor AsColor<TColor>() where TColor : struct, IColor<TColor>
|
||||
{
|
||||
Type type = typeof(TColor);
|
||||
if (type == typeof(ColorRGB)) return (TColor)(object)AsRgb();
|
||||
else if (type == typeof(ColorHSV)) return (TColor)(object)AsHsv();
|
||||
else if (type == typeof(ColorCMYK)) return (TColor)(object)this;
|
||||
else throw new InvalidCastException();
|
||||
}
|
||||
public ColorRGB AsRgb()
|
||||
{
|
||||
// Thanks https://www.rapidtables.com/convert/color/cmyk-to-rgb.html
|
||||
double diffK = 1 - k;
|
||||
return new ColorRGB((1 - c) * diffK,
|
||||
(1 - m) * diffK,
|
||||
(1 - y) * diffK);
|
||||
}
|
||||
public ColorHSV AsHsv()
|
||||
{
|
||||
// Inlined version of AsRgb().AsHsv()
|
||||
double diffK = 1 - k;
|
||||
double r = (1 - c) * diffK, g = (1 - m) * diffK, b = (1 - y) * diffK;
|
||||
double[] group = new double[] { r, g, b };
|
||||
double cMax = MathE.Max(group), cMin = MathE.Min(group), delta = cMax - cMin;
|
||||
Angle h;
|
||||
|
||||
if (delta == 0) h = Angle.Zero;
|
||||
else if (cMax == r) h = Angle.FromDegrees(60 * MathE.ModAbs((g - b) / delta, 6));
|
||||
else if (cMax == g) h = Angle.FromDegrees(60 * ((b - r) / delta + 2));
|
||||
else if (cMax == b) h = Angle.FromDegrees(60 * ((r - g) / delta + 4));
|
||||
else h = Angle.Zero;
|
||||
|
||||
double s = cMax == 0 ? 0 : delta / cMax;
|
||||
return new ColorHSV(h, s, cMax);
|
||||
}
|
||||
public ColorCMYK AsCmyk() => this;
|
||||
|
||||
public string HexCode() => AsRgb().HexCode();
|
||||
|
||||
public IEnumerator<double> GetEnumerator()
|
||||
{
|
||||
yield return c;
|
||||
yield return m;
|
||||
yield return y;
|
||||
yield return k;
|
||||
yield return a;
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Deconstruct(out double c, out double m, out double y, out double k)
|
||||
{
|
||||
c = this.c;
|
||||
m = this.m;
|
||||
y = this.y;
|
||||
k = this.k;
|
||||
}
|
||||
public void Deconstruct(out double c, out double m, out double y, out double k, out double a)
|
||||
{
|
||||
c = this.c;
|
||||
m = this.m;
|
||||
y = this.y;
|
||||
k = this.k;
|
||||
a = this.a;
|
||||
}
|
||||
|
||||
public bool Equals(ColorCMYK other)
|
||||
{
|
||||
if (a <= 0 && other.a <= 0) return true;
|
||||
else if (k >= 1 && other.k >= 1) return true;
|
||||
else return c == other.c && m == other.m && y == other.y && k == other.k && a == other.a;
|
||||
}
|
||||
public bool Equals(IColor other) => Equals(other.AsCmyk());
|
||||
#if CS8_OR_GREATER
|
||||
public override bool Equals(object? other)
|
||||
#else
|
||||
public override bool Equals(object other)
|
||||
#endif
|
||||
{
|
||||
if (other is IColor color) return Equals(color.AsRgb());
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
public override string ToString() => $"{{ c={c:0.00}, m={m:0.00}, y={y:0.00}, k={k:0.00}, a={a:0.00} }}";
|
||||
|
||||
public double[] ToArray() => new double[] { c, m, y, k, a };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
ColorCMYK copy = this;
|
||||
return i => copy[i];
|
||||
}
|
||||
public List<double> ToList() => new List<double> { c, m, y, k, a };
|
||||
|
||||
public static ColorCMYK operator +(ColorCMYK a, ColorCMYK b) => new ColorCMYK(a.c + b.c, a.m + b.m, a.y + b.y, a.k + b.k, 1 - (1 - a.a) * (1 - b.a));
|
||||
public static ColorCMYK operator *(ColorCMYK a, ColorCMYK b) => new ColorCMYK(a.c * b.c, a.m * b.m, a.y * b.y, a.k * b.k, a.a * b.a);
|
||||
public static ColorCMYK operator *(ColorCMYK a, double b) => new ColorCMYK(a.c, a.m, a.y, b == 0 ? 1 : MathE.Clamp(a.k / b, 0, 1), a.a);
|
||||
public static bool operator ==(ColorCMYK a, IColor b) => a.Equals(b.AsCmyk());
|
||||
public static bool operator !=(ColorCMYK a, IColor b) => !a.Equals(b.AsCmyk());
|
||||
public static bool operator ==(ColorCMYK a, ColorCMYK b) => a.Equals(b);
|
||||
public static bool operator !=(ColorCMYK a, ColorCMYK b) => !a.Equals(b);
|
||||
|
||||
public static implicit operator ColorCMYK(ListTuple<double> tuple)
|
||||
{
|
||||
if (tuple.Length == 4) return new ColorCMYK(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
else if (tuple.Length == 5) return new ColorCMYK(tuple[0], tuple[1], tuple[2], tuple[3], tuple[4]);
|
||||
else throw new InvalidCastException();
|
||||
}
|
||||
public static implicit operator ColorCMYK((double, double, double, double) tuple) => new ColorCMYK(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||
public static implicit operator ColorCMYK((double, double, double, double, double) tuple) => new ColorCMYK(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5);
|
||||
|
||||
public static implicit operator ListTuple<double>(ColorCMYK color) => new ListTuple<double>(color.c, color.m, color.y, color.k, color.a);
|
||||
public static implicit operator ValueTuple<double, double, double, double>(ColorCMYK color) => (color.c, color.m, color.y, color.k);
|
||||
public static implicit operator ValueTuple<double, double, double, double, double>(ColorCMYK color) => (color.c, color.m, color.y, color.k, color.a);
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,18 @@
|
||||
namespace Nerd_STF.Graphics;
|
||||
|
||||
public enum ColorChannel : byte
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
public enum ColorChannel
|
||||
{
|
||||
Alpha,
|
||||
Black,
|
||||
Blue,
|
||||
Cyan,
|
||||
Green,
|
||||
Hue,
|
||||
Luminance,
|
||||
Magenta,
|
||||
Matte,
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
Alpha,
|
||||
Hue,
|
||||
Saturation,
|
||||
Yellow,
|
||||
Value,
|
||||
ZDepth
|
||||
Cyan,
|
||||
Magenta,
|
||||
Yellow,
|
||||
Key,
|
||||
Index
|
||||
}
|
||||
}
|
||||
|
||||
350
Nerd_STF/Graphics/ColorHSV.cs
Normal file
350
Nerd_STF/Graphics/ColorHSV.cs
Normal file
@ -0,0 +1,350 @@
|
||||
using Nerd_STF.Helpers;
|
||||
using Nerd_STF.Mathematics;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
public struct ColorHSV : IColor<ColorHSV>,
|
||||
IEnumerable<double>
|
||||
#if CS11_OR_GREATER
|
||||
,IFromTuple<ColorHSV, (Angle, double, double)>,
|
||||
IFromTuple<ColorHSV, (double, double, double)>,
|
||||
IFromTuple<ColorHSV, (Angle, double, double, double)>,
|
||||
IFromTuple<ColorHSV, (double, double, double, double)>,
|
||||
ISplittable<ColorHSV, (Angle[] Hs, double[] Ss, double[] Vs, double[] As)>
|
||||
#endif
|
||||
{
|
||||
public static int ChannelCount => 4;
|
||||
|
||||
public static ColorHSV Black => new ColorHSV(Angle.FromDegrees( 0), 0, 0 , 1);
|
||||
public static ColorHSV Blue => new ColorHSV(Angle.FromDegrees(240), 1, 1 , 1);
|
||||
public static ColorHSV Clear => new ColorHSV(Angle.FromDegrees( 0), 0, 0 , 0);
|
||||
public static ColorHSV Cyan => new ColorHSV(Angle.FromDegrees(180), 1, 1 , 1);
|
||||
public static ColorHSV Gray => new ColorHSV(Angle.FromDegrees( 0), 0, 0.5, 1);
|
||||
public static ColorHSV Green => new ColorHSV(Angle.FromDegrees(120), 1, 1 , 1);
|
||||
public static ColorHSV Magenta => new ColorHSV(Angle.FromDegrees(300), 1, 1 , 1);
|
||||
public static ColorHSV Orange => new ColorHSV(Angle.FromDegrees( 30), 1, 1 , 1);
|
||||
public static ColorHSV Purple => new ColorHSV(Angle.FromDegrees(270), 1, 1 , 1);
|
||||
public static ColorHSV Red => new ColorHSV(Angle.FromDegrees( 0), 1, 1 , 1);
|
||||
public static ColorHSV White => new ColorHSV(Angle.FromDegrees( 0), 0, 1 , 1);
|
||||
public static ColorHSV Yellow => new ColorHSV(Angle.FromDegrees( 60), 0, 1 , 1);
|
||||
|
||||
public Angle h;
|
||||
public double s, v, a;
|
||||
|
||||
public ColorHSV(Angle hue)
|
||||
{
|
||||
h = hue.Normalized;
|
||||
s = 1;
|
||||
v = 1;
|
||||
a = 1;
|
||||
}
|
||||
public ColorHSV(double hue)
|
||||
{
|
||||
h = Angle.FromRevolutions(hue);
|
||||
s = 1;
|
||||
v = 1;
|
||||
a = 1;
|
||||
}
|
||||
public ColorHSV(Angle hue, double saturation, double value)
|
||||
{
|
||||
h = hue.Normalized;
|
||||
s = saturation;
|
||||
v = value;
|
||||
a = 1;
|
||||
}
|
||||
public ColorHSV(double hue, double saturation, double value)
|
||||
{
|
||||
h = Angle.FromRevolutions(hue);
|
||||
s = saturation;
|
||||
v = value;
|
||||
a = 1;
|
||||
}
|
||||
public ColorHSV(Angle hue, double saturation, double value, double alpha)
|
||||
{
|
||||
h = hue.Normalized;
|
||||
s = saturation;
|
||||
v = value;
|
||||
a = alpha;
|
||||
}
|
||||
public ColorHSV(double hue, double saturation, double value, double alpha)
|
||||
{
|
||||
h = Angle.FromRevolutions(hue);
|
||||
s = saturation;
|
||||
v = value;
|
||||
a = alpha;
|
||||
}
|
||||
public ColorHSV(IEnumerable<double> nums)
|
||||
{
|
||||
h = Angle.Zero;
|
||||
s = 0;
|
||||
v = 0;
|
||||
a = 1;
|
||||
|
||||
int index = 0;
|
||||
foreach (double item in nums)
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index == 4) break;
|
||||
}
|
||||
}
|
||||
public ColorHSV(Fill<double> fill)
|
||||
{
|
||||
h = Angle.FromRevolutions(fill(0) % 1);
|
||||
s = fill(1);
|
||||
v = fill(2);
|
||||
a = fill(3);
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return h.Revolutions;
|
||||
case 1: return s;
|
||||
case 2: return v;
|
||||
case 3: return a;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: h = Angle.FromRevolutions(value); break;
|
||||
case 1: s = value; break;
|
||||
case 2: v = value; break;
|
||||
case 3: a = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
public ListTuple<double> this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
double[] items = new double[key.Length];
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
{
|
||||
char c = key[i];
|
||||
switch (c)
|
||||
{
|
||||
case 'h': items[i] = h.Revolutions; break;
|
||||
case 'H': items[i] = h.Degrees; break;
|
||||
case 's': items[i] = s; break;
|
||||
case 'v': items[i] = v; break;
|
||||
case 'a': items[i] = a; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
return new ListTuple<double>(items);
|
||||
}
|
||||
set
|
||||
{
|
||||
IEnumerator<double> stepper = value.GetEnumerator();
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
{
|
||||
char c = key[i];
|
||||
stepper.MoveNext();
|
||||
switch (c)
|
||||
{
|
||||
case 'h': h = Angle.FromRevolutions(stepper.Current); break;
|
||||
case 'H': h = Angle.FromDegrees(stepper.Current); break;
|
||||
case 's': s = stepper.Current; break;
|
||||
case 'v': v = stepper.Current; break;
|
||||
case 'a': a = stepper.Current; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ColorHSV Average(IEnumerable<ColorHSV> colors)
|
||||
{
|
||||
Angle avgH = Angle.Zero;
|
||||
double avgS = 0, avgV = 0, avgA = 0;
|
||||
int count = 0;
|
||||
foreach (ColorHSV color in colors)
|
||||
{
|
||||
avgH += color.h;
|
||||
avgS += color.s;
|
||||
avgV += color.v;
|
||||
avgA += color.a;
|
||||
}
|
||||
return new ColorHSV(avgH / count, avgS / count, avgV / count, avgA / count);
|
||||
}
|
||||
public static ColorHSV Clamp(ColorHSV color, ColorHSV min, ColorHSV max) =>
|
||||
new ColorHSV(Angle.Clamp(color.h, min.h, max.h),
|
||||
MathE.Clamp(color.s, min.s, max.s),
|
||||
MathE.Clamp(color.v, min.v, max.v),
|
||||
MathE.Clamp(color.a, min.a, max.a));
|
||||
public static ColorHSV Lerp(ColorHSV a, ColorHSV b, double t, bool clamp = true) =>
|
||||
new ColorHSV(Angle.Lerp(a.h, b.h, t, clamp),
|
||||
MathE.Lerp(a.s, b.s, t, clamp),
|
||||
MathE.Lerp(a.v, b.v, t, clamp),
|
||||
MathE.Lerp(a.a, b.a, t, clamp));
|
||||
public static ColorHSV Product(IEnumerable<ColorHSV> colors)
|
||||
{
|
||||
bool any = false;
|
||||
ColorHSV result = new ColorHSV(Angle.Full, 1, 1, 1);
|
||||
foreach (ColorHSV color in colors)
|
||||
{
|
||||
any = true;
|
||||
result *= color;
|
||||
}
|
||||
return any ? result : Black;
|
||||
}
|
||||
public static ColorHSV Sum(IEnumerable<ColorHSV> colors)
|
||||
{
|
||||
bool any = false;
|
||||
ColorHSV result = new ColorHSV(Angle.Zero, 0, 0, 0);
|
||||
foreach (ColorHSV color in colors)
|
||||
{
|
||||
any = true;
|
||||
result += color;
|
||||
}
|
||||
return any ? result : Black;
|
||||
}
|
||||
public static (Angle[] Hs, double[] Ss, double[] Vs, double[] As) SplitArray(IEnumerable<ColorHSV> colors)
|
||||
{
|
||||
int count = colors.Count();
|
||||
Angle[] Hs = new Angle[count];
|
||||
double[] Ss = new double[count], Vs = new double[count], As = new double[count];
|
||||
int index = 0;
|
||||
foreach (ColorHSV c in colors)
|
||||
{
|
||||
Hs[index] = c.h;
|
||||
Ss[index] = c.s;
|
||||
Vs[index] = c.v;
|
||||
As[index] = c.a;
|
||||
index++;
|
||||
}
|
||||
return (Hs, Ss, Vs, As);
|
||||
}
|
||||
|
||||
public Dictionary<ColorChannel, double> GetChannels() => new Dictionary<ColorChannel, double>()
|
||||
{
|
||||
{ ColorChannel.Hue, h.Revolutions },
|
||||
{ ColorChannel.Saturation, s },
|
||||
{ ColorChannel.Value, v },
|
||||
{ ColorChannel.Alpha, a }
|
||||
};
|
||||
|
||||
public TColor AsColor<TColor>() where TColor : struct, IColor<TColor>
|
||||
{
|
||||
Type type = typeof(TColor);
|
||||
if (type == typeof(ColorRGB)) return (TColor)(object)AsRgb();
|
||||
else if (type == typeof(ColorHSV)) return (TColor)(object)this;
|
||||
else if (type == typeof(ColorCMYK)) return (TColor)(object)AsCmyk();
|
||||
else throw new InvalidCastException();
|
||||
}
|
||||
public ColorRGB AsRgb()
|
||||
{
|
||||
// Thanks https://www.rapidtables.com/convert/color/hsv-to-rgb.html
|
||||
double H = h.Normalized.Degrees,
|
||||
c = v * s,
|
||||
x = c * (1 - MathE.Abs(MathE.ModAbs(H / 60, 2) - 1)),
|
||||
m = v - c;
|
||||
|
||||
ColorRGB color;
|
||||
if (H < 60) color = new ColorRGB(c, x, 0);
|
||||
else if (H < 120) color = new ColorRGB(x, c, 0);
|
||||
else if (H < 180) color = new ColorRGB(0, c, x);
|
||||
else if (H < 240) color = new ColorRGB(0, x, c);
|
||||
else if (H < 300) color = new ColorRGB(x, 0, c);
|
||||
else if (H < 360) color = new ColorRGB(c, 0, x);
|
||||
else throw new ArgumentOutOfRangeException();
|
||||
|
||||
color.r += m;
|
||||
color.g += m;
|
||||
color.b += m;
|
||||
return color;
|
||||
}
|
||||
public ColorHSV AsHsv() => this;
|
||||
public ColorCMYK AsCmyk() => AsRgb().AsCmyk();
|
||||
|
||||
public string HexCode() => AsRgb().HexCode();
|
||||
|
||||
public IEnumerator<double> GetEnumerator()
|
||||
{
|
||||
yield return h.Revolutions;
|
||||
yield return s;
|
||||
yield return v;
|
||||
yield return a;
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Deconstruct(out Angle h, out double s, out double v)
|
||||
{
|
||||
h = this.h;
|
||||
s = this.s;
|
||||
v = this.v;
|
||||
}
|
||||
public void Deconstruct(out Angle h, out double s, out double v, out double a)
|
||||
{
|
||||
h = this.h;
|
||||
s = this.s;
|
||||
v = this.v;
|
||||
a = this.a;
|
||||
}
|
||||
|
||||
public bool Equals(ColorHSV other)
|
||||
{
|
||||
if (a <= 0 && other.a <= 0) return true;
|
||||
else if (v <= 0 && other.v <= 0) return true;
|
||||
else if (s <= 0 && other.s <= 0) return true;
|
||||
else return h == other.h && s == other.s && v == other.v && a == other.a;
|
||||
}
|
||||
public bool Equals(IColor other) => Equals(other.AsHsv());
|
||||
#if CS8_OR_GREATER
|
||||
public override bool Equals(object? other)
|
||||
#else
|
||||
public override bool Equals(object other)
|
||||
#endif
|
||||
{
|
||||
if (other is IColor color) return Equals(color.AsHsv());
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
public override string ToString() => $"{{ h={h.Degrees:0}°, s={s:0.00}, v={v:0.00}, a={a:0.00} }}";
|
||||
|
||||
public double[] ToArray() => new double[] { h.Revolutions, s, v, a };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
ColorHSV copy = this;
|
||||
return i => copy[i];
|
||||
}
|
||||
public List<double> ToList() => new List<double> { h.Revolutions, s, v, a };
|
||||
|
||||
public static ColorHSV operator +(ColorHSV a, ColorHSV b) => new ColorHSV(a.h + b.h, a.s + b.s, a.v + b.v, 1 - (1 - a.a) * (1 - b.a));
|
||||
public static ColorHSV operator *(ColorHSV a, ColorHSV b) => new ColorHSV(Angle.FromRevolutions(a.h.Revolutions * b.h.Revolutions), a.s * b.s, a.v * b.v, a.a * b.a);
|
||||
public static ColorHSV operator *(ColorHSV a, double b) => new ColorHSV(a.h, a.s * b, a.v, a.a);
|
||||
public static bool operator ==(ColorHSV a, IColor b) => a.Equals(b.AsHsv());
|
||||
public static bool operator !=(ColorHSV a, IColor b) => !a.Equals(b.AsHsv());
|
||||
public static bool operator ==(ColorHSV a, ColorHSV b) => a.Equals(b);
|
||||
public static bool operator !=(ColorHSV a, ColorHSV b) => !a.Equals(b);
|
||||
|
||||
public static implicit operator ColorHSV(ListTuple<double> tuple)
|
||||
{
|
||||
if (tuple.Length == 3) return new ColorHSV(tuple[0], tuple[1], tuple[2]);
|
||||
else if (tuple.Length == 4) return new ColorHSV(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
else throw new InvalidCastException();
|
||||
}
|
||||
public static implicit operator ColorHSV((Angle, double, double) tuple) => new ColorHSV(tuple.Item1, tuple.Item2, tuple.Item3);
|
||||
public static implicit operator ColorHSV((double, double, double) tuple) => new ColorHSV(tuple.Item1, tuple.Item2, tuple.Item3);
|
||||
public static implicit operator ColorHSV((Angle, double, double, double) tuple) => new ColorHSV(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||
public static implicit operator ColorHSV((double, double, double, double) tuple) => new ColorHSV(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||
|
||||
public static implicit operator ListTuple<double>(ColorHSV color) => new ListTuple<double>(color.h.Revolutions, color.s, color.v, color.a);
|
||||
public static implicit operator ValueTuple<Angle, double, double>(ColorHSV color) => (color.h, color.s, color.v);
|
||||
public static implicit operator ValueTuple<double, double, double>(ColorHSV color) => (color.h.Revolutions, color.s, color.v);
|
||||
public static implicit operator ValueTuple<Angle, double, double, double>(ColorHSV color) => (color.h, color.s, color.v, color.a);
|
||||
public static implicit operator ValueTuple<double, double, double, double>(ColorHSV color) => (color.h.Revolutions, color.s, color.v, color.a);
|
||||
}
|
||||
}
|
||||
158
Nerd_STF/Graphics/ColorPalette.cs
Normal file
158
Nerd_STF/Graphics/ColorPalette.cs
Normal file
@ -0,0 +1,158 @@
|
||||
using Nerd_STF.Graphics.Formats;
|
||||
using Nerd_STF.Helpers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
// TODO: Should this be a ref struct?
|
||||
public class ColorPalette<TColor> : IEnumerable<TColor>, IEquatable<ColorPalette<TColor>>
|
||||
where TColor : struct, IColor<TColor>
|
||||
{
|
||||
public int BitDepth { get; private set; }
|
||||
public int Length => colors.Length;
|
||||
|
||||
private TColor[] colors;
|
||||
private IndexedColor<TColor>[] indexedColors;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
private ColorPalette() { }
|
||||
#pragma warning restore CS8618
|
||||
public ColorPalette(int colors)
|
||||
{
|
||||
int size = GetSizeFor(colors, out int bits);
|
||||
this.colors = new TColor[size];
|
||||
indexedColors = new IndexedColor<TColor>[size];
|
||||
for (int i = 0; i < size; i++) indexedColors[i] = new IndexedColor<TColor>(this, i);
|
||||
BitDepth = bits;
|
||||
}
|
||||
public ColorPalette(ReadOnlySpan<TColor> colors)
|
||||
{
|
||||
int size = GetSizeFor(colors.Length, out int bits);
|
||||
this.colors = new TColor[size];
|
||||
colors.CopyTo(this.colors);
|
||||
indexedColors = new IndexedColor<TColor>[size];
|
||||
for (int i = 0; i < size; i++) indexedColors[i] = new IndexedColor<TColor>(this, i);
|
||||
BitDepth = bits;
|
||||
}
|
||||
|
||||
public static ColorPalette<TColor> FromBitDepth(int bits)
|
||||
{
|
||||
int size = 1 << bits;
|
||||
ColorPalette<TColor> palette = new ColorPalette<TColor>()
|
||||
{
|
||||
BitDepth = bits,
|
||||
colors = new TColor[size],
|
||||
indexedColors = new IndexedColor<TColor>[size]
|
||||
};
|
||||
for (int i = 0; i < size; i++) palette.indexedColors[i] = new IndexedColor<TColor>(palette, i);
|
||||
return palette;
|
||||
}
|
||||
|
||||
public IndexedColor<TColor> this[int index] => indexedColors[index];
|
||||
public ref TColor Color(int index) => ref colors[index];
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
colors[i] = default;
|
||||
}
|
||||
}
|
||||
public bool Contains(TColor color)
|
||||
{
|
||||
for (int i = 0; i < Length; i++)
|
||||
{
|
||||
if (colors[i].Equals(color)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public bool Contains(Predicate<TColor> predicate)
|
||||
{
|
||||
for (int i = 0; i < Length; i++)
|
||||
{
|
||||
if (predicate(colors[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public void CopyTo(Span<TColor> destination) => CopyTo(0, destination, 0, Length);
|
||||
public void CopyTo(int sourceIndex, Span<TColor> destination, int destIndex, int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
destination[destIndex + i] = colors[sourceIndex + i];
|
||||
}
|
||||
}
|
||||
public void Expand(int newSize)
|
||||
{
|
||||
int newLength = GetSizeFor(newSize, out int bits);
|
||||
if (newLength <= Length) return; // Contraction not currently supported.
|
||||
TColor[] newColors = new TColor[newLength];
|
||||
IndexedColor<TColor>[] newIndexedColors = new IndexedColor<TColor>[newLength];
|
||||
Array.Copy(colors, newColors, colors.Length);
|
||||
Array.Copy(indexedColors, newIndexedColors, indexedColors.Length);
|
||||
for (int i = Length; i < newLength; i++) newIndexedColors[i] = new IndexedColor<TColor>(this, i);
|
||||
colors = newColors;
|
||||
indexedColors = newIndexedColors;
|
||||
BitDepth = bits;
|
||||
}
|
||||
|
||||
#if CS8_OR_GREATER
|
||||
public bool ReferenceEquals(ColorPalette<TColor>? other)
|
||||
#else
|
||||
public bool ReferenceEquals(ColorPalette<TColor> other)
|
||||
#endif
|
||||
{
|
||||
return ReferenceEquals(this, other);
|
||||
}
|
||||
#if CS8_OR_GREATER
|
||||
public bool Equals(ColorPalette<TColor>? other)
|
||||
#else
|
||||
public bool Equals(ColorPalette<TColor> other)
|
||||
#endif
|
||||
{
|
||||
if (other is null) return false;
|
||||
else if (Length != other.Length) return false;
|
||||
|
||||
for (int i = 0; i < Length; i++)
|
||||
{
|
||||
if (!colors[i].Equals(other.colors[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#if CS8_OR_GREATER
|
||||
public override bool Equals(object? other)
|
||||
#else
|
||||
public override bool Equals(object other)
|
||||
#endif
|
||||
{
|
||||
if (other is null) return false;
|
||||
else if (other is ColorPalette<TColor> otherColor) return Equals(otherColor);
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
public override string ToString() => $"{BitDepth} BPP Palette: {typeof(TColor).Name}[{Length}]";
|
||||
|
||||
public IEnumerator<TColor> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < colors.Length; i++) yield return colors[i];
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
private static int GetSizeFor(int colors, out int bitDepth)
|
||||
{
|
||||
int maxSize = 1;
|
||||
bitDepth = 0;
|
||||
colors--;
|
||||
while (colors > 0)
|
||||
{
|
||||
maxSize <<= 1;
|
||||
colors >>= 1;
|
||||
bitDepth++;
|
||||
}
|
||||
return maxSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
387
Nerd_STF/Graphics/ColorRGB.cs
Normal file
387
Nerd_STF/Graphics/ColorRGB.cs
Normal file
@ -0,0 +1,387 @@
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Mathematics;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
public struct ColorRGB : IColor<ColorRGB>,
|
||||
INumberGroup<ColorRGB, double>
|
||||
#if CS11_OR_GREATER
|
||||
,IFromTuple<ColorRGB, (double, double, double)>,
|
||||
IFromTuple<ColorRGB, (double, double, double, double)>,
|
||||
ISplittable<ColorRGB, (double[] Rs, double[] Gs, double[] Bs, double[] As)>
|
||||
#endif
|
||||
{
|
||||
public static int ChannelCount => 4;
|
||||
|
||||
public static ColorRGB Black => new ColorRGB(0 , 0 , 0 , 1);
|
||||
public static ColorRGB Blue => new ColorRGB(0 , 0 , 1 , 1);
|
||||
public static ColorRGB Clear => new ColorRGB(0 , 0 , 0 , 0);
|
||||
public static ColorRGB Cyan => new ColorRGB(0 , 1 , 1 , 1);
|
||||
public static ColorRGB Gray => new ColorRGB(0.5, 0.5, 0.5, 1);
|
||||
public static ColorRGB Green => new ColorRGB(0 , 1 , 0 , 1);
|
||||
public static ColorRGB Magenta => new ColorRGB(1 , 0 , 1 , 1);
|
||||
public static ColorRGB Orange => new ColorRGB(1 , 0.5, 0 , 1);
|
||||
public static ColorRGB Purple => new ColorRGB(0.5, 0 , 1 , 1);
|
||||
public static ColorRGB Red => new ColorRGB(1 , 0 , 0 , 1);
|
||||
public static ColorRGB White => new ColorRGB(1 , 1 , 1 , 1);
|
||||
public static ColorRGB Yellow => new ColorRGB(1 , 1 , 0 , 1);
|
||||
|
||||
public double Magnitude => MathE.Sqrt(r * r + g * g + b * b);
|
||||
|
||||
public double r, g, b, a;
|
||||
|
||||
public ColorRGB(double red, double green, double blue)
|
||||
{
|
||||
r = red;
|
||||
g = green;
|
||||
b = blue;
|
||||
a = 1;
|
||||
}
|
||||
public ColorRGB(double red, double green, double blue, double alpha)
|
||||
{
|
||||
r = red;
|
||||
g = green;
|
||||
b = blue;
|
||||
a = alpha;
|
||||
}
|
||||
public ColorRGB(IEnumerable<double> nums)
|
||||
{
|
||||
r = 0;
|
||||
g = 0;
|
||||
b = 0;
|
||||
a = 1;
|
||||
|
||||
int index = 0;
|
||||
foreach (double item in nums)
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index == 4) break;
|
||||
}
|
||||
}
|
||||
public ColorRGB(Fill<double> fill)
|
||||
{
|
||||
r = fill(0);
|
||||
g = fill(1);
|
||||
b = fill(2);
|
||||
a = fill(3);
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return r;
|
||||
case 1: return g;
|
||||
case 2: return b;
|
||||
case 3: return a;
|
||||
default: throw new ArgumentOutOfRangeException(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 ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
public ListTuple<double> this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
double[] items = new double[key.Length];
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
{
|
||||
char c = key[i];
|
||||
switch (c)
|
||||
{
|
||||
case 'r': items[i] = r; break;
|
||||
case 'g': items[i] = g; break;
|
||||
case 'b': items[i] = b; break;
|
||||
case 'a': items[i] = a; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
return new ListTuple<double>(items);
|
||||
}
|
||||
set
|
||||
{
|
||||
IEnumerator<double> stepper = value.GetEnumerator();
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
{
|
||||
char c = key[i];
|
||||
stepper.MoveNext();
|
||||
switch (c)
|
||||
{
|
||||
case 'r': r = stepper.Current; break;
|
||||
case 'g': g = stepper.Current; break;
|
||||
case 'b': b = stepper.Current; break;
|
||||
case 'a': a = stepper.Current; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ColorRGB Average(double gamma, IEnumerable<ColorRGB> colors)
|
||||
{
|
||||
double avgR = 0, avgG = 0, avgB = 0, avgA = 0;
|
||||
int count = 0;
|
||||
foreach (ColorRGB color in colors)
|
||||
{
|
||||
double correctR = MathE.Pow(color.r, gamma),
|
||||
correctG = MathE.Pow(color.g, gamma),
|
||||
correctB = MathE.Pow(color.b, gamma);
|
||||
// Gamma doesn't apply to the alpha channel.
|
||||
|
||||
avgR += correctR;
|
||||
avgG += correctG;
|
||||
avgB += correctB;
|
||||
avgA += color.a;
|
||||
count++;
|
||||
}
|
||||
avgR /= count;
|
||||
avgG /= count;
|
||||
avgB /= count;
|
||||
avgA /= count;
|
||||
double invGamma = 1 / gamma;
|
||||
return new ColorRGB(MathE.Pow(avgR, invGamma),
|
||||
MathE.Pow(avgG, invGamma),
|
||||
MathE.Pow(avgB, invGamma),
|
||||
avgA);
|
||||
}
|
||||
#if CS11_OR_GREATER
|
||||
static ColorRGB IColor<ColorRGB>.Average(IEnumerable<ColorRGB> colors) => Average(1, colors);
|
||||
#endif
|
||||
public static ColorRGB Clamp(ColorRGB color, ColorRGB min, ColorRGB max) =>
|
||||
new ColorRGB(MathE.Clamp(color.r, min.r, max.r),
|
||||
MathE.Clamp(color.g, min.g, max.g),
|
||||
MathE.Clamp(color.b, min.b, max.b),
|
||||
MathE.Clamp(color.a, min.a, max.a));
|
||||
public static ColorRGB ClampMagnitude(ColorRGB color, double minMag, double maxMag)
|
||||
{
|
||||
ColorRGB copy = color;
|
||||
ClampMagnitude(ref copy, minMag, maxMag);
|
||||
return copy;
|
||||
}
|
||||
public static void ClampMagnitude(ref ColorRGB color, double minMag, double maxMag)
|
||||
{
|
||||
if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag));
|
||||
double mag = color.Magnitude;
|
||||
|
||||
if (mag < minMag)
|
||||
{
|
||||
double factor = minMag / mag;
|
||||
color.r *= factor;
|
||||
color.g *= factor;
|
||||
color.b *= factor;
|
||||
}
|
||||
else if (mag > maxMag)
|
||||
{
|
||||
double factor = maxMag / mag;
|
||||
color.r *= factor;
|
||||
color.g *= factor;
|
||||
color.b *= factor;
|
||||
}
|
||||
}
|
||||
public static double Dot(ColorRGB a, ColorRGB b) => a.r * b.r + a.g * b.g + a.b * b.b;
|
||||
public static double Dot(IEnumerable<ColorRGB> colors)
|
||||
{
|
||||
bool any = false;
|
||||
double r = 1, g = 1, b = 1;
|
||||
foreach (ColorRGB c in colors)
|
||||
{
|
||||
r *= c.r;
|
||||
g *= c.g;
|
||||
b *= c.b;
|
||||
}
|
||||
return any ? (r + g + b) : 0;
|
||||
}
|
||||
public static ColorRGB Lerp(double gamma, ColorRGB a, ColorRGB b, double t, bool clamp = true)
|
||||
{
|
||||
double aCorrectedR = MathE.Pow(a.r, gamma), bCorrectedR = MathE.Pow(b.r, gamma),
|
||||
aCorrectedG = MathE.Pow(a.g, gamma), bCorrectedG = MathE.Pow(b.g, gamma),
|
||||
aCorrectedB = MathE.Pow(a.b, gamma), bCorrectedB = MathE.Pow(b.b, gamma);
|
||||
// Gamma doesn't apply to the alpha channel.
|
||||
|
||||
double newR = MathE.Lerp(aCorrectedR, bCorrectedR, t, clamp),
|
||||
newG = MathE.Lerp(aCorrectedG, bCorrectedG, t, clamp),
|
||||
newB = MathE.Lerp(aCorrectedB, bCorrectedB, t, clamp),
|
||||
newA = MathE.Lerp(a.a, b.a, t, clamp);
|
||||
|
||||
double invGamma = 1 / gamma;
|
||||
return new ColorRGB(MathE.Pow(newR, invGamma),
|
||||
MathE.Pow(newG, invGamma),
|
||||
MathE.Pow(newB, invGamma),
|
||||
newA);
|
||||
}
|
||||
#if CS11_OR_GREATER
|
||||
static ColorRGB IInterpolable<ColorRGB>.Lerp(ColorRGB a, ColorRGB b, double t, bool clamp) => Lerp(1, a, b, t, clamp);
|
||||
#endif
|
||||
public static ColorRGB Product(IEnumerable<ColorRGB> colors)
|
||||
{
|
||||
bool any = false;
|
||||
ColorRGB result = new ColorRGB(1, 1, 1, 1);
|
||||
foreach (ColorRGB color in colors)
|
||||
{
|
||||
any = true;
|
||||
result *= color;
|
||||
}
|
||||
return any ? result : Black;
|
||||
}
|
||||
public static ColorRGB Sum(IEnumerable<ColorRGB> colors)
|
||||
{
|
||||
bool any = false;
|
||||
ColorRGB result = new ColorRGB(0, 0, 0, 0);
|
||||
foreach (ColorRGB color in colors)
|
||||
{
|
||||
any = true;
|
||||
result += color;
|
||||
}
|
||||
return any ? result : Black;
|
||||
}
|
||||
public static (double[] Rs, double[] Gs, double[] Bs, double[] As) SplitArray(IEnumerable<ColorRGB> colors)
|
||||
{
|
||||
int count = colors.Count();
|
||||
double[] Rs = new double[count], Gs = new double[count], Bs = new double[count], As = new double[count];
|
||||
int index = 0;
|
||||
foreach (ColorRGB c in colors)
|
||||
{
|
||||
Rs[index] = c.r;
|
||||
Gs[index] = c.g;
|
||||
Bs[index] = c.b;
|
||||
As[index] = c.a;
|
||||
index++;
|
||||
}
|
||||
return (Rs, Gs, Bs, As);
|
||||
}
|
||||
|
||||
public Dictionary<ColorChannel, double> GetChannels() => new Dictionary<ColorChannel, double>()
|
||||
{
|
||||
{ ColorChannel.Red, r },
|
||||
{ ColorChannel.Green, g },
|
||||
{ ColorChannel.Blue, b },
|
||||
{ ColorChannel.Alpha, a },
|
||||
};
|
||||
|
||||
public TColor AsColor<TColor>() where TColor : struct, IColor<TColor>
|
||||
{
|
||||
Type type = typeof(TColor);
|
||||
if (type == typeof(ColorRGB)) return (TColor)(object)this;
|
||||
else if (type == typeof(ColorHSV)) return (TColor)(object)AsHsv();
|
||||
else if (type == typeof(ColorCMYK)) return (TColor)(object)AsCmyk();
|
||||
else throw new InvalidCastException();
|
||||
}
|
||||
public ColorRGB AsRgb() => this;
|
||||
public ColorHSV AsHsv()
|
||||
{
|
||||
// Thanks https://www.rapidtables.com/convert/color/rgb-to-hsv.html
|
||||
double[] group = new double[] { r, g, b };
|
||||
double cMax = MathE.Max(group), cMin = MathE.Min(group), delta = cMax - cMin;
|
||||
Angle h;
|
||||
|
||||
if (delta == 0) h = Angle.Zero;
|
||||
else if (cMax == r) h = Angle.FromDegrees(60 * MathE.ModAbs((g - b) / delta, 6));
|
||||
else if (cMax == g) h = Angle.FromDegrees(60 * ((b - r) / delta + 2));
|
||||
else if (cMax == b) h = Angle.FromDegrees(60 * ((r - g) / delta + 4));
|
||||
else h = Angle.Zero;
|
||||
|
||||
double s = cMax == 0 ? 0 : delta / cMax;
|
||||
return new ColorHSV(h, s, cMax);
|
||||
}
|
||||
public ColorCMYK AsCmyk()
|
||||
{
|
||||
// Thanks https://www.rapidtables.com/convert/color/rgb-to-cmyk.html
|
||||
double diffK = MathE.Max(new double[] { r, g, b }), invDiffK = 1 / diffK;
|
||||
return new ColorCMYK((diffK - r) * invDiffK,
|
||||
(diffK - g) * invDiffK,
|
||||
(diffK - b) * invDiffK,
|
||||
1 - diffK);
|
||||
}
|
||||
|
||||
public string HexCode() => $"#{(int)(r * 255):X2}{(int)(g * 255):X2}{(int)(b * 255):X2}";
|
||||
|
||||
public IEnumerator<double> GetEnumerator()
|
||||
{
|
||||
yield return r;
|
||||
yield return g;
|
||||
yield return b;
|
||||
yield return a;
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Deconstruct(out double r, out double g, out double b)
|
||||
{
|
||||
r = this.r;
|
||||
g = this.g;
|
||||
b = this.b;
|
||||
}
|
||||
public void Deconstruct(out double r, out double g, out double b, out double a)
|
||||
{
|
||||
r = this.r;
|
||||
g = this.g;
|
||||
b = this.b;
|
||||
a = this.a;
|
||||
}
|
||||
|
||||
public bool Equals(ColorRGB other)
|
||||
{
|
||||
if (a <= 0 && other.a <= 0) return true;
|
||||
else return r == other.r && g == other.g && b == other.b && a == other.a;
|
||||
}
|
||||
public bool Equals(IColor other) => Equals(other.AsRgb());
|
||||
#if CS8_OR_GREATER
|
||||
public override bool Equals(object? other)
|
||||
#else
|
||||
public override bool Equals(object other)
|
||||
#endif
|
||||
{
|
||||
if (other is IColor color) return Equals(color.AsRgb());
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
public override string ToString() => $"{{ r={r:0.00}, g={g:0.00}, b={b:0.00}, a={a:0.00} }}";
|
||||
|
||||
public double[] ToArray() => new double[] { r, g, b, a };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
ColorRGB copy = this;
|
||||
return i => copy[i];
|
||||
}
|
||||
public List<double> ToList() => new List<double> { r, g, b, a };
|
||||
|
||||
public static ColorRGB operator +(ColorRGB a, ColorRGB b) => new ColorRGB(a.r + b.r, a.g + b.g, a.b + b.b, 1 - (1 - a.a) * (1 - b.a));
|
||||
public static ColorRGB operator *(ColorRGB a, ColorRGB b) => new ColorRGB(a.r * b.r, a.g * b.g, a.b * b.b, a.a * b.a);
|
||||
public static ColorRGB operator *(ColorRGB a, double b) => new ColorRGB(a.r * b, a.g * b, a.b * b, a.a);
|
||||
public static bool operator ==(ColorRGB a, IColor b) => a.Equals(b.AsRgb());
|
||||
public static bool operator !=(ColorRGB a, IColor b) => !a.Equals(b.AsRgb());
|
||||
public static bool operator ==(ColorRGB a, ColorRGB b) => a.Equals(b);
|
||||
public static bool operator !=(ColorRGB a, ColorRGB b) => !a.Equals(b);
|
||||
|
||||
public static implicit operator ColorRGB(ListTuple<double> tuple)
|
||||
{
|
||||
if (tuple.Length == 3) return new ColorRGB(tuple[0], tuple[1], tuple[2]);
|
||||
else if (tuple.Length == 4) return new ColorRGB(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
else throw new InvalidCastException();
|
||||
}
|
||||
public static implicit operator ColorRGB((double, double, double) tuple) => new ColorRGB(tuple.Item1, tuple.Item2, tuple.Item3);
|
||||
public static implicit operator ColorRGB((double, double, double, double) tuple) => new ColorRGB(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||
public static explicit operator ColorRGB(Float3 group) => new ColorRGB(group.x, group.y, group.z);
|
||||
public static explicit operator ColorRGB(Float4 group) => new ColorRGB(group.x, group.y, group.z, group.w);
|
||||
|
||||
public static implicit operator ListTuple<double>(ColorRGB color) => new ListTuple<double>(color.r, color.g, color.b, color.a);
|
||||
public static implicit operator ValueTuple<double, double, double>(ColorRGB color) => (color.r, color.g, color.b);
|
||||
public static implicit operator ValueTuple<double, double, double, double>(ColorRGB color) => (color.r, color.g, color.b, color.a);
|
||||
}
|
||||
}
|
||||
53
Nerd_STF/Graphics/Formats/IColorFormat.cs
Normal file
53
Nerd_STF/Graphics/Formats/IColorFormat.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nerd_STF.Graphics.Formats
|
||||
{
|
||||
public interface IColorFormat
|
||||
{
|
||||
int ChannelCount { get; }
|
||||
int BitLength { get; }
|
||||
Dictionary<ColorChannel, int> BitsPerChannel { get; }
|
||||
byte[] GetBitfield(ColorChannel channel);
|
||||
|
||||
byte[] GetBits();
|
||||
IColor GetColor();
|
||||
|
||||
// TODO: Bitwriter?
|
||||
// write to stream
|
||||
}
|
||||
|
||||
public interface IColorFormat<TSelf, TColor> : IColorFormat,
|
||||
IEquatable<TSelf>
|
||||
where TSelf : IColorFormat<TSelf, TColor>
|
||||
where TColor : struct, IColor<TColor>
|
||||
{
|
||||
#if CS11_OR_GREATER
|
||||
new static abstract int BitLength { get; }
|
||||
int IColorFormat.BitLength => TSelf.BitLength;
|
||||
|
||||
new static abstract Dictionary<ColorChannel, int> BitsPerChannel { get; }
|
||||
Dictionary<ColorChannel, int> IColorFormat.BitsPerChannel => TSelf.BitsPerChannel;
|
||||
|
||||
new static abstract byte[] GetBitfield(ColorChannel channel);
|
||||
byte[] IColorFormat.GetBitfield(ColorChannel channel) => TSelf.GetBitfield(channel);
|
||||
|
||||
static abstract TSelf FromColor(IColor color);
|
||||
static abstract TSelf FromColor(TColor color);
|
||||
#endif
|
||||
|
||||
new TColor GetColor();
|
||||
#if CS8_OR_GREATER
|
||||
IColor IColorFormat.GetColor() => GetColor();
|
||||
#endif
|
||||
void SetColor(TColor color);
|
||||
|
||||
#if CS11_OR_GREATER
|
||||
static abstract TSelf operator +(TSelf a, TSelf b);
|
||||
static abstract TSelf operator *(TSelf a, TSelf b);
|
||||
|
||||
static abstract bool operator ==(TSelf a, TSelf b);
|
||||
static abstract bool operator !=(TSelf a, TSelf b);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
80
Nerd_STF/Graphics/Formats/IndexedColor.cs
Normal file
80
Nerd_STF/Graphics/Formats/IndexedColor.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using Nerd_STF.Helpers;
|
||||
using Nerd_STF.Mathematics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nerd_STF.Graphics.Formats
|
||||
{
|
||||
public class IndexedColor<TColor> : IColorFormat, IEquatable<IndexedColor<TColor>>
|
||||
where TColor : struct, IColor<TColor>
|
||||
{
|
||||
public int BitLength => palette.BitDepth;
|
||||
public int Index { get; }
|
||||
|
||||
int IColorFormat.ChannelCount => 1;
|
||||
Dictionary<ColorChannel, int> IColorFormat.BitsPerChannel => new Dictionary<ColorChannel, int>()
|
||||
{
|
||||
{ ColorChannel.Index, palette.BitDepth }
|
||||
};
|
||||
byte[] IColorFormat.GetBitfield(ColorChannel channel)
|
||||
{
|
||||
byte[] buf = new byte[MathE.Ceiling(palette.BitDepth / 8.0)];
|
||||
if (channel != ColorChannel.Index) return buf; // All zeroes.
|
||||
int wholes = palette.BitDepth / 8, parts = palette.BitDepth % 8;
|
||||
for (int i = 0; i < wholes; i++) buf[i] = 0xFF;
|
||||
for (int i = 0; i < parts; i++) buf[wholes] = (byte)((buf[wholes] << 1) + 1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
private readonly ColorPalette<TColor> palette;
|
||||
|
||||
public IndexedColor(ColorPalette<TColor> palette, int index)
|
||||
{
|
||||
this.palette = palette;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public ColorPalette<TColor> GetPalette() => palette;
|
||||
|
||||
public ref TColor Color() => ref palette.Color(Index);
|
||||
IColor IColorFormat.GetColor() => Color();
|
||||
public byte[] GetBits()
|
||||
{
|
||||
byte[] buf = new byte[MathE.Ceiling(palette.BitDepth / 8.0)];
|
||||
int bitIndex = 0, byteIndex = 0, remaining = Index;
|
||||
while (remaining > 0)
|
||||
{
|
||||
buf[byteIndex] |= (byte)((remaining & 1) << bitIndex);
|
||||
remaining >>= 1;
|
||||
bitIndex++;
|
||||
if (bitIndex == 8)
|
||||
{
|
||||
bitIndex = 0;
|
||||
byteIndex++;
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
public bool ReferenceEquals(IndexedColor<TColor> other) => ReferenceEquals(this, other);
|
||||
#if CS8_OR_GREATER
|
||||
public bool Equals(IndexedColor<TColor>? other)
|
||||
#else
|
||||
public bool Equals(IndexedColor<TColor> other)
|
||||
#endif
|
||||
=> !(other is null) && Color().Equals(other.Color());
|
||||
#if CS8_OR_GREATER
|
||||
public override bool Equals(object? other)
|
||||
#else
|
||||
public override bool Equals(object other)
|
||||
#endif
|
||||
{
|
||||
if (other is null) return false;
|
||||
else if (other is IndexedColor<TColor> otherIndexed) return Equals(otherIndexed);
|
||||
else if (other is TColor otherColor) return Color().Equals(otherColor);
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
public override string ToString() => $"#0x{Index:X}: {Color()}";
|
||||
}
|
||||
}
|
||||
141
Nerd_STF/Graphics/Formats/R8G8B8A8.cs
Normal file
141
Nerd_STF/Graphics/Formats/R8G8B8A8.cs
Normal file
@ -0,0 +1,141 @@
|
||||
using Nerd_STF.Mathematics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Nerd_STF.Graphics.Formats
|
||||
{
|
||||
public class R8G8B8A8 : IColorFormat<R8G8B8A8, ColorRGB>
|
||||
{
|
||||
public static int ChannelCount => 4;
|
||||
public static int BitLength => 32;
|
||||
public static Dictionary<ColorChannel, int> BitsPerChannel { get; } = new Dictionary<ColorChannel, int>()
|
||||
{
|
||||
{ ColorChannel.Red, 8 },
|
||||
{ ColorChannel.Green, 8 },
|
||||
{ ColorChannel.Blue, 8 },
|
||||
{ ColorChannel.Alpha, 8 }
|
||||
};
|
||||
|
||||
int IColorFormat.ChannelCount => ChannelCount;
|
||||
int IColorFormat.BitLength => BitLength;
|
||||
Dictionary<ColorChannel, int> IColorFormat.BitsPerChannel => BitsPerChannel;
|
||||
|
||||
public byte R
|
||||
{
|
||||
get => r;
|
||||
set => r = value;
|
||||
}
|
||||
public byte G
|
||||
{
|
||||
get => g;
|
||||
set => g = value;
|
||||
}
|
||||
public byte B
|
||||
{
|
||||
get => b;
|
||||
set => b = value;
|
||||
}
|
||||
public byte A
|
||||
{
|
||||
get => a;
|
||||
set => a = value;
|
||||
}
|
||||
|
||||
private byte r, g, b, a;
|
||||
|
||||
public R8G8B8A8(ColorRGB color)
|
||||
{
|
||||
r = (byte)MathE.Clamp(color.r * 255, 0, 255);
|
||||
g = (byte)MathE.Clamp(color.g * 255, 0, 255);
|
||||
b = (byte)MathE.Clamp(color.b * 255, 0, 255);
|
||||
a = (byte)MathE.Clamp(color.a * 255, 0, 255);
|
||||
}
|
||||
public R8G8B8A8(byte r, byte g, byte b, byte a)
|
||||
{
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
public static R8G8B8A8 FromColor(IColor color) => new R8G8B8A8(color.AsRgb());
|
||||
public static R8G8B8A8 FromColor(ColorRGB color) => new R8G8B8A8(color);
|
||||
|
||||
public static byte[] GetBitfield(ColorChannel channel)
|
||||
{
|
||||
byte[] buf = new byte[4];
|
||||
switch (channel)
|
||||
{
|
||||
case ColorChannel.Red: buf[0] = 0xFF; break;
|
||||
case ColorChannel.Green: buf[1] = 0xFF; break;
|
||||
case ColorChannel.Blue: buf[2] = 0xFF; break;
|
||||
case ColorChannel.Alpha: buf[3] = 0xFF; break;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
byte[] IColorFormat.GetBitfield(ColorChannel channel) => GetBitfield(channel);
|
||||
|
||||
#if CS8_OR_GREATER
|
||||
public bool Equals(R8G8B8A8? other) =>
|
||||
#else
|
||||
public bool Equals(R8G8B8A8 other) =>
|
||||
#endif
|
||||
!(other is null) && r == other.r && g == other.g && b == other.b && a == other.a;
|
||||
#if CS8_OR_GREATER
|
||||
public override bool Equals(object? obj)
|
||||
#else
|
||||
public override bool Equals(object obj)
|
||||
#endif
|
||||
{
|
||||
if (obj is null) return false;
|
||||
else if (obj is R8G8B8A8 formatObj) return Equals(formatObj);
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
public override string ToString() => $"{{ r={r}, g={g}, b={b}, a={a} }}";
|
||||
|
||||
public byte[] GetBits() => new byte[] { r, g, b, a };
|
||||
|
||||
public ColorRGB GetColor()
|
||||
{
|
||||
const double inv255 = 0.00392156862745; // Constant for 1/255
|
||||
return new ColorRGB(r * inv255,
|
||||
g * inv255,
|
||||
b * inv255,
|
||||
a * inv255);
|
||||
}
|
||||
IColor IColorFormat.GetColor() => GetColor();
|
||||
public void SetColor(ColorRGB color)
|
||||
{
|
||||
r = (byte)MathE.Clamp(color.r * 255, 0, 255);
|
||||
g = (byte)MathE.Clamp(color.g * 255, 0, 255);
|
||||
b = (byte)MathE.Clamp(color.b * 255, 0, 255);
|
||||
a = (byte)MathE.Clamp(color.a * 255, 0, 255);
|
||||
}
|
||||
public void SetColor(byte r, byte g, byte b, byte a)
|
||||
{
|
||||
this.r = (byte)MathE.Clamp(r * 255, 0, 255);
|
||||
this.g = (byte)MathE.Clamp(g * 255, 0, 255);
|
||||
this.b = (byte)MathE.Clamp(b * 255, 0, 255);
|
||||
this.a = (byte)MathE.Clamp(a * 255, 0, 255);
|
||||
}
|
||||
|
||||
public static R8G8B8A8 operator +(R8G8B8A8 a, R8G8B8A8 b)
|
||||
{
|
||||
return new R8G8B8A8((byte)MathE.Clamp(a.r + b.r, 0, 255),
|
||||
(byte)MathE.Clamp(a.g + b.g, 0, 255),
|
||||
(byte)MathE.Clamp(a.b + b.b, 0, 255),
|
||||
(byte)MathE.Clamp(a.a + b.a, 0, 255));
|
||||
}
|
||||
public static R8G8B8A8 operator *(R8G8B8A8 a, R8G8B8A8 b)
|
||||
{
|
||||
const double inv255 = 0.00392156862745; // Constant for 1/255
|
||||
return new R8G8B8A8((byte)MathE.Clamp(a.r * b.r * inv255, 0, 255),
|
||||
(byte)MathE.Clamp(a.g * b.g * inv255, 0, 255),
|
||||
(byte)MathE.Clamp(a.b * b.b * inv255, 0, 255),
|
||||
(byte)MathE.Clamp(a.a * b.a * inv255, 0, 255));
|
||||
}
|
||||
public static bool operator ==(R8G8B8A8 a, R8G8B8A8 b) => a.Equals(b);
|
||||
public static bool operator !=(R8G8B8A8 a, R8G8B8A8 b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
@ -1,249 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,247 +0,0 @@
|
||||
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);
|
||||
}
|
||||
36
Nerd_STF/Graphics/IColor.cs
Normal file
36
Nerd_STF/Graphics/IColor.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using Nerd_STF.Mathematics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
public interface IColor : INumberGroupBase<double>
|
||||
{
|
||||
Dictionary<ColorChannel, double> GetChannels();
|
||||
|
||||
TColor AsColor<TColor>() where TColor : struct, IColor<TColor>;
|
||||
ColorRGB AsRgb();
|
||||
ColorHSV AsHsv();
|
||||
ColorCMYK AsCmyk();
|
||||
|
||||
string HexCode();
|
||||
|
||||
bool Equals(IColor other);
|
||||
}
|
||||
|
||||
public interface IColor<TSelf> : IColor,
|
||||
IEquatable<TSelf>
|
||||
#if CS11_OR_GREATER
|
||||
,IColorOperators<TSelf>,
|
||||
IColorPresets<TSelf>,
|
||||
IInterpolable<TSelf>
|
||||
#endif
|
||||
where TSelf : struct, IColor<TSelf>
|
||||
{
|
||||
#if CS11_OR_GREATER
|
||||
static abstract int ChannelCount { get; }
|
||||
|
||||
static abstract TSelf Average(IEnumerable<TSelf> colors);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
15
Nerd_STF/Graphics/IColorOperators.cs
Normal file
15
Nerd_STF/Graphics/IColorOperators.cs
Normal file
@ -0,0 +1,15 @@
|
||||
#if CS11_OR_GREATER
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
public interface IColorOperators<TSelf> where TSelf : IColorOperators<TSelf>
|
||||
{
|
||||
static abstract TSelf operator +(TSelf a, TSelf b);
|
||||
static abstract TSelf operator *(TSelf a, TSelf b);
|
||||
static abstract TSelf operator *(TSelf a, double b);
|
||||
static abstract bool operator ==(TSelf a, IColor b);
|
||||
static abstract bool operator !=(TSelf a, IColor b);
|
||||
static abstract bool operator ==(TSelf a, TSelf b);
|
||||
static abstract bool operator !=(TSelf a, TSelf b);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
20
Nerd_STF/Graphics/IColorPresets.cs
Normal file
20
Nerd_STF/Graphics/IColorPresets.cs
Normal file
@ -0,0 +1,20 @@
|
||||
#if CS11_OR_GREATER
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
public interface IColorPresets<TSelf> where TSelf : IColorPresets<TSelf>
|
||||
{
|
||||
static abstract TSelf Black { get; }
|
||||
static abstract TSelf Blue { get; }
|
||||
static abstract TSelf Clear { get; }
|
||||
static abstract TSelf Cyan { get; }
|
||||
static abstract TSelf Gray { get; }
|
||||
static abstract TSelf Green { get; }
|
||||
static abstract TSelf Magenta { get; }
|
||||
static abstract TSelf Orange { get; }
|
||||
static abstract TSelf Purple { get; }
|
||||
static abstract TSelf Red { get; }
|
||||
static abstract TSelf White { get; }
|
||||
static abstract TSelf Yellow { get; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1,14 +0,0 @@
|
||||
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,
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
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,
|
||||
}
|
||||
@ -1,149 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,191 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,254 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,260 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,317 +1,176 @@
|
||||
namespace Nerd_STF.Helpers;
|
||||
using Nerd_STF.Mathematics;
|
||||
|
||||
// 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
|
||||
namespace Nerd_STF.Helpers
|
||||
{
|
||||
private static readonly float[] p_cosTable =
|
||||
internal static class CordicHelper
|
||||
{
|
||||
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)
|
||||
// Starts at 4 radians. Each index downwards is half that.
|
||||
// Goes from 2^2 to 2^-19.
|
||||
// Got the values from Desmos. Thanks guys!
|
||||
private static readonly double[] cosTable = new double[]
|
||||
{
|
||||
-0.653643620864,
|
||||
-0.416146836547,
|
||||
0.540302305868,
|
||||
0.87758256189,
|
||||
0.968912421711,
|
||||
0.992197667229,
|
||||
0.9980475107,
|
||||
0.999511758485,
|
||||
0.999877932171,
|
||||
0.999969482577,
|
||||
0.999992370615,
|
||||
0.999998092652,
|
||||
0.999999523163,
|
||||
0.999999880791,
|
||||
0.999999970198,
|
||||
0.999999992549,
|
||||
0.999999998137,
|
||||
0.999999999534,
|
||||
0.999999999884,
|
||||
0.999999999971,
|
||||
0.999999999993,
|
||||
0.999999999998
|
||||
};
|
||||
private static readonly float[] p_sinTable =
|
||||
private static readonly double[] sinTable = new double[]
|
||||
{
|
||||
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)
|
||||
-0.756802495308,
|
||||
0.909297426826,
|
||||
0.841470984808,
|
||||
0.479425538604,
|
||||
0.247403959255,
|
||||
0.124674733385,
|
||||
0.0624593178424,
|
||||
0.0312449139853,
|
||||
0.0156243642249,
|
||||
0.00781242052738,
|
||||
0.0039062400659,
|
||||
0.00195312375824,
|
||||
0.00097656234478,
|
||||
0.000488281230597,
|
||||
0.000244140622575,
|
||||
0.000122070312197,
|
||||
0.0000610351562121,
|
||||
0.0000305175781203,
|
||||
0.0000152587890619,
|
||||
0.00000762939453118,
|
||||
0.00000381469726562,
|
||||
0.00000190734863281
|
||||
};
|
||||
|
||||
private static readonly float[] p_coshTable =
|
||||
// Unused sin(x) and cos(x) CORDIC approximator.
|
||||
// Not as fast as a taylor series for the same precision
|
||||
// unfortunately, so it won't be used.
|
||||
public static (double cos, double sin) SinAndCos(double theta, int maxTableIndex = int.MaxValue)
|
||||
{
|
||||
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)
|
||||
double curTheta = 0, curCos = 1, curSin = 0;
|
||||
double deltaTheta = 4;
|
||||
|
||||
// Crazy for loop, but that's for optimization purposes.
|
||||
for (int index = 0, countedIndex = 0;
|
||||
index < cosTable.Length && countedIndex < maxTableIndex;
|
||||
index++, deltaTheta *= 0.5)
|
||||
{
|
||||
if (curTheta + deltaTheta > theta) continue;
|
||||
|
||||
// cos(a + b) = cos(a)cos(b) - sin(a)sin(b)
|
||||
// sin(a + b) = cos(a)sin(b) + sin(a)cos(b)
|
||||
double deltaCos = cosTable[index],
|
||||
deltaSin = sinTable[index];
|
||||
double newCos = curCos * deltaCos - curSin * deltaSin,
|
||||
newSin = curCos * deltaSin + curSin * deltaCos;
|
||||
curCos = newCos;
|
||||
curSin = newSin;
|
||||
curTheta += deltaTheta;
|
||||
countedIndex++;
|
||||
}
|
||||
return (curCos, curSin);
|
||||
}
|
||||
|
||||
private static readonly double[] powETable = new double[]
|
||||
{
|
||||
54.5981500331,
|
||||
7.38905609893,
|
||||
2.71828182846,
|
||||
1.6487212707,
|
||||
1.28402541669,
|
||||
1.13314845307,
|
||||
1.06449445892,
|
||||
1.0317434075,
|
||||
1.01574770859,
|
||||
1.00784309721,
|
||||
1.00391388934,
|
||||
1.00195503359,
|
||||
1.00097703949,
|
||||
1.00048840048,
|
||||
1.00024417043,
|
||||
1.00012207776,
|
||||
1.00006103702,
|
||||
1.00003051804,
|
||||
1.00001525891,
|
||||
1.00000762942,
|
||||
1.0000038147,
|
||||
1.00000190735
|
||||
};
|
||||
private static readonly float[] p_sinhTable =
|
||||
public static double PowE(double pow, int maxTableIndex = int.MaxValue)
|
||||
{
|
||||
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)
|
||||
};
|
||||
double curPow = 0, curResult = 1;
|
||||
double deltaPow = 4;
|
||||
|
||||
private static readonly Dictionary<(float bas, int depth), float[]> p_expTables;
|
||||
|
||||
static CordicHelper()
|
||||
int index = 0, countedIndex = 0;
|
||||
while (index < powETable.Length && countedIndex < maxTableIndex)
|
||||
{
|
||||
p_expTables = new();
|
||||
if (curPow + deltaPow > pow)
|
||||
{
|
||||
index++;
|
||||
deltaPow *= 0.5;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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)
|
||||
curResult *= powETable[index];
|
||||
curPow += deltaPow;
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
float approximateX = 0,
|
||||
approximateCos = 1,
|
||||
approximateSin = 0;
|
||||
index++;
|
||||
deltaPow *= 0.5;
|
||||
}
|
||||
}
|
||||
return curResult;
|
||||
}
|
||||
|
||||
// Iterate continuously until it gets better.
|
||||
for (int i = 0; i < iterations; i++)
|
||||
// Generates a CORDIC table on demand.
|
||||
public static double PowAnyBase(double bass, double pow, int maxTableIndex)
|
||||
{
|
||||
// We need to find the biggest step that'll move us
|
||||
// closer to the real X (without overshooting).
|
||||
float diffX = x - approximateX;
|
||||
if (pow % 1 == 0) return MathE.Pow(bass, (int)pow);
|
||||
else if (bass < 0) return double.NaN;
|
||||
else if (pow < 0) return 1 / PowAnyBase(bass, -pow, maxTableIndex);
|
||||
|
||||
// This is assuming that cosTable and sinTable
|
||||
// have the same length.
|
||||
for (int j = 0; j < p_cosTable.Length; j++)
|
||||
double curPow = 0, curResult = 1;
|
||||
double deltaResult = bass, deltaPow = 1;
|
||||
|
||||
int countedIndex = 0;
|
||||
while (countedIndex < maxTableIndex)
|
||||
{
|
||||
// The amount the difference will shrink.
|
||||
float incX = FastGenExp2((sbyte)-j);
|
||||
|
||||
if (diffX >= incX)
|
||||
if (curPow + deltaPow > pow)
|
||||
{
|
||||
// Because here we go big to small, the first one that triggers
|
||||
// this if statement should also be the biggest one that can.
|
||||
deltaPow *= 0.5;
|
||||
deltaResult = MathE.Sqrt(deltaResult);
|
||||
countedIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the sin and cos values for this power of two.
|
||||
float valCos = p_cosTable[j],
|
||||
valSin = p_sinTable[j];
|
||||
curResult *= deltaResult;
|
||||
curPow += deltaPow;
|
||||
|
||||
// Do the products.
|
||||
float newCos = approximateCos * valCos - approximateSin * valSin,
|
||||
newSin = approximateCos * valSin + approximateSin * valCos;
|
||||
|
||||
// Apply differences
|
||||
approximateX += incX;
|
||||
approximateCos = newCos;
|
||||
approximateSin = newSin;
|
||||
break;
|
||||
if (countedIndex > 0)
|
||||
{
|
||||
deltaPow *= 0.5;
|
||||
deltaResult = MathE.Sqrt(deltaResult);
|
||||
countedIndex++;
|
||||
}
|
||||
}
|
||||
return curResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
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.
|
||||
}
|
||||
}
|
||||
72
Nerd_STF/Helpers/MatrixHelper.cs
Normal file
72
Nerd_STF/Helpers/MatrixHelper.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using Nerd_STF.Mathematics;
|
||||
using Nerd_STF.Mathematics.Algebra;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nerd_STF.Helpers
|
||||
{
|
||||
internal static class MatrixHelper
|
||||
{
|
||||
public static void SetMatrixValues<TMat>(TMat matrix, IEnumerable<IEnumerable<double>> vals, bool byRows)
|
||||
where TMat : IMatrix<TMat>
|
||||
{
|
||||
Int2 size = matrix.Size;
|
||||
int x = 0;
|
||||
foreach (IEnumerable<double> part in vals)
|
||||
{
|
||||
int y = 0;
|
||||
foreach (double v in part)
|
||||
{
|
||||
matrix[byRows ? (y, x) : (x, y)] = v;
|
||||
y++;
|
||||
if (byRows ? y >= size.x : y >= size.y) break;
|
||||
}
|
||||
x++;
|
||||
if (byRows ? x >= size.y : x >= size.x) break;
|
||||
}
|
||||
}
|
||||
public static void SetMatrixValues<TMat>(TMat matrix, IEnumerable<ListTuple<double>> vals, bool byRows)
|
||||
where TMat : IMatrix<TMat>
|
||||
{
|
||||
// Literally the same code. Sucks that casting doesn't work here.
|
||||
Int2 size = matrix.Size;
|
||||
int x = 0;
|
||||
foreach (IEnumerable<double> part in vals)
|
||||
{
|
||||
int y = 0;
|
||||
foreach (double v in part)
|
||||
{
|
||||
matrix[byRows ? (y, x) : (x, y)] = v;
|
||||
y++;
|
||||
if (byRows ? y >= size.y : y >= size.x) break;
|
||||
}
|
||||
x++;
|
||||
if (byRows ? x >= size.x : x >= size.y) break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetRow<TMat>(TMat matrix, int row, IEnumerable<double> vals)
|
||||
where TMat : IMatrix<TMat>
|
||||
{
|
||||
int col = 0;
|
||||
int max = matrix.Size.y;
|
||||
foreach (double v in vals)
|
||||
{
|
||||
matrix[row, col] = v;
|
||||
col++;
|
||||
if (col >= max) return;
|
||||
}
|
||||
}
|
||||
public static void SetColumn<TMat>(TMat matrix, int col, IEnumerable<double> vals)
|
||||
where TMat : IMatrix<TMat>
|
||||
{
|
||||
int row = 0;
|
||||
int max = matrix.Size.x;
|
||||
foreach (double v in vals)
|
||||
{
|
||||
matrix[row, col] = v;
|
||||
row++;
|
||||
if (row >= max) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Nerd_STF/Helpers/ParseHelper.cs
Normal file
54
Nerd_STF/Helpers/ParseHelper.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
|
||||
namespace Nerd_STF.Helpers
|
||||
{
|
||||
internal static class ParseHelper
|
||||
{
|
||||
// TODO: Allow parsing more stuff (hexadecimal).
|
||||
public static double ParseDouble(ReadOnlySpan<char> str)
|
||||
{
|
||||
// Turns out this is less accurate than copying and modifying
|
||||
// the code from ParseDoubleWholeDecimals. I think because applying
|
||||
// 0.1 to the whole number is worse than 0.1 to a each individual
|
||||
// decimal point.
|
||||
int raw = ParseDoubleWholeDecimals(str, out int places);
|
||||
double value = raw;
|
||||
for (int i = 0; i < places; i++) value *= 0.1;
|
||||
return value;
|
||||
}
|
||||
public static int ParseDoubleWholeDecimals(ReadOnlySpan<char> str, out int places)
|
||||
{
|
||||
str = str.Trim();
|
||||
if (str.Length == 0) goto _fail;
|
||||
places = 0;
|
||||
|
||||
bool negative = str.StartsWith("-".AsSpan());
|
||||
|
||||
int result = 0;
|
||||
ReadOnlySpan<char>.Enumerator stepper = str.GetEnumerator();
|
||||
if (negative) stepper.MoveNext();
|
||||
bool decFound = false;
|
||||
while (stepper.MoveNext())
|
||||
{
|
||||
char c = stepper.Current;
|
||||
if (c == ',') continue;
|
||||
else if (c == '.')
|
||||
{
|
||||
decFound = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c < '0' || c > '9') goto _fail;
|
||||
int value = c - '0';
|
||||
|
||||
result = result * 10 + value;
|
||||
if (decFound) places++;
|
||||
}
|
||||
|
||||
return negative ? -result : result;
|
||||
|
||||
_fail:
|
||||
throw new FormatException("Cannot parse double from span.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
48
Nerd_STF/Helpers/TargetHelper.cs
Normal file
48
Nerd_STF/Helpers/TargetHelper.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Nerd_STF.Helpers
|
||||
{
|
||||
internal static class TargetHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsFinite(double d)
|
||||
{
|
||||
#if CS11_OR_GREATER
|
||||
return double.IsFinite(d);
|
||||
#else
|
||||
long bits = BitConverter.DoubleToInt64Bits(d);
|
||||
return (bits & 0x7FFFFFFFFFFFFFFF) < 0x7FF0000000000000;
|
||||
#endif
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsInfinity(double d)
|
||||
{
|
||||
#if CS11_OR_GREATER
|
||||
return double.IsInfinity(d);
|
||||
#else
|
||||
long bits = BitConverter.DoubleToInt64Bits(d);
|
||||
return (bits & 0x7FFFFFFFFFFFFFFF) == 0x7FF0000000000000;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteLine(string content)
|
||||
{
|
||||
#if NETSTANDARD1_1
|
||||
#else
|
||||
Console.WriteLine(content);
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T[] EmptyArray<T>()
|
||||
{
|
||||
#if NETSTANDARD1_1
|
||||
return new T[0];
|
||||
#else
|
||||
return Array.Empty<T>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
154
Nerd_STF/Helpers/ToStringHelper.cs
Normal file
154
Nerd_STF/Helpers/ToStringHelper.cs
Normal file
@ -0,0 +1,154 @@
|
||||
using Nerd_STF.Mathematics;
|
||||
using Nerd_STF.Mathematics.Algebra;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Nerd_STF.Helpers
|
||||
{
|
||||
internal static class ToStringHelper
|
||||
{
|
||||
#if CS8_OR_GREATER
|
||||
public static string PolynomialToString(double[] terms, bool showC, string? format)
|
||||
#else
|
||||
public static string PolynomialToString(double[] terms, bool showC, string format)
|
||||
#endif
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
bool first = true;
|
||||
for (int i = terms.Length - 1; i >= 0; i--)
|
||||
{
|
||||
double term = terms[i];
|
||||
if (term != 0)
|
||||
{
|
||||
if (term > 0 && (term != 1 || i == 0))
|
||||
{
|
||||
if (first) builder.Append(term.ToString(format));
|
||||
else builder.Append("+ ").Append(term.ToString(format));
|
||||
}
|
||||
else if (term < 0)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
if (term != -1 || i == 0) builder.Append(term.ToString(format));
|
||||
else builder.Append('-');
|
||||
}
|
||||
else
|
||||
{
|
||||
if (term != -1 || i == 0) builder.Append("- ").Append((-term).ToString(format));
|
||||
else builder.Append("- ");
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
builder.Append('x');
|
||||
if (i > 1) builder.Append('^').Append(i);
|
||||
}
|
||||
builder.Append(' ');
|
||||
}
|
||||
else if (showC && i == 0) builder.Append("+ C ");
|
||||
}
|
||||
return builder.Remove(builder.Length - 1, 1).ToString();
|
||||
}
|
||||
|
||||
#if CS8_OR_GREATER
|
||||
public static string MatrixToString<T>(T matrix, string? format)
|
||||
#else
|
||||
public static string MatrixToString<T>(T matrix, string format)
|
||||
#endif
|
||||
where T : IMatrix<T>
|
||||
{
|
||||
// First convert all items to their string counterparts,
|
||||
// then measure the lengths and do spacing accordingly.
|
||||
Int2 size = matrix.Size;
|
||||
string[,] items = new string[size.y, size.x];
|
||||
for (int x = 0; x < size.x; x++) for (int y = 0; y < size.y; y++)
|
||||
items[y, x] = matrix[x, y].ToString(format);
|
||||
|
||||
// Then write each line separately.
|
||||
StringBuilder[] lines = new StringBuilder[size.x + 2];
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (i == 0) builder.Append('┌');
|
||||
else if (i == lines.Length - 1) builder.Append('└');
|
||||
else builder.Append('│');
|
||||
|
||||
lines[i] = builder;
|
||||
}
|
||||
int totalLen = 0;
|
||||
for (int x = 0; x < size.y; x++)
|
||||
{
|
||||
int maxLen = 0;
|
||||
for (int y = 0; y < size.x; y++)
|
||||
{
|
||||
string item = items[x, y];
|
||||
if (item.Length > maxLen) maxLen = item.Length;
|
||||
}
|
||||
totalLen += maxLen + 1;
|
||||
for (int y = 0; y < size.x; y++)
|
||||
{
|
||||
StringBuilder builder = lines[y + 1];
|
||||
string item = items[x, y];
|
||||
int spacing = maxLen - item.Length;
|
||||
builder.Append(new string(' ', spacing + 1));
|
||||
builder.Append(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Finish up and merge.
|
||||
StringBuilder total = new StringBuilder();
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
StringBuilder builder = lines[i];
|
||||
if (i == 0) builder.Append(new string(' ', totalLen)).Append(" ┐");
|
||||
else if (i == lines.Length - 1) builder.Append(new string(' ', totalLen)).Append(" ┘");
|
||||
else builder.Append(" │");
|
||||
total.Append(builder);
|
||||
if (i != lines.Length - 1) total.AppendLine();
|
||||
}
|
||||
return total.ToString();
|
||||
}
|
||||
|
||||
private static readonly string dimNumSymbols = " ijk?";
|
||||
#if CS8_OR_GREATER
|
||||
public static string HighDimNumberToString(IEnumerable<double> terms, string? format, IFormatProvider? provider)
|
||||
#else
|
||||
public static string HighDimNumberToString(IEnumerable<double> terms, string format, IFormatProvider provider)
|
||||
#endif
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int index = 0;
|
||||
bool first = true;
|
||||
foreach (double term in terms)
|
||||
{
|
||||
if (term == 0)
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
if (first) builder.Append(term.ToString(format, provider));
|
||||
else
|
||||
{
|
||||
if (term > 0)
|
||||
{
|
||||
builder.Append(" + ");
|
||||
builder.Append(term.ToString(format, provider));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(" - ");
|
||||
builder.Append((-term).ToString(format, provider));
|
||||
}
|
||||
}
|
||||
if (index > 0) builder.Append(dimNumSymbols[MathE.Min(index, dimNumSymbols.Length)]);
|
||||
first = false;
|
||||
index++;
|
||||
}
|
||||
if (first) builder.Append(0.0.ToString(format, provider));
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
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;
|
||||
}
|
||||
7
Nerd_STF/ICombinationIndexer.cs
Normal file
7
Nerd_STF/ICombinationIndexer.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Nerd_STF
|
||||
{
|
||||
public interface ICombinationIndexer<TItem>
|
||||
{
|
||||
ListTuple<TItem> this[string key] { get; set; }
|
||||
}
|
||||
}
|
||||
14
Nerd_STF/IFromTuple.cs
Normal file
14
Nerd_STF/IFromTuple.cs
Normal file
@ -0,0 +1,14 @@
|
||||
#if CS11_OR_GREATER
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Nerd_STF
|
||||
{
|
||||
public interface IFromTuple<TSelf, TTuple>
|
||||
where TSelf : IFromTuple<TSelf, TTuple>
|
||||
where TTuple : struct, ITuple
|
||||
{
|
||||
static abstract implicit operator TSelf(TTuple tuple);
|
||||
static abstract implicit operator TTuple(TSelf tuple);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1,8 +0,0 @@
|
||||
namespace Nerd_STF;
|
||||
|
||||
public interface IGroup<T> : IEnumerable<T>
|
||||
{
|
||||
public T[] ToArray();
|
||||
public Fill<T> ToFill();
|
||||
public List<T> ToList();
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
namespace Nerd_STF;
|
||||
|
||||
public interface IGroup2d<T> : IGroup<T>
|
||||
{
|
||||
public T[,] ToArray2D();
|
||||
public Fill2d<T> ToFill2D();
|
||||
}
|
||||
14
Nerd_STF/ISplittable.cs
Normal file
14
Nerd_STF/ISplittable.cs
Normal file
@ -0,0 +1,14 @@
|
||||
#if CS11_OR_GREATER
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Nerd_STF
|
||||
{
|
||||
public interface ISplittable<TSelf, TTuple>
|
||||
where TSelf : ISplittable<TSelf, TTuple>
|
||||
where TTuple : struct, ITuple
|
||||
{
|
||||
static abstract TTuple SplitArray(IEnumerable<TSelf> values);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
145
Nerd_STF/ListTuple.cs
Normal file
145
Nerd_STF/ListTuple.cs
Normal file
@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Nerd_STF
|
||||
{
|
||||
public readonly struct ListTuple<T> : IEnumerable<T>,
|
||||
IEquatable<ListTuple<T>>
|
||||
#if NET471_OR_GREATER || NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
|
||||
,ITuple
|
||||
#endif
|
||||
{
|
||||
public int Length => items.Length;
|
||||
|
||||
private readonly T[] items;
|
||||
|
||||
public ListTuple(IEnumerable<T> items)
|
||||
{
|
||||
this.items = items.ToArray();
|
||||
}
|
||||
public ListTuple(params T[] items)
|
||||
{
|
||||
this.items = items;
|
||||
}
|
||||
public ListTuple(Fill<T> items, int length)
|
||||
{
|
||||
this.items = new T[length];
|
||||
for (int i = 0; i < length; i++) this.items[i] = items(i);
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => items[index];
|
||||
set => items[index] = value;
|
||||
}
|
||||
#if NET471_OR_GREATER || NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
|
||||
#if CS8_OR_GREATER
|
||||
object? ITuple.this[int index] => this[index];
|
||||
#else
|
||||
object ITuple.this[int index] => this[index];
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public Enumerator GetEnumerator() => new Enumerator(this);
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public bool Equals(ListTuple<T> other)
|
||||
{
|
||||
if (Length != other.Length) return false;
|
||||
for (int i = 0; i < Length; i++)
|
||||
{
|
||||
T itemA = items[i], itemB = other.items[i];
|
||||
if (itemA == null || itemB == null)
|
||||
{
|
||||
if (itemA == null && itemB == null) continue;
|
||||
else return false;
|
||||
}
|
||||
if (!itemA.Equals(itemB)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#if CS8_OR_GREATER
|
||||
public override bool Equals(object? other)
|
||||
#else
|
||||
public override bool Equals(object other)
|
||||
#endif
|
||||
{
|
||||
if (other is null) return false;
|
||||
else if (other is ListTuple<T> otherTuple) return Equals(otherTuple);
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode() => items.GetHashCode();
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder("(");
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
builder.Append(items[i]);
|
||||
if (i != items.Length - 1) builder.Append(", ");
|
||||
}
|
||||
builder.Append(')');
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public Fill<T> ToFill()
|
||||
{
|
||||
T[] items = this.items;
|
||||
return i => items[i];
|
||||
}
|
||||
|
||||
public static bool operator ==(ListTuple<T> a, ListTuple<T> b) => a.Equals(b);
|
||||
public static bool operator !=(ListTuple<T> a, ListTuple<T> b) => !a.Equals(b);
|
||||
|
||||
public static implicit operator ValueTuple<T>(ListTuple<T> tuple) => new ValueTuple<T>(tuple[0]);
|
||||
public static implicit operator ValueTuple<T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1]);
|
||||
public static implicit operator ValueTuple<T, T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1], tuple[2]);
|
||||
public static implicit operator ValueTuple<T, T, T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
public static implicit operator ValueTuple<T, T, T, T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1], tuple[2], tuple[3], tuple[4]);
|
||||
public static implicit operator ValueTuple<T, T, T, T, T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1], tuple[2], tuple[3], tuple[4], tuple[5]);
|
||||
public static implicit operator ValueTuple<T, T, T, T, T, T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1], tuple[2], tuple[3], tuple[4], tuple[5], tuple[6]);
|
||||
public static implicit operator T[](ListTuple<T> tuple) => tuple.items;
|
||||
|
||||
public static implicit operator ListTuple<T>(ValueTuple<T> tuple) => new ListTuple<T>(tuple.Item1);
|
||||
public static implicit operator ListTuple<T>((T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2);
|
||||
public static implicit operator ListTuple<T>((T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3);
|
||||
public static implicit operator ListTuple<T>((T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||
public static implicit operator ListTuple<T>((T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5);
|
||||
public static implicit operator ListTuple<T>((T, T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5, tuple.Item6);
|
||||
public static implicit operator ListTuple<T>((T, T, T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5, tuple.Item6, tuple.Item7);
|
||||
public static implicit operator ListTuple<T>(T[] array) => new ListTuple<T>(array);
|
||||
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private int index;
|
||||
private readonly ListTuple<T> tuple;
|
||||
|
||||
public T Current => tuple.items[index];
|
||||
#if CS8_OR_GREATER
|
||||
object? IEnumerator.Current => Current;
|
||||
#else
|
||||
object IEnumerator.Current => Current;
|
||||
#endif
|
||||
public bool MoveNext()
|
||||
{
|
||||
index++;
|
||||
return index < tuple.items.Length;
|
||||
}
|
||||
public void Reset()
|
||||
{
|
||||
index = -1;
|
||||
}
|
||||
public void Dispose() { }
|
||||
|
||||
internal Enumerator(ListTuple<T> tuple)
|
||||
{
|
||||
index = -1;
|
||||
this.tuple = tuple;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
namespace Nerd_STF;
|
||||
|
||||
public enum LogSeverity
|
||||
{
|
||||
Debug = 1,
|
||||
Information = 2,
|
||||
Warning = 4,
|
||||
Error = 8,
|
||||
Fatal = 16,
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IAbsolute<T> where T : IAbsolute<T>
|
||||
{
|
||||
public static abstract T Absolute(T val);
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IAverage<T> where T : IAverage<T>
|
||||
{
|
||||
public static abstract T Average(params T[] vals);
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IClamp<T> where T : IClamp<T>
|
||||
{
|
||||
public static abstract T Clamp(T val, T min, T max);
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
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>;
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IClosestTo<T> where T : IEquatable<T>
|
||||
{
|
||||
public T ClosestTo(T item);
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IContains<T> where T : IEquatable<T>
|
||||
{
|
||||
public bool Contains(T item);
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
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>;
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IIndexAll<TSub> : IIndexGet<TSub>, IIndexSet<TSub> { }
|
||||
@ -1,7 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IIndexGet<TSub>
|
||||
{
|
||||
public TSub this[int index] { get; }
|
||||
public TSub this[Index index] { get; }
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IIndexRangeAll<TSub> : IIndexRangeGet<TSub>, IIndexRangeSet<TSub> { }
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IIndexRangeGet<TSub>
|
||||
{
|
||||
public TSub[] this[Range range] { get; }
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IIndexRangeSet<TSub>
|
||||
{
|
||||
public TSub[] this[Range range] { set; }
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IIndexSet<TSub>
|
||||
{
|
||||
public TSub this[int index] { set; }
|
||||
public TSub this[Index index] { set; }
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
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>;
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IMagnitude<TNumber> where TNumber : INumber<TNumber>
|
||||
{
|
||||
public TNumber Magnitude { get; }
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
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> { }
|
||||
@ -1,37 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IMedian<T> where T : IMedian<T>
|
||||
{
|
||||
public static virtual T Median(params T[] vals) => Mathf.Median(vals);
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
public interface IPresets0d<T> where T : IPresets0d<T>
|
||||
{
|
||||
public static abstract T Unit { get; }
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
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);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user