Compare commits
11 Commits
v3.0.0-bet
...
v3.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d2b18f969 | |||
| 3a7a3c320e | |||
| 664ba0fab7 | |||
| 2762dab872 | |||
| 48890c236e | |||
| 6182db049e | |||
| e070aa097f | |||
| 866326863b | |||
| 27c64c4291 | |||
| 0f9bcd2720 | |||
| 938c95fa18 |
96
Changelog.md
96
Changelog.md
@ -1,61 +1,53 @@
|
||||
# Nerd_STF v3.0-beta2
|
||||
# Nerd_STF v3.0-beta3
|
||||
|
||||
I've added a substantial number of things in this update, mostly matrix related.
|
||||
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.
|
||||
|
||||
## List Tuples
|
||||
Here's what's new:
|
||||
|
||||
In the previous beta, I introduced **Combination Indexers** for the double and int groups, however a problem was that they only returned `IEnumerable`s. So while some interesting things were supported, some things were not.
|
||||
## `Fill<T>` and `Fill2d<T>` are back!
|
||||
|
||||
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.
|
||||
|
||||
## Slight Matrix Constructor Change
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
IEnumerable<double> vals1 = wxyz["xy"]; // Yields [2, 3]
|
||||
void MethodA()
|
||||
{
|
||||
ColorPalette<ColorRGB> palette = new(8);
|
||||
|
||||
Float2 vals2 = wxyz["xy"]; // Not allowed!
|
||||
// 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;
|
||||
}
|
||||
```
|
||||
|
||||
And that kind of sucked. So I created the `ListTuple<T>` type. It's job is to act like a regular tuple, but be able to impliclty convert to either an `IEnumerable` or a regular `ValueTuple<T>`, thus allowing conversions to the double and int groups indirectly. Now, all combination indexers return a `ListTuple` instead of an `IEnumerable`.
|
||||
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.
|
||||
|
||||
Under the hood, the `ListTuple` actually uses an array, but you get the idea.
|
||||
|
||||
```csharp
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
ListTuple<double> vals1 = wxyz["xy"]; // Yields (2, 3)
|
||||
|
||||
Float2 vals2 = vals1; // Yields (2, 3)
|
||||
IEnumerable<double> vals3 = vals1; // Yields [2, 3]
|
||||
```
|
||||
|
||||
Problem is, now the names have the potential to make much less sense.
|
||||
```csharp
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
Float2 xy = wxyz["xy"]; // x <- x, y <- y
|
||||
Float2 wz = wxyz["wz"]; // x <- w, y <- z
|
||||
```
|
||||
|
||||
But whatever. You can always stick to using `IEnumerable`s if you want.
|
||||
|
||||
## No More `*.Abstract`
|
||||
|
||||
I got rid of all the `Abstract` namespaces, since they don't really make much sense in the grand scheme of things. They've all been moved to the namespace that applies to them most (eg. `INumberGroup` went to `Nerd_STF.Mathematics`, `ICombinationIndexer` went to `Nerd_STF` since it applies to more than just mathematics).
|
||||
|
||||
## The `Fraction` Type
|
||||
|
||||
This type originally went under the name of `Rational` in Nerd_STF 2.x, but that name is actually incorrect, right? So in the rework, it changed names. But it also can do much more now thanks to the `INumber` interface added in .NET 7.0. If you're using that framework or above, the fraction type is fully compatible with that type, and all the math functions in `MathE` and elsewhere that use `INumber` will work with it.
|
||||
|
||||
Can I just say that the `INumber` interface is really annoying to write a type for? There's so many weird casting functions and a whole lot of methods that even the .NET developers will hide in public declarations. Why have them at all?
|
||||
|
||||
---
|
||||
|
||||
And I want to change the name of the `MathE` class. I'm thinking `Math2`, but I'm open to suggestions.
|
||||
|
||||
## And Best of All, Matrices
|
||||
|
||||
Oh yeah, we're adding those things again. I haven't completed (or even started) the dynamic `Matrix` class, that will arrive in beta3. But I have the `Matrix2x2`, `Matrix3x3`, and `Matrix4x4` fully implemented. The `ToString()` methods are much better with the new implementation than previously, and the `GetHashCode()` methods give different results even if the numbers have their positions swapped (which they originally didn't do).
|
||||
|
||||
And it's much faster. Much, much faster. Don't get me wrong, multiplying a 4x4 matrix still requires 64 multiplications and 48 additions, which is quite a lot, but my original implementation was littered with many method calls, easily doubling the runtime. I have now super-inlined basically all of the static matrix code. And I mean, replacing all method calls with nothing but multiplication and addition for things like the determinants, the cofactors, the inverses, and more. Don't look at the source, it's really ugly.
|
||||
|
||||
---
|
||||
|
||||
That's all the major stuff in this update! I'll see you guys in beta3!
|
||||
|
||||
P.S. I know that the System library also includes `Vector2`, `Vector3`, and `Vector4` types. I'll add casting support for them soon.
|
||||
I think the Image type will be completely reworked and might be what version 3.1 is.
|
||||
|
||||
5
Nerd_STF/Fill.cs
Normal file
5
Nerd_STF/Fill.cs
Normal file
@ -0,0 +1,5 @@
|
||||
namespace Nerd_STF
|
||||
{
|
||||
public delegate T Fill<T>(int index);
|
||||
public delegate T Fill2d<T>(int x, int y);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
18
Nerd_STF/Graphics/ColorChannel.cs
Normal file
18
Nerd_STF/Graphics/ColorChannel.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
public enum ColorChannel
|
||||
{
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
Alpha,
|
||||
Hue,
|
||||
Saturation,
|
||||
Value,
|
||||
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);
|
||||
}
|
||||
}
|
||||
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
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Nerd_STF.Helpers
|
||||
{
|
||||
public static class CordicHelper
|
||||
internal static class CordicHelper
|
||||
{
|
||||
// Starts at 4 radians. Each index downwards is half that.
|
||||
// Goes from 2^2 to 2^-19.
|
||||
|
||||
@ -16,7 +16,7 @@ namespace Nerd_STF.Helpers
|
||||
int y = 0;
|
||||
foreach (double v in part)
|
||||
{
|
||||
matrix[byRows ? (x, y) : (y, x)] = v;
|
||||
matrix[byRows ? (y, x) : (x, y)] = v;
|
||||
y++;
|
||||
if (byRows ? y >= size.x : y >= size.y) break;
|
||||
}
|
||||
@ -35,12 +35,12 @@ namespace Nerd_STF.Helpers
|
||||
int y = 0;
|
||||
foreach (double v in part)
|
||||
{
|
||||
matrix[byRows ? (x, y) : (y, x)] = v;
|
||||
matrix[byRows ? (y, x) : (x, y)] = v;
|
||||
y++;
|
||||
if (byRows ? y >= size.x : y >= size.y) break;
|
||||
if (byRows ? y >= size.y : y >= size.x) break;
|
||||
}
|
||||
x++;
|
||||
if (byRows ? x >= size.y : x >= size.x) break;
|
||||
if (byRows ? x >= size.x : x >= size.y) break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,41 @@
|
||||
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
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Nerd_STF.Mathematics;
|
||||
using Nerd_STF.Mathematics.Algebra;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
@ -62,12 +63,12 @@ namespace Nerd_STF.Helpers
|
||||
// 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.x, size.y];
|
||||
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[x, y] = matrix[y, x].ToString(format);
|
||||
items[y, x] = matrix[x, y].ToString(format);
|
||||
|
||||
// Then write each line separately.
|
||||
StringBuilder[] lines = new StringBuilder[size.y + 2];
|
||||
StringBuilder[] lines = new StringBuilder[size.x + 2];
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
@ -78,16 +79,16 @@ namespace Nerd_STF.Helpers
|
||||
lines[i] = builder;
|
||||
}
|
||||
int totalLen = 0;
|
||||
for (int x = 0; x < size.x; x++)
|
||||
for (int x = 0; x < size.y; x++)
|
||||
{
|
||||
int maxLen = 0;
|
||||
for (int y = 0; y < size.y; y++)
|
||||
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.y; y++)
|
||||
for (int y = 0; y < size.x; y++)
|
||||
{
|
||||
StringBuilder builder = lines[y + 1];
|
||||
string item = items[x, y];
|
||||
@ -110,5 +111,44 @@ namespace Nerd_STF.Helpers
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,11 @@ namespace Nerd_STF
|
||||
{
|
||||
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]
|
||||
{
|
||||
@ -81,6 +86,12 @@ namespace Nerd_STF
|
||||
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);
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ namespace Nerd_STF.Mathematics.Algebra
|
||||
|
||||
static abstract TSelf operator *(TSelf a, double b);
|
||||
static abstract TSelf operator /(TSelf a, double b);
|
||||
static abstract TSelf operator ^(TSelf a, TSelf b);
|
||||
static abstract TSelf? operator ~(TSelf m);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ namespace Nerd_STF.Mathematics.Algebra
|
||||
double Magnitude { get; }
|
||||
|
||||
static abstract TSelf ClampMagnitude(TSelf val, double minMag, double maxMag);
|
||||
static abstract void ClampMagnitude(ref TSelf val, double minMag, double maxMag);
|
||||
static abstract double Dot(TSelf a, TSelf b);
|
||||
static abstract double Dot(IEnumerable<TSelf> vals);
|
||||
}
|
||||
|
||||
424
Nerd_STF/Mathematics/Algebra/Matrix.cs
Normal file
424
Nerd_STF/Mathematics/Algebra/Matrix.cs
Normal file
@ -0,0 +1,424 @@
|
||||
using Nerd_STF.Helpers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Nerd_STF.Mathematics.Algebra
|
||||
{
|
||||
public class Matrix : IMatrix<Matrix>,
|
||||
ISubmatrixOperations<Matrix, Matrix>
|
||||
{
|
||||
public static Matrix Identity(int size) =>
|
||||
new Matrix((size, size), (int r, int c) => r == c ? 1 : 0);
|
||||
public static Matrix IdentityIsh(Int2 size) =>
|
||||
new Matrix(size, (int r, int c) => r == c ? 1 : 0);
|
||||
|
||||
public static Matrix Empty => new Matrix();
|
||||
public static Matrix Zero(Int2 size) => new Matrix(size);
|
||||
|
||||
public Int2 Size => size;
|
||||
|
||||
private readonly double[] terms;
|
||||
private readonly Int2 size;
|
||||
|
||||
public Matrix()
|
||||
{
|
||||
size = (0, 0);
|
||||
terms = TargetHelper.EmptyArray<double>();
|
||||
}
|
||||
public Matrix(Matrix copy)
|
||||
{
|
||||
size = copy.size;
|
||||
terms = new double[copy.terms.Length];
|
||||
Array.Copy(copy.terms, terms, copy.terms.Length);
|
||||
}
|
||||
public Matrix(Matrix2x2 copy)
|
||||
{
|
||||
size = (2, 2);
|
||||
terms = new double[]
|
||||
{
|
||||
copy.r0c0, copy.r0c1,
|
||||
copy.r1c0, copy.r1c1
|
||||
};
|
||||
}
|
||||
public Matrix(Matrix3x3 copy)
|
||||
{
|
||||
size = (3, 3);
|
||||
terms = new double[]
|
||||
{
|
||||
copy.r0c0, copy.r0c1, copy.r0c2,
|
||||
copy.r1c0, copy.r1c1, copy.r1c2,
|
||||
copy.r2c0, copy.r2c1, copy.r2c2
|
||||
};
|
||||
}
|
||||
public Matrix(Matrix4x4 copy)
|
||||
{
|
||||
size = (4, 4);
|
||||
terms = new double[]
|
||||
{
|
||||
copy.r0c0, copy.r0c1, copy.r0c2, copy.r0c3,
|
||||
copy.r1c0, copy.r1c1, copy.r1c2, copy.r1c3,
|
||||
copy.r2c0, copy.r2c1, copy.r2c2, copy.r2c3,
|
||||
copy.r3c0, copy.r3c1, copy.r3c2, copy.r3c3
|
||||
};
|
||||
}
|
||||
public Matrix(Int2 size)
|
||||
{
|
||||
this.size = size;
|
||||
terms = new double[size.x * size.y];
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the array is of the form [c, r], <see langword="false"/> if the array is of the form [r, c].</param>
|
||||
public Matrix(Int2 size, double[,] terms, bool byRows = false)
|
||||
{
|
||||
this.size = size;
|
||||
this.terms = new double[size.x * size.y];
|
||||
for (int r = 0; r < size.x; r++)
|
||||
{
|
||||
for (int c = 0; c < size.y; c++)
|
||||
{
|
||||
if (byRows) this.terms[FlattenIndex(r, c)] = terms[c, r];
|
||||
else this.terms[FlattenIndex(r, c)] = terms[r, c];
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the enumerable is a collection of rows (form [c, r]), <see langword="false"/> if the enumerable is a collection of columns (form [r, c]).</param>
|
||||
public Matrix(Int2 size, IEnumerable<IEnumerable<double>> vals, bool byRows = false)
|
||||
{
|
||||
this.size = size;
|
||||
terms = new double[size.x * size.y];
|
||||
MatrixHelper.SetMatrixValues(this, vals, byRows);
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the enumerable is a collection of rows (form [c, r]), <see langword="false"/> if the enumerable is a collection of columns (form [r, c]).</param>
|
||||
public Matrix(Int2 size, IEnumerable<ListTuple<double>> vals, bool byRows = false)
|
||||
{
|
||||
this.size = size;
|
||||
terms = new double[size.x * size.y];
|
||||
MatrixHelper.SetMatrixValues(this, vals, byRows);
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the fill is a collection of rows (form [c, r]), <see langword="false"/> if the fill is a collection of columns (form [r, c]).</param>
|
||||
public Matrix(Int2 size, Fill2d<double> fill, bool byRows = false)
|
||||
{
|
||||
this.size = size;
|
||||
terms = new double[size.x * size.y];
|
||||
for (int r = 0; r < size.x; r++)
|
||||
{
|
||||
for (int c = 0; c < size.y; c++)
|
||||
{
|
||||
if (byRows) terms[FlattenIndex(r, c)] = fill(c, r);
|
||||
else terms[FlattenIndex(r, c)] = fill(r, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
private Matrix(Int2 size, double[] terms)
|
||||
{
|
||||
this.size = size;
|
||||
this.terms = terms;
|
||||
}
|
||||
|
||||
public double this[int r, int c]
|
||||
{
|
||||
get => terms[FlattenIndex(r, c)];
|
||||
set => terms[FlattenIndex(r, c)] = value;
|
||||
}
|
||||
public double this[Int2 index]
|
||||
{
|
||||
get => terms[FlattenIndex(index.x, index.y)];
|
||||
set => terms[FlattenIndex(index.x, index.y)] = value;
|
||||
}
|
||||
public ListTuple<double> this[int index, RowColumn direction]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case RowColumn.Row: return GetRow(index);
|
||||
case RowColumn.Column: return GetColumn(index);
|
||||
default: throw new ArgumentException($"Invalid direction {direction}.");
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case RowColumn.Row: SetRow(index, value); break;
|
||||
case RowColumn.Column: SetColumn(index, value); break;
|
||||
default: throw new ArgumentException($"Invalid direction {direction}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double TryGet(int r, int c)
|
||||
{
|
||||
if (r >= Size.x || c >= Size.y)
|
||||
{
|
||||
if (r == c) return 1;
|
||||
else return 0;
|
||||
}
|
||||
else return terms[FlattenIndex(r, c)];
|
||||
}
|
||||
|
||||
public static Matrix FromMatrix<T>(T mat)
|
||||
where T : IMatrix<T> =>
|
||||
new Matrix(mat.Size, (int r, int c) => mat[r, c]);
|
||||
|
||||
public static Matrix Average(IEnumerable<Matrix> vals)
|
||||
{
|
||||
#if CS8_OR_GREATER
|
||||
Matrix? result = null;
|
||||
#else
|
||||
Matrix result = null;
|
||||
#endif
|
||||
int count = 0;
|
||||
foreach (Matrix m in vals)
|
||||
{
|
||||
if (result is null) result = m;
|
||||
else ThrowIfSizeDifferent(result, m);
|
||||
result += m;
|
||||
count++;
|
||||
}
|
||||
return result is null ? Empty : result / count;
|
||||
}
|
||||
public static Matrix Lerp(Matrix a, Matrix b, double t, bool clamp = true)
|
||||
{
|
||||
ThrowIfSizeDifferent(a, b);
|
||||
if (clamp) MathE.Clamp(ref t, 0, 1);
|
||||
double[] vals = new double[a.terms.Length];
|
||||
for (int i = 0; i < vals.Length; i++)
|
||||
{
|
||||
vals[i] = a.terms[i] + t * (b.terms[i] - a.terms[i]);
|
||||
}
|
||||
return new Matrix(a.size, vals);
|
||||
}
|
||||
public static Matrix Product(IEnumerable<Matrix> vals)
|
||||
{
|
||||
#if CS8_OR_GREATER
|
||||
Matrix? result = null;
|
||||
#else
|
||||
Matrix result = null;
|
||||
#endif
|
||||
foreach (Matrix m in vals)
|
||||
{
|
||||
if (result is null) result = m;
|
||||
else ThrowIfSizeDifferent(result, m);
|
||||
result *= m;
|
||||
}
|
||||
return result ?? Empty;
|
||||
}
|
||||
public static Matrix Sum(IEnumerable<Matrix> vals)
|
||||
{
|
||||
#if CS8_OR_GREATER
|
||||
Matrix? result = null;
|
||||
#else
|
||||
Matrix result = null;
|
||||
#endif
|
||||
foreach (Matrix m in vals)
|
||||
{
|
||||
if (result is null) result = m;
|
||||
else ThrowIfSizeDifferent(result, m);
|
||||
result += m;
|
||||
}
|
||||
return result ?? Empty;
|
||||
}
|
||||
|
||||
public ListTuple<double> GetRow(int row)
|
||||
{
|
||||
double[] vals = new double[size.y];
|
||||
for (int c = 0; c < size.y; c++) vals[c] = terms[FlattenIndex(row, c)];
|
||||
return new ListTuple<double>(vals);
|
||||
}
|
||||
public ListTuple<double> GetColumn(int col)
|
||||
{
|
||||
double[] vals = new double[size.x];
|
||||
for (int r = 0; r < size.x; r++) vals[r] = terms[FlattenIndex(r, col)];
|
||||
return new ListTuple<double>(vals);
|
||||
}
|
||||
public void SetRow(int row, IEnumerable<double> vals) => MatrixHelper.SetRow(this, row, vals);
|
||||
public void SetColumn(int column, IEnumerable<double> vals) => MatrixHelper.SetColumn(this, column, vals);
|
||||
public void SetRow(int row, ListTuple<double> vals)
|
||||
{
|
||||
for (int c = 0; c < size.y; c++) terms[FlattenIndex(row, c)] = vals[c];
|
||||
}
|
||||
public void SetColumn(int col, ListTuple<double> vals)
|
||||
{
|
||||
for (int r = 0; r < size.x; r++) terms[FlattenIndex(r, col)] = vals[r];
|
||||
}
|
||||
|
||||
public double Determinant()
|
||||
{
|
||||
ThrowIfNotSquare();
|
||||
|
||||
if (size.x == 1) return terms[0];
|
||||
else if (size.x == 2) return terms[0] * terms[3] - terms[1] * terms[2];
|
||||
else
|
||||
{
|
||||
double sum = 0;
|
||||
for (int c = 0; c < size.y; c++)
|
||||
{
|
||||
Matrix sub = Submatrix(0, c);
|
||||
if (c % 2 == 0) sum += terms[FlattenIndex(0, c)] * sub.Determinant();
|
||||
else sum -= terms[FlattenIndex(0, c)] * sub.Determinant();
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
public Matrix Adjoint() => Cofactor().Transpose();
|
||||
public Matrix Cofactor() =>
|
||||
new Matrix(size, (int r, int c) => Submatrix(r, c).Determinant() * ((r + c) % 2 == 0 ? 1 : -1));
|
||||
public Matrix Inverse() => Adjoint() / Determinant();
|
||||
public Matrix Transpose() =>
|
||||
new Matrix((size.y, size.x), (int r, int c) => terms[FlattenIndex(c, r)]);
|
||||
public Matrix Submatrix(int r, int c)
|
||||
{
|
||||
if (size.x <= 1 || size.y <= 1) throw new InvalidOperationException($"This matrix is too small to contain any sub-matrices.");
|
||||
Matrix smaller = new Matrix((size.x - 1, size.y - 1));
|
||||
|
||||
bool pastR = false;
|
||||
for (int r2 = 0; r2 < size.x; r2++)
|
||||
{
|
||||
if (r2 == r)
|
||||
{
|
||||
pastR = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool pastC = false;
|
||||
for (int c2 = 0; c2 < size.y; c2++)
|
||||
{
|
||||
if (c2 == c)
|
||||
{
|
||||
pastC = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
int trueR = pastR ? r2 - 1 : r2,
|
||||
trueC = pastC ? c2 - 1 : c2;
|
||||
smaller.terms[smaller.FlattenIndex(trueR, trueC)] = terms[FlattenIndex(r2, c2)];
|
||||
}
|
||||
}
|
||||
return smaller;
|
||||
}
|
||||
public double Trace()
|
||||
{
|
||||
ThrowIfNotSquare();
|
||||
double sum = 0;
|
||||
for (int i = 0; i < size.x; i++) sum += terms[FlattenIndex(i, i)];
|
||||
return sum;
|
||||
}
|
||||
public double TraceIsh()
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < MathE.Min(size.x, size.y); i++) sum += terms[FlattenIndex(i, i)];
|
||||
return sum;
|
||||
}
|
||||
|
||||
public IEnumerator<double> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < terms.Length; i++) yield return terms[i];
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
#if CS8_OR_GREATER
|
||||
public bool Equals(Matrix? other)
|
||||
#else
|
||||
public bool Equals(Matrix other)
|
||||
#endif
|
||||
{
|
||||
if (other is null) return false;
|
||||
else if (size != other.size) return false;
|
||||
|
||||
for (int i = 0; i < terms.Length; i++)
|
||||
if (terms[i] != other.terms[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 Matrix otherMat) return Equals(otherMat);
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int total = 0;
|
||||
for (int i = 0; i < terms.Length; i++)
|
||||
{
|
||||
total ^= terms[i].GetHashCode() & i.GetHashCode();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
public override string ToString() => ToStringHelper.MatrixToString(this, null);
|
||||
#if CS8_OR_GREATER
|
||||
public string ToString(string? format) => ToStringHelper.MatrixToString(this, format);
|
||||
public string ToString(string? format, IFormatProvider? provider) => ToStringHelper.MatrixToString(this, format);
|
||||
#else
|
||||
public string ToString(string format) => ToStringHelper.MatrixToString(this, format);
|
||||
public string ToString(string format, IFormatProvider provider) => ToStringHelper.MatrixToString(this, format);
|
||||
#endif
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int FlattenIndex(int r, int c) => r * size.y + c;
|
||||
|
||||
private void ThrowIfNotSquare()
|
||||
{
|
||||
if (size.x != size.y) throw new InvalidOperationException("This operation only applies to a square matrix.");
|
||||
}
|
||||
private static void ThrowIfSizeDifferent(Matrix a, Matrix b)
|
||||
{
|
||||
if (a.size != b.size) throw new InvalidOperationException("This operation only applies to matrices with the same dimensions.");
|
||||
}
|
||||
|
||||
public static Matrix operator +(Matrix a) => new Matrix(a);
|
||||
public static Matrix operator +(Matrix a, Matrix b)
|
||||
{
|
||||
ThrowIfSizeDifferent(a, b);
|
||||
double[] terms = new double[a.terms.Length];
|
||||
for (int i = 0; i < a.terms.Length; i++) terms[i] = a.terms[i] + b.terms[i];
|
||||
return new Matrix(a.size, terms);
|
||||
}
|
||||
public static Matrix operator -(Matrix a)
|
||||
{
|
||||
double[] terms = new double[a.terms.Length];
|
||||
for (int i = 0; i < a.terms.Length; i++) terms[i] = -a.terms[i];
|
||||
return new Matrix(a.size, terms);
|
||||
}
|
||||
public static Matrix operator -(Matrix a, Matrix b)
|
||||
{
|
||||
ThrowIfSizeDifferent(a, b);
|
||||
double[] terms = new double[a.terms.Length];
|
||||
for (int i = 0; i < a.terms.Length; i++) terms[i] = a.terms[i] - b.terms[i];
|
||||
return new Matrix(a.size, terms);
|
||||
}
|
||||
public static Matrix operator *(Matrix a, double b)
|
||||
{
|
||||
double[] terms = new double[a.terms.Length];
|
||||
for (int i = 0; i < a.terms.Length; i++) terms[i] = a.terms[i] * b;
|
||||
return new Matrix(a.size, terms);
|
||||
}
|
||||
public static Matrix operator *(Matrix a, Matrix b)
|
||||
{
|
||||
if (a.size.y != b.size.x) throw new InvalidOperationException("The dimensions of these matrices are incompatible with one another.");
|
||||
return new Matrix((a.size.x, b.size.y), (int r, int c) => MathE.Dot(a.GetRow(r), b.GetColumn(c)));
|
||||
}
|
||||
public static Matrix operator /(Matrix a, double b)
|
||||
{
|
||||
double[] terms = new double[a.terms.Length];
|
||||
for (int i = 0; i < a.terms.Length; i++) terms[i] = a.terms[i] / b;
|
||||
return new Matrix(a.size, terms);
|
||||
}
|
||||
public static Matrix operator ^(Matrix a, Matrix b)
|
||||
{
|
||||
ThrowIfSizeDifferent(a, b);
|
||||
double[] terms = new double[a.terms.Length];
|
||||
for (int i = 0; i < a.terms.Length; i++) terms[i] = a.terms[i] * b.terms[i];
|
||||
return new Matrix(a.size, terms);
|
||||
}
|
||||
public static Matrix operator ~(Matrix a) => a.Inverse();
|
||||
public static bool operator ==(Matrix a, Matrix b) => a.Equals(b);
|
||||
public static bool operator !=(Matrix a, Matrix b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
@ -46,29 +46,57 @@ namespace Nerd_STF.Mathematics.Algebra
|
||||
this.r1c1 = r1c1;
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the array is of the form [c, r], <see langword="false"/> if the array is of the form [r, c].</param>
|
||||
public Matrix2x2(double[,] vals, bool byRows = true)
|
||||
public Matrix2x2(double[,] vals, bool byRows = false)
|
||||
{
|
||||
if (byRows) // Collection of rows ([c, r])
|
||||
{
|
||||
r0c0 = vals[0, 0]; r0c1 = vals[0, 1];
|
||||
r1c0 = vals[1, 0]; r1c1 = vals[1, 1];
|
||||
}
|
||||
else // Collection of columns ([r, c])
|
||||
{
|
||||
r0c0 = vals[0, 0]; r0c1 = vals[1, 0];
|
||||
r1c0 = vals[0, 1]; r1c1 = vals[1, 1];
|
||||
}
|
||||
else // Collection of columns ([r, c])
|
||||
{
|
||||
r0c0 = vals[0, 0]; r0c1 = vals[0, 1];
|
||||
r1c0 = vals[1, 0]; r1c1 = vals[1, 1];
|
||||
}
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the enumerable is a collection of rows (form [c, r]), <see langword="false"/> if the enumerable is a collection of columns (form [r, c]).</param>
|
||||
public Matrix2x2(IEnumerable<IEnumerable<double>> vals, bool byRows = true)
|
||||
public Matrix2x2(IEnumerable<IEnumerable<double>> vals, bool byRows = false)
|
||||
{
|
||||
MatrixHelper.SetMatrixValues(this, vals, byRows);
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the enumerable is a collection of rows (form [c, r]), <see langword="false"/> if the enumerable is a collection of columns (form [r, c]).</param>
|
||||
public Matrix2x2(IEnumerable<ListTuple<double>> vals, bool byRows = true)
|
||||
public Matrix2x2(IEnumerable<ListTuple<double>> vals, bool byRows = false)
|
||||
{
|
||||
MatrixHelper.SetMatrixValues(this, vals, byRows);
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the fill goes through columns for each row, <see langword="false"/> if the fill goes through rows for each column.</param>
|
||||
public Matrix2x2(Fill<double> fill, bool byRows = false)
|
||||
{
|
||||
if (byRows)
|
||||
{
|
||||
r0c0 = fill(0); r0c1 = fill(2);
|
||||
r1c0 = fill(1); r1c1 = fill(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
r0c0 = fill(0); r0c1 = fill(1);
|
||||
r1c0 = fill(2); r1c1 = fill(3);
|
||||
}
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the fill is a collection of rows (form [c, r]), <see langword="false"/> if the fill is a collection of columns (form [r, c]).</param>
|
||||
public Matrix2x2(Fill2d<double> fill, bool byRows = false)
|
||||
{
|
||||
if (byRows)
|
||||
{
|
||||
r0c0 = fill(0, 0); r0c1 = fill(1, 0);
|
||||
r1c0 = fill(0, 1); r1c1 = fill(1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
r0c0 = fill(0, 0); r0c1 = fill(0, 1);
|
||||
r1c0 = fill(1, 0); r1c1 = fill(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public double this[int r, int c]
|
||||
{
|
||||
@ -314,10 +342,16 @@ namespace Nerd_STF.Mathematics.Algebra
|
||||
public static Matrix2x2 operator /(Matrix2x2 a, double b) =>
|
||||
new Matrix2x2(a.r0c0 / b, a.r0c1 / b,
|
||||
a.r1c0 / b, a.r1c1 / b);
|
||||
public static Matrix2x2 operator ^(Matrix2x2 a, Matrix2x2 b) =>
|
||||
new Matrix2x2(a.r0c0 * b.r0c0, a.r0c1 * b.r0c1,
|
||||
a.r1c0 * b.r1c0, a.r1c1 * b.r1c1);
|
||||
public static Matrix2x2 operator ~(Matrix2x2 a) => a.Inverse();
|
||||
public static bool operator ==(Matrix2x2 a, Matrix2x2 b) => a.Equals(b);
|
||||
public static bool operator !=(Matrix2x2 a, Matrix2x2 b) => !a.Equals(b);
|
||||
|
||||
public static explicit operator Matrix2x2(Matrix mat) =>
|
||||
new Matrix2x2(mat.TryGet(0, 0), mat.TryGet(0, 1),
|
||||
mat.TryGet(1, 0), mat.TryGet(1, 1));
|
||||
public static explicit operator Matrix2x2(Matrix3x3 mat) =>
|
||||
new Matrix2x2(mat.r0c0, mat.r0c1,
|
||||
mat.r1c0, mat.r1c1);
|
||||
|
||||
@ -51,31 +51,63 @@ namespace Nerd_STF.Mathematics.Algebra
|
||||
this.r2c0 = r2c0; this.r2c1 = r2c1; this.r2c2 = r2c2;
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the array is of the form [c, r], <see langword="false"/> if the array is of the form [r, c].</param>
|
||||
public Matrix3x3(double[,] vals, bool byRows = true)
|
||||
public Matrix3x3(double[,] vals, bool byRows = false)
|
||||
{
|
||||
if (byRows) // Collection of rows ([c, r])
|
||||
{
|
||||
r0c0 = vals[0, 0]; r0c1 = vals[0, 1]; r0c2 = vals[0, 2];
|
||||
r1c0 = vals[1, 0]; r1c1 = vals[1, 1]; r1c2 = vals[1, 2];
|
||||
r2c0 = vals[2, 0]; r2c1 = vals[2, 1]; r2c2 = vals[2, 2];
|
||||
}
|
||||
else // Collection of columns ([r, c])
|
||||
{
|
||||
r0c0 = vals[0, 0]; r0c1 = vals[1, 0]; r0c2 = vals[2, 0];
|
||||
r1c0 = vals[0, 1]; r1c1 = vals[1, 1]; r1c2 = vals[2, 1];
|
||||
r2c0 = vals[0, 2]; r2c1 = vals[1, 2]; r2c2 = vals[2, 2];
|
||||
}
|
||||
else // Collection of columns ([r, c])
|
||||
{
|
||||
r0c0 = vals[0, 0]; r0c1 = vals[0, 1]; r0c2 = vals[0, 2];
|
||||
r1c0 = vals[1, 0]; r1c1 = vals[1, 1]; r1c2 = vals[1, 2];
|
||||
r2c0 = vals[2, 0]; r2c1 = vals[2, 1]; r2c2 = vals[2, 2];
|
||||
}
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the enumerable is a collection of rows (form [c, r]), <see langword="false"/> if the enumerable is a collection of columns (form [r, c]).</param>
|
||||
public Matrix3x3(IEnumerable<IEnumerable<double>> vals, bool byRows = true)
|
||||
public Matrix3x3(IEnumerable<IEnumerable<double>> vals, bool byRows = false)
|
||||
{
|
||||
MatrixHelper.SetMatrixValues(this, vals, byRows);
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the enumerable is a collection of rows (form [c, r]), <see langword="false"/> if the enumerable is a collection of columns (form [r, c]).</param>
|
||||
public Matrix3x3(IEnumerable<ListTuple<double>> vals, bool byRows = true)
|
||||
public Matrix3x3(IEnumerable<ListTuple<double>> vals, bool byRows = false)
|
||||
{
|
||||
MatrixHelper.SetMatrixValues(this, vals, byRows);
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the fill goes through columns for each row, <see langword="false"/> if the fill goes through rows for each column.</param>
|
||||
public Matrix3x3(Fill<double> fill, bool byRows = false)
|
||||
{
|
||||
if (byRows)
|
||||
{
|
||||
r0c0 = fill(0); r0c1 = fill(3); r0c2 = fill(6);
|
||||
r1c0 = fill(1); r1c1 = fill(4); r1c2 = fill(7);
|
||||
r2c0 = fill(2); r2c1 = fill(5); r2c2 = fill(8);
|
||||
}
|
||||
else
|
||||
{
|
||||
r0c0 = fill(0); r0c1 = fill(1); r0c2 = fill(2);
|
||||
r1c0 = fill(3); r1c1 = fill(4); r1c2 = fill(5);
|
||||
r2c0 = fill(6); r2c1 = fill(7); r2c2 = fill(8);
|
||||
}
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the fill is a collection of rows (form [c, r]), <see langword="false"/> if the fill is a collection of columns (form [r, c]).</param>
|
||||
public Matrix3x3(Fill2d<double> fill, bool byRows = false)
|
||||
{
|
||||
if (byRows)
|
||||
{
|
||||
r0c0 = fill(0, 0); r0c1 = fill(1, 0); r0c2 = fill(2, 0);
|
||||
r1c0 = fill(0, 1); r1c1 = fill(1, 1); r1c2 = fill(2, 1);
|
||||
r2c0 = fill(0, 2); r2c1 = fill(1, 2); r2c2 = fill(2, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
r0c0 = fill(0, 0); r0c1 = fill(0, 1); r0c2 = fill(0, 2);
|
||||
r1c0 = fill(1, 0); r1c1 = fill(1, 1); r1c2 = fill(1, 2);
|
||||
r2c0 = fill(2, 0); r2c1 = fill(2, 1); r2c2 = fill(2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public double this[int r, int c]
|
||||
{
|
||||
@ -402,10 +434,18 @@ namespace Nerd_STF.Mathematics.Algebra
|
||||
new Matrix3x3(a.r0c0 / b, a.r0c1 / b, a.r0c2 / b,
|
||||
a.r1c0 / b, a.r1c1 / b, a.r1c2 / b,
|
||||
a.r2c0 / b, a.r2c1 / b, a.r2c2 / b);
|
||||
public static Matrix3x3 operator ^(Matrix3x3 a, Matrix3x3 b) =>
|
||||
new Matrix3x3(a.r0c0 * b.r0c0, a.r0c1 * b.r0c1, a.r0c2 * b.r0c2,
|
||||
a.r1c0 * b.r1c0, a.r1c1 * b.r1c1, a.r1c2 * b.r1c2,
|
||||
a.r2c0 * b.r2c0, a.r2c1 * b.r2c1, a.r2c2 * b.r2c2);
|
||||
public static Matrix3x3 operator ~(Matrix3x3 a) => a.Inverse();
|
||||
public static bool operator ==(Matrix3x3 a, Matrix3x3 b) => a.Equals(b);
|
||||
public static bool operator !=(Matrix3x3 a, Matrix3x3 b) => !a.Equals(b);
|
||||
|
||||
public static explicit operator Matrix3x3(Matrix mat) =>
|
||||
new Matrix3x3(mat.TryGet(0, 0), mat.TryGet(0, 1), mat.TryGet(0, 2),
|
||||
mat.TryGet(1, 0), mat.TryGet(1, 1), mat.TryGet(1, 2),
|
||||
mat.TryGet(2, 0), mat.TryGet(2, 1), mat.TryGet(2, 2));
|
||||
public static implicit operator Matrix3x3(Matrix2x2 mat) =>
|
||||
new Matrix3x3(mat.r0c0, mat.r0c1, 0,
|
||||
mat.r1c0, mat.r1c1, 0,
|
||||
|
||||
@ -57,33 +57,69 @@ namespace Nerd_STF.Mathematics.Algebra
|
||||
this.r3c0 = r3c0; this.r3c1 = r3c1; this.r3c2 = r3c2; this.r3c3 = r3c3;
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the array is of the form [c, r], <see langword="false"/> if the array is of the form [r, c].</param>
|
||||
public Matrix4x4(double[,] vals, bool byRows = true)
|
||||
public Matrix4x4(double[,] vals, bool byRows = false)
|
||||
{
|
||||
if (byRows) // Collection of rows ([c, r])
|
||||
{
|
||||
r0c0 = vals[0, 0]; r0c1 = vals[0, 1]; r0c2 = vals[0, 2]; r0c3 = vals[0, 3];
|
||||
r1c0 = vals[1, 0]; r1c1 = vals[1, 1]; r1c2 = vals[1, 2]; r1c3 = vals[1, 3];
|
||||
r2c0 = vals[2, 0]; r2c1 = vals[2, 1]; r2c2 = vals[2, 2]; r2c3 = vals[2, 3];
|
||||
r3c0 = vals[3, 0]; r3c1 = vals[3, 1]; r3c2 = vals[3, 2]; r3c3 = vals[3, 3];
|
||||
}
|
||||
else
|
||||
{
|
||||
r0c0 = vals[0, 0]; r0c1 = vals[1, 0]; r0c2 = vals[2, 0]; r0c3 = vals[3, 0];
|
||||
r1c0 = vals[0, 1]; r1c1 = vals[1, 1]; r1c2 = vals[2, 1]; r1c3 = vals[3, 1];
|
||||
r2c0 = vals[0, 2]; r2c1 = vals[1, 2]; r2c2 = vals[2, 2]; r2c3 = vals[3, 2];
|
||||
r3c0 = vals[0, 3]; r3c1 = vals[1, 3]; r3c2 = vals[2, 3]; r3c3 = vals[3, 3];
|
||||
}
|
||||
else
|
||||
{
|
||||
r0c0 = vals[0, 0]; r0c1 = vals[0, 1]; r0c2 = vals[0, 2]; r0c3 = vals[0, 3];
|
||||
r1c0 = vals[1, 0]; r1c1 = vals[1, 1]; r1c2 = vals[1, 2]; r1c3 = vals[1, 3];
|
||||
r2c0 = vals[2, 0]; r2c1 = vals[2, 1]; r2c2 = vals[2, 2]; r2c3 = vals[2, 3];
|
||||
r3c0 = vals[3, 0]; r3c1 = vals[3, 1]; r3c2 = vals[3, 2]; r3c3 = vals[3, 3];
|
||||
}
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the enumerable is a collection of rows (form [c, r]), <see langword="false"/> if the enumerable is a collection of columns (form [r, c]).</param>
|
||||
public Matrix4x4(IEnumerable<IEnumerable<double>> vals, bool byRows = true)
|
||||
public Matrix4x4(IEnumerable<IEnumerable<double>> vals, bool byRows = false)
|
||||
{
|
||||
MatrixHelper.SetMatrixValues(this, vals, byRows);
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the enumerable is a collection of rows (form [c, r]), <see langword="false"/> if the enumerable is a collection of columns (form [r, c]).</param>
|
||||
public Matrix4x4(IEnumerable<ListTuple<double>> vals, bool byRows = true)
|
||||
public Matrix4x4(IEnumerable<ListTuple<double>> vals, bool byRows = false)
|
||||
{
|
||||
MatrixHelper.SetMatrixValues(this, vals, byRows);
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the fill goes through columns for each row, <see langword="false"/> if the fill goes through rows for each column.</param>
|
||||
public Matrix4x4(Fill<double> fill, bool byRows = false)
|
||||
{
|
||||
if (byRows)
|
||||
{
|
||||
r0c0 = fill(0); r0c1 = fill(4); r0c2 = fill( 8); r0c3 = fill(12);
|
||||
r1c0 = fill(1); r1c1 = fill(5); r1c2 = fill( 9); r1c3 = fill(13);
|
||||
r2c0 = fill(2); r2c1 = fill(6); r2c2 = fill(10); r2c3 = fill(14);
|
||||
r3c0 = fill(3); r3c1 = fill(7); r3c2 = fill(11); r3c3 = fill(15);
|
||||
}
|
||||
else
|
||||
{
|
||||
r0c0 = fill( 0); r0c1 = fill( 1); r0c2 = fill( 2); r0c3 = fill( 3);
|
||||
r1c0 = fill( 4); r1c1 = fill( 5); r1c2 = fill( 6); r1c3 = fill( 7);
|
||||
r2c0 = fill( 8); r2c1 = fill( 9); r2c2 = fill(10); r2c3 = fill(11);
|
||||
r3c0 = fill(12); r3c1 = fill(13); r3c2 = fill(14); r3c3 = fill(15);
|
||||
}
|
||||
}
|
||||
/// <param name="byRows"><see langword="true"/> if the fill is a collection of rows (form [c, r]), <see langword="false"/> if the fill is a collection of columns (form [r, c]).</param>
|
||||
public Matrix4x4(Fill2d<double> fill, bool byRows = false)
|
||||
{
|
||||
if (byRows)
|
||||
{
|
||||
r0c0 = fill(0, 0); r0c1 = fill(1, 0); r0c2 = fill(2, 0); r0c3 = fill(3, 0);
|
||||
r1c0 = fill(0, 1); r1c1 = fill(1, 1); r1c2 = fill(2, 1); r1c3 = fill(3, 1);
|
||||
r2c0 = fill(0, 2); r2c1 = fill(1, 2); r2c2 = fill(2, 2); r2c3 = fill(3, 2);
|
||||
r3c0 = fill(0, 3); r3c1 = fill(1, 3); r3c2 = fill(2, 3); r3c3 = fill(3, 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
r0c0 = fill(0, 0); r0c1 = fill(0, 1); r0c2 = fill(0, 2); r0c3 = fill(0, 3);
|
||||
r1c0 = fill(1, 0); r1c1 = fill(1, 1); r1c2 = fill(1, 2); r1c3 = fill(1, 3);
|
||||
r2c0 = fill(2, 0); r2c1 = fill(2, 1); r2c2 = fill(2, 2); r2c3 = fill(2, 3);
|
||||
r3c0 = fill(3, 0); r3c1 = fill(3, 1); r3c2 = fill(3, 2); r3c3 = fill(3, 3);
|
||||
}
|
||||
}
|
||||
|
||||
public double this[int r, int c]
|
||||
{
|
||||
@ -544,10 +580,20 @@ namespace Nerd_STF.Mathematics.Algebra
|
||||
a.r1c0 / b, a.r1c1 / b, a.r1c2 / b, a.r1c3 / b,
|
||||
a.r2c0 / b, a.r2c1 / b, a.r2c2 / b, a.r2c3 / b,
|
||||
a.r3c0 / b, a.r3c1 / b, a.r3c2 / b, a.r3c3 / b);
|
||||
public static Matrix4x4 operator ^(Matrix4x4 a, Matrix4x4 b) =>
|
||||
new Matrix4x4(a.r0c0 * b.r0c0, a.r0c1 * b.r0c1, a.r0c2 * b.r0c2, a.r0c3 * b.r0c3,
|
||||
a.r1c0 * b.r1c0, a.r1c1 * b.r1c1, a.r1c2 * b.r1c2, a.r1c3 * b.r1c3,
|
||||
a.r2c0 * b.r2c0, a.r2c1 * b.r2c1, a.r2c2 * b.r2c2, a.r2c3 * b.r2c3,
|
||||
a.r3c0 * b.r3c0, a.r3c1 * b.r3c1, a.r3c2 * b.r3c2, a.r3c3 * b.r3c3);
|
||||
public static Matrix4x4 operator ~(Matrix4x4 a) => a.Inverse();
|
||||
public static bool operator ==(Matrix4x4 a, Matrix4x4 b) => a.Equals(b);
|
||||
public static bool operator !=(Matrix4x4 a, Matrix4x4 b) => !a.Equals(b);
|
||||
|
||||
public static explicit operator Matrix4x4(Matrix mat) =>
|
||||
new Matrix4x4(mat.TryGet(0, 0), mat.TryGet(0, 1), mat.TryGet(0, 2), mat.TryGet(0, 3),
|
||||
mat.TryGet(1, 0), mat.TryGet(1, 1), mat.TryGet(1, 2), mat.TryGet(1, 3),
|
||||
mat.TryGet(2, 0), mat.TryGet(2, 1), mat.TryGet(2, 2), mat.TryGet(2, 3),
|
||||
mat.TryGet(3, 0), mat.TryGet(3, 1), mat.TryGet(3, 2), mat.TryGet(3, 3));
|
||||
public static implicit operator Matrix4x4(Matrix2x2 mat) =>
|
||||
new Matrix4x4(1, 0 , 0 , 0,
|
||||
0, mat.r0c0, mat.r0c1, 0,
|
||||
|
||||
@ -9,6 +9,7 @@ namespace Nerd_STF.Mathematics
|
||||
IEquatable<Angle>
|
||||
#if CS11_OR_GREATER
|
||||
,IFromTuple<Angle, (double, Angle.Units)>,
|
||||
IInterpolable<Angle>,
|
||||
IPresets2d<Angle>
|
||||
#endif
|
||||
{
|
||||
@ -38,6 +39,11 @@ namespace Nerd_STF.Mathematics
|
||||
|
||||
private readonly double revTheta;
|
||||
|
||||
public static Angle FromDegrees(double deg) => new Angle(deg, Units.Degrees);
|
||||
public static Angle FromRadians(double rad) => new Angle(rad, Units.Radians);
|
||||
public static Angle FromGradians(double grade) => new Angle(grade, Units.Gradians);
|
||||
public static Angle FromRevolutions(double turns) => new Angle(turns, Units.Revolutions);
|
||||
|
||||
public Angle(double theta, Units unit)
|
||||
{
|
||||
switch (unit)
|
||||
@ -127,6 +133,8 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
public static Angle Clamp(Angle value, Angle min, Angle max) =>
|
||||
new Angle(MathE.Clamp(value.revTheta, min.revTheta, max.revTheta));
|
||||
public static Angle Lerp(Angle a, Angle b, double t, bool clamp = true) =>
|
||||
new Angle(MathE.Lerp(a.revTheta, b.revTheta, t, clamp));
|
||||
public static Angle Max(IEnumerable<Angle> values) => Max(false, values);
|
||||
public static Angle Max(bool normalize, IEnumerable<Angle> values)
|
||||
{
|
||||
|
||||
@ -21,6 +21,11 @@ namespace Nerd_STF.Mathematics.Equations
|
||||
M = m;
|
||||
B = b;
|
||||
}
|
||||
public Linear(Fill<double> fill)
|
||||
{
|
||||
B = fill(0);
|
||||
M = fill(1);
|
||||
}
|
||||
|
||||
public double this[double x] => M * x + B;
|
||||
public double Get(double x) => M * x + B;
|
||||
|
||||
@ -30,6 +30,16 @@ namespace Nerd_STF.Mathematics.Equations
|
||||
if (reverse) this.terms = TrimExcessTerms(terms.Reverse().ToArray());
|
||||
else this.terms = TrimExcessTerms(terms.ToArray());
|
||||
}
|
||||
public Polynomial(bool reverse, Fill<double> terms, int length)
|
||||
{
|
||||
this.terms = new double[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (reverse) this.terms[i] = terms(length - 1 - i);
|
||||
else this.terms[i] = terms(i);
|
||||
}
|
||||
this.terms = TrimExcessTerms(this.terms);
|
||||
}
|
||||
private static double[] TrimExcessTerms(double[] terms)
|
||||
{
|
||||
int newLength = terms.Length;
|
||||
|
||||
@ -42,6 +42,12 @@ namespace Nerd_STF.Mathematics.Equations
|
||||
B = 0;
|
||||
C = c;
|
||||
}
|
||||
public Quadratic(Fill<double> fill)
|
||||
{
|
||||
C = fill(0);
|
||||
B = fill(1);
|
||||
A = fill(2);
|
||||
}
|
||||
|
||||
public double this[double x] => A * x * x + B * x + C;
|
||||
public double Get(double x) => A * x * x + B * x + C;
|
||||
|
||||
@ -4,6 +4,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nerd_STF.Mathematics
|
||||
{
|
||||
@ -45,9 +46,14 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index >= 2) break;
|
||||
if (index == 2) break;
|
||||
}
|
||||
}
|
||||
public Float2(Fill<double> fill)
|
||||
{
|
||||
x = fill(0);
|
||||
y = fill(1);
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
@ -255,6 +261,19 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)})";
|
||||
|
||||
public double[] ToArray() => new double[] { x, y };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
Float2 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.x;
|
||||
case 1: return copy.y;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<double> ToList() => new List<double> { x, y };
|
||||
|
||||
public static Float2 operator +(Float2 a, Float2 b) => new Float2(a.x + b.x, a.y + b.y);
|
||||
@ -267,6 +286,7 @@ namespace Nerd_STF.Mathematics
|
||||
public static bool operator ==(Float2 a, Float2 b) => a.Equals(b);
|
||||
public static bool operator !=(Float2 a, Float2 b) => !a.Equals(b);
|
||||
|
||||
public static explicit operator Float2(Complex complex) => new Float2(complex.Real, complex.Imaginary);
|
||||
public static explicit operator Float2(Float3 floats) => new Float2(floats.x, floats.y);
|
||||
public static explicit operator Float2(Float4 floats) => new Float2(floats.x, floats.y);
|
||||
public static implicit operator Float2(Int2 ints) => new Float2(ints.x, ints.y);
|
||||
@ -276,6 +296,9 @@ namespace Nerd_STF.Mathematics
|
||||
public static implicit operator Float2(PointF point) => new Float2(point.X, point.Y);
|
||||
public static implicit operator Float2(Size point) => new Float2(point.Width, point.Height);
|
||||
public static implicit operator Float2(SizeF size) => new Float2(size.Width, size.Height);
|
||||
public static implicit operator Float2(Vector2 vec) => new Float2(vec.X, vec.Y);
|
||||
public static explicit operator Float2(Vector3 vec) => new Float2(vec.X, vec.Y);
|
||||
public static explicit operator Float2(Vector4 vec) => new Float2(vec.X, vec.Y);
|
||||
public static implicit operator Float2(ListTuple<double> tuple) => new Float2(tuple[0], tuple[1]);
|
||||
public static implicit operator Float2(ListTuple<int> tuple) => new Float2(tuple[0], tuple[1]);
|
||||
public static implicit operator Float2((double, double) tuple) => new Float2(tuple.Item1, tuple.Item2);
|
||||
@ -284,6 +307,9 @@ namespace Nerd_STF.Mathematics
|
||||
public static implicit operator PointF(Float2 group) => new PointF((float)group.x, (float)group.y);
|
||||
public static explicit operator Size(Float2 group) => new Size((int)group.x, (int)group.y);
|
||||
public static implicit operator SizeF(Float2 group) => new SizeF((float)group.x, (float)group.y);
|
||||
public static implicit operator Vector2(Float2 group) => new Vector2((float)group.x, (float)group.y);
|
||||
public static implicit operator Vector3(Float2 group) => new Vector3((float)group.x, (float)group.y, 0);
|
||||
public static implicit operator Vector4(Float2 group) => new Vector4((float)group.x, (float)group.y, 0, 0);
|
||||
public static implicit operator ListTuple<double>(Float2 group) => new ListTuple<double>(group.x, group.y);
|
||||
public static explicit operator ListTuple<int>(Float2 group) => new ListTuple<int>((int)group.x, (int)group.y);
|
||||
public static implicit operator ValueTuple<double, double>(Float2 group) => (group.x, group.y);
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Graphics;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nerd_STF.Mathematics
|
||||
{
|
||||
@ -48,9 +50,15 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index >= 2) break;
|
||||
if (index == 3) break;
|
||||
}
|
||||
}
|
||||
public Float3(Fill<double> fill)
|
||||
{
|
||||
x = fill(0);
|
||||
y = fill(1);
|
||||
z = fill(2);
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
@ -120,7 +128,7 @@ namespace Nerd_STF.Mathematics
|
||||
total += val;
|
||||
count++;
|
||||
}
|
||||
return total;
|
||||
return total / count;
|
||||
}
|
||||
public static Int3 Ceiling(Float3 val) =>
|
||||
new Int3(MathE.Ceiling(val.x),
|
||||
@ -281,6 +289,20 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})";
|
||||
|
||||
public double[] ToArray() => new double[] { x, y, z };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
Float3 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.x;
|
||||
case 1: return copy.y;
|
||||
case 2: return copy.z;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<double> ToList() => new List<double> { x, y, z };
|
||||
|
||||
public static Float3 operator +(Float3 a, Float3 b) => new Float3(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
@ -293,15 +315,22 @@ namespace Nerd_STF.Mathematics
|
||||
public static bool operator ==(Float3 a, Float3 b) => a.Equals(b);
|
||||
public static bool operator !=(Float3 a, Float3 b) => !a.Equals(b);
|
||||
|
||||
public static explicit operator Float3(ColorRGB color) => new Float3(color.r, color.g, color.b);
|
||||
public static implicit operator Float3(Float2 floats) => new Float3(floats.x, floats.y, 0);
|
||||
public static explicit operator Float3(Float4 floats) => new Float3(floats.x, floats.y, floats.z);
|
||||
public static implicit operator Float3(Int2 ints) => new Float3(ints.x, ints.y, 0);
|
||||
public static implicit operator Float3(Int3 ints) => new Float3(ints.x, ints.y, ints.z);
|
||||
public static explicit operator Float3(Int4 ints) => new Float3(ints.x, ints.y, ints.z);
|
||||
public static implicit operator Float3(Vector2 vec) => new Float3(vec.X, vec.Y, 0);
|
||||
public static implicit operator Float3(Vector3 vec) => new Float3(vec.X, vec.Y, vec.Z);
|
||||
public static explicit operator Float3(Vector4 vec) => new Float3(vec.X, vec.Y, vec.Z);
|
||||
public static implicit operator Float3(ListTuple<double> tuple) => new Float3(tuple[0], tuple[1], tuple[2]);
|
||||
public static implicit operator Float3(ListTuple<int> tuple) => new Float3(tuple[0], tuple[1], tuple[2]);
|
||||
public static implicit operator Float3((double, double, double) tuple) => new Float3(tuple.Item1, tuple.Item2, tuple.Item3);
|
||||
|
||||
public static explicit operator Vector2(Float3 group) => new Vector2((float)group.x, (float)group.y);
|
||||
public static implicit operator Vector3(Float3 group) => new Vector3((float)group.x, (float)group.y, (float)group.z);
|
||||
public static implicit operator Vector4(Float3 group) => new Vector4((float)group.x, (float)group.y, (float)group.z, 0);
|
||||
public static implicit operator ListTuple<double>(Float3 group) => new ListTuple<double>(group.x, group.y, group.z);
|
||||
public static explicit operator ListTuple<int>(Float3 group) => new ListTuple<int>((int)group.x, (int)group.y, (int)group.z);
|
||||
public static implicit operator ValueTuple<double, double, double>(Float3 group) => (group.x, group.y, group.z);
|
||||
|
||||
@ -2,7 +2,9 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Graphics;
|
||||
|
||||
namespace Nerd_STF.Mathematics
|
||||
{
|
||||
@ -52,9 +54,16 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index >= 2) break;
|
||||
if (index == 4) break;
|
||||
}
|
||||
}
|
||||
public Float4(Fill<double> fill)
|
||||
{
|
||||
w = fill(0);
|
||||
x = fill(1);
|
||||
y = fill(2);
|
||||
z = fill(3);
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
@ -128,7 +137,7 @@ namespace Nerd_STF.Mathematics
|
||||
total += val;
|
||||
count++;
|
||||
}
|
||||
return total;
|
||||
return total / count;
|
||||
}
|
||||
public static Int4 Ceiling(Float4 val) =>
|
||||
new Int4(MathE.Ceiling(val.w),
|
||||
@ -301,6 +310,21 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({w.ToString(format)}, {x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})";
|
||||
|
||||
public double[] ToArray() => new double[] { w, x, y, z };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
Float4 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.w;
|
||||
case 1: return copy.x;
|
||||
case 2: return copy.y;
|
||||
case 3: return copy.z;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<double> ToList() => new List<double> { w, x, y, z };
|
||||
|
||||
public static Float4 operator +(Float4 a, Float4 b) => new Float4(a.w + b.w, a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
@ -313,15 +337,23 @@ namespace Nerd_STF.Mathematics
|
||||
public static bool operator ==(Float4 a, Float4 b) => a.Equals(b);
|
||||
public static bool operator !=(Float4 a, Float4 b) => !a.Equals(b);
|
||||
|
||||
public static explicit operator Float4(ColorRGB color) => new Float4(color.a, color.r, color.g, color.b);
|
||||
public static implicit operator Float4(Int2 ints) => new Float4(0, ints.x, ints.y, 0);
|
||||
public static implicit operator Float4(Int3 ints) => new Float4(0, ints.x, ints.y, ints.z);
|
||||
public static implicit operator Float4(Int4 ints) => new Float4(ints.w, ints.x, ints.y, ints.z);
|
||||
public static implicit operator Float4(Float2 floats) => new Float4(0, floats.x, floats.y, 0);
|
||||
public static implicit operator Float4(Float3 floats) => new Float4(0, floats.x, floats.y, floats.z);
|
||||
public static implicit operator Float4(Numbers.Quaternion quat) => new Float4(quat.w, quat.x, quat.y, quat.z);
|
||||
public static implicit operator Float4(Vector2 vec) => new Float4(0, vec.X, vec.Y, 0);
|
||||
public static implicit operator Float4(Vector3 vec) => new Float4(0, vec.X, vec.Y, vec.Z);
|
||||
public static implicit operator Float4(Vector4 vec) => new Float4(vec.W, vec.X, vec.Y, vec.Z);
|
||||
public static implicit operator Float4(ListTuple<double> tuple) => new Float4(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
public static implicit operator Float4(ListTuple<int> tuple) => new Float4(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
public static implicit operator Float4((double, double, double, double) tuple) => new Float4(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||
|
||||
public static explicit operator Vector2(Float4 group) => new Vector2((float)group.x, (float)group.y);
|
||||
public static explicit operator Vector3(Float4 group) => new Vector3((float)group.x, (float)group.y, (float)group.z);
|
||||
public static implicit operator Vector4(Float4 group) => new Vector4((float)group.x, (float)group.y, (float)group.z, (float)group.w);
|
||||
public static implicit operator ListTuple<double>(Float4 group) => new ListTuple<double>(group.w, group.x, group.y, group.z);
|
||||
public static explicit operator ListTuple<int>(Float4 group) => new ListTuple<int>((int)group.w, (int)group.x, (int)group.y, (int)group.z);
|
||||
public static implicit operator ValueTuple<double, double, double, double>(Float4 group) => (group.w, group.x, group.y, group.z);
|
||||
|
||||
@ -7,7 +7,8 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
public interface INumberGroup<TSelf, TItem> : ICombinationIndexer<TItem>,
|
||||
IEnumerable<TItem>,
|
||||
IEquatable<TSelf>
|
||||
IEquatable<TSelf>,
|
||||
INumberGroupBase<TItem>
|
||||
#if CS11_OR_GREATER
|
||||
, IInterpolable<TSelf>,
|
||||
ISimpleMathOperations<TSelf>,
|
||||
@ -17,10 +18,5 @@ namespace Nerd_STF.Mathematics
|
||||
#if CS11_OR_GREATER
|
||||
where TItem : INumber<TItem>
|
||||
#endif
|
||||
{
|
||||
TItem this[int index] { get; set; }
|
||||
|
||||
TItem[] ToArray();
|
||||
List<TItem> ToList();
|
||||
}
|
||||
{ }
|
||||
}
|
||||
|
||||
17
Nerd_STF/Mathematics/INumberGroupBase.cs
Normal file
17
Nerd_STF/Mathematics/INumberGroupBase.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nerd_STF.Mathematics
|
||||
{
|
||||
public interface INumberGroupBase<TItem>
|
||||
#if CS11_OR_GREATER
|
||||
where TItem : INumber<TItem>
|
||||
#endif
|
||||
{
|
||||
TItem this[int index] { get; set; }
|
||||
|
||||
TItem[] ToArray();
|
||||
Fill<TItem> ToFill();
|
||||
List<TItem> ToList();
|
||||
}
|
||||
}
|
||||
@ -4,8 +4,9 @@ namespace Nerd_STF.Mathematics
|
||||
public interface IPresets4d<TSelf> : IPresets3d<TSelf>
|
||||
where TSelf : IPresets4d<TSelf>
|
||||
{
|
||||
static abstract TSelf LowW { get; }
|
||||
static abstract TSelf HighW { get; }
|
||||
// TODO: The HighW and LowW vectors could also be called "ana" and "kata."
|
||||
static abstract TSelf LowW { get; } // Kata
|
||||
static abstract TSelf HighW { get; } // Ana
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -5,7 +5,6 @@ using System.Numerics;
|
||||
namespace Nerd_STF.Mathematics
|
||||
{
|
||||
public interface ISimpleMathOperations<TSelf> : IAdditionOperators<TSelf, TSelf, TSelf>,
|
||||
ISubtractionOperators<TSelf, TSelf, TSelf>,
|
||||
IMultiplyOperators<TSelf, TSelf, TSelf>
|
||||
where TSelf : ISimpleMathOperations<TSelf>
|
||||
{
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Mathematics.Algebra;
|
||||
using Nerd_STF.Mathematics.Numbers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@ -44,9 +45,14 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index >= 2) break;
|
||||
if (index == 2) break;
|
||||
}
|
||||
}
|
||||
public Int2(Fill<int> fill)
|
||||
{
|
||||
x = fill(0);
|
||||
y = fill(1);
|
||||
}
|
||||
|
||||
public int this[int index]
|
||||
{
|
||||
@ -226,6 +232,19 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)})";
|
||||
|
||||
public int[] ToArray() => new int[] { x, y };
|
||||
public Fill<int> ToFill()
|
||||
{
|
||||
Int2 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.x;
|
||||
case 1: return copy.y;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<int> ToList() => new List<int> { x, y };
|
||||
|
||||
public static Int2 operator +(Int2 a, Int2 b) => new Int2(a.x + b.x, a.y + b.y);
|
||||
@ -241,6 +260,7 @@ namespace Nerd_STF.Mathematics
|
||||
public static bool operator ==(Int2 a, Int2 b) => a.Equals(b);
|
||||
public static bool operator !=(Int2 a, Int2 b) => !a.Equals(b);
|
||||
|
||||
public static explicit operator Int2(Complex complex) => new Int2((int)complex.r, (int)complex.i);
|
||||
public static explicit operator Int2(Float2 floats) => new Int2((int)floats.x, (int)floats.y);
|
||||
public static explicit operator Int2(Float3 floats) => new Int2((int)floats.x, (int)floats.y);
|
||||
public static explicit operator Int2(Float4 floats) => new Int2((int)floats.x, (int)floats.y);
|
||||
|
||||
@ -47,9 +47,15 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index >= 2) break;
|
||||
if (index == 3) break;
|
||||
}
|
||||
}
|
||||
public Int3(Fill<int> fill)
|
||||
{
|
||||
x = fill(0);
|
||||
y = fill(1);
|
||||
z = fill(2);
|
||||
}
|
||||
|
||||
public int this[int index]
|
||||
{
|
||||
@ -246,6 +252,20 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})";
|
||||
|
||||
public int[] ToArray() => new int[] { x, y, z };
|
||||
public Fill<int> ToFill()
|
||||
{
|
||||
Int3 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.x;
|
||||
case 1: return copy.y;
|
||||
case 2: return copy.z;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<int> ToList() => new List<int> { x, y, z };
|
||||
|
||||
public static Int3 operator +(Int3 a, Int3 b) => new Int3(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
|
||||
@ -51,9 +51,16 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index >= 2) break;
|
||||
if (index == 4) break;
|
||||
}
|
||||
}
|
||||
public Int4(Fill<int> fill)
|
||||
{
|
||||
w = fill(0);
|
||||
x = fill(1);
|
||||
y = fill(2);
|
||||
z = fill(3);
|
||||
}
|
||||
|
||||
public int this[int index]
|
||||
{
|
||||
@ -258,6 +265,21 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({w.ToString(format)}, {x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})";
|
||||
|
||||
public int[] ToArray() => new int[] { w, x, y, z };
|
||||
public Fill<int> ToFill()
|
||||
{
|
||||
Int4 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.w;
|
||||
case 1: return copy.x;
|
||||
case 2: return copy.y;
|
||||
case 3: return copy.z;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<int> ToList() => new List<int> { w, x, y, z };
|
||||
|
||||
public static Int4 operator +(Int4 a, Int4 b) => new Int4(a.w + b.w, a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
@ -278,6 +300,7 @@ namespace Nerd_STF.Mathematics
|
||||
public static explicit operator Int4(Float2 floats) => new Int4(0, (int)floats.x, (int)floats.y, 0);
|
||||
public static explicit operator Int4(Float3 floats) => new Int4(0, (int)floats.x, (int)floats.y, (int)floats.z);
|
||||
public static explicit operator Int4(Float4 floats) => new Int4((int)floats.w, (int)floats.x, (int)floats.y, (int)floats.z);
|
||||
public static explicit operator Int4(Numbers.Quaternion quat) => new Int4((int)quat.w, (int)quat.x, (int)quat.y, (int)quat.z);
|
||||
public static explicit operator Int4(ListTuple<double> tuple) => new Int4((int)tuple[0], (int)tuple[1], (int)tuple[2], (int)tuple[3]);
|
||||
public static implicit operator Int4(ListTuple<int> tuple) => new Int4(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
public static implicit operator Int4((int, int, int, int) tuple) => new Int4(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||
|
||||
@ -374,6 +374,24 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
public static IEquation Lerp(IEquation a, IEquation b, double t, bool clamp = true) =>
|
||||
new Equation((double x) => Lerp(a.Get(x), b.Get(x), t, clamp));
|
||||
public static Fill<double> Lerp(Fill<double> a, Fill<double> b, double t, bool clamp = true)
|
||||
{
|
||||
if (clamp) Clamp(ref t, 0, 1);
|
||||
return delegate (int index)
|
||||
{
|
||||
double aVal = a(index);
|
||||
return aVal + t * (b(index) - aVal);
|
||||
};
|
||||
}
|
||||
public static Fill<int> Lerp(Fill<int> a, Fill<int> b, double t, bool clamp = true)
|
||||
{
|
||||
if (clamp) Clamp(ref t, 0, 1);
|
||||
return delegate (int index)
|
||||
{
|
||||
int aVal = a(index);
|
||||
return (int)(aVal + t * (b(index) - aVal));
|
||||
};
|
||||
}
|
||||
#if CS11_OR_GREATER
|
||||
public static T Lerp<T>(T a, T b, T t, bool clamp = true)
|
||||
where T : INumber<T>
|
||||
@ -381,6 +399,20 @@ namespace Nerd_STF.Mathematics
|
||||
if (clamp) Clamp(ref t, T.Zero, T.One);
|
||||
return a + t * (b - a);
|
||||
}
|
||||
public static T Lerp<T>(T a, T b, double t, bool clamp = true)
|
||||
where T : IInterpolable<T> => T.Lerp(a, b, t, clamp);
|
||||
public static Fill<T> Lerp<T>(Fill<T> a, Fill<T> b, T t, bool clamp = true)
|
||||
where T : INumber<T>
|
||||
{
|
||||
if (clamp) Clamp(ref t, T.Zero, T.One);
|
||||
return delegate (int index)
|
||||
{
|
||||
T aVal = a(index);
|
||||
return aVal + t * (b(index) - aVal);
|
||||
};
|
||||
}
|
||||
public static Fill<T> Lerp<T>(Fill<T> a, Fill<T> b, double t, bool clamp = true)
|
||||
where T : IInterpolable<T> => (int index) => T.Lerp(a(index), b(index), t, clamp);
|
||||
#endif
|
||||
|
||||
public static int Max(int a, int b) => a > b ? a : b;
|
||||
@ -648,30 +680,46 @@ namespace Nerd_STF.Mathematics
|
||||
|
||||
return flip ? result : -result;
|
||||
}
|
||||
public static double Sin(Angle angle, int terms = 8) =>
|
||||
Sin(angle.Radians, terms);
|
||||
public static IEquation Sin(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Sin(inputRad[x], terms));
|
||||
public static double Cos(double rad, int terms = 8) =>
|
||||
Sin(rad + Constants.HalfPi, terms);
|
||||
public static double Cos(Angle angle, int terms = 8) =>
|
||||
Cos(angle.Radians, terms);
|
||||
public static IEquation Cos(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Cos(inputRad[x], terms));
|
||||
public static double Tan(double rad, int terms = 8) =>
|
||||
Sin(rad + Constants.HalfPi, terms) / Sin(rad, terms);
|
||||
public static double Tan(Angle angle, int terms = 8) =>
|
||||
Tan(angle.Radians, terms);
|
||||
public static IEquation Tan(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Tan(inputRad[x], terms));
|
||||
public static double Csc(double rad, int terms = 8) =>
|
||||
1 / Sin(rad, terms);
|
||||
public static double Csc(Angle angle, int terms = 8) =>
|
||||
Csc(angle.Radians, terms);
|
||||
public static IEquation Csc(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Csc(inputRad[x], terms));
|
||||
public static double Sec(double rad, int terms = 8) =>
|
||||
1 / Sin(rad + Constants.HalfPi, terms);
|
||||
public static double Sec(Angle angle, int terms = 8) =>
|
||||
Sec(angle.Radians, terms);
|
||||
public static IEquation Sec(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Sec(inputRad[x], terms));
|
||||
public static double Cot(double rad, int terms = 8) =>
|
||||
Sin(rad, terms) / Sin(rad + Constants.HalfPi, terms);
|
||||
public static double Cot(Angle angle, int terms = 8) =>
|
||||
Cot(angle.Radians, terms);
|
||||
public static IEquation Cot(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Cot(inputRad[x], terms));
|
||||
|
||||
public static double Sqrt(double num) => 1 / InverseSqrtFast((float)num); // !!TODO!!: Bring back Newton's
|
||||
// YOU CANNOT USE POW HERE!!!
|
||||
// The CordicHelper uses the Sqrt function for the Pow method.
|
||||
// It'll cause a stack overflow.
|
||||
// !!TODO!! - Bring back Newton's
|
||||
public static double Sqrt(double num) => 1 / InverseSqrtFast((float)num);
|
||||
public static IEquation Sqrt(IEquation equ) =>
|
||||
new Equation((double x) => Sqrt(equ.Get(x)));
|
||||
|
||||
|
||||
511
Nerd_STF/Mathematics/Numbers/Complex.cs
Normal file
511
Nerd_STF/Mathematics/Numbers/Complex.cs
Normal file
@ -0,0 +1,511 @@
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Helpers;
|
||||
using Nerd_STF.Mathematics.Algebra;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nerd_STF.Mathematics.Numbers
|
||||
{
|
||||
public struct Complex : IComparable<Complex>,
|
||||
IEquatable<Complex>,
|
||||
IFormattable,
|
||||
INumberGroup<Complex, double>
|
||||
#if CS11_OR_GREATER
|
||||
,INumber<Complex>,
|
||||
IFromTuple<Complex, (double, double)>,
|
||||
IInterpolable<Complex>,
|
||||
IPresets2d<Complex>,
|
||||
IRoundable<Complex>,
|
||||
ISimpleMathOperations<Complex>,
|
||||
ISplittable<Complex, (double[] reals, double[] imaginaries)>,
|
||||
IVectorOperations<Complex>
|
||||
#endif
|
||||
{
|
||||
public static Complex Down => new Complex(0, 1);
|
||||
public static Complex Left => new Complex(-1, 0);
|
||||
public static Complex Right => new Complex(1, 0);
|
||||
public static Complex Up => new Complex(0, -1);
|
||||
|
||||
public static Complex One => new Complex(1, 0);
|
||||
public static Complex Zero => new Complex(0, 0);
|
||||
|
||||
public static Complex NaN => new Complex(double.NaN, double.NaN);
|
||||
|
||||
public Complex Conjugate => new Complex(r, -i);
|
||||
public double Magnitude => MathE.Sqrt(r * r + i * i);
|
||||
public double MagnitudeSqr => r * r + i * i;
|
||||
|
||||
public double Real
|
||||
{
|
||||
get => r;
|
||||
set => r = value;
|
||||
}
|
||||
public double Imaginary
|
||||
{
|
||||
get => i;
|
||||
set => i = value;
|
||||
}
|
||||
|
||||
public double r, i;
|
||||
|
||||
public Complex(double real, double imaginary)
|
||||
{
|
||||
r = real;
|
||||
i = imaginary;
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return r;
|
||||
case 1: return i;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: r = value; break;
|
||||
case 1: i = 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 'i': items[i] = this.i; 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 'i': this.i = stepper.Current; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if CS8_OR_GREATER
|
||||
public static Complex Parse(string? str) =>
|
||||
#else
|
||||
public static Complex Parse(string str) =>
|
||||
#endif
|
||||
str is null ? NaN : Parse(str.AsSpan());
|
||||
public static Complex Parse(ReadOnlySpan<char> str)
|
||||
{
|
||||
if (TryParse(str, out Complex result)) return result;
|
||||
else throw new FormatException("Cannot parse complex number from input.");
|
||||
}
|
||||
#if CS8_OR_GREATER
|
||||
public static bool TryParse(string? str, out Complex frac) =>
|
||||
#else
|
||||
public static bool TryParse(string str, out Complex frac) =>
|
||||
#endif
|
||||
TryParse(str.AsSpan(), out frac);
|
||||
public static bool TryParse(ReadOnlySpan<char> str, out Complex num)
|
||||
{
|
||||
if (str.Length == 0)
|
||||
{
|
||||
num = NaN;
|
||||
return false;
|
||||
}
|
||||
|
||||
str = str.Trim();
|
||||
int signFirst, signSecond;
|
||||
|
||||
if (str.StartsWith("+".AsSpan()))
|
||||
{
|
||||
signFirst = 1;
|
||||
str = str.Slice(1);
|
||||
}
|
||||
else if (str.StartsWith("-".AsSpan()))
|
||||
{
|
||||
signFirst = -1;
|
||||
str = str.Slice(1);
|
||||
}
|
||||
else signFirst = 1;
|
||||
|
||||
ReadOnlySpan<char> first, second;
|
||||
int splitIndex = str.IndexOf('+');
|
||||
if (splitIndex == -1) splitIndex = str.IndexOf('-');
|
||||
|
||||
if (splitIndex != -1)
|
||||
{
|
||||
first = str;
|
||||
second = ReadOnlySpan<char>.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
first = str.Slice(0, splitIndex);
|
||||
second = str.Slice(splitIndex);
|
||||
}
|
||||
first = first.Trim();
|
||||
second = second.Trim();
|
||||
|
||||
if (second.StartsWith("+".AsSpan()))
|
||||
{
|
||||
signSecond = 1;
|
||||
second = second.Slice(1).Trim();
|
||||
}
|
||||
else if (str.StartsWith("-".AsSpan()))
|
||||
{
|
||||
signSecond = -1;
|
||||
second = second.Slice(1).Trim();
|
||||
}
|
||||
else signSecond = 1;
|
||||
|
||||
bool firstIsImag;
|
||||
if (first.EndsWith("i".AsSpan()))
|
||||
{
|
||||
firstIsImag = true;
|
||||
first = first.Slice(0, first.Length - 1).Trim();
|
||||
}
|
||||
else if (second.EndsWith("i".AsSpan()))
|
||||
{
|
||||
firstIsImag = false;
|
||||
second = first.Slice(0, second.Length - 1).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
num = NaN;
|
||||
return false;
|
||||
}
|
||||
|
||||
double firstNum = ParseHelper.ParseDouble(first) * signFirst,
|
||||
secondNum = ParseHelper.ParseDouble(second) * signSecond;
|
||||
|
||||
if (firstIsImag) num = new Complex(secondNum, firstNum);
|
||||
else num = new Complex(firstNum, secondNum);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Complex Abs(Complex num) => new Complex(num.Magnitude, 0);
|
||||
public static Complex Ceiling(Complex num) =>
|
||||
new Complex(MathE.Ceiling(num.r),
|
||||
MathE.Ceiling(num.i));
|
||||
public static Complex Clamp(Complex num, Complex min, Complex max) =>
|
||||
new Complex(MathE.Clamp(num.r, min.r, max.r),
|
||||
MathE.Clamp(num.i, min.i, max.i));
|
||||
public static Complex ClampMagnitude(Complex num, double minMag, double maxMag)
|
||||
{
|
||||
Complex copy = num;
|
||||
ClampMagnitude(ref copy, minMag, maxMag);
|
||||
return copy;
|
||||
}
|
||||
public static void ClampMagnitude(ref Complex num, double minMag, double maxMag)
|
||||
{
|
||||
if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag));
|
||||
double mag = num.Magnitude;
|
||||
|
||||
if (mag < minMag)
|
||||
{
|
||||
double factor = minMag / mag;
|
||||
num.r *= factor;
|
||||
num.i *= factor;
|
||||
}
|
||||
else if (mag > maxMag)
|
||||
{
|
||||
double factor = maxMag / mag;
|
||||
num.r *= factor;
|
||||
num.i *= factor;
|
||||
}
|
||||
}
|
||||
public static double Dot(Complex a, Complex b) => a.r * b.r + a.i * b.i;
|
||||
public static double Dot(IEnumerable<Complex> values)
|
||||
{
|
||||
double r = 1, i = 1;
|
||||
foreach (Complex val in values)
|
||||
{
|
||||
r *= val.r;
|
||||
i *= val.i;
|
||||
}
|
||||
return r + i;
|
||||
}
|
||||
public static Complex Floor(Complex num) =>
|
||||
new Complex(MathE.Floor(num.r),
|
||||
MathE.Floor(num.i));
|
||||
public static Complex Lerp(Complex a, Complex b, double t, bool clamp = true) =>
|
||||
new Complex(MathE.Lerp(a.r, b.r, t, clamp),
|
||||
MathE.Lerp(a.i, b.i, t, clamp));
|
||||
public static Complex Product(IEnumerable<Complex> vals)
|
||||
{
|
||||
bool any = false;
|
||||
Complex result = One;
|
||||
foreach (Complex val in vals)
|
||||
{
|
||||
any = true;
|
||||
result *= val;
|
||||
}
|
||||
return any ? result : Zero;
|
||||
}
|
||||
public static Complex Round(Complex val) =>
|
||||
new Complex(MathE.Round(val.r),
|
||||
MathE.Round(val.i));
|
||||
public static Complex Sum(IEnumerable<Complex> vals)
|
||||
{
|
||||
double resultR = 0;
|
||||
double resultI = 0;
|
||||
foreach (Complex val in vals)
|
||||
{
|
||||
resultR += val.r;
|
||||
resultI += val.i;
|
||||
}
|
||||
return new Complex(resultR, resultI);
|
||||
}
|
||||
|
||||
#if CS8_OR_GREATER
|
||||
private static bool TryConvertFrom(object? value, out Complex result)
|
||||
#else
|
||||
private static bool TryConvertFrom(object value, out Complex result)
|
||||
#endif
|
||||
{
|
||||
if (value is Complex vComplex) result = vComplex;
|
||||
else if (value is Fraction vFrac) result = new Complex(vFrac.GetValue(), 0);
|
||||
else if (value is Float2 vFloat2) result = new Complex(vFloat2.x, vFloat2.y);
|
||||
else if (value is Vector2 vVector2) result = new Complex(vVector2.X, vVector2.Y);
|
||||
else if (value is Int2 vInt2) result = new Complex(vInt2.x, vInt2.y);
|
||||
else if (value is double vDouble) result = new Complex(vDouble, 0);
|
||||
else if (value is float vSingle) result = new Complex(vSingle, 0);
|
||||
#if NET5_0_OR_GREATER
|
||||
else if (value is Half vHalf) result = new Complex((double)vHalf, 0);
|
||||
#endif
|
||||
else if (value is int vInt32) result = new Complex(vInt32, 0);
|
||||
else
|
||||
{
|
||||
result = new Complex(0, 0);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsEvenInteger(Complex value) => value.i == 0 && value.r % 2 == 0;
|
||||
public static bool IsOddInteger(Complex value) => value.i == 0 && value.r % 2 == 1;
|
||||
public static bool IsFinite(Complex value) => TargetHelper.IsFinite(value.r) &&
|
||||
TargetHelper.IsFinite(value.i);
|
||||
public static bool IsInfinity(Complex value) => TargetHelper.IsInfinity(value.r) ||
|
||||
TargetHelper.IsInfinity(value.i);
|
||||
public static bool IsInteger(Complex value) => value.i == 0 && value.r % 1 == 0;
|
||||
public static bool IsNaN(Complex value)
|
||||
{
|
||||
// NaN never equals itself.
|
||||
#pragma warning disable CS1718
|
||||
return (value.r != value.r) ||
|
||||
(value.i != value.i);
|
||||
#pragma warning restore CS1718
|
||||
}
|
||||
public static bool IsNegative(Complex value) => value.r < 0;
|
||||
public static bool IsNegativeInfinity(Complex value) => value.r == double.NegativeInfinity ||
|
||||
value.i == double.NegativeInfinity;
|
||||
public static bool IsNormal(Complex value) => false; // ??? uhh i think this is right
|
||||
public static bool IsPositive(Complex value) => value.r > 0;
|
||||
public static bool IsPositiveInfinity(Complex value) => value.r == double.PositiveInfinity ||
|
||||
value.i == double.PositiveInfinity;
|
||||
public static bool IsRealNumber(Complex value) => value.i == 0;
|
||||
public static bool IsZero(Complex value) => value.r == 0 && value.i == 0;
|
||||
public static Complex MaxMagnitude(Complex a, Complex b) => a.MagnitudeSqr > b.MagnitudeSqr ? a : b;
|
||||
public static Complex MinMagnitude(Complex a, Complex b) => a.MagnitudeSqr < b.MagnitudeSqr ? a : b;
|
||||
#if CS11_OR_GREATER
|
||||
static Complex INumberBase<Complex>.MaxMagnitudeNumber(Complex a, Complex b) => MaxMagnitude(a, b);
|
||||
static Complex INumberBase<Complex>.MinMagnitudeNumber(Complex a, Complex b) => MinMagnitude(a, b);
|
||||
static bool INumberBase<Complex>.IsCanonical(Complex value) => true;
|
||||
static bool INumberBase<Complex>.IsComplexNumber(Complex value) => value.i != 0;
|
||||
static bool INumberBase<Complex>.IsImaginaryNumber(Complex value) => value.i != 0;
|
||||
static bool INumberBase<Complex>.IsSubnormal(Complex value) => false; // What does this mean???
|
||||
|
||||
static Complex INumberBase<Complex>.Parse(string str, NumberStyles numStyles, IFormatProvider? provider) => Parse(str);
|
||||
static Complex INumberBase<Complex>.Parse(ReadOnlySpan<char> str, NumberStyles numStyles, IFormatProvider? provider) => Parse(str);
|
||||
static Complex IParsable<Complex>.Parse(string str, IFormatProvider? provider) => Parse(str);
|
||||
static Complex ISpanParsable<Complex>.Parse(ReadOnlySpan<char> str, IFormatProvider? provider) => Parse(str);
|
||||
static bool INumberBase<Complex>.TryParse(string? str, NumberStyles numStyles, IFormatProvider? provider, out Complex num) => TryParse(str, out num);
|
||||
static bool INumberBase<Complex>.TryParse(ReadOnlySpan<char> str, NumberStyles numStyles, IFormatProvider? provider, out Complex num) => TryParse(str, out num);
|
||||
static bool IParsable<Complex>.TryParse(string? str, IFormatProvider? provider, out Complex num) => TryParse(str, out num);
|
||||
static bool ISpanParsable<Complex>.TryParse(ReadOnlySpan<char> str, IFormatProvider? provider, out Complex num) => TryParse(str, out num);
|
||||
|
||||
static Complex IAdditiveIdentity<Complex, Complex>.AdditiveIdentity => Zero;
|
||||
static Complex IMultiplicativeIdentity<Complex, Complex>.MultiplicativeIdentity => One;
|
||||
static int INumberBase<Complex>.Radix => 2; // Not super sure what to put here.
|
||||
|
||||
private static bool TryConvertTo<T>(Complex num, out T result)
|
||||
{
|
||||
object? tempValue;
|
||||
|
||||
if (typeof(T) == typeof(Complex)) tempValue = num;
|
||||
else if (typeof(T) == typeof(Fraction)) tempValue = Fraction.Approximate(num.r);
|
||||
else if (typeof(T) == typeof(Float2)) tempValue = new Float2(num.r, num.i);
|
||||
else if (typeof(T) == typeof(Vector2)) tempValue = new Vector2((float)num.r, (float)num.i);
|
||||
else if (typeof(T) == typeof(Int2)) tempValue = new Int2((int)num.r, (int)num.i);
|
||||
else if (typeof(T) == typeof(double)) tempValue = num.r;
|
||||
else if (typeof(T) == typeof(float)) tempValue = (float)num.r;
|
||||
#if NET5_0_OR_GREATER
|
||||
else if (typeof(T) == typeof(Half)) tempValue = (Half)num.r;
|
||||
#endif
|
||||
else if (typeof(T) == typeof(int)) tempValue = (int)num.r;
|
||||
else
|
||||
{
|
||||
result = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
result = (T)tempValue;
|
||||
return true;
|
||||
}
|
||||
static bool INumberBase<Complex>.TryConvertFromChecked<TOther>(TOther value, out Complex result) => TryConvertFrom(value, out result);
|
||||
static bool INumberBase<Complex>.TryConvertFromSaturating<TOther>(TOther value, out Complex result) => TryConvertFrom(value, out result);
|
||||
static bool INumberBase<Complex>.TryConvertFromTruncating<TOther>(TOther value, out Complex result) => TryConvertFrom(value, out result);
|
||||
static bool INumberBase<Complex>.TryConvertToChecked<TOther>(Complex value, out TOther result) => TryConvertTo(value, out result);
|
||||
static bool INumberBase<Complex>.TryConvertToSaturating<TOther>(Complex value, out TOther result) => TryConvertTo(value, out result);
|
||||
static bool INumberBase<Complex>.TryConvertToTruncating<TOther>(Complex value, out TOther result) => TryConvertTo(value, out result);
|
||||
#endif
|
||||
|
||||
public static (double[] reals, double[] imaginaries) SplitArray(IEnumerable<Complex> vals)
|
||||
{
|
||||
int count = vals.Count();
|
||||
double[] reals = new double[count], imaginaries = new double[count];
|
||||
int index = 0;
|
||||
foreach (Complex val in vals)
|
||||
{
|
||||
reals[index] = val.r;
|
||||
imaginaries[index] = val.i;
|
||||
index++;
|
||||
}
|
||||
return (reals, imaginaries);
|
||||
}
|
||||
|
||||
public IEnumerator<double> GetEnumerator()
|
||||
{
|
||||
yield return r;
|
||||
yield return i;
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public int CompareTo(double other) => MagnitudeSqr.CompareTo(MathE.Abs(other * other));
|
||||
public int CompareTo(Complex other) => MagnitudeSqr.CompareTo(other.MagnitudeSqr);
|
||||
#if CS8_OR_GREATER
|
||||
public int CompareTo(object? other)
|
||||
#else
|
||||
public int CompareTo(object other)
|
||||
#endif
|
||||
{
|
||||
if (other is null) return 1;
|
||||
else if (other is Complex otherComplex) return CompareTo(otherComplex);
|
||||
else if (other is double otherDouble) return CompareTo(otherDouble);
|
||||
else if (TryConvertFrom(other, out Complex otherConvert)) return CompareTo(otherConvert);
|
||||
else return 1;
|
||||
}
|
||||
public bool Equals(double other) => r == other && i == 0;
|
||||
public bool Equals(Complex other) => r == other.r && i == other.i;
|
||||
#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 Complex otherComplex) return Equals(otherComplex);
|
||||
else if (TryConvertFrom(other, out Complex otherConvert)) return Equals(otherConvert);
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
public override string ToString() => ToStringHelper.HighDimNumberToString(this, null, null);
|
||||
#if CS8_OR_GREATER
|
||||
public string ToString(string? format) => ToStringHelper.HighDimNumberToString(this, format, null);
|
||||
public string ToString(IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, null, provider);
|
||||
public string ToString(string? format, IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, format, provider);
|
||||
#else
|
||||
public string ToString(string format) => ToStringHelper.HighDimNumberToString(this, format, null);
|
||||
public string ToString(IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, null, provider);
|
||||
public string ToString(string format, IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, format, provider);
|
||||
#endif
|
||||
|
||||
public double[] ToArray() => new double[] { r, i };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
Complex @this = this;
|
||||
return i => @this[i];
|
||||
}
|
||||
public List<double> ToList() => new List<double>() { r, i };
|
||||
|
||||
#if CS11_OR_GREATER
|
||||
public bool TryFormat(Span<char> dest, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
|
||||
{
|
||||
// Not really great, but I don't want to do this right now.
|
||||
string result = ToString(format.ToString(), provider);
|
||||
result.CopyTo(dest);
|
||||
charsWritten = result.Length;
|
||||
return true;
|
||||
}
|
||||
|
||||
static Complex IIncrementOperators<Complex>.operator ++(Complex a) => new Complex(a.r + 1, a.i);
|
||||
static Complex IDecrementOperators<Complex>.operator --(Complex a) => new Complex(a.r - 1, a.i);
|
||||
#endif
|
||||
|
||||
public static Complex operator +(Complex a) => a;
|
||||
public static Complex operator +(Complex a, Complex b) => new Complex(a.r + b.r, a.i + b.i);
|
||||
public static Complex operator +(Complex a, double b) => new Complex(a.r + b, a.i);
|
||||
public static Complex operator -(Complex a) => new Complex(-a.r, -a.i);
|
||||
public static Complex operator -(Complex a, Complex b) => new Complex(a.r - b.r, a.i - b.i);
|
||||
public static Complex operator -(Complex a, double b) => new Complex(a.r - b, a.i);
|
||||
public static Complex operator *(Complex a, Complex b) => new Complex(a.r * b.r - a.i * b.i, a.r * b.i + a.i * b.r);
|
||||
public static Complex operator *(Complex a, double b) => new Complex(a.r * b, a.i * b);
|
||||
public static Complex operator /(Complex a, Complex b)
|
||||
{
|
||||
double scaleFactor = 1 / (b.r * b.r + b.i * b.i);
|
||||
return new Complex((a.r * b.r + a.i * b.i) * scaleFactor,
|
||||
(a.i * b.r - a.r * b.i) * scaleFactor);
|
||||
}
|
||||
public static Complex operator /(Complex a, double b) => new Complex(a.r / b, a.i / b);
|
||||
public static Complex operator %(Complex a, Complex b)
|
||||
{
|
||||
// TODO: Maybe expand and inline this. Don't feel like it at the moment.
|
||||
return a + b * Ceiling(-a / b);
|
||||
}
|
||||
public static Complex operator %(Complex a, double b) => a % new Complex(b, 0);
|
||||
public static bool operator ==(Complex a, Complex b) => a.Equals(b);
|
||||
public static bool operator !=(Complex a, Complex b) => !a.Equals(b);
|
||||
public static bool operator >(Complex a, Complex b) => a.CompareTo(b) > 0;
|
||||
public static bool operator <(Complex a, Complex b) => a.CompareTo(b) < 0;
|
||||
public static bool operator >=(Complex a, Complex b) => a.CompareTo(b) >= 0;
|
||||
public static bool operator <=(Complex a, Complex b) => a.CompareTo(b) <= 0;
|
||||
|
||||
public static implicit operator Complex(System.Numerics.Complex num) => new Complex(num.Real, num.Imaginary);
|
||||
public static implicit operator Complex(Float2 group) => new Complex(group.x, group.y);
|
||||
public static explicit operator Complex(Int2 group) => new Complex(group.x, group.y);
|
||||
public static implicit operator Complex(ListTuple<double> tuple) => new Complex(tuple[0], tuple[1]);
|
||||
public static explicit operator Complex(Quaternion quat) => new Complex(quat.w, quat.x);
|
||||
public static implicit operator Complex(ValueTuple<double, double> tuple) => new Complex(tuple.Item1, tuple.Item2);
|
||||
public static explicit operator Complex(Vector2 group) => new Complex(group.X, group.Y);
|
||||
|
||||
public static implicit operator System.Numerics.Complex(Complex num) => new Complex(num.r, num.i);
|
||||
public static implicit operator ListTuple<double>(Complex num) => new ListTuple<double>(num.r, num.i);
|
||||
public static implicit operator ValueTuple<double, double>(Complex num) => (num.r, num.i);
|
||||
}
|
||||
}
|
||||
@ -325,25 +325,13 @@ namespace Nerd_STF.Mathematics.Numbers
|
||||
return false;
|
||||
}
|
||||
else if (value is Fraction valueFrac) result = valueFrac;
|
||||
else if (value is Complex valueComp) result = Approximate(valueComp.r);
|
||||
else if (value is double valueDouble) result = Approximate(valueDouble);
|
||||
else if (value is float valueSingle) result = Approximate(valueSingle);
|
||||
#if NET5_0_OR_GREATER
|
||||
else if (value is Half valueHalf) result = Approximate((double)valueHalf);
|
||||
#endif
|
||||
#if NET7_0_OR_GREATER
|
||||
else if (value is UInt128 valueUInt128) result = new Fraction((int)valueUInt128, 1);
|
||||
else if (value is Int128 valueInt128) result = new Fraction((int)valueInt128, 1);
|
||||
#endif
|
||||
else if (value is ulong valueUInt64) result = new Fraction((int)valueUInt64, 1);
|
||||
else if (value is long valueInt64) result = new Fraction((int)valueInt64, 1);
|
||||
else if (value is uint valueUInt32) result = new Fraction((int)valueUInt32, 1);
|
||||
else if (value is int valueInt32) result = new Fraction(valueInt32, 1);
|
||||
else if (value is ushort valueUInt16) result = new Fraction(valueUInt16, 1);
|
||||
else if (value is short valueInt16) result = new Fraction(valueInt16, 1);
|
||||
else if (value is byte valueUInt8) result = new Fraction(valueUInt8, 1);
|
||||
else if (value is sbyte valueInt8) result = new Fraction(valueInt8, 1);
|
||||
else if (value is IntPtr valueInt) result = new Fraction((int)valueInt, 1);
|
||||
else if (value is UIntPtr valueUInt) result = new Fraction((int)valueUInt, 1);
|
||||
else
|
||||
{
|
||||
result = NaN;
|
||||
@ -361,7 +349,7 @@ namespace Nerd_STF.Mathematics.Numbers
|
||||
}
|
||||
public static bool IsEvenInteger(Fraction val) =>
|
||||
val.num % val.den == 0 && val.num / val.den % 2 == 0;
|
||||
public static bool IsFinite(Fraction val) => val.den != 0 || val.num != 0;
|
||||
public static bool IsFinite(Fraction val) => val.den != 0;
|
||||
public static bool IsInfinity(Fraction val) => val.den == 0 && val.num != 0;
|
||||
public static bool IsInteger(Fraction val) => val.num % val.den == 0;
|
||||
public static bool IsNaN(Fraction val) => val.num == 0 && val.den == 0;
|
||||
@ -375,13 +363,12 @@ namespace Nerd_STF.Mathematics.Numbers
|
||||
public static bool IsRealNumber(Fraction val) => val.den != 0;
|
||||
public static bool IsZero(Fraction val) => val.num == 0 && val.den != 0;
|
||||
public static Fraction MaxMagnitude(Fraction a, Fraction b) => a > b ? a : b;
|
||||
public static Fraction MaxMagnitudeNumber(Fraction a, Fraction b) => a > b ? a : b;
|
||||
public static Fraction MinMagnitude(Fraction a, Fraction b) => a < b ? a : b;
|
||||
public static Fraction MinMagnitudeNumber(Fraction a, Fraction b) => a < b ? a : b;
|
||||
#if CS11_OR_GREATER
|
||||
static bool INumberBase<Fraction>.IsComplexNumber(Fraction val) => false;
|
||||
static bool INumberBase<Fraction>.IsImaginaryNumber(Fraction val) => false;
|
||||
static bool INumberBase<Fraction>.IsSubnormal(Fraction val) => false; // What does this mean???
|
||||
|
||||
static Fraction INumberBase<Fraction>.Parse(string? str, NumberStyles style, IFormatProvider? provider) => Parse(str);
|
||||
static Fraction INumberBase<Fraction>.Parse(ReadOnlySpan<char> str, NumberStyles style, IFormatProvider? provider) => Parse(str);
|
||||
static bool INumberBase<Fraction>.TryParse(string? str, NumberStyles style, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac);
|
||||
@ -390,32 +377,26 @@ namespace Nerd_STF.Mathematics.Numbers
|
||||
static bool IParsable<Fraction>.TryParse(string? str, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac);
|
||||
static Fraction ISpanParsable<Fraction>.Parse(ReadOnlySpan<char> str, IFormatProvider? provider) => Parse(str);
|
||||
static bool ISpanParsable<Fraction>.TryParse(ReadOnlySpan<char> str, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac);
|
||||
|
||||
static Fraction IAdditiveIdentity<Fraction, Fraction>.AdditiveIdentity => Zero;
|
||||
static Fraction IMultiplicativeIdentity<Fraction, Fraction>.MultiplicativeIdentity => One;
|
||||
static int INumberBase<Fraction>.Radix => 2; // Not super sure what to put here.
|
||||
|
||||
static Fraction INumberBase<Fraction>.MaxMagnitudeNumber(Fraction a, Fraction b) => a > b ? a : b;
|
||||
static Fraction INumberBase<Fraction>.MinMagnitudeNumber(Fraction a, Fraction b) => a < b ? a : b;
|
||||
|
||||
private static bool TryConvertTo<T>(Fraction frac, out T value)
|
||||
{
|
||||
object? tempValue;
|
||||
|
||||
if (typeof(T) == typeof(Fraction)) tempValue = frac;
|
||||
else if (typeof(T) == typeof(Complex)) tempValue = (frac.GetValue(), 0);
|
||||
else if (typeof(T) == typeof(double)) tempValue = frac.GetValue();
|
||||
else if (typeof(T) == typeof(float)) tempValue = (float)frac.GetValue();
|
||||
#if NET5_0_OR_GREATER
|
||||
else if (typeof(T) == typeof(Half)) tempValue = (Half)frac.GetValue();
|
||||
#endif
|
||||
#if NET7_0_OR_GREATER
|
||||
else if (typeof(T) == typeof(UInt128)) tempValue = (UInt128)frac.GetValue();
|
||||
else if (typeof(T) == typeof(Int128)) tempValue = (Int128)frac.GetValue();
|
||||
#endif
|
||||
else if (typeof(T) == typeof(ulong)) tempValue = (ulong)frac.GetValue();
|
||||
else if (typeof(T) == typeof(long)) tempValue = (long)frac.GetValue();
|
||||
else if (typeof(T) == typeof(uint)) tempValue = (uint)frac.GetValue();
|
||||
else if (typeof(T) == typeof(int)) tempValue = (int)frac.GetValue();
|
||||
else if (typeof(T) == typeof(ushort)) tempValue = (ushort)frac.GetValue();
|
||||
else if (typeof(T) == typeof(short)) tempValue = (short)frac.GetValue();
|
||||
else if (typeof(T) == typeof(byte)) tempValue = (byte)frac.GetValue();
|
||||
else if (typeof(T) == typeof(sbyte)) tempValue = (sbyte)frac.GetValue();
|
||||
else
|
||||
{
|
||||
value = default!;
|
||||
|
||||
336
Nerd_STF/Mathematics/Numbers/Quaternion.cs
Normal file
336
Nerd_STF/Mathematics/Numbers/Quaternion.cs
Normal file
@ -0,0 +1,336 @@
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Helpers;
|
||||
using Nerd_STF.Mathematics.Algebra;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nerd_STF.Mathematics.Numbers
|
||||
{
|
||||
public struct Quaternion : IComparable<double>,
|
||||
IEnumerable<double>,
|
||||
IFormattable,
|
||||
INumberGroup<Quaternion, double>
|
||||
#if CS11_OR_GREATER
|
||||
,//INumber<Quaternion>, Maybe some day.
|
||||
IFromTuple<Quaternion, (double, double, double, double)>,
|
||||
IInterpolable<Quaternion>,
|
||||
IPresets4d<Quaternion>,
|
||||
IRoundable<Quaternion>,
|
||||
ISimpleMathOperations<Quaternion>,
|
||||
ISplittable<Quaternion, (double[] Ws, double[] Xs, double[] Ys, double[] Zs)>,
|
||||
IVectorOperations<Quaternion>
|
||||
#endif
|
||||
{
|
||||
public static Quaternion Backward => new Quaternion(0, 0, 0, -1);
|
||||
public static Quaternion Down => new Quaternion(0, 0, -1, 0);
|
||||
public static Quaternion Forward => new Quaternion(0, 0, 0, 1);
|
||||
public static Quaternion HighW => new Quaternion(1, 0, 0, 0);
|
||||
public static Quaternion Left => new Quaternion(0, -1, 0, 0);
|
||||
public static Quaternion LowW => new Quaternion(-1, 0, 0, 0);
|
||||
public static Quaternion Right => new Quaternion(0, 1, 0, 0);
|
||||
public static Quaternion Up => new Quaternion(0, 0, 1, 0);
|
||||
|
||||
public static Quaternion One => new Quaternion(1, 1, 1, 1);
|
||||
public static Quaternion Zero => new Quaternion(0, 0, 0, 0);
|
||||
|
||||
public Quaternion Conjugate => new Quaternion(w, -x, -y, -z);
|
||||
public double Magnitude => MathE.Sqrt(w * w + x * x + y * y + z * z);
|
||||
public double MagnitudeSqr => w * w + x * x + y * y + z * z;
|
||||
|
||||
public double w, x, y, z;
|
||||
|
||||
public Quaternion(double w, double x, double y, double z)
|
||||
{
|
||||
this.w = w;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
public Quaternion(IEnumerable<double> nums)
|
||||
{
|
||||
w = 0;
|
||||
x = 0;
|
||||
y = 0;
|
||||
z = 0;
|
||||
|
||||
int index = 0;
|
||||
foreach (double item in nums)
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index == 4) break;
|
||||
}
|
||||
}
|
||||
public Quaternion(Fill<double> fill)
|
||||
{
|
||||
w = fill(0);
|
||||
x = fill(1);
|
||||
y = fill(2);
|
||||
z = fill(3);
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return w;
|
||||
case 1: return x;
|
||||
case 2: return y;
|
||||
case 3: return z;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: w = value; break;
|
||||
case 1: x = value; break;
|
||||
case 2: y = value; break;
|
||||
case 3: z = 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 'w': items[i] = w; break;
|
||||
case 'x': items[i] = x; break;
|
||||
case 'y': items[i] = y; break;
|
||||
case 'z': items[i] = z; 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 'w': w = stepper.Current; break;
|
||||
case 'x': x = stepper.Current; break;
|
||||
case 'y': y = stepper.Current; break;
|
||||
case 'z': z = stepper.Current; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Quaternion Abs(Quaternion num) => new Quaternion(num.Magnitude, 0, 0, 0);
|
||||
public static Quaternion Ceiling(Quaternion num) =>
|
||||
new Quaternion(MathE.Ceiling(num.w),
|
||||
MathE.Ceiling(num.x),
|
||||
MathE.Ceiling(num.y),
|
||||
MathE.Ceiling(num.z));
|
||||
public static Quaternion Ceiling(Quaternion num, Quaternion min, Quaternion max) =>
|
||||
new Quaternion(MathE.Clamp(num.w, min.w, max.w),
|
||||
MathE.Clamp(num.x, min.x, max.x),
|
||||
MathE.Clamp(num.y, min.y, max.y),
|
||||
MathE.Clamp(num.z, min.z, max.z));
|
||||
public static Quaternion ClampMagnitude(Quaternion num, double minMag, double maxMag)
|
||||
{
|
||||
Quaternion copy = num;
|
||||
ClampMagnitude(ref copy, minMag, maxMag);
|
||||
return copy;
|
||||
}
|
||||
public static void ClampMagnitude(ref Quaternion num, double minMag, double maxMag)
|
||||
{
|
||||
if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag));
|
||||
double mag = num.Magnitude;
|
||||
|
||||
double factor;
|
||||
if (mag < minMag) factor = minMag / mag;
|
||||
else if (mag > maxMag) factor = maxMag / mag;
|
||||
else factor = 1;
|
||||
|
||||
num.w *= factor;
|
||||
num.x *= factor;
|
||||
num.y *= factor;
|
||||
num.z *= factor;
|
||||
}
|
||||
public static double Dot(Quaternion a, Quaternion b) => a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
public static double Dot(IEnumerable<Quaternion> nums)
|
||||
{
|
||||
double w = 1, x = 1, y = 1, z = 1;
|
||||
foreach (Quaternion q in nums)
|
||||
{
|
||||
w *= q.w;
|
||||
x *= q.x;
|
||||
y *= q.y;
|
||||
z *= q.z;
|
||||
}
|
||||
return w + x + y + z;
|
||||
}
|
||||
public static Quaternion Floor(Quaternion num) =>
|
||||
new Quaternion(MathE.Floor(num.w),
|
||||
MathE.Floor(num.x),
|
||||
MathE.Floor(num.y),
|
||||
MathE.Floor(num.z));
|
||||
public static Quaternion Lerp(Quaternion a, Quaternion b, double t, bool clamp = true) =>
|
||||
new Quaternion(MathE.Lerp(a.w, b.w, t, clamp),
|
||||
MathE.Lerp(a.x, b.x, t, clamp),
|
||||
MathE.Lerp(a.y, b.y, t, clamp),
|
||||
MathE.Lerp(a.z, b.z, t, clamp));
|
||||
public static Quaternion Product(IEnumerable<Quaternion> nums)
|
||||
{
|
||||
bool any = false;
|
||||
Quaternion result = One;
|
||||
foreach (Quaternion q in nums)
|
||||
{
|
||||
any = true;
|
||||
result *= q;
|
||||
}
|
||||
return any ? result : Zero;
|
||||
}
|
||||
public static Quaternion Round(Quaternion num) =>
|
||||
new Quaternion(MathE.Round(num.w),
|
||||
MathE.Round(num.x),
|
||||
MathE.Round(num.y),
|
||||
MathE.Round(num.z));
|
||||
public static Quaternion Sum(IEnumerable<Quaternion> nums)
|
||||
{
|
||||
bool any = false;
|
||||
Quaternion result = One;
|
||||
foreach (Quaternion q in nums)
|
||||
{
|
||||
any = true;
|
||||
result += q;
|
||||
}
|
||||
return any ? result : Zero;
|
||||
}
|
||||
|
||||
public static (double[] Ws, double[] Xs, double[] Ys, double[] Zs) SplitArray(IEnumerable<Quaternion> nums)
|
||||
{
|
||||
int count = nums.Count();
|
||||
double[] Ws = new double[count],
|
||||
Xs = new double[count],
|
||||
Ys = new double[count],
|
||||
Zs = new double[count];
|
||||
int index = 0;
|
||||
foreach (Quaternion q in nums)
|
||||
{
|
||||
Ws[index] = q.w;
|
||||
Xs[index] = q.x;
|
||||
Ys[index] = q.y;
|
||||
Zs[index] = q.z;
|
||||
index++;
|
||||
}
|
||||
return (Ws, Xs, Ys, Zs);
|
||||
}
|
||||
|
||||
public IEnumerator<double> GetEnumerator()
|
||||
{
|
||||
yield return w;
|
||||
yield return x;
|
||||
yield return y;
|
||||
yield return z;
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public int CompareTo(double other) => MagnitudeSqr.CompareTo(other * other);
|
||||
public int CompareTo(Quaternion other) => MagnitudeSqr.CompareTo(other.MagnitudeSqr);
|
||||
#if CS8_OR_GREATER
|
||||
public int CompareTo(object? other)
|
||||
#else
|
||||
public int CompareTo(object other)
|
||||
#endif
|
||||
{
|
||||
if (other is null) return 1;
|
||||
else if (other is Quaternion otherQuat) return CompareTo(otherQuat);
|
||||
else if (other is double otherNum) return CompareTo(otherNum);
|
||||
//else if (TryConvertFrom(other, out Quaternion otherConvert)) return CompareTo(otherConvert);
|
||||
else return 1;
|
||||
}
|
||||
public bool Equals(double other) => w == other && x == 0 && y == 0 && z == 0;
|
||||
public bool Equals(Complex other) => w == other.r && x == other.i && y == 0 && z == 0;
|
||||
public bool Equals(Quaternion other) => w == other.w && x == other.x && y == other.y && z == other.z;
|
||||
#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 Quaternion otherQuat) return Equals(otherQuat);
|
||||
else if (other is Complex otherComp) return Equals(otherComp);
|
||||
else if (other is double otherNum) return Equals(otherNum);
|
||||
//else if (TryConvertFrom(other, out Quaternion otherConvert)) return Equals(otherConvert);
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
public override string ToString() => ToStringHelper.HighDimNumberToString(this, null, null);
|
||||
#if CS8_OR_GREATER
|
||||
public string ToString(string? format) => ToStringHelper.HighDimNumberToString(this, format, null);
|
||||
public string ToString(IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, null, provider);
|
||||
public string ToString(string? format, IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, format, provider);
|
||||
#else
|
||||
public string ToString(string format) => ToStringHelper.HighDimNumberToString(this, format, null);
|
||||
public string ToString(IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, null, provider);
|
||||
public string ToString(string format, IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, format, provider);
|
||||
#endif
|
||||
|
||||
public double[] ToArray() => new double[] { w, x, y, z };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
Quaternion @this = this;
|
||||
return i => @this[i];
|
||||
}
|
||||
public List<double> ToList() => new List<double>() { w, x, y, z };
|
||||
|
||||
public static Quaternion operator +(Quaternion a) => a;
|
||||
public static Quaternion operator +(Quaternion a, Quaternion b) => new Quaternion(a.w + b.w, a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
public static Quaternion operator +(Quaternion a, Complex b) => new Quaternion(a.w + b.r, a.x + b.i, a.y, a.z);
|
||||
public static Quaternion operator +(Quaternion a, double b) => new Quaternion(a.w + b, a.x, a.y, a.z);
|
||||
public static Quaternion operator -(Quaternion a) => new Quaternion(-a.w, -a.x, -a.y, -a.z);
|
||||
public static Quaternion operator -(Quaternion a, Quaternion b) => new Quaternion(a.w - b.w, a.x - b.x, a.y - b.y, a.z - b.z);
|
||||
public static Quaternion operator -(Quaternion a, Complex b) => new Quaternion(a.w - b.r, a.x - b.i, a.y, a.z);
|
||||
public static Quaternion operator -(Quaternion a, double b) => new Quaternion(a.w - b, a.x, a.y, a.z);
|
||||
public static Quaternion operator *(Quaternion a, Quaternion b) =>
|
||||
new Quaternion(a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
|
||||
a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
|
||||
a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
|
||||
a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w);
|
||||
public static Quaternion operator *(Quaternion a, Complex b) =>
|
||||
new Quaternion(a.w * b.r - a.x * b.i,
|
||||
a.x * b.r + a.w * b.i,
|
||||
a.y * b.r + a.z * b.i,
|
||||
a.z * b.r - a.y * b.i);
|
||||
public static Quaternion operator *(Quaternion a, double b) => new Quaternion(a.w * b, a.x * b, a.y * b, a.z * b);
|
||||
public static bool operator ==(Quaternion a, Quaternion b) => a.Equals(b);
|
||||
public static bool operator !=(Quaternion a, Quaternion b) => !a.Equals(b);
|
||||
public static bool operator >(Quaternion a, Quaternion b) => a.CompareTo(b) > 0;
|
||||
public static bool operator <(Quaternion a, Quaternion b) => a.CompareTo(b) < 0;
|
||||
public static bool operator >=(Quaternion a, Quaternion b) => a.CompareTo(b) >= 0;
|
||||
public static bool operator <=(Quaternion a, Quaternion b) => a.CompareTo(b) <= 0;
|
||||
|
||||
public static implicit operator Quaternion(Complex complex) => new Quaternion(complex.r, complex.i, 0, 0);
|
||||
public static implicit operator Quaternion(Float4 group) => new Quaternion(group.w, group.x, group.y, group.z);
|
||||
public static explicit operator Quaternion(Int4 group) => new Quaternion(group.w, group.x, group.y, group.z);
|
||||
public static implicit operator Quaternion(ListTuple<double> tuple) => new Quaternion(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
public static explicit operator Quaternion(System.Numerics.Quaternion quat) => new Quaternion(quat.W, quat.X, quat.Y, quat.Z);
|
||||
public static implicit operator Quaternion(ValueTuple<double, double, double, double> tuple) => new Quaternion(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||
|
||||
public static implicit operator ListTuple<double>(Quaternion quat) => new ListTuple<double>(quat.w, quat.x, quat.y, quat.z);
|
||||
public static implicit operator System.Numerics.Quaternion(Quaternion quat) => new System.Numerics.Quaternion((float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w);
|
||||
public static implicit operator ValueTuple<double, double, double, double>(Quaternion quat) => (quat.w, quat.x, quat.y, quat.z);
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
<!-- General stuff -->
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.1;netstandard1.3;netstandard2.1;netcoreapp3.0;net5.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.1;netstandard1.3;netstandard2.1;net46;net462;net47;netcoreapp3.0;net5.0;net7.0</TargetFrameworks>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
|
||||
<DebugType>portable</DebugType>
|
||||
@ -15,7 +15,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<Title>Nerd_STF</Title>
|
||||
<Version>3.0.0-beta2</Version>
|
||||
<Version>3.0.0</Version>
|
||||
<Authors>That_One_Nerd</Authors>
|
||||
<Description>A general-purpose mathematics library for C#.</Description>
|
||||
<PackageProjectUrl>https://github.com/That-One-Nerd/Nerd_STF</PackageProjectUrl>
|
||||
@ -28,13 +28,118 @@
|
||||
<PackageTags>c#;csharp;c sharp;math;mathematics;mathametics;maths;color;rgb;rgba;cmyk;cmyka;hsv;hsva;calculus;linear algebra;linalg;linearalgebra;matrix;matrix2x2;matrix 2x2;matrix3x3;matrix 3x3;matrix4x4;matrix 4x4;matrix multiplication;vector;vector2d;vector3d;vector2;vector3;float2;float3;float4;int2;int3;int4;angle;geometry;vert;line;polygon;triangle;quadrilateral;sphere;circle;number system;numbersystem;complex numbers;complex;2d numbers;2dnumbers;quaternions;4d numbers;4dnumbers;equation;equations;polynomial;quadratic;linear equation</PackageTags>
|
||||
|
||||
<!-- Sorry this is stupidly long, wish I could have linked a markdown file instead. -->
|
||||
<PackageReleaseNotes># Nerd_STF v3.0-beta2
|
||||
<PackageReleaseNotes># Nerd_STF Version 3.0
|
||||
|
||||
I've added a substantial number of things in this update, mostly matrix related.
|
||||
It's time to get this thing out of beta.
|
||||
|
||||
Nerd_STF 3.0 is finally out! Honestly, there's not a whole lot of tweaks here from the last prerelease, because I got bored of implementing `INumber` methods for the `Quaternion` class, so I'm putting that on hold for now.
|
||||
|
||||
For those that haven't seen the pre-releases, the v3.0 update is basically an entire rewrite of the previous library. Almost everything made it over, but there's also a lot of breaking changes. Here's the gist:
|
||||
|
||||
# What's New
|
||||
|
||||
## More Compatibility
|
||||
|
||||
Nerd_STF now targets several versions of .NET and .NET Standard, making it basically run anywhere. You can use [this website](https://dotnet.microsoft.com/en-us/platform/dotnet-standard#versions) to see what different versions of .NET Standard support, but if your project uses a version of .NET that was released in the last 10 years, chances are Nerd_STF supports it.
|
||||
|
||||
In addition, Nerd_STF uses some of the new C# features while still retaining older compatibility. If you want to use Nerd_STF in your .NET 8.0 project, you will reference the version of Nerd_STF compiled for .NET 7.0 and retain those fancy new interface features (among others) found in C# 11. Nullability support has been added to all versions of .NET that use C# 8 and above. And if I decide to use more new C# features for Nerd_STF, I'll just target another version of .NET.
|
||||
|
||||
## Committed to Doubles
|
||||
|
||||
Nerd_STF is a precision library, not one meant to be highly optimized at the sacrifice of precision. So I've decided to fully commit to doubles. The double groups are still called `Float2`, `Float3` and `Float4`, because `Double2` doesn't have quite the same ring to it now, does it? Hope it doesn't get too confusing.
|
||||
|
||||
But all math functions are now using doubles (with a few exceptions).
|
||||
|
||||
## 'w' Goes in Front Now
|
||||
|
||||
I think this is how it should have been. I was really breaking the rules of the alphabet before. Previously in a `Float4`, the `w` component was fourth. Now it is first. The order goes w, x, y, z. You know, how it should.
|
||||
|
||||
This means though that casting a `Float3` to a `Float4` will put the extra zero at the start, not the end (because `x` -> `x` in the cast).
|
||||
```csharp
|
||||
Float3 xyz = (5, 6, 7);
|
||||
Float4 wxyz = xyz; // Gives (0, 5, 6, 7)
|
||||
```
|
||||
This also means that truncating a `Float4` removes the front `w` first, giving some odd results.
|
||||
```csharp
|
||||
Float2 xy = (10, 9);
|
||||
Float4 wxyz = xy; // Gives (0, 10, 9, 0)
|
||||
```
|
||||
```csharp
|
||||
Float4 wxyz = (9, 8, 7, 6);
|
||||
Float2 xy = (Float2)wxyz; // Must be explicitly stated. Yields (8, 7)
|
||||
```
|
||||
|
||||
But `x` always goes to `x` when casting between groups, same with the other variables. Hopefully that'll make more sense.
|
||||
|
||||
## Combination Indexers
|
||||
|
||||
One thing I've always been envious of was HLSL's ability to easily make a subset of a group.
|
||||
```c++
|
||||
float3 group = float3(1, 2, 3);
|
||||
float2 part = group.yz; // Like this.
|
||||
```
|
||||
And I had a crude version of this in Nerd_STF before, with properties for `XY`, `YZW`, and stuff like that. But you couldn't do things out of order (for example, you could never do `.ZY`). Also, the naming scheme would not make very much sense. `x` was always the first item Now, you can do it with an indexer.
|
||||
|
||||
```csharp
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
IEnumerable<double> zyx = wxyz["zyx"]; // Yields [ 4, 3, 2 ]
|
||||
```
|
||||
|
||||
I think you get it, it makes sense. It returns an IEnumerable though, so support has been added in the group constructors to read data from an IEnumerable. You can also set things this way.
|
||||
|
||||
```csharp
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
wxyz["xy"] = [ 9, 8 ]; // Yields (9, 8, 3, 4)
|
||||
```
|
||||
|
||||
You can also have duplicates. Why you would want duplicates is beyond me. And the order can be whatever you'd like.
|
||||
```csharp
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
IEnumerable<double> nums = wxyz["wyyxzzwy"]; // Yields [ 1, 3, 3, 2, 4, 4, 1, 3 ]
|
||||
```
|
||||
|
||||
## Better Equations
|
||||
|
||||
The previous equation system was just a delegate to a method. While it worked for unoptimized things, it won't automatically give precise results. So that system has been overhauled.
|
||||
|
||||
Now, every equation derives from the `IEquation` interface, which defines a few operators (most importantly the `Get(double)` method, which is intended to evaluate the equation at the given input). And there are multiple types. There's the base `Equation` type that replicates the method delegate it used to be, but there are also now `Polynomial` equations which specialize in... uh... polynomials, including `Quadratic` and `Linear` along with the dynamic `Polynomial` type.
|
||||
|
||||
The indexer is equivalent to calling the `Get(double)` method.
|
||||
|
||||
Creating your own is easy, simply derive from the interface and implement the methods required. You should never throw an exception if the two equations you are adding (or multiplying or whatever) are not the same type. If they cannot be combined in a nice way, you should default to the delegate-based approach. Here is an example:
|
||||
```csharp
|
||||
public IEquation Add(IEquation other) {
|
||||
if (other is YourEquation yourEqu) {
|
||||
// Properly add your two equations.
|
||||
} else {
|
||||
// Unknown other equation type, do a basic addition system.
|
||||
return new Equation(x => Get(x) + other.Get(x));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And in practice, you should avoid referring to a general equation by its type. Go by the interface operators instead.
|
||||
```csharp
|
||||
double Fun(double x) => 0.5 * MathE.Sin(x);
|
||||
Equation a = (Equation)Fun; // The traditional delegate approach from previous versions.
|
||||
Polynomial b = new Polynomial(1, 5, 4); // x^2 + 5x + 4
|
||||
|
||||
IEquation c = a.Add(b).Multiply(2); // Result is technically an `Equation`, but we should not cast here.
|
||||
```
|
||||
|
||||
## Renamed the `Mathf` class.
|
||||
|
||||
I chose that name because I thought Unity did it well, but I also intend for this project to be compatible with Unity. So I've renamed it to `MathE`. I'm still iffy on that name. I'll commit to one before this project goes out of beta, but it might change until then. Other ideas I'm considering are `Mathe` and `Math2`. Feel free to give your input!
|
||||
|
||||
## Support for `System.Drawing` types.
|
||||
|
||||
I've tried to use this library when working with Windows Forms a few times. Problem is, it sucks having to manually set the variables from `Point` and `Size`. So Nerd_STF 3.0 now does that for you, with implicit casts to and from both, along with their float variations.
|
||||
|
||||
**It's worth mentioning that `Float2` is a double group, while `PointF` is a float group. Data *will* be lost slightly when implicitly casting. Watch out!**
|
||||
|
||||
## List Tuples
|
||||
|
||||
In the previous beta, I introduced **Combination Indexers** for the double and int groups, however a problem was that they only returned `IEnumerable`s. So while some interesting things were supported, some things were not.
|
||||
In the beta1, I introduced **Combination Indexers** for the double and int groups, however a problem was that they only returned `IEnumerable`s. So while some interesting things were supported, some things were not.
|
||||
|
||||
```csharp
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
@ -74,21 +179,57 @@ This type originally went under the name of `Rational` in Nerd_STF 2.x, but that
|
||||
|
||||
Can I just say that the `INumber` interface is really annoying to write a type for? There's so many weird casting functions and a whole lot of methods that even the .NET developers will hide in public declarations. Why have them at all?
|
||||
|
||||
---
|
||||
|
||||
And I want to change the name of the `MathE` class. I'm thinking `Math2`, but I'm open to suggestions.
|
||||
|
||||
## And Best of All, Matrices
|
||||
|
||||
Oh yeah, we're adding those things again. I haven't completed (or even started) the dynamic `Matrix` class, that will arrive in beta3. But I have the `Matrix2x2`, `Matrix3x3`, and `Matrix4x4` fully implemented. The `ToString()` methods are much better with the new implementation than previously, and the `GetHashCode()` methods give different results even if the numbers have their positions swapped (which they originally didn't do).
|
||||
Oh yeah, we're adding those things again. We've got hard-coded `Matrix2x2`, `Matrix3x3`, and `Matrix4x4` classes, as well as a dynamic-sized `Matrix` class fully implemented. The `ToString()` methods are much better with the new implementation than previously, and the `GetHashCode()` methods give different results even if the numbers have their positions swapped (which they originally didn't do).
|
||||
|
||||
And it's much faster. Much, much faster. Don't get me wrong, multiplying a 4x4 matrix still requires 64 multiplications and 48 additions, which is quite a lot, but my original implementation was littered with many method calls, easily doubling the runtime. I have now super-inlined basically all of the static matrix code. And I mean, replacing all method calls with nothing but multiplication and addition for things like the determinants, the cofactors, the inverses, and more. Don't look at the source, it's really ugly.
|
||||
And they're much faster. Much, much faster. Don't get me wrong, multiplying a 4x4 matrix still requires 64 multiplications and 48 additions, which is quite a lot, but my original implementation was littered with many method calls, easily doubling the runtime. I have now super-inlined basically all of the static matrix code. And I mean, replacing all method calls with nothing but multiplication and addition for things like the determinants, the cofactors, the inverses, and more. Don't look at the source, it's really ugly.
|
||||
|
||||
**Note that the dynamic matrix class is not optimized this way, only the constant-size classes.**
|
||||
|
||||
## `Fill<T>` and `Fill2d<T>` are back!
|
||||
|
||||
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.
|
||||
|
||||
## 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;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
That's all the major stuff in this update! I'll see you guys in beta3!
|
||||
Anyway, that's a lot of stuff. It's a big update. You should download it!
|
||||
|
||||
P.S. I know that the System library also includes `Vector2`, `Vector3`, and `Vector4` types. I'll add casting support for them soon.</PackageReleaseNotes>
|
||||
Sorry my original writing has been a bit off. I kind of forgot about this project again. I can't promise a new update any time soon, but maybe there'll be one!
|
||||
|
||||
Enjoy the full release!</PackageReleaseNotes>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- ItemGroup customization based on framework. -->
|
||||
@ -97,24 +238,61 @@ P.S. I know that the System library also includes `Vector2`, `Vector3`, and `Vec
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.1'">
|
||||
<PackageReference Include="System.Drawing.Primitives" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" /> <!-- Version that comes with .NET has vulnerability. -->
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" /> <!-- Version that comes with .NET has vulnerabilities. -->
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" /> <!-- Newer versions not supported. -->
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> <!-- Version that comes with .NET has vulnerability. -->
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" /> <!-- Newer versions not supported. -->
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> <!-- Version that comes with .NET has vulnerabilities. -->
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <!-- Newer version not supported. -->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
|
||||
<PackageReference Include="System.Drawing.Primitives" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" /> <!-- Version that comes with .NET has vulnerability. -->
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" /> <!-- Version that comes with .NET has vulnerabilities. -->
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" /> <!-- Newer versions not supported. -->
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> <!-- Version that comes with .NET has vulnerability. -->
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" /> <!-- Newer versions not supported. -->
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> <!-- Version that comes with .NET has vulnerabilities. -->
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <!-- Newer version not supported. -->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net46'">
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" /> <!-- Newer versions not supported. -->
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <!-- Newer versions not supported. -->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<PackageReference Include="System.Memory" Version="4.6.2" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net47'">
|
||||
<PackageReference Include="System.Memory" Version="4.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- PropertyGroup customization based on framework. -->
|
||||
<!-- Used to define environment variables based on features the framework supports. -->
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net471'">
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard1.1'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER</DefineConstants>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net46'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net47'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
@ -133,19 +311,6 @@ P.S. I know that the System library also includes `Vector2`, `Vector3`, and `Vec
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard1.1'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER</DefineConstants>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Pack extra stuff into the NuGet package. -->
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user