Compare commits

..

16 Commits

Author SHA1 Message Date
7d2b18f969 Let's get this thing out of beta. 2025-09-18 12:36:07 -04:00
3a7a3c320e Partial work on quaternions and some other tweaks. 2025-05-13 08:59:02 -04:00
664ba0fab7 Finally got around to finishing up complex numbers. Sorry it took so long. 2025-05-12 08:53:42 -04:00
2762dab872 Some small things. Was working on complex numbers but got bored. 2025-02-26 09:19:12 -05:00
48890c236e Ready for a new beta release. 2025-02-19 10:30:07 -05:00
6182db049e Some color format work and indexed colors. 2025-02-18 09:06:41 -05:00
e070aa097f Tested HSV and untested CMYK types. This beta will concern colors. 2025-02-13 11:24:19 -05:00
866326863b Some color things. More to come. 2025-01-23 08:01:59 -05:00
27c64c4291 Small matrix things. I think I'm going to start color next. 2025-01-13 09:40:01 -05:00
0f9bcd2720 Wrote the dynamic matrix class. 2024-11-26 13:24:43 -05:00
938c95fa18 Added fill support. Also reworked the matrix constructors to make more sense. 2024-11-26 08:30:40 -05:00
e59f253cc8 Beta2 completed. 2024-11-25 10:20:20 -05:00
10e70a3574 Boy that 4x4 matrix was a piece of work. I think I'm ready for beta2. 2024-11-25 09:42:12 -05:00
36d4411d70 Prerequisites for matrices and 2x2 matrix done. 2024-11-14 08:56:10 -05:00
fcee608322 Not sure why I haven't been committing more. Pretty much finished fraction, made some other small changes. 2024-11-07 10:36:28 -05:00
0704b8eec7 Lots of nice changes. Made angle, more interfaces, working on fraction. 2024-10-31 12:52:21 -04:00
67 changed files with 6432 additions and 342 deletions

View File

@ -1,108 +1,53 @@
# Nerd_STF v3.0-beta1 # Nerd_STF v3.0-beta3
Hi! Pretty much nothing has remained the same from version 2. There are plenty of breaking changes, and the betas will have plenty of missing features from 2.4.1. The betas will continue until every feature from 2.4.1 has been added to 3.0 or scrapped. 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.
In the mean time, here's what's new. Here's what's new:
## More Compatibility ## `Fill<T>` and `Fill2d<T>` are back!
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. 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.
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. ## Slight Matrix Constructor Change
## Committed to Doubles 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.
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. ## And Best of All: Colors!
But all math functions are now using doubles (with a few exceptions). 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.
## 'w' Goes in Front Now 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 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. 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.
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). 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.
```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. 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.
## 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 ```csharp
Float4 wxyz = (1, 2, 3, 4); void MethodA()
IEnumerable<double> zyx = wxyz["zyx"]; // Yields [ 4, 3, 2 ] {
``` ColorPalette<ColorRGB> palette = new(8);
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. // palette[3] is currently set to black.
MethodB(palette[3]);
// palette[3] is now set to blue.
}
```csharp void MethodB(IndexedColor<ColorRGB> color)
Float4 wxyz = (1, 2, 3, 4); {
wxyz["xy"] = [ 9, 8 ]; // Yields (9, 8, 3, 4) color.Color() = ColorRGB.Blue;
```
You can also have duplicates. Why you would want duplicates is beyond me. And the order can be whatever you'd like. // You could also:
```csharp ref ColorRGB val = ref color.Color();
Float4 wxyz = (1, 2, 3, 4); val = ColorRGB.Blue;
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. 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:
```csharp - Complex numbers and quaternions.
double Fun(double x) => 0.5 * MathE.Sin(x); - More color types and formats.
Equation a = (Equation)Fun; // The traditional delegate approach from previous versions. - Bit-offset compatible streams.
Polynomial b = new Polynomial(1, 5, 4); // x^2 + 5x + 4 - Fix bugs/inconveniences I've noted.
IEquation c = a.Add(b).Multiply(2); // Result is technically an `Equation`, but we should not cast here. I think the Image type will be completely reworked and might be what version 3.1 is.
```
## 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!**
---
Anyway, that's most of the big changes! I don't know if I'll do the full changelog like I have before. It takes a really long time to compile for large updates. We'll see. Thanks for checking out the update and I hope you use it well (or wait for the release version, that's fine too)!

View File

5
Nerd_STF/Fill.cs Normal file
View File

@ -0,0 +1,5 @@
namespace Nerd_STF
{
public delegate T Fill<T>(int index);
public delegate T Fill2d<T>(int x, int y);
}

View File

@ -5,7 +5,9 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0066:Use 'switch' expression", Justification = "Only available for C#8+.")] [assembly: SuppressMessage("Style", "IDE0066:Use 'switch' expression", Justification = "Only available for C#8+.")]
[assembly: SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "Only available for C#9+")] [assembly: SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "Only available for C#9+")]
[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Only available for C#9+")] [assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Only available for C#9+")]
[assembly: SuppressMessage("Style", "IDE0251:Make member 'readonly'", Justification = "Only available for C#8+. Also, what does applying 'readonly' to a method even do?")] [assembly: SuppressMessage("Style", "IDE0251:Make member 'readonly'", Justification = "Only available for C#8+. Also, what does applying 'readonly' to a method even do?")]
[assembly: SuppressMessage("Style", "IDE0057:Use range operator", Justification = "Not supported in .NET Standard 1.1 and 1.3.")]
[assembly: SuppressMessage("Performance", "CA1846:Prefer 'AsSpan' over 'Substring'", Justification = "Not supported in .NET Standard 1.1 and 1.3.")]

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

View File

@ -0,0 +1,18 @@
namespace Nerd_STF.Graphics
{
public enum ColorChannel
{
Red,
Green,
Blue,
Alpha,
Hue,
Saturation,
Value,
Cyan,
Magenta,
Yellow,
Key,
Index
}
}

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

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

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

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

View 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()}";
}
}

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

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

View 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

View 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

View File

@ -2,7 +2,7 @@
namespace Nerd_STF.Helpers namespace Nerd_STF.Helpers
{ {
public static class CordicHelper internal static class CordicHelper
{ {
// Starts at 4 radians. Each index downwards is half that. // Starts at 4 radians. Each index downwards is half that.
// Goes from 2^2 to 2^-19. // Goes from 2^2 to 2^-19.

View File

@ -0,0 +1,72 @@
using Nerd_STF.Mathematics;
using Nerd_STF.Mathematics.Algebra;
using System.Collections.Generic;
namespace Nerd_STF.Helpers
{
internal static class MatrixHelper
{
public static void SetMatrixValues<TMat>(TMat matrix, IEnumerable<IEnumerable<double>> vals, bool byRows)
where TMat : IMatrix<TMat>
{
Int2 size = matrix.Size;
int x = 0;
foreach (IEnumerable<double> part in vals)
{
int y = 0;
foreach (double v in part)
{
matrix[byRows ? (y, x) : (x, y)] = v;
y++;
if (byRows ? y >= size.x : y >= size.y) break;
}
x++;
if (byRows ? x >= size.y : x >= size.x) break;
}
}
public static void SetMatrixValues<TMat>(TMat matrix, IEnumerable<ListTuple<double>> vals, bool byRows)
where TMat : IMatrix<TMat>
{
// Literally the same code. Sucks that casting doesn't work here.
Int2 size = matrix.Size;
int x = 0;
foreach (IEnumerable<double> part in vals)
{
int y = 0;
foreach (double v in part)
{
matrix[byRows ? (y, x) : (x, y)] = v;
y++;
if (byRows ? y >= size.y : y >= size.x) break;
}
x++;
if (byRows ? x >= size.x : x >= size.y) break;
}
}
public static void SetRow<TMat>(TMat matrix, int row, IEnumerable<double> vals)
where TMat : IMatrix<TMat>
{
int col = 0;
int max = matrix.Size.y;
foreach (double v in vals)
{
matrix[row, col] = v;
col++;
if (col >= max) return;
}
}
public static void SetColumn<TMat>(TMat matrix, int col, IEnumerable<double> vals)
where TMat : IMatrix<TMat>
{
int row = 0;
int max = matrix.Size.x;
foreach (double v in vals)
{
matrix[row, col] = v;
row++;
if (row >= max) return;
}
}
}
}

View File

@ -0,0 +1,54 @@
using System;
namespace Nerd_STF.Helpers
{
internal static class ParseHelper
{
// TODO: Allow parsing more stuff (hexadecimal).
public static double ParseDouble(ReadOnlySpan<char> str)
{
// Turns out this is less accurate than copying and modifying
// the code from ParseDoubleWholeDecimals. I think because applying
// 0.1 to the whole number is worse than 0.1 to a each individual
// decimal point.
int raw = ParseDoubleWholeDecimals(str, out int places);
double value = raw;
for (int i = 0; i < places; i++) value *= 0.1;
return value;
}
public static int ParseDoubleWholeDecimals(ReadOnlySpan<char> str, out int places)
{
str = str.Trim();
if (str.Length == 0) goto _fail;
places = 0;
bool negative = str.StartsWith("-".AsSpan());
int result = 0;
ReadOnlySpan<char>.Enumerator stepper = str.GetEnumerator();
if (negative) stepper.MoveNext();
bool decFound = false;
while (stepper.MoveNext())
{
char c = stepper.Current;
if (c == ',') continue;
else if (c == '.')
{
decFound = true;
continue;
}
if (c < '0' || c > '9') goto _fail;
int value = c - '0';
result = result * 10 + value;
if (decFound) places++;
}
return negative ? -result : result;
_fail:
throw new FormatException("Cannot parse double from span.");
}
}
}

View File

@ -1,9 +1,41 @@
using System; using System;
using System.Runtime.CompilerServices;
namespace Nerd_STF.Helpers namespace Nerd_STF.Helpers
{ {
internal static class TargetHelper 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>() public static T[] EmptyArray<T>()
{ {
#if NETSTANDARD1_1 #if NETSTANDARD1_1

View File

@ -1,4 +1,8 @@
using System.Text; using Nerd_STF.Mathematics;
using Nerd_STF.Mathematics.Algebra;
using System;
using System.Collections.Generic;
using System.Text;
namespace Nerd_STF.Helpers namespace Nerd_STF.Helpers
{ {
@ -48,5 +52,103 @@ namespace Nerd_STF.Helpers
} }
return builder.Remove(builder.Length - 1, 1).ToString(); return builder.Remove(builder.Length - 1, 1).ToString();
} }
#if CS8_OR_GREATER
public static string MatrixToString<T>(T matrix, string? format)
#else
public static string MatrixToString<T>(T matrix, string format)
#endif
where T : IMatrix<T>
{
// First convert all items to their string counterparts,
// then measure the lengths and do spacing accordingly.
Int2 size = matrix.Size;
string[,] items = new string[size.y, size.x];
for (int x = 0; x < size.x; x++) for (int y = 0; y < size.y; y++)
items[y, x] = matrix[x, y].ToString(format);
// Then write each line separately.
StringBuilder[] lines = new StringBuilder[size.x + 2];
for (int i = 0; i < lines.Length; i++)
{
StringBuilder builder = new StringBuilder();
if (i == 0) builder.Append('┌');
else if (i == lines.Length - 1) builder.Append('└');
else builder.Append('│');
lines[i] = builder;
}
int totalLen = 0;
for (int x = 0; x < size.y; x++)
{
int maxLen = 0;
for (int y = 0; y < size.x; y++)
{
string item = items[x, y];
if (item.Length > maxLen) maxLen = item.Length;
}
totalLen += maxLen + 1;
for (int y = 0; y < size.x; y++)
{
StringBuilder builder = lines[y + 1];
string item = items[x, y];
int spacing = maxLen - item.Length;
builder.Append(new string(' ', spacing + 1));
builder.Append(item);
}
}
// Finish up and merge.
StringBuilder total = new StringBuilder();
for (int i = 0; i < lines.Length; i++)
{
StringBuilder builder = lines[i];
if (i == 0) builder.Append(new string(' ', totalLen)).Append(" ┐");
else if (i == lines.Length - 1) builder.Append(new string(' ', totalLen)).Append(" ┘");
else builder.Append(" │");
total.Append(builder);
if (i != lines.Length - 1) total.AppendLine();
}
return total.ToString();
}
private static readonly string dimNumSymbols = " ijk?";
#if CS8_OR_GREATER
public static string HighDimNumberToString(IEnumerable<double> terms, string? format, IFormatProvider? provider)
#else
public static string HighDimNumberToString(IEnumerable<double> terms, string format, IFormatProvider provider)
#endif
{
StringBuilder builder = new StringBuilder();
int index = 0;
bool first = true;
foreach (double term in terms)
{
if (term == 0)
{
index++;
continue;
}
if (first) builder.Append(term.ToString(format, provider));
else
{
if (term > 0)
{
builder.Append(" + ");
builder.Append(term.ToString(format, provider));
}
else
{
builder.Append(" - ");
builder.Append((-term).ToString(format, provider));
}
}
if (index > 0) builder.Append(dimNumSymbols[MathE.Min(index, dimNumSymbols.Length)]);
first = false;
index++;
}
if (first) builder.Append(0.0.ToString(format, provider));
return builder.ToString();
}
} }
} }

View File

@ -0,0 +1,7 @@
namespace Nerd_STF
{
public interface ICombinationIndexer<TItem>
{
ListTuple<TItem> this[string key] { get; set; }
}
}

View File

@ -1,14 +1,14 @@
#if CS11_OR_GREATER #if CS11_OR_GREATER
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Nerd_STF.Mathematics.Abstract namespace Nerd_STF
{ {
public interface IFromTuple<TSelf, TTuple> public interface IFromTuple<TSelf, TTuple>
where TSelf : IFromTuple<TSelf, TTuple> where TSelf : IFromTuple<TSelf, TTuple>
where TTuple : struct, ITuple where TTuple : struct, ITuple
{ {
public static abstract implicit operator TSelf(TTuple tuple); static abstract implicit operator TSelf(TTuple tuple);
public static abstract implicit operator TTuple(TSelf tuple); static abstract implicit operator TTuple(TSelf tuple);
} }
} }
#endif #endif

View File

@ -2,13 +2,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Nerd_STF.Mathematics.Abstract namespace Nerd_STF
{ {
public interface ISplittable<TSelf, TTuple> public interface ISplittable<TSelf, TTuple>
where TSelf : ISplittable<TSelf, TTuple> where TSelf : ISplittable<TSelf, TTuple>
where TTuple : struct, ITuple where TTuple : struct, ITuple
{ {
public static abstract TTuple SplitArray(IEnumerable<TSelf> values); static abstract TTuple SplitArray(IEnumerable<TSelf> values);
} }
} }
#endif #endif

145
Nerd_STF/ListTuple.cs Normal file
View File

@ -0,0 +1,145 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
namespace Nerd_STF
{
public readonly struct ListTuple<T> : IEnumerable<T>,
IEquatable<ListTuple<T>>
#if NET471_OR_GREATER || NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
,ITuple
#endif
{
public int Length => items.Length;
private readonly T[] items;
public ListTuple(IEnumerable<T> items)
{
this.items = items.ToArray();
}
public ListTuple(params T[] items)
{
this.items = items;
}
public ListTuple(Fill<T> items, int length)
{
this.items = new T[length];
for (int i = 0; i < length; i++) this.items[i] = items(i);
}
public T this[int index]
{
get => items[index];
set => items[index] = value;
}
#if NET471_OR_GREATER || NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#if CS8_OR_GREATER
object? ITuple.this[int index] => this[index];
#else
object ITuple.this[int index] => this[index];
#endif
#endif
public Enumerator GetEnumerator() => new Enumerator(this);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public bool Equals(ListTuple<T> other)
{
if (Length != other.Length) return false;
for (int i = 0; i < Length; i++)
{
T itemA = items[i], itemB = other.items[i];
if (itemA == null || itemB == null)
{
if (itemA == null && itemB == null) continue;
else return false;
}
if (!itemA.Equals(itemB)) return false;
}
return true;
}
#if CS8_OR_GREATER
public override bool Equals(object? other)
#else
public override bool Equals(object other)
#endif
{
if (other is null) return false;
else if (other is ListTuple<T> otherTuple) return Equals(otherTuple);
else return false;
}
public override int GetHashCode() => items.GetHashCode();
public override string ToString()
{
StringBuilder builder = new StringBuilder("(");
for (int i = 0; i < items.Length; i++)
{
builder.Append(items[i]);
if (i != items.Length - 1) builder.Append(", ");
}
builder.Append(')');
return builder.ToString();
}
public Fill<T> ToFill()
{
T[] items = this.items;
return i => items[i];
}
public static bool operator ==(ListTuple<T> a, ListTuple<T> b) => a.Equals(b);
public static bool operator !=(ListTuple<T> a, ListTuple<T> b) => !a.Equals(b);
public static implicit operator ValueTuple<T>(ListTuple<T> tuple) => new ValueTuple<T>(tuple[0]);
public static implicit operator ValueTuple<T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1]);
public static implicit operator ValueTuple<T, T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1], tuple[2]);
public static implicit operator ValueTuple<T, T, T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1], tuple[2], tuple[3]);
public static implicit operator ValueTuple<T, T, T, T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1], tuple[2], tuple[3], tuple[4]);
public static implicit operator ValueTuple<T, T, T, T, T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1], tuple[2], tuple[3], tuple[4], tuple[5]);
public static implicit operator ValueTuple<T, T, T, T, T, T, T>(ListTuple<T> tuple) => (tuple[0], tuple[1], tuple[2], tuple[3], tuple[4], tuple[5], tuple[6]);
public static implicit operator T[](ListTuple<T> tuple) => tuple.items;
public static implicit operator ListTuple<T>(ValueTuple<T> tuple) => new ListTuple<T>(tuple.Item1);
public static implicit operator ListTuple<T>((T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2);
public static implicit operator ListTuple<T>((T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3);
public static implicit operator ListTuple<T>((T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
public static implicit operator ListTuple<T>((T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5);
public static implicit operator ListTuple<T>((T, T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5, tuple.Item6);
public static implicit operator ListTuple<T>((T, T, T, T, T, T, T) tuple) => new ListTuple<T>(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5, tuple.Item6, tuple.Item7);
public static implicit operator ListTuple<T>(T[] array) => new ListTuple<T>(array);
public struct Enumerator : IEnumerator<T>
{
private int index;
private readonly ListTuple<T> tuple;
public T Current => tuple.items[index];
#if CS8_OR_GREATER
object? IEnumerator.Current => Current;
#else
object IEnumerator.Current => Current;
#endif
public bool MoveNext()
{
index++;
return index < tuple.items.Length;
}
public void Reset()
{
index = -1;
}
public void Dispose() { }
internal Enumerator(ListTuple<T> tuple)
{
index = -1;
this.tuple = tuple;
}
}
}
}

View File

@ -1,9 +0,0 @@
using System.Collections.Generic;
namespace Nerd_STF.Mathematics.Abstract
{
public interface ICombinationIndexer<TItem>
{
IEnumerable<TItem> this[string key] { get; set; }
}
}

View File

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
namespace Nerd_STF.Mathematics.Abstract
{
public interface INumberGroup<TSelf, TItem> : ICombinationIndexer<TItem>,
IEnumerable<TItem>,
IEquatable<TSelf>
where TSelf : INumberGroup<TSelf, TItem>
{
TItem this[int index] { get; set; }
TItem[] ToArray();
List<TItem> ToList();
}
}

View File

@ -1,10 +0,0 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics.Abstract
{
public interface IPresets1d<TSelf> where TSelf : IPresets1d<TSelf>
{
public static abstract TSelf One { get; }
public static abstract TSelf Zero { get; }
}
}
#endif

View File

@ -1,13 +0,0 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics.Abstract
{
public interface IPresets2d<TSelf> : IPresets1d<TSelf>
where TSelf : IPresets2d<TSelf>
{
public static abstract TSelf Down { get; }
public static abstract TSelf Left { get; }
public static abstract TSelf Right { get; }
public static abstract TSelf Up { get; }
}
}
#endif

View File

@ -1,11 +0,0 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics.Abstract
{
public interface IPresets3d<TSelf> : IPresets2d<TSelf>
where TSelf : IPresets3d<TSelf>
{
public static abstract TSelf Backward { get; }
public static abstract TSelf Forward { get; }
}
}
#endif

View File

@ -1,11 +0,0 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics.Abstract
{
public interface IPresets4d<TSelf> : IPresets3d<TSelf>
where TSelf : IPresets4d<TSelf>
{
public static abstract TSelf LowW { get; }
public static abstract TSelf HighW { get; }
}
}
#endif

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
namespace Nerd_STF.Mathematics.Algebra
{
public interface IMatrix<TSelf> : IEnumerable<double>,
IEquatable<TSelf>,
IFormattable
#if CS11_OR_GREATER
,IMatrixOperations<TSelf>
#endif
where TSelf : IMatrix<TSelf>
{
Int2 Size { get; }
double this[int r, int c] { get; set; }
double this[Int2 index] { get; set; }
ListTuple<double> this[int index, RowColumn direction] { get; set; }
ListTuple<double> GetRow(int row);
ListTuple<double> GetColumn(int column);
void SetRow(int row, IEnumerable<double> vals);
void SetColumn(int column, IEnumerable<double> vals);
double Determinant();
TSelf Adjoint();
TSelf Cofactor();
TSelf Transpose();
#if CS9_OR_GREATER
TSelf? Inverse();
#else
TSelf Inverse();
#endif
}
}

View File

@ -0,0 +1,18 @@
#if CS11_OR_GREATER
using System.Collections.Generic;
namespace Nerd_STF.Mathematics.Algebra
{
public interface IMatrixOperations<TSelf> : ISimpleMathOperations<TSelf>
where TSelf : IMatrix<TSelf>, IMatrixOperations<TSelf>
{
static abstract TSelf Average(IEnumerable<TSelf> vals);
static abstract TSelf Lerp(TSelf a, TSelf b, double t, bool clamp = true);
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);
}
}
#endif

View File

@ -0,0 +1,8 @@
namespace Nerd_STF.Mathematics.Algebra
{
public interface ISquareMatrix<TSelf> : IMatrix<TSelf>
where TSelf : ISquareMatrix<TSelf>
{
double Trace();
}
}

View File

@ -0,0 +1,11 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics.Algebra
{
public interface IStaticMatrix<TSelf> : IPresets1d<TSelf>, IMatrix<TSelf>
where TSelf : IStaticMatrix<TSelf>
{
static abstract TSelf Identity { get; }
static abstract TSelf SignField { get; }
}
}
#endif

View File

@ -0,0 +1,9 @@
namespace Nerd_STF.Mathematics.Algebra
{
public interface ISubmatrixOperations<TSelf, TSmaller>
where TSelf : IMatrix<TSelf>, ISubmatrixOperations<TSelf, TSmaller>
where TSmaller : IMatrix<TSmaller>
{
TSmaller Submatrix(int r, int c);
}
}

View File

@ -0,0 +1,16 @@
#if CS11_OR_GREATER
using System.Collections.Generic;
namespace Nerd_STF.Mathematics.Algebra
{
public interface IVectorOperations<TSelf> : ISimpleMathOperations<TSelf>
where TSelf : IVectorOperations<TSelf>
{
double Magnitude { get; }
static abstract TSelf ClampMagnitude(TSelf val, double minMag, double maxMag);
static abstract double Dot(TSelf a, TSelf b);
static abstract double Dot(IEnumerable<TSelf> vals);
}
}
#endif

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

View File

@ -0,0 +1,362 @@
using Nerd_STF.Helpers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Nerd_STF.Mathematics.Algebra
{
public class Matrix2x2 : IMatrix<Matrix2x2>,
ISquareMatrix<Matrix2x2>
#if CS11_OR_GREATER
,ISplittable<Matrix2x2, (double[] r0c0, double[] r0c1, double[] r1c0, double[] r1c1)>,
IStaticMatrix<Matrix2x2>
#endif
{
public static Matrix2x2 Identity =>
new Matrix2x2(1, 0,
0, 1);
public static Matrix2x2 SignField =>
new Matrix2x2(+1, -1,
-1, +1);
public static Matrix2x2 One => new Matrix2x2(1, 1, 1, 1);
public static Matrix2x2 Zero => new Matrix2x2(0, 0, 0, 0);
public Int2 Size => (2, 2);
public double r0c0, r0c1,
r1c0, r1c1;
public Matrix2x2()
{
r0c0 = 0; r0c1 = 0;
r1c0 = 0; r1c1 = 0;
}
public Matrix2x2(Matrix2x2 copy)
{
r0c0 = copy.r0c0; r0c1 = copy.r0c1;
r1c0 = copy.r1c0; r1c1 = copy.r1c1;
}
public Matrix2x2(double r0c0, double r0c1, double r1c0, double r1c1)
{
this.r0c0 = r0c0;
this.r0c1 = r0c1;
this.r1c0 = r1c0;
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 = false)
{
if (byRows) // Collection of rows ([c, r])
{
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 = 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 = 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]
{
get
{
switch (r)
{
case 0:
switch (c)
{
case 0: return r0c0;
case 1: return r0c1;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 1:
switch (c)
{
case 0: return r1c0;
case 1: return r1c1;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
default: throw new ArgumentOutOfRangeException(nameof(r));
}
}
set
{
switch (r)
{
case 0:
switch (c)
{
case 0: r0c0 = value; return;
case 1: r0c1 = value; return;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 1:
switch (c)
{
case 0: r1c0 = value; return;
case 1: r1c1 = value; return;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
default: throw new ArgumentOutOfRangeException(nameof(r));
}
}
}
public double this[Int2 index]
{
get => this[index.x, index.y];
set => this[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 static Matrix2x2 Average(IEnumerable<Matrix2x2> vals)
{
Matrix2x2 sum = Zero;
int count = 0;
foreach (Matrix2x2 m in vals)
{
sum += m;
count++;
}
return sum / count;
}
public static Matrix2x2 Lerp(Matrix2x2 a, Matrix2x2 b, double t, bool clamp = true) =>
new Matrix2x2(MathE.Lerp(a.r0c0, b.r0c0, t, clamp), MathE.Lerp(a.r0c1, b.r0c1, t, clamp),
MathE.Lerp(a.r1c0, b.r1c0, t, clamp), MathE.Lerp(a.r1c1, b.r1c1, t, clamp));
public static Matrix2x2 Product(IEnumerable<Matrix2x2> vals)
{
bool any = false;
Matrix2x2 result = One;
foreach (Matrix2x2 m in vals)
{
any = true;
result *= m;
}
return any ? result : Zero;
}
public static Matrix2x2 Sum(IEnumerable<Matrix2x2> vals)
{
Matrix2x2 result = Zero;
foreach (Matrix2x2 m in vals) result += m;
return result;
}
public static (double[] r0c0, double[] r0c1, double[] r1c0, double[] r1c1) SplitArray(IEnumerable<Matrix2x2> vals)
{
int count = vals.Count();
double[] r0c0 = new double[count], r0c1 = new double[count],
r1c0 = new double[count], r1c1 = new double[count];
int index = 0;
foreach (Matrix2x2 m in vals)
{
r0c0[index] = m.r0c0; r0c1[index] = m.r0c1;
r1c0[index] = m.r1c0; r1c1[index] = m.r1c1;
}
return (r0c0, r0c1,
r1c0, r1c1);
}
public ListTuple<double> GetRow(int row)
{
double[] vals;
switch (row)
{
case 0: vals = new double[] { r0c0, r0c1 }; break;
case 1: vals = new double[] { r1c0, r1c1 }; break;
default: throw new ArgumentOutOfRangeException(nameof(row));
}
return new ListTuple<double>(vals);
}
public ListTuple<double> GetColumn(int column)
{
double[] vals;
switch (column)
{
case 0: vals = new double[] { r0c0, r1c0 }; break;
case 1: vals = new double[] { r0c1, r1c1 }; break;
default: throw new ArgumentOutOfRangeException(nameof(column));
}
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)
{
switch (row)
{
case 0: r0c0 = vals[0]; r0c1 = vals[1]; break;
case 1: r1c0 = vals[0]; r1c1 = vals[1]; break;
default: throw new ArgumentOutOfRangeException(nameof(row));
}
}
public void SetColumn(int column, ListTuple<double> vals)
{
switch (column)
{
case 0: r0c0 = vals[0]; r1c0 = vals[1]; break;
case 1: r0c1 = vals[0]; r1c1 = vals[1]; break;
default: throw new ArgumentOutOfRangeException(nameof(column));
}
}
public double Determinant() => r0c0 * r1c1 - r0c1 * r1c0;
public Matrix2x2 Adjoint() =>
new Matrix2x2( r1c1, -r0c1,
-r1c0, r0c0);
public Matrix2x2 Cofactor() =>
new Matrix2x2( r1c1, -r1c0,
-r0c1, r0c0);
public Matrix2x2 Inverse()
{
double invDet = 1 / Determinant();
return new Matrix2x2( r1c1 * invDet, -r0c1 * invDet,
-r1c0 * invDet, r0c0 * invDet);
}
public Matrix2x2 Transpose() =>
new Matrix2x2(r0c0, r1c0,
r0c1, r1c1);
public double Trace() => r0c0 + r1c1;
public IEnumerator<double> GetEnumerator()
{
yield return r0c0; yield return r0c1;
yield return r1c0; yield return r1c1;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#if CS8_OR_GREATER
public bool Equals(Matrix2x2? other) =>
#else
public bool Equals(Matrix2x2 other) =>
#endif
!(other is null) &&
r0c0 == other.r0c0 && r0c1 == other.r0c1 &&
r1c0 == other.r1c0 && r1c1 == other.r1c1;
#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 Matrix2x2 otherMat) return Equals(otherMat);
else return false;
}
public override int GetHashCode() =>
(int)((uint)r0c0.GetHashCode() & 0xFF000000 |
(uint)r0c1.GetHashCode() & 0x00FF0000 |
(uint)r1c0.GetHashCode() & 0x0000FF00 |
(uint)r1c1.GetHashCode() & 0x000000FF);
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
public static Matrix2x2 operator +(Matrix2x2 a) =>
new Matrix2x2(a.r0c0, a.r0c1,
a.r1c0, a.r1c1);
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) =>
new Matrix2x2(-a.r0c0, -a.r0c1,
-a.r1c0, -a.r1c1);
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, double b) =>
new Matrix2x2(a.r0c0 * b, a.r0c1 * b,
a.r1c0 * b, a.r1c1 * b);
public static Float2 operator *(Matrix2x2 a, Float2 b) =>
new Float2(a.r0c0 * b.x + a.r0c1 * b.y,
a.r1c0 * b.x + a.r1c1 * b.y);
public static Matrix2x2 operator *(Matrix2x2 a, Matrix2x2 b) =>
new Matrix2x2(a.r0c0 * b.r0c0 + a.r0c1 * b.r1c0, a.r0c0 * b.r0c1 + a.r0c1 * b.r1c1,
a.r1c0 * b.r0c0 + a.r1c1 * b.r1c0, a.r1c0 * b.r0c1 + a.r1c1 * b.r1c1);
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);
public static explicit operator Matrix2x2(Matrix4x4 mat) =>
new Matrix2x2(mat.r0c0, mat.r0c1,
mat.r1c0, mat.r1c1);
}
}

View File

@ -0,0 +1,458 @@
using Nerd_STF.Helpers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Nerd_STF.Mathematics.Algebra
{
public class Matrix3x3 : IMatrix<Matrix3x3>,
ISquareMatrix<Matrix3x3>,
ISubmatrixOperations<Matrix3x3, Matrix2x2>
#if CS11_OR_GREATER
,ISplittable<Matrix3x3, (double[] r0c0, double[] r0c1, double[] r0c2, double[] r1c0, double[] r1c1, double[] r1c2, double[] r2c0, double[] r2c1, double[] r2c2)>,
IStaticMatrix<Matrix3x3>
#endif
{
public static Matrix3x3 Identity =>
new Matrix3x3(1, 0, 0,
0, 1, 0,
0, 0, 1);
public static Matrix3x3 SignField =>
new Matrix3x3(+1, -1, +1,
-1, +1, -1,
+1, -1, +1);
public static Matrix3x3 One => new Matrix3x3(1, 1, 1, 1, 1, 1, 1, 1, 1);
public static Matrix3x3 Zero => new Matrix3x3(0, 0, 0, 0, 0, 0, 0, 0, 0);
public Int2 Size => (3, 3);
public double r0c0, r0c1, r0c2,
r1c0, r1c1, r1c2,
r2c0, r2c1, r2c2;
public Matrix3x3()
{
r0c0 = 0; r0c1 = 0; r0c2 = 0;
r1c0 = 0; r1c1 = 0; r1c2 = 0;
r2c0 = 0; r2c1 = 0; r2c2 = 0;
}
public Matrix3x3(Matrix3x3 copy)
{
r0c0 = copy.r0c0; r0c1 = copy.r0c1; r0c2 = copy.r0c2;
r1c0 = copy.r1c0; r1c1 = copy.r1c1; r1c2 = copy.r1c2;
r2c0 = copy.r2c0; r2c1 = copy.r2c1; r2c2 = copy.r2c2;
}
public Matrix3x3(double r0c0, double r0c1, double r0c2, double r1c0, double r1c1, double r1c2, double r2c0, double r2c1, double r2c2)
{
this.r0c0 = r0c0; this.r0c1 = r0c1; this.r0c2 = r0c2;
this.r1c0 = r1c0; this.r1c1 = r1c1; this.r1c2 = r1c2;
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 = false)
{
if (byRows) // Collection of rows ([c, r])
{
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 = 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 = 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]
{
get
{
switch (r)
{
case 0:
switch (c)
{
case 0: return r0c0;
case 1: return r0c1;
case 2: return r0c2;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 1:
switch (c)
{
case 0: return r1c0;
case 1: return r1c1;
case 2: return r1c2;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 2:
switch (c)
{
case 0: return r2c0;
case 1: return r2c1;
case 2: return r2c2;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
default: throw new ArgumentOutOfRangeException(nameof(r));
}
}
set
{
switch (r)
{
case 0:
switch (c)
{
case 0: r0c0 = value; return;
case 1: r0c1 = value; return;
case 2: r0c2 = value; return;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 1:
switch (c)
{
case 0: r1c0 = value; return;
case 1: r1c1 = value; return;
case 2: r1c2 = value; return;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 2:
switch (c)
{
case 0: r2c0 = value; return;
case 1: r2c1 = value; return;
case 2: r2c2 = value; return;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
default: throw new ArgumentOutOfRangeException(nameof(r));
}
}
}
public double this[Int2 index]
{
get => this[index.x, index.y];
set => this[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 static Matrix3x3 Average(IEnumerable<Matrix3x3> vals)
{
Matrix3x3 result = Zero;
int count = 0;
foreach (Matrix3x3 m in vals)
{
result += m;
count++;
}
return result / count;
}
public static Matrix3x3 Lerp(Matrix3x3 a, Matrix3x3 b, double t, bool clamp = true) =>
new Matrix3x3(MathE.Lerp(a.r0c0, b.r0c0, t, clamp), MathE.Lerp(a.r0c1, b.r0c1, t, clamp), MathE.Lerp(a.r0c2, b.r0c2, t, clamp),
MathE.Lerp(a.r1c0, b.r1c0, t, clamp), MathE.Lerp(a.r1c1, b.r1c1, t, clamp), MathE.Lerp(a.r1c2, b.r1c2, t, clamp),
MathE.Lerp(a.r2c0, b.r2c0, t, clamp), MathE.Lerp(a.r2c1, b.r2c1, t, clamp), MathE.Lerp(a.r2c2, b.r2c2, t, clamp));
public static Matrix3x3 Product(IEnumerable<Matrix3x3> vals)
{
Matrix3x3 result = One;
bool any = false;
foreach (Matrix3x3 m in vals)
{
any = true;
result *= m;
}
return any ? result : Zero;
}
public static Matrix3x3 Sum(IEnumerable<Matrix3x3> vals)
{
Matrix3x3 result = Zero;
foreach (Matrix3x3 m in vals) result += m;
return result;
}
public static (double[] r0c0, double[] r0c1, double[] r0c2, double[] r1c0, double[] r1c1, double[] r1c2, double[] r2c0, double[] r2c1, double[] r2c2) SplitArray(IEnumerable<Matrix3x3> vals)
{
int count = vals.Count();
double[] r0c0 = new double[count], r0c1 = new double[count], r0c2 = new double[count],
r1c0 = new double[count], r1c1 = new double[count], r1c2 = new double[count],
r2c0 = new double[count], r2c1 = new double[count], r2c2 = new double[count];
int index = 0;
foreach (Matrix3x3 m in vals)
{
r0c0[index] = m.r0c0; r0c1[index] = m.r0c1; r0c2[index] = m.r0c2;
r1c0[index] = m.r1c0; r1c1[index] = m.r1c1; r1c2[index] = m.r1c2;
r2c0[index] = m.r2c0; r2c1[index] = m.r2c1; r2c2[index] = m.r2c2;
}
return (r0c0, r0c1, r0c2,
r1c0, r1c1, r1c2,
r2c0, r2c1, r2c2);
}
public ListTuple<double> GetRow(int row)
{
double[] vals;
switch (row)
{
case 0: vals = new double[] { r0c0, r0c1, r0c2 }; break;
case 1: vals = new double[] { r1c0, r1c1, r1c2 }; break;
case 2: vals = new double[] { r2c0, r2c1, r2c2 }; break;
default: throw new ArgumentOutOfRangeException(nameof(row));
}
return new ListTuple<double>(vals);
}
public ListTuple<double> GetColumn(int column)
{
double[] vals;
switch (column)
{
case 0: vals = new double[] { r0c0, r1c0, r2c0 }; break;
case 1: vals = new double[] { r0c1, r1c1, r2c1 }; break;
case 2: vals = new double[] { r0c2, r1c2, r2c2 }; break;
default: throw new ArgumentOutOfRangeException(nameof(column));
}
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)
{
switch (row)
{
case 0: r0c0 = vals[0]; r0c1 = vals[1]; r0c2 = vals[2]; break;
case 1: r1c0 = vals[0]; r1c1 = vals[1]; r1c2 = vals[2]; break;
case 2: r2c0 = vals[0]; r2c1 = vals[1]; r2c2 = vals[2]; break;
default: throw new ArgumentOutOfRangeException(nameof(row));
}
}
public void SetColumn(int column, ListTuple<double> vals)
{
switch (column)
{
case 0: r0c0 = vals[0]; r1c0 = vals[1]; r2c0 = vals[2]; break;
case 1: r0c1 = vals[0]; r1c1 = vals[1]; r2c1 = vals[2]; break;
case 2: r0c2 = vals[0]; r1c2 = vals[1]; r2c2 = vals[2]; break;
default: throw new ArgumentOutOfRangeException(nameof(column));
}
}
public double Determinant() => // Alternating sum of the determinants of the first row of submatrices.
r0c0 * (r1c1 * r2c2 - r1c2 * r2c1) -
r0c1 * (r1c0 * r2c2 - r1c2 * r2c0) +
r0c2 * (r1c0 * r2c1 - r1c1 * r2c0);
public Matrix3x3 Adjoint() => // Transpose(Cofactor)
new Matrix3x3(r1c1 * r2c2 - r1c2 * r2c1, r0c2 * r2c1 - r0c1 * r2c2, r0c1 * r1c2 - r0c2 * r1c1,
r1c2 * r2c0 - r1c0 * r2c2, r0c0 * r2c2 - r0c2 * r2c0, r0c2 * r1c0 - r0c0 * r1c2,
r1c0 * r2c1 - r1c1 * r2c0, r0c1 * r2c0 - r0c0 * r2c1, r0c0 * r1c1 - r0c1 * r1c0);
public Matrix3x3 Cofactor() => // [r, c] = Determinant(Submatrix(r, c))
new Matrix3x3(r1c1 * r2c2 - r1c2 * r2c1, r1c2 * r2c0 - r1c0 * r2c2, r1c0 * r2c1 - r1c1 * r2c0,
r0c2 * r2c1 - r0c1 * r2c2, r0c0 * r2c2 - r0c2 * r2c0, r0c1 * r2c0 - r0c0 * r2c1,
r0c1 * r1c2 - r0c2 * r1c1, r0c2 * r1c0 - r0c0 * r1c2, r0c0 * r1c1 - r0c1 * r1c0);
public Matrix3x3 Inverse() // Adjoint / Determinant
{
double invDet = 1 / Determinant();
return new Matrix3x3(invDet * (r1c1 * r2c2 - r1c2 * r2c1), invDet * (r0c2 * r2c1 - r0c1 * r2c2), invDet * (r0c1 * r1c2 - r0c2 * r1c1),
invDet * (r1c2 * r2c0 - r1c0 * r2c2), invDet * (r0c0 * r2c2 - r0c2 * r2c0), invDet * (r0c2 * r1c0 - r0c0 * r1c2),
invDet * (r1c0 * r2c1 - r1c1 * r2c0), invDet * (r0c1 * r2c0 - r0c0 * r2c1), invDet * (r0c0 * r1c1 - r0c1 * r1c0));
}
public Matrix3x3 Transpose() =>
new Matrix3x3(r0c0, r1c0, r2c0,
r0c1, r1c1, r2c1,
r0c2, r1c2, r2c2);
public Matrix2x2 Submatrix(int row, int column)
{
switch (row)
{
case 0:
switch (column)
{
case 0: return new Matrix2x2(r1c1, r1c2, r2c1, r2c2);
case 1: return new Matrix2x2(r1c0, r1c2, r2c0, r2c2);
case 2: return new Matrix2x2(r1c0, r1c1, r2c0, r2c1);
default: throw new ArgumentOutOfRangeException(nameof(column));
}
case 1:
switch (column)
{
case 0: return new Matrix2x2(r0c1, r0c2, r2c1, r2c2);
case 1: return new Matrix2x2(r0c0, r0c2, r2c0, r2c2);
case 2: return new Matrix2x2(r0c0, r0c1, r2c0, r2c1);
default: throw new ArgumentOutOfRangeException(nameof(column));
}
case 2:
switch (column)
{
case 0: return new Matrix2x2(r0c1, r0c2, r1c1, r1c2);
case 1: return new Matrix2x2(r0c0, r0c2, r1c0, r1c2);
case 2: return new Matrix2x2(r0c0, r0c1, r1c0, r1c1);
default: throw new ArgumentOutOfRangeException(nameof(column));
}
default: throw new ArgumentOutOfRangeException(nameof(column));
}
}
public double Trace() => r0c0 + r1c1 + r2c2;
public IEnumerator<double> GetEnumerator()
{
yield return r0c0; yield return r0c1; yield return r0c2;
yield return r1c0; yield return r1c1; yield return r1c2;
yield return r2c0; yield return r2c1; yield return r2c2;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#if CS8_OR_GREATER
public bool Equals(Matrix3x3? other) =>
#else
public bool Equals(Matrix3x3 other) =>
#endif
!(other is null) &&
r0c0 == other.r0c0 && r0c1 == other.r0c1 && r0c2 == other.r0c2 &&
r1c0 == other.r1c0 && r1c1 == other.r1c1 && r1c2 == other.r1c2 &&
r2c0 == other.r2c0 && r2c1 == other.r2c1 && r2c2 == other.r2c2;
#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 Matrix3x3 otherMat) return Equals(otherMat);
else return false;
}
public override int GetHashCode() =>
(int)((uint)r0c0.GetHashCode() & 0xE0000000 |
(uint)r0c1.GetHashCode() & 0x1E000000 |
(uint)r0c2.GetHashCode() & 0x01C00000 |
(uint)r1c0.GetHashCode() & 0x003C0000 |
(uint)r1c1.GetHashCode() & 0x00038000 |
(uint)r1c2.GetHashCode() & 0x00007800 |
(uint)r2c0.GetHashCode() & 0x00000700 |
(uint)r2c1.GetHashCode() & 0x000000F0 |
(uint)r2c2.GetHashCode() & 0x0000000F);
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
public static Matrix3x3 operator +(Matrix3x3 a) =>
new Matrix3x3(a.r0c0, a.r0c1, a.r0c2,
a.r1c0, a.r1c1, a.r1c2,
a.r2c0, a.r2c1, a.r2c2);
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) =>
new Matrix3x3(-a.r0c0, -a.r0c1, -a.r0c2,
-a.r1c0, -a.r1c1, -a.r1c2,
-a.r2c0, -a.r2c1, -a.r2c2);
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, double b) =>
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 Float3 operator *(Matrix3x3 a, Float3 b) =>
new Float3(a.r0c0 * b.x + a.r0c1 * b.y + a.r0c2 * b.z,
a.r1c0 * b.x + a.r1c1 * b.y + a.r1c2 * b.z,
a.r2c0 * b.x + a.r2c1 * b.y + a.r2c2 * b.z);
public static Matrix3x3 operator *(Matrix3x3 a, Matrix3x3 b) =>
new Matrix3x3(a.r0c0 * b.r0c0 + a.r0c1 * b.r1c0 + a.r0c2 * b.r2c0, a.r0c0 * b.r0c1 + a.r0c1 * b.r1c1 + a.r0c2 * b.r2c1, a.r0c0 * b.r0c2 + a.r0c1 * b.r1c2 + a.r0c2 * b.r2c2,
a.r1c0 * b.r0c0 + a.r1c1 * b.r1c0 + a.r1c2 * b.r2c0, a.r1c0 * b.r0c1 + a.r1c1 * b.r1c1 + a.r1c2 * b.r2c1, a.r1c0 * b.r0c2 + a.r1c1 * b.r1c2 + a.r1c2 * b.r2c2,
a.r2c0 * b.r0c0 + a.r2c1 * b.r1c0 + a.r2c2 * b.r2c0, a.r2c0 * b.r0c1 + a.r2c1 * b.r1c1 + a.r2c2 * b.r2c1, a.r2c0 * b.r0c2 + a.r2c1 * b.r1c2 + a.r2c2 * b.r2c2);
public static Matrix3x3 operator /(Matrix3x3 a, double b) =>
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,
0 , 0 , 1);
public static explicit operator Matrix3x3(Matrix4x4 mat) =>
new Matrix3x3(mat.r0c0, mat.r0c1, mat.r0c2,
mat.r1c0, mat.r1c1, mat.r1c2,
mat.r2c0, mat.r2c1, mat.r2c2);
}
}

View File

@ -0,0 +1,608 @@
using Nerd_STF.Helpers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Nerd_STF.Mathematics.Algebra
{
public class Matrix4x4 : IMatrix<Matrix4x4>,
ISquareMatrix<Matrix4x4>,
ISubmatrixOperations<Matrix4x4, Matrix3x3>
#if CS11_OR_GREATER
,ISplittable<Matrix4x4, (double[] r0c0, double[] r0c1, double[] r0c2, double[] r0c3, double[] r1c0, double[] r1c1, double[] r1c2, double[] r1c3, double[] r2c0, double[] r2c1, double[] r2c2, double[] r2c3, double[] r3c0, double[] r3c1, double[] r3c2, double[] r3c3)>,
IStaticMatrix<Matrix4x4>
#endif
{
public static Matrix4x4 Identity =>
new Matrix4x4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
public static Matrix4x4 SignField =>
new Matrix4x4(+1, -1, +1, -1,
-1, +1, -1, +1,
+1, -1, +1, -1,
-1, +1, -1, +1);
public static Matrix4x4 One => new Matrix4x4(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
public static Matrix4x4 Zero => new Matrix4x4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
public Int2 Size => (4, 4);
public double r0c0, r0c1, r0c2, r0c3,
r1c0, r1c1, r1c2, r1c3,
r2c0, r2c1, r2c2, r2c3,
r3c0, r3c1, r3c2, r3c3;
public Matrix4x4()
{
r0c0 = 0; r0c1 = 0; r0c2 = 0; r0c3 = 0;
r1c0 = 0; r1c1 = 0; r1c2 = 0; r1c3 = 0;
r2c0 = 0; r2c1 = 0; r2c2 = 0; r2c3 = 0;
r3c0 = 0; r3c1 = 0; r3c2 = 0; r3c3 = 0;
}
public Matrix4x4(Matrix4x4 copy)
{
r0c0 = copy.r0c0; r0c1 = copy.r0c1; r0c2 = copy.r0c2; r0c3 = copy.r0c3;
r1c0 = copy.r1c0; r1c1 = copy.r1c1; r1c2 = copy.r1c2; r1c3 = copy.r1c3;
r2c0 = copy.r2c0; r2c1 = copy.r2c1; r2c2 = copy.r2c2; r2c3 = copy.r2c3;
r3c0 = copy.r3c0; r3c1 = copy.r3c1; r3c2 = copy.r3c2; r3c3 = copy.r3c3;
}
public Matrix4x4(double r0c0, double r0c1, double r0c2, double r0c3, double r1c0, double r1c1, double r1c2, double r1c3, double r2c0, double r2c1, double r2c2, double r2c3, double r3c0, double r3c1, double r3c2, double r3c3)
{
this.r0c0 = r0c0; this.r0c1 = r0c1; this.r0c2 = r0c2; this.r0c3 = r0c3;
this.r1c0 = r1c0; this.r1c1 = r1c1; this.r1c2 = r1c2; this.r1c3 = r1c3;
this.r2c0 = r2c0; this.r2c1 = r2c1; this.r2c2 = r2c2; this.r2c3 = r2c3;
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 = false)
{
if (byRows) // Collection of rows ([c, r])
{
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 = 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 = 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]
{
get
{
switch (r)
{
case 0:
switch (c)
{
case 0: return r0c0;
case 1: return r0c1;
case 2: return r0c2;
case 3: return r0c3;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 1:
switch (c)
{
case 0: return r1c0;
case 1: return r1c1;
case 2: return r1c2;
case 3: return r1c3;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 2:
switch (c)
{
case 0: return r2c0;
case 1: return r2c1;
case 2: return r2c2;
case 3: return r2c3;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 3:
switch (c)
{
case 0: return r3c0;
case 1: return r3c1;
case 2: return r3c2;
case 3: return r3c3;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
default: throw new ArgumentOutOfRangeException(nameof(r));
}
}
set
{
switch (r)
{
case 0:
switch (c)
{
case 0: r0c0 = value; return;
case 1: r0c1 = value; return;
case 2: r0c2 = value; return;
case 3: r0c3 = value; return;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 1:
switch (c)
{
case 0: r1c0 = value; return;
case 1: r1c1 = value; return;
case 2: r1c2 = value; return;
case 3: r1c3 = value; return;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 2:
switch (c)
{
case 0: r2c0 = value; return;
case 1: r2c1 = value; return;
case 2: r2c2 = value; return;
case 3: r2c3 = value; return;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
case 3:
switch (c)
{
case 0: r3c0 = value; return;
case 1: r3c1 = value; return;
case 2: r3c2 = value; return;
case 3: r3c3 = value; return;
default: throw new ArgumentOutOfRangeException(nameof(c));
}
default: throw new ArgumentOutOfRangeException(nameof(r));
}
}
}
public double this[Int2 index]
{
get => this[index.x, index.y];
set => this[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 static Matrix4x4 Average(IEnumerable<Matrix4x4> vals)
{
Matrix4x4 result = Zero;
int count = 0;
foreach (Matrix4x4 m in vals)
{
result += m;
count++;
}
return result / count;
}
public static Matrix4x4 Lerp(Matrix4x4 a, Matrix4x4 b, double t, bool clamp = true) =>
new Matrix4x4(MathE.Lerp(a.r0c0, b.r0c0, t, clamp), MathE.Lerp(a.r0c1, b.r0c1, t, clamp), MathE.Lerp(a.r0c2, b.r0c2, t, clamp), MathE.Lerp(a.r0c3, b.r0c3, t, clamp),
MathE.Lerp(a.r1c0, b.r1c0, t, clamp), MathE.Lerp(a.r1c1, b.r1c1, t, clamp), MathE.Lerp(a.r1c2, b.r1c2, t, clamp), MathE.Lerp(a.r1c3, b.r1c3, t, clamp),
MathE.Lerp(a.r2c0, b.r2c0, t, clamp), MathE.Lerp(a.r2c1, b.r2c1, t, clamp), MathE.Lerp(a.r2c2, b.r2c2, t, clamp), MathE.Lerp(a.r2c3, b.r2c3, t, clamp),
MathE.Lerp(a.r3c0, b.r3c0, t, clamp), MathE.Lerp(a.r3c1, b.r3c1, t, clamp), MathE.Lerp(a.r3c2, b.r3c2, t, clamp), MathE.Lerp(a.r3c3, b.r3c3, t, clamp));
public static Matrix4x4 Product(IEnumerable<Matrix4x4> vals)
{
Matrix4x4 result = One;
bool any = false;
foreach (Matrix4x4 m in vals)
{
any = true;
result *= m;
}
return any ? result : Zero;
}
public static Matrix4x4 Sum(IEnumerable<Matrix4x4> vals)
{
Matrix4x4 result = Zero;
foreach (Matrix4x4 m in vals) result += m;
return result;
}
public static (double[] r0c0, double[] r0c1, double[] r0c2, double[] r0c3, double[] r1c0, double[] r1c1, double[] r1c2, double[] r1c3, double[] r2c0, double[] r2c1, double[] r2c2, double[] r2c3, double[] r3c0, double[] r3c1, double[] r3c2, double[] r3c3) SplitArray(IEnumerable<Matrix4x4> vals)
{
int count = vals.Count();
double[] r0c0 = new double[count], r0c1 = new double[count], r0c2 = new double[count], r0c3 = new double[count],
r1c0 = new double[count], r1c1 = new double[count], r1c2 = new double[count], r1c3 = new double[count],
r2c0 = new double[count], r2c1 = new double[count], r2c2 = new double[count], r2c3 = new double[count],
r3c0 = new double[count], r3c1 = new double[count], r3c2 = new double[count], r3c3 = new double[count];
int index = 0;
foreach (Matrix4x4 m in vals)
{
r0c0[index] = m.r0c0; r0c1[index] = m.r0c1; r0c2[index] = m.r0c2; r0c3[index] = m.r0c3;
r1c0[index] = m.r1c0; r1c1[index] = m.r1c1; r1c2[index] = m.r1c2; r1c3[index] = m.r1c3;
r2c0[index] = m.r2c0; r2c1[index] = m.r2c1; r2c2[index] = m.r2c2; r2c3[index] = m.r2c3;
r3c0[index] = m.r3c0; r3c1[index] = m.r3c1; r3c2[index] = m.r3c2; r3c3[index] = m.r3c3;
}
return (r0c0, r0c1, r0c2, r0c3,
r1c0, r1c1, r1c2, r1c3,
r2c0, r2c1, r2c2, r2c3,
r3c0, r3c1, r3c2, r3c3);
}
public ListTuple<double> GetRow(int row)
{
double[] vals;
switch (row)
{
case 0: vals = new double[] { r0c0, r0c1, r0c2, r0c3 }; break;
case 1: vals = new double[] { r1c0, r1c1, r1c2, r1c3 }; break;
case 2: vals = new double[] { r2c0, r2c1, r2c2, r2c3 }; break;
case 3: vals = new double[] { r3c0, r3c1, r3c2, r3c3 }; break;
default: throw new ArgumentOutOfRangeException(nameof(row));
}
return new ListTuple<double>(vals);
}
public ListTuple<double> GetColumn(int column)
{
double[] vals;
switch (column)
{
case 0: vals = new double[] { r0c0, r1c0, r2c0, r3c0 }; break;
case 1: vals = new double[] { r0c1, r1c1, r2c1, r3c1 }; break;
case 2: vals = new double[] { r0c2, r1c2, r2c2, r3c2 }; break;
case 3: vals = new double[] { r0c3, r1c3, r2c3, r3c3 }; break;
default: throw new ArgumentOutOfRangeException(nameof(column));
}
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)
{
switch (row)
{
case 0: r0c0 = vals[0]; r0c1 = vals[1]; r0c2 = vals[2]; r0c3 = vals[3]; break;
case 1: r1c0 = vals[0]; r1c1 = vals[1]; r1c2 = vals[2]; r1c3 = vals[3]; break;
case 2: r2c0 = vals[0]; r2c1 = vals[1]; r2c2 = vals[2]; r2c3 = vals[3]; break;
case 3: r3c0 = vals[0]; r3c1 = vals[1]; r3c2 = vals[2]; r3c3 = vals[3]; break;
default: throw new ArgumentOutOfRangeException(nameof(row));
}
}
public void SetColumn(int column, ListTuple<double> vals)
{
switch (column)
{
case 0: r0c0 = vals[0]; r1c0 = vals[1]; r2c0 = vals[2]; r3c0 = vals[3]; break;
case 1: r0c1 = vals[0]; r1c1 = vals[1]; r2c1 = vals[2]; r3c1 = vals[3]; break;
case 2: r0c2 = vals[0]; r1c2 = vals[1]; r2c2 = vals[2]; r3c2 = vals[3]; break;
case 3: r0c3 = vals[0]; r1c3 = vals[1]; r2c3 = vals[2]; r3c3 = vals[3]; break;
default: throw new ArgumentOutOfRangeException(nameof(column));
}
}
// Sorry some of these are huge. I just want to inline
// it as much as physically possible.
public double Determinant()
{
double A = r2c2 * r3c3 - r2c3 * r3c2,
B = r2c1 * r3c3 - r2c3 * r3c1,
C = r2c1 * r3c2 - r2c2 * r3c1,
D = r2c0 * r3c3 - r2c3 * r3c0,
E = r2c0 * r3c2 - r2c2 * r3c0,
F = r2c0 * r3c1 - r2c1 * r3c0;
return r0c0 * (r1c1 * A - r1c2 * B + r1c3 * C) -
r0c1 * (r1c0 * A - r1c2 * D + r1c3 * E) +
r0c2 * (r1c0 * B - r1c1 * D + r1c3 * F) -
r0c3 * (r1c0 * C - r1c1 * E + r1c2 * F);
}
public Matrix4x4 Adjoint() // Transpose(Cofactor)
{
double A = r2c2 * r3c3 - r2c3 * r3c2,
B = r2c1 * r3c3 - r2c3 * r3c1,
C = r2c1 * r3c2 - r2c2 * r3c1,
D = r2c0 * r3c3 - r2c3 * r3c0,
E = r2c0 * r3c2 - r2c2 * r3c0,
F = r2c0 * r3c1 - r2c1 * r3c0,
G = r1c2 * r3c3 - r1c3 * r3c2,
H = r1c1 * r3c3 - r1c3 * r3c1,
I = r1c1 * r3c2 - r1c2 * r3c1,
J = r1c0 * r3c3 - r1c3 * r3c0,
K = r1c0 * r3c2 - r1c2 * r3c0,
L = r1c0 * r3c1 - r1c1 * r3c0,
M = r1c2 * r2c3 - r1c3 * r2c2,
N = r1c1 * r2c3 - r1c3 * r2c1,
O = r1c1 * r2c2 - r1c2 * r2c1,
P = r1c0 * r2c3 - r1c3 * r2c0,
Q = r1c0 * r2c2 - r1c2 * r2c0,
R = r1c0 * r2c1 - r1c1 * r2c0;
return new Matrix4x4(r1c1 * A - r1c2 * B + r1c3 * C, r0c2 * B - r0c3 * C - r0c1 * A, r0c1 * G - r0c2 * H + r0c3 * I, r0c2 * N - r0c3 * O - r0c1 * M,
r1c2 * D - r1c3 * E - r1c0 * A, r0c0 * A - r0c2 * D + r0c3 * E, r0c2 * J - r0c3 * K - r0c0 * G, r0c0 * M - r0c2 * P + r0c3 * Q,
r1c0 * B - r1c1 * D + r1c3 * F, r0c1 * D - r0c3 * F - r0c0 * B, r0c0 * H - r0c1 * J + r0c3 * L, r0c1 * P - r0c3 * R - r0c0 * N,
r1c1 * E - r1c2 * F - r1c0 * C, r0c0 * C - r0c1 * E + r0c2 * F, r0c1 * K - r0c2 * L - r0c0 * I, r0c0 * O - r0c1 * Q + r0c2 * R);
}
public Matrix4x4 Cofactor() // [r, c] = Determinant(Submatrix(r, c))
{
double A = r2c2 * r3c3 - r2c3 * r3c2,
B = r2c1 * r3c3 - r2c3 * r3c1,
C = r2c1 * r3c2 - r2c2 * r3c1,
D = r2c0 * r3c3 - r2c3 * r3c0,
E = r2c0 * r3c2 - r2c2 * r3c0,
F = r2c0 * r3c1 - r2c1 * r3c0,
G = r1c2 * r3c3 - r1c3 * r3c2,
H = r1c1 * r3c3 - r1c3 * r3c1,
I = r1c1 * r3c2 - r1c2 * r3c1,
J = r1c0 * r3c3 - r1c3 * r3c0,
K = r1c0 * r3c2 - r1c2 * r3c0,
L = r1c0 * r3c1 - r1c1 * r3c0,
M = r1c2 * r2c3 - r1c3 * r2c2,
N = r1c1 * r2c3 - r1c3 * r2c1,
O = r1c1 * r2c2 - r1c2 * r2c1,
P = r1c0 * r2c3 - r1c3 * r2c0,
Q = r1c0 * r2c2 - r1c2 * r2c0,
R = r1c0 * r2c1 - r1c1 * r2c0;
return new Matrix4x4(r1c1 * A - r1c2 * B + r1c3 * C, r1c2 * D - r1c3 * E - r1c0 * A, r1c0 * B - r1c1 * D + r1c3 * F, r1c1 * E - r1c2 * F - r1c0 * C,
r0c2 * B - r0c3 * C - r0c1 * A, r0c0 * A - r0c2 * D + r0c3 * E, r0c1 * D - r0c3 * F - r0c0 * B, r0c0 * C - r0c1 * E + r0c2 * F,
r0c1 * G - r0c2 * H + r0c3 * I, r0c2 * J - r0c3 * K - r0c0 * G, r0c0 * H - r0c1 * J + r0c3 * L, r0c1 * K - r0c2 * L - r0c0 * I,
r0c2 * N - r0c3 * O - r0c1 * M, r0c0 * M - r0c2 * P + r0c3 * Q, r0c1 * P - r0c3 * R - r0c0 * N, r0c0 * O - r0c1 * Q + r0c2 * R);
}
public Matrix4x4 Inverse() // Adjoint / Determinant()
{
double invDet = 1 / Determinant(),
A = r2c2 * r3c3 - r2c3 * r3c2,
B = r2c1 * r3c3 - r2c3 * r3c1,
C = r2c1 * r3c2 - r2c2 * r3c1,
D = r2c0 * r3c3 - r2c3 * r3c0,
E = r2c0 * r3c2 - r2c2 * r3c0,
F = r2c0 * r3c1 - r2c1 * r3c0,
G = r1c2 * r3c3 - r1c3 * r3c2,
H = r1c1 * r3c3 - r1c3 * r3c1,
I = r1c1 * r3c2 - r1c2 * r3c1,
J = r1c0 * r3c3 - r1c3 * r3c0,
K = r1c0 * r3c2 - r1c2 * r3c0,
L = r1c0 * r3c1 - r1c1 * r3c0,
M = r1c2 * r2c3 - r1c3 * r2c2,
N = r1c1 * r2c3 - r1c3 * r2c1,
O = r1c1 * r2c2 - r1c2 * r2c1,
P = r1c0 * r2c3 - r1c3 * r2c0,
Q = r1c0 * r2c2 - r1c2 * r2c0,
R = r1c0 * r2c1 - r1c1 * r2c0;
return new Matrix4x4(invDet * (r1c1 * A - r1c2 * B + r1c3 * C), invDet * (r0c2 * B - r0c3 * C - r0c1 * A), invDet * (r0c1 * G - r0c2 * H + r0c3 * I), invDet * (r0c2 * N - r0c3 * O - r0c1 * M),
invDet * (r1c2 * D - r1c3 * E - r1c0 * A), invDet * (r0c0 * A - r0c2 * D + r0c3 * E), invDet * (r0c2 * J - r0c3 * K - r0c0 * G), invDet * (r0c0 * M - r0c2 * P + r0c3 * Q),
invDet * (r1c0 * B - r1c1 * D + r1c3 * F), invDet * (r0c1 * D - r0c3 * F - r0c0 * B), invDet * (r0c0 * H - r0c1 * J + r0c3 * L), invDet * (r0c1 * P - r0c3 * R - r0c0 * N),
invDet * (r1c1 * E - r1c2 * F - r1c0 * C), invDet * (r0c0 * C - r0c1 * E + r0c2 * F), invDet * (r0c1 * K - r0c2 * L - r0c0 * I), invDet * (r0c0 * O - r0c1 * Q + r0c2 * R));
}
public Matrix4x4 Transpose() =>
new Matrix4x4(r0c0, r1c0, r2c0, r3c0,
r0c1, r1c1, r2c1, r3c1,
r0c2, r1c2, r2c2, r3c2,
r0c3, r1c3, r2c3, r3c3);
public Matrix3x3 Submatrix(int row, int column)
{
switch (row)
{
case 0:
switch (column)
{
case 0: return new Matrix3x3(r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3);
case 1: return new Matrix3x3(r1c0, r1c2, r1c3, r2c0, r2c2, r2c3, r3c0, r3c2, r3c3);
case 2: return new Matrix3x3(r1c0, r1c1, r1c3, r2c0, r2c1, r2c3, r3c0, r3c1, r3c3);
case 3: return new Matrix3x3(r1c0, r1c1, r1c2, r2c0, r2c1, r2c2, r3c0, r3c1, r3c2);
default: throw new ArgumentOutOfRangeException(nameof(column));
}
case 1:
switch (column)
{
case 0: return new Matrix3x3(r0c1, r0c2, r0c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3);
case 1: return new Matrix3x3(r0c0, r0c2, r0c3, r2c0, r2c2, r2c3, r3c0, r3c2, r3c3);
case 2: return new Matrix3x3(r0c0, r0c1, r0c3, r2c0, r2c1, r2c3, r3c0, r3c1, r3c3);
case 3: return new Matrix3x3(r0c0, r0c1, r0c2, r2c0, r2c1, r2c2, r3c0, r3c1, r3c2);
default: throw new ArgumentOutOfRangeException(nameof(column));
}
case 2:
switch (column)
{
case 0: return new Matrix3x3(r0c1, r0c2, r0c3, r1c1, r1c2, r1c3, r3c1, r3c2, r3c3);
case 1: return new Matrix3x3(r0c0, r0c2, r0c3, r1c0, r1c2, r1c3, r3c0, r3c2, r3c3);
case 2: return new Matrix3x3(r0c0, r0c1, r0c3, r1c0, r1c1, r1c3, r3c0, r3c1, r3c3);
case 3: return new Matrix3x3(r0c0, r0c1, r0c2, r1c0, r1c1, r1c2, r3c0, r3c1, r3c2);
default: throw new ArgumentOutOfRangeException(nameof(column));
}
case 3:
switch (column)
{
case 0: return new Matrix3x3(r0c1, r0c2, r0c3, r1c1, r1c2, r1c3, r2c1, r2c2, r2c3);
case 1: return new Matrix3x3(r0c0, r0c2, r0c3, r1c0, r1c2, r1c3, r2c0, r2c2, r2c3);
case 2: return new Matrix3x3(r0c0, r0c1, r0c3, r1c0, r1c1, r1c3, r2c0, r2c1, r2c3);
case 3: return new Matrix3x3(r0c0, r0c1, r0c2, r1c0, r1c1, r1c2, r2c0, r2c1, r2c2);
default: throw new ArgumentOutOfRangeException(nameof(column));
}
default: throw new ArgumentOutOfRangeException(nameof(row));
}
}
public double Trace() => r0c0 + r1c1 + r2c2 + r3c3;
public IEnumerator<double> GetEnumerator()
{
yield return r0c0; yield return r0c1; yield return r0c2; yield return r0c3;
yield return r1c0; yield return r1c1; yield return r1c2; yield return r1c3;
yield return r2c0; yield return r2c1; yield return r2c2; yield return r2c3;
yield return r3c0; yield return r3c1; yield return r3c2; yield return r3c3;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#if CS8_OR_GREATER
public bool Equals(Matrix4x4? other) =>
#else
public bool Equals(Matrix4x4 other) =>
#endif
!(other is null) &&
r0c0 == other.r0c0 && r0c1 == other.r0c1 && r0c2 == other.r0c2 && r0c3 == other.r0c3 &&
r1c0 == other.r1c0 && r1c1 == other.r1c1 && r1c2 == other.r1c2 && r1c3 == other.r1c3 &&
r2c0 == other.r2c0 && r2c1 == other.r2c1 && r2c2 == other.r2c2 && r2c3 == other.r2c3 &&
r3c0 == other.r3c0 && r3c1 == other.r3c1 && r3c2 == other.r3c2 && r3c3 == other.r3c3;
#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 Matrix4x4 otherMat) return Equals(otherMat);
else return false;
}
public override int GetHashCode() =>
(int)((uint)r0c0.GetHashCode() & 0xC0000000 |
(uint)r0c1.GetHashCode() & 0x30000000 |
(uint)r0c2.GetHashCode() & 0x0C000000 |
(uint)r0c3.GetHashCode() & 0x03000000 |
(uint)r1c0.GetHashCode() & 0x00C00000 |
(uint)r1c1.GetHashCode() & 0x00300000 |
(uint)r1c2.GetHashCode() & 0x000C0000 |
(uint)r1c3.GetHashCode() & 0x00030000 |
(uint)r2c0.GetHashCode() & 0x0000C000 |
(uint)r2c1.GetHashCode() & 0x00003000 |
(uint)r2c2.GetHashCode() & 0x00000C00 |
(uint)r2c3.GetHashCode() & 0x00000300 |
(uint)r3c0.GetHashCode() & 0x000000C0 |
(uint)r3c1.GetHashCode() & 0x00000030 |
(uint)r3c2.GetHashCode() & 0x0000000C |
(uint)r3c3.GetHashCode() & 0x00000003);
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
public static Matrix4x4 operator +(Matrix4x4 a) =>
new Matrix4x4(a.r0c0, a.r0c1, a.r0c2, a.r0c3,
a.r1c0, a.r1c1, a.r1c2, a.r1c3,
a.r2c0, a.r2c1, a.r2c2, a.r2c3,
a.r3c0, a.r3c1, a.r3c2, a.r3c3);
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) =>
new Matrix4x4(-a.r0c0, -a.r0c1, -a.r0c2, -a.r0c3,
-a.r1c0, -a.r1c1, -a.r1c2, -a.r1c3,
-a.r2c0, -a.r2c1, -a.r2c2, -a.r2c3,
-a.r3c0, -a.r3c1, -a.r3c2, -a.r3c3);
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, double b) =>
new Matrix4x4(a.r0c0 * b, a.r0c1 * b, a.r0c2 * b, a.r0c3 * b,
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 Float4 operator *(Matrix4x4 a, Float4 b) =>
new Float4(a.r0c0 * b.w + a.r0c1 * b.x + a.r0c2 * b.y + a.r0c3 * b.z,
a.r1c0 * b.w + a.r1c1 * b.x + a.r1c2 * b.y + a.r1c3 * b.z,
a.r2c0 * b.w + a.r2c1 * b.x + a.r2c2 * b.y + a.r2c3 * b.z,
a.r3c0 * b.w + a.r3c1 * b.x + a.r3c2 * b.y + a.r3c3 * b.z);
public static Matrix4x4 operator *(Matrix4x4 a, Matrix4x4 b) =>
new Matrix4x4(a.r0c0 * b.r0c0 + a.r0c1 * b.r1c0 + a.r0c2 * b.r2c0 + a.r0c3 * b.r3c0, a.r0c0 * b.r0c1 + a.r0c1 * b.r1c1 + a.r0c2 * b.r2c1 + a.r0c3 * b.r3c1, a.r0c0 * b.r0c2 + a.r0c1 * b.r1c2 + a.r0c2 * b.r2c2 + a.r0c3 * b.r3c2, a.r0c0 * b.r0c3 + a.r0c1 * b.r1c3 + a.r0c2 * b.r2c3 + a.r0c3 * b.r3c3,
a.r1c0 * b.r0c0 + a.r1c1 * b.r1c0 + a.r1c2 * b.r2c0 + a.r1c3 * b.r3c0, a.r1c0 * b.r0c1 + a.r1c1 * b.r1c1 + a.r1c2 * b.r2c1 + a.r1c3 * b.r3c1, a.r1c0 * b.r0c2 + a.r1c1 * b.r1c2 + a.r1c2 * b.r2c2 + a.r1c3 * b.r3c2, a.r1c0 * b.r0c3 + a.r1c1 * b.r1c3 + a.r1c2 * b.r2c3 + a.r1c3 * b.r3c3,
a.r2c0 * b.r0c0 + a.r2c1 * b.r1c0 + a.r2c2 * b.r2c0 + a.r2c3 * b.r3c0, a.r2c0 * b.r0c1 + a.r2c1 * b.r1c1 + a.r2c2 * b.r2c1 + a.r2c3 * b.r3c1, a.r2c0 * b.r0c2 + a.r2c1 * b.r1c2 + a.r2c2 * b.r2c2 + a.r2c3 * b.r3c2, a.r2c0 * b.r0c3 + a.r2c1 * b.r1c3 + a.r2c2 * b.r2c3 + a.r2c3 * b.r3c3,
a.r3c0 * b.r0c0 + a.r3c1 * b.r1c0 + a.r3c2 * b.r2c0 + a.r3c3 * b.r3c0, a.r3c0 * b.r0c1 + a.r3c1 * b.r1c1 + a.r3c2 * b.r2c1 + a.r3c3 * b.r3c1, a.r3c0 * b.r0c2 + a.r3c1 * b.r1c2 + a.r3c2 * b.r2c2 + a.r3c3 * b.r3c2, a.r3c0 * b.r0c3 + a.r3c1 * b.r1c3 + a.r3c2 * b.r2c3 + a.r3c3 * b.r3c3);
public static Matrix4x4 operator /(Matrix4x4 a, double b) =>
new Matrix4x4(a.r0c0 / b, a.r0c1 / b, a.r0c2 / b, a.r0c3 / b,
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,
0, mat.r1c0, mat.r1c1, 0,
0, 0 , 0 , 1);
public static implicit operator Matrix4x4(Matrix3x3 mat) =>
new Matrix4x4(1, 0 , 0 , 0 ,
0, mat.r0c0, mat.r0c1, mat.r0c2,
0, mat.r1c0, mat.r1c1, mat.r1c2,
0, mat.r2c0, mat.r2c1, mat.r2c2);
}
}

View File

@ -0,0 +1,8 @@
namespace Nerd_STF.Mathematics.Algebra
{
public enum RowColumn
{
Row,
Column
}
}

View File

@ -0,0 +1,276 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Nerd_STF.Mathematics
{
// Maybe move to .Numbers and add inheritance to INumber? Does this make sense?
public readonly struct Angle : IComparable<Angle>,
IEquatable<Angle>
#if CS11_OR_GREATER
,IFromTuple<Angle, (double, Angle.Units)>,
IInterpolable<Angle>,
IPresets2d<Angle>
#endif
{
public static Angle Down => new Angle(0.75);
public static Angle Left => new Angle(0.5);
public static Angle Right => new Angle(0);
public static Angle Up => new Angle(0.25);
public static Angle Full => new Angle(1);
public static Angle Half => new Angle(0.5);
public static Angle Quarter => new Angle(0.25);
public static Angle Zero => new Angle(0);
#if CS11_OR_GREATER
static Angle IPresets1d<Angle>.One => new Angle(1, Units.Degrees);
#endif
public double Degrees => revTheta * 360;
public double Gradians => revTheta * 400;
public double Radians => revTheta * Constants.Tau;
public double Revolutions => revTheta;
public Angle Complimentary => new Angle(0.25 - MathE.ModAbs(revTheta, 1));
public Angle Supplimentary => new Angle(0.5 - MathE.ModAbs(revTheta, 1));
public Angle Normalized => new Angle(MathE.ModAbs(revTheta, 1));
public Angle Reflected => new Angle(MathE.ModAbs(-revTheta, 1));
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)
{
case Units.Revolutions: revTheta = theta; break;
case Units.Degrees: revTheta = theta / 360; break;
case Units.Radians: revTheta = theta / Constants.Tau; break;
case Units.Gradians: revTheta = theta / 400; break;
default: throw new ArgumentException($"Unknown angle unit \"{unit}.\"", nameof(unit));
}
}
private Angle(double revTheta)
{
this.revTheta = revTheta;
}
public double this[Units unit]
{
get
{
switch (unit)
{
case Units.Revolutions: return revTheta;
case Units.Degrees: return revTheta * 360;
case Units.Radians: return revTheta * Constants.Tau;
case Units.Gradians: return revTheta * 400;
default: throw new ArgumentException($"Unknown angle unit \"{unit}.\"", nameof(unit));
}
}
}
public static double Convert(double value, Units from, Units to)
{
switch (from)
{
case Units.Revolutions:
switch (to)
{
case Units.Revolutions: return value;
case Units.Degrees: return value * 360;
case Units.Radians: return value * 6.28318530718;
case Units.Gradians: return value * 400;
default: goto _fail;
}
case Units.Degrees:
switch (to)
{
case Units.Revolutions: return value * 0.00277777777778;
case Units.Degrees: return value;
case Units.Radians: return value * 0.0174532925199;
case Units.Gradians: return value * 1.11111111111;
default: goto _fail;
}
case Units.Radians:
switch (to)
{
case Units.Revolutions: return value * 0.159154943092;
case Units.Degrees: return value * 57.2957795131;
case Units.Radians: return value;
case Units.Gradians: return value * 63.6619772368;
default: goto _fail;
}
case Units.Gradians:
switch (to)
{
case Units.Revolutions: return value * 0.0025;
case Units.Degrees: return value * 0.9;
case Units.Radians: return value * 0.0157079632679;
case Units.Gradians: return value;
default: goto _fail;
}
default: goto _fail;
}
_fail: throw new ArgumentException($"Invalid conversion: {from} -> {to}.");
}
public static Angle Average(IEnumerable<Angle> angles)
{
Angle sum = Zero;
int count = 0;
foreach (Angle ang in angles)
{
sum += ang;
count++;
}
return sum;
}
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)
{
bool any = false;
Angle best = Zero;
double bestNormalized = 0;
foreach (Angle ang in values)
{
if (!any)
{
best = ang;
if (normalize) bestNormalized = MathE.ModAbs(ang.revTheta, 1);
any = true;
}
else if (normalize)
{
double angNormalized = MathE.ModAbs(ang.revTheta, 1);
if (angNormalized > bestNormalized)
{
best = ang;
bestNormalized = angNormalized;
}
}
else if (ang.revTheta > best.revTheta) best = ang;
}
return best;
}
public static Angle Min(IEnumerable<Angle> values) => Min(false, values);
public static Angle Min(bool normalize, IEnumerable<Angle> values)
{
bool any = false;
Angle best = Zero;
double bestNormalized = 0;
foreach (Angle ang in values)
{
if (!any)
{
best = ang;
if (normalize) bestNormalized = MathE.ModAbs(ang.revTheta, 1);
any = true;
}
else if (normalize)
{
double angNormalized = MathE.ModAbs(ang.revTheta, 1);
if (angNormalized < bestNormalized)
{
best = ang;
bestNormalized = angNormalized;
}
}
else if (ang.revTheta < best.revTheta) best = ang;
}
return best;
}
public static Angle Sum(IEnumerable<Angle> angles)
{
Angle sum = Zero;
foreach (Angle ang in angles) sum += ang;
return sum;
}
public static double[] SplitArray(Units unit, IEnumerable<Angle> values)
{
int count = values.Count();
double[] angles = new double[count];
int index = 0;
foreach (Angle val in values)
{
angles[index] = val[unit];
index++;
}
return angles;
}
public Angle Coterminal(int turns) => new Angle(revTheta + turns);
public int CompareTo(Angle other) => revTheta.CompareTo(other.revTheta);
public bool Equals(Angle other) => revTheta == other.revTheta;
#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 Angle otherAng) return Equals(otherAng);
else return false;
}
public override int GetHashCode() => revTheta.GetHashCode();
public override string ToString() => ToString(Units.Degrees, null);
public string ToString(Units unit) => ToString(unit, null);
#if CS8_OR_GREATER
public string ToString(string? format) =>
#else
public string ToString(string format) =>
#endif
ToString(Units.Degrees, format);
#if CS8_OR_GREATER
public string ToString(Units unit, string? format)
#else
public string ToString(Units unit, string format)
#endif
{
switch (unit)
{
case Units.Revolutions: return $"{revTheta.ToString(format)} rev";
case Units.Degrees: return $"{(revTheta * 360).ToString(format)} deg";
case Units.Radians: return $"{(revTheta * Constants.Tau).ToString(format)} rad";
case Units.Gradians: return $"{(revTheta * 400).ToString(format)} grad";
default: throw new ArgumentException($"Unknown angle unit \"{unit}.\"", nameof(unit));
}
}
public static Angle operator +(Angle a, Angle b) => new Angle(a.revTheta + b.revTheta);
public static Angle operator -(Angle a) => new Angle(-a.revTheta);
public static Angle operator -(Angle a, Angle b) => new Angle(a.revTheta - b.revTheta);
public static Angle operator *(Angle a, double b) => new Angle(a.revTheta * b);
public static Angle operator /(Angle a, double b) => new Angle(a.revTheta / b);
public static bool operator ==(Angle a, Angle b) => a.Equals(b);
public static bool operator !=(Angle a, Angle b) => !a.Equals(b);
public static bool operator >(Angle a, Angle b) => a.CompareTo(b) > 0;
public static bool operator <(Angle a, Angle b) => a.CompareTo(b) < 0;
public static bool operator >=(Angle a, Angle b) => a.CompareTo(b) >= 0;
public static bool operator <=(Angle a, Angle b) => a.CompareTo(b) <= 0;
public static implicit operator Angle((double, Units) tuple) => new Angle(tuple.Item1, tuple.Item2);
#if CS11_OR_GREATER
static implicit IFromTuple<Angle, (double, Units)>.operator ValueTuple<double, Units>(Angle angle) => (angle.revTheta, Units.Revolutions);
#endif
public enum Units
{
Revolutions,
Degrees,
Radians,
Gradians
}
}
}

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Numerics;
namespace Nerd_STF.Mathematics.Equations namespace Nerd_STF.Mathematics.Equations
{ {
@ -23,5 +24,17 @@ namespace Nerd_STF.Mathematics.Equations
double Integrate(double lower, double upper); double Integrate(double lower, double upper);
// TODO: Solve // TODO: Solve
#if CS8_OR_GREATER
static IEquation operator +(IEquation a, IEquation b) => a.Add(b);
static IEquation operator +(IEquation a, double b) => a.Add(b);
static IEquation operator -(IEquation a) => a.Negate();
static IEquation operator -(IEquation a, IEquation b) => a.Subtract(b);
static IEquation operator -(IEquation a, double b) => a.Subtract(b);
static IEquation operator *(IEquation a, IEquation b) => a.Multiply(b);
static IEquation operator *(IEquation a, double b) => a.Multiply(b);
static IEquation operator /(IEquation a, IEquation b) => a.Divide(b);
static IEquation operator /(IEquation a, double b) => a.Divide(b);
#endif
} }
} }

View File

@ -21,6 +21,11 @@ namespace Nerd_STF.Mathematics.Equations
M = m; M = m;
B = b; B = b;
} }
public Linear(Fill<double> fill)
{
B = fill(0);
M = fill(1);
}
public double this[double x] => M * x + B; public double this[double x] => M * x + B;
public double Get(double x) => M * x + B; public double Get(double x) => M * x + B;

View File

@ -30,6 +30,16 @@ namespace Nerd_STF.Mathematics.Equations
if (reverse) this.terms = TrimExcessTerms(terms.Reverse().ToArray()); if (reverse) this.terms = TrimExcessTerms(terms.Reverse().ToArray());
else this.terms = TrimExcessTerms(terms.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) private static double[] TrimExcessTerms(double[] terms)
{ {
int newLength = terms.Length; int newLength = terms.Length;
@ -79,6 +89,8 @@ namespace Nerd_STF.Mathematics.Equations
public IEquation Add(IEquation other) public IEquation Add(IEquation other)
{ {
if (other is Polynomial otherPoly) return Add(otherPoly); if (other is Polynomial otherPoly) return Add(otherPoly);
else if (other is Quadratic otherQuad) return Add((Polynomial)otherQuad);
else if (other is Linear otherLinear) return Add((Polynomial)otherLinear);
else return new Equation((double x) => Get(x) + other.Get(x)); else return new Equation((double x) => Get(x) + other.Get(x));
} }
public Polynomial Add(double constant) public Polynomial Add(double constant)

View File

@ -42,6 +42,12 @@ namespace Nerd_STF.Mathematics.Equations
B = 0; B = 0;
C = c; 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 this[double x] => A * x * x + B * x + C;
public double Get(double x) => A * x * x + B * x + C; public double Get(double x) => A * x * x + B * x + C;
@ -183,6 +189,7 @@ namespace Nerd_STF.Mathematics.Equations
public static Polynomial operator *(Quadratic a, double b) => a.Multiply(b); public static Polynomial operator *(Quadratic a, double b) => a.Multiply(b);
public static IEquation operator /(Quadratic a, IEquation b) => a.Divide(b); public static IEquation operator /(Quadratic a, IEquation b) => a.Divide(b);
public static Quadratic operator /(Quadratic a, double b) => a.Divide(b); public static Quadratic operator /(Quadratic a, double b) => a.Divide(b);
public static bool operator ==(Quadratic a, Quadratic b) => a.Equals(b); public static bool operator ==(Quadratic a, Quadratic b) => a.Equals(b);
public static bool operator ==(Quadratic a, Polynomial b) => a.Equals(b); public static bool operator ==(Quadratic a, Polynomial b) => a.Equals(b);
public static bool operator !=(Quadratic a, Quadratic b) => !a.Equals(b); public static bool operator !=(Quadratic a, Quadratic b) => !a.Equals(b);

View File

@ -1,11 +1,10 @@
using Nerd_STF.Exceptions; using Nerd_STF.Exceptions;
using Nerd_STF.Mathematics.Abstract;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Numerics;
namespace Nerd_STF.Mathematics namespace Nerd_STF.Mathematics
{ {
@ -13,6 +12,8 @@ namespace Nerd_STF.Mathematics
#if CS11_OR_GREATER #if CS11_OR_GREATER
,IFromTuple<Float2, (double, double)>, ,IFromTuple<Float2, (double, double)>,
IPresets2d<Float2>, IPresets2d<Float2>,
IRoundable<Float2, Int2>,
IRefRoundable<Float2>,
ISplittable<Float2, (double[] Xs, double[] Ys)> ISplittable<Float2, (double[] Xs, double[] Ys)>
#endif #endif
{ {
@ -45,9 +46,14 @@ namespace Nerd_STF.Mathematics
{ {
this[index] = item; this[index] = item;
index++; 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] public double this[int index]
{ {
@ -70,20 +76,22 @@ namespace Nerd_STF.Mathematics
} }
} }
} }
public IEnumerable<double> this[string key] public ListTuple<double> this[string key]
{ {
get get
{ {
double[] items = new double[key.Length];
for (int i = 0; i < key.Length; i++) for (int i = 0; i < key.Length; i++)
{ {
char c = key[i]; char c = key[i];
switch (c) switch (c)
{ {
case 'x': yield return x; break; case 'x': items[i] = x; break;
case 'y': yield return y; break; case 'y': items[i] = y; break;
default: throw new ArgumentException("Invalid key.", nameof(key)); default: throw new ArgumentException("Invalid key.", nameof(key));
} }
} }
return new ListTuple<double>(items);
} }
set set
{ {
@ -253,6 +261,19 @@ namespace Nerd_STF.Mathematics
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)})"; public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)})";
public double[] ToArray() => new double[] { x, y }; 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 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); public static Float2 operator +(Float2 a, Float2 b) => new Float2(a.x + b.x, a.y + b.y);
@ -265,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 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(Float3 floats) => new Float2(floats.x, floats.y);
public static explicit operator Float2(Float4 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); public static implicit operator Float2(Int2 ints) => new Float2(ints.x, ints.y);
@ -274,12 +296,22 @@ namespace Nerd_STF.Mathematics
public static implicit operator Float2(PointF point) => new Float2(point.X, point.Y); 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(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(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); public static implicit operator Float2((double, double) tuple) => new Float2(tuple.Item1, tuple.Item2);
public static explicit operator Point(Float2 group) => new Point((int)group.x, (int)group.y); public static explicit operator Point(Float2 group) => new Point((int)group.x, (int)group.y);
public static implicit operator PointF(Float2 group) => new PointF((float)group.x, (float)group.y); 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 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 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); public static implicit operator ValueTuple<double, double>(Float2 group) => (group.x, group.y);
} }
} }

View File

@ -1,9 +1,10 @@
using Nerd_STF.Exceptions; using Nerd_STF.Exceptions;
using Nerd_STF.Mathematics.Abstract; using Nerd_STF.Graphics;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics;
namespace Nerd_STF.Mathematics namespace Nerd_STF.Mathematics
{ {
@ -11,6 +12,8 @@ namespace Nerd_STF.Mathematics
#if CS11_OR_GREATER #if CS11_OR_GREATER
,IFromTuple<Float3, (double, double, double)>, ,IFromTuple<Float3, (double, double, double)>,
IPresets2d<Float3>, IPresets2d<Float3>,
IRoundable<Float3, Int3>,
IRefRoundable<Float3>,
ISplittable<Float3, (double[] Xs, double[] Ys, double[] Zs)> ISplittable<Float3, (double[] Xs, double[] Ys, double[] Zs)>
#endif #endif
{ {
@ -47,9 +50,15 @@ namespace Nerd_STF.Mathematics
{ {
this[index] = item; this[index] = item;
index++; 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] public double this[int index]
{ {
@ -74,21 +83,23 @@ namespace Nerd_STF.Mathematics
} }
} }
} }
public IEnumerable<double> this[string key] public ListTuple<double> this[string key]
{ {
get get
{ {
double[] items = new double[key.Length];
for (int i = 0; i < key.Length; i++) for (int i = 0; i < key.Length; i++)
{ {
char c = key[i]; char c = key[i];
switch (c) switch (c)
{ {
case 'x': yield return x; break; case 'x': items[i] = x; break;
case 'y': yield return y; break; case 'y': items[i] = y; break;
case 'z': yield return z; break; case 'z': items[i] = z; break;
default: throw new ArgumentException("Invalid key.", nameof(key)); default: throw new ArgumentException("Invalid key.", nameof(key));
} }
} }
return new ListTuple<double>(items);
} }
set set
{ {
@ -117,7 +128,7 @@ namespace Nerd_STF.Mathematics
total += val; total += val;
count++; count++;
} }
return total; return total / count;
} }
public static Int3 Ceiling(Float3 val) => public static Int3 Ceiling(Float3 val) =>
new Int3(MathE.Ceiling(val.x), new Int3(MathE.Ceiling(val.x),
@ -278,6 +289,20 @@ namespace Nerd_STF.Mathematics
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})"; public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})";
public double[] ToArray() => new double[] { x, y, z }; 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 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); public static Float3 operator +(Float3 a, Float3 b) => new Float3(a.x + b.x, a.y + b.y, a.z + b.z);
@ -290,13 +315,24 @@ 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 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 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 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(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 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 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 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); public static implicit operator ValueTuple<double, double, double>(Float3 group) => (group.x, group.y, group.z);
} }
} }

View File

@ -2,8 +2,9 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics;
using Nerd_STF.Exceptions; using Nerd_STF.Exceptions;
using Nerd_STF.Mathematics.Abstract; using Nerd_STF.Graphics;
namespace Nerd_STF.Mathematics namespace Nerd_STF.Mathematics
{ {
@ -11,6 +12,8 @@ namespace Nerd_STF.Mathematics
#if CS11_OR_GREATER #if CS11_OR_GREATER
,IFromTuple<Float4, (double, double, double, double)>, ,IFromTuple<Float4, (double, double, double, double)>,
IPresets4d<Float4>, IPresets4d<Float4>,
IRoundable<Float4, Int4>,
IRefRoundable<Float4>,
ISplittable<Float4, (double[] Ws, double[] Xs, double[] Ys, double[] Zs)> ISplittable<Float4, (double[] Ws, double[] Xs, double[] Ys, double[] Zs)>
#endif #endif
{ {
@ -51,9 +54,16 @@ namespace Nerd_STF.Mathematics
{ {
this[index] = item; this[index] = item;
index++; 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] public double this[int index]
{ {
@ -80,22 +90,24 @@ namespace Nerd_STF.Mathematics
} }
} }
} }
public IEnumerable<double> this[string key] public ListTuple<double> this[string key]
{ {
get get
{ {
double[] items = new double[key.Length];
for (int i = 0; i < key.Length; i++) for (int i = 0; i < key.Length; i++)
{ {
char c = key[i]; char c = key[i];
switch (c) switch (c)
{ {
case 'w': yield return w; break; case 'w': items[i] = w; break;
case 'x': yield return x; break; case 'x': items[i] = x; break;
case 'y': yield return y; break; case 'y': items[i] = y; break;
case 'z': yield return z; break; case 'z': items[i] = z; break;
default: throw new ArgumentException("Invalid key.", nameof(key)); default: throw new ArgumentException("Invalid key.", nameof(key));
} }
} }
return new ListTuple<double>(items);
} }
set set
{ {
@ -125,7 +137,7 @@ namespace Nerd_STF.Mathematics
total += val; total += val;
count++; count++;
} }
return total; return total / count;
} }
public static Int4 Ceiling(Float4 val) => public static Int4 Ceiling(Float4 val) =>
new Int4(MathE.Ceiling(val.w), new Int4(MathE.Ceiling(val.w),
@ -298,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 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 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 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); 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);
@ -310,13 +337,25 @@ 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 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(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(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(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(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(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 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); public static implicit operator ValueTuple<double, double, double, double>(Float4 group) => (group.w, group.x, group.y, group.z);
} }
} }

View File

@ -0,0 +1,10 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics
{
public interface IInterpolable<TSelf>
where TSelf : IInterpolable<TSelf>
{
static abstract TSelf Lerp(TSelf a, TSelf b, double t, bool clamp = true);
}
}
#endif

View File

@ -0,0 +1,22 @@
using Nerd_STF.Mathematics.Algebra;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace Nerd_STF.Mathematics
{
public interface INumberGroup<TSelf, TItem> : ICombinationIndexer<TItem>,
IEnumerable<TItem>,
IEquatable<TSelf>,
INumberGroupBase<TItem>
#if CS11_OR_GREATER
, IInterpolable<TSelf>,
ISimpleMathOperations<TSelf>,
IVectorOperations<TSelf>
#endif
where TSelf : INumberGroup<TSelf, TItem>
#if CS11_OR_GREATER
where TItem : INumber<TItem>
#endif
{ }
}

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

View File

@ -0,0 +1,10 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics
{
public interface IPresets1d<TSelf> where TSelf : IPresets1d<TSelf>
{
static abstract TSelf One { get; }
static abstract TSelf Zero { get; }
}
}
#endif

View File

@ -0,0 +1,13 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics
{
public interface IPresets2d<TSelf> : IPresets1d<TSelf>
where TSelf : IPresets2d<TSelf>
{
static abstract TSelf Down { get; }
static abstract TSelf Left { get; }
static abstract TSelf Right { get; }
static abstract TSelf Up { get; }
}
}
#endif

View File

@ -0,0 +1,11 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics
{
public interface IPresets3d<TSelf> : IPresets2d<TSelf>
where TSelf : IPresets3d<TSelf>
{
static abstract TSelf Backward { get; }
static abstract TSelf Forward { get; }
}
}
#endif

View File

@ -0,0 +1,12 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics
{
public interface IPresets4d<TSelf> : IPresets3d<TSelf>
where TSelf : IPresets4d<TSelf>
{
// 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

View File

@ -0,0 +1,12 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics
{
public interface IRefRoundable<TSelf>
where TSelf : IRefRoundable<TSelf>
{
static abstract void Ceiling(ref TSelf val);
static abstract void Floor(ref TSelf val);
static abstract void Round(ref TSelf val);
}
}
#endif

View File

@ -0,0 +1,15 @@
#if CS11_OR_GREATER
namespace Nerd_STF.Mathematics
{
public interface IRoundable<TSelf> : IRoundable<TSelf, TSelf>
where TSelf : IRoundable<TSelf> { }
public interface IRoundable<TSelf, TOut>
where TSelf : IRoundable<TSelf, TOut>
{
static abstract TOut Ceiling(TSelf val);
static abstract TOut Floor(TSelf val);
static abstract TOut Round(TSelf val);
}
}
#endif

View File

@ -0,0 +1,15 @@
#if CS11_OR_GREATER
using System.Collections.Generic;
using System.Numerics;
namespace Nerd_STF.Mathematics
{
public interface ISimpleMathOperations<TSelf> : IAdditionOperators<TSelf, TSelf, TSelf>,
IMultiplyOperators<TSelf, TSelf, TSelf>
where TSelf : ISimpleMathOperations<TSelf>
{
static abstract TSelf Product(IEnumerable<TSelf> vals);
static abstract TSelf Sum(IEnumerable<TSelf> vals);
}
}
#endif

View File

@ -1,5 +1,6 @@
using Nerd_STF.Exceptions; using Nerd_STF.Exceptions;
using Nerd_STF.Mathematics.Abstract; using Nerd_STF.Mathematics.Algebra;
using Nerd_STF.Mathematics.Numbers;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
@ -44,9 +45,14 @@ namespace Nerd_STF.Mathematics
{ {
this[index] = item; this[index] = item;
index++; 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] public int this[int index]
{ {
@ -69,20 +75,22 @@ namespace Nerd_STF.Mathematics
} }
} }
} }
public IEnumerable<int> this[string key] public ListTuple<int> this[string key]
{ {
get get
{ {
int[] items = new int[key.Length];
for (int i = 0; i < key.Length; i++) for (int i = 0; i < key.Length; i++)
{ {
char c = key[i]; char c = key[i];
switch (c) switch (c)
{ {
case 'x': yield return x; break; case 'x': items[i] = x; break;
case 'y': yield return y; break; case 'y': items[i] = y; break;
default: throw new ArgumentException("Invalid key.", nameof(key)); default: throw new ArgumentException("Invalid key.", nameof(key));
} }
} }
return new ListTuple<int>(items);
} }
set set
{ {
@ -120,27 +128,27 @@ namespace Nerd_STF.Mathematics
MathE.Clamp(ref value.x, min.x, max.x); MathE.Clamp(ref value.x, min.x, max.x);
MathE.Clamp(ref value.y, min.y, max.y); MathE.Clamp(ref value.y, min.y, max.y);
} }
public static Int2 ClampMagnitude(Int2 value, int minMag, int maxMag) public static Int2 ClampMagnitude(Int2 value, double minMag, double maxMag)
{ {
Int2 copy = value; Int2 copy = value;
ClampMagnitude(ref copy, minMag, maxMag); ClampMagnitude(ref copy, minMag, maxMag);
return copy; return copy;
} }
public static void ClampMagnitude(ref Int2 value, int minMag, int maxMag) public static void ClampMagnitude(ref Int2 value, double minMag, double maxMag)
{ {
if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag)); if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag));
double mag = value.Magnitude; double mag = value.Magnitude;
if (mag < minMag) if (mag < minMag)
{ {
double factor = minMag / mag; double factor = minMag / mag;
value.x = (int)(value.x * factor); value.x = MathE.Ceiling(value.x * factor);
value.y = (int)(value.y * factor); value.y = MathE.Ceiling(value.y * factor);
} }
else if (mag > maxMag) else if (mag > maxMag)
{ {
double factor = maxMag / mag; double factor = maxMag / mag;
value.x = (int)(value.x * factor); value.x = MathE.Floor(value.x * factor);
value.y = (int)(value.y * factor); value.y = MathE.Floor(value.y * factor);
} }
} }
public static Int3 Cross(Int2 a, Int2 b) => Int3.Cross(a, b); public static Int3 Cross(Int2 a, Int2 b) => Int3.Cross(a, b);
@ -155,6 +163,10 @@ namespace Nerd_STF.Mathematics
} }
return x + y; return x + y;
} }
#if CS11_OR_GREATER
static double IVectorOperations<Int2>.Dot(Int2 a, Int2 b) => Dot(a, b);
static double IVectorOperations<Int2>.Dot(IEnumerable<Int2> vals) => Dot(vals);
#endif
public static Int2 Lerp(Int2 a, Int2 b, double t, bool clamp = true) => public static Int2 Lerp(Int2 a, Int2 b, double t, bool clamp = true) =>
new Int2(MathE.Lerp(a.x, b.x, t, clamp), new Int2(MathE.Lerp(a.x, b.x, t, clamp),
MathE.Lerp(a.y, b.y, t, clamp)); MathE.Lerp(a.y, b.y, t, clamp));
@ -220,6 +232,19 @@ namespace Nerd_STF.Mathematics
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)})"; public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)})";
public int[] ToArray() => new int[] { x, y }; 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 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); public static Int2 operator +(Int2 a, Int2 b) => new Int2(a.x + b.x, a.y + b.y);
@ -235,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 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(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(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); public static explicit operator Int2(Float4 floats) => new Int2((int)floats.x, (int)floats.y);
@ -244,12 +270,16 @@ namespace Nerd_STF.Mathematics
public static explicit operator Int2(PointF point) => new Int2((int)point.X, (int)point.Y); public static explicit operator Int2(PointF point) => new Int2((int)point.X, (int)point.Y);
public static implicit operator Int2(Size size) => new Int2(size.Width, size.Height); public static implicit operator Int2(Size size) => new Int2(size.Width, size.Height);
public static explicit operator Int2(SizeF size) => new Int2((int)size.Width, (int)size.Height); public static explicit operator Int2(SizeF size) => new Int2((int)size.Width, (int)size.Height);
public static explicit operator Int2(ListTuple<double> tuple) => new Int2((int)tuple[0], (int)tuple[1]);
public static implicit operator Int2(ListTuple<int> tuple) => new Int2(tuple[0], tuple[1]);
public static implicit operator Int2((int, int) tuple) => new Int2(tuple.Item1, tuple.Item2); public static implicit operator Int2((int, int) tuple) => new Int2(tuple.Item1, tuple.Item2);
public static implicit operator Point(Int2 group) => new Point(group.x, group.y); public static implicit operator Point(Int2 group) => new Point(group.x, group.y);
public static explicit operator PointF(Int2 group) => new PointF(group.x, group.y); public static explicit operator PointF(Int2 group) => new PointF(group.x, group.y);
public static implicit operator Size(Int2 group) => new Size(group.x, group.y); public static implicit operator Size(Int2 group) => new Size(group.x, group.y);
public static explicit operator SizeF(Int2 group) => new SizeF(group.x, group.y); public static explicit operator SizeF(Int2 group) => new SizeF(group.x, group.y);
public static implicit operator ListTuple<double>(Int2 group) => new ListTuple<double>(group.x, group.y);
public static implicit operator ListTuple<int>(Int2 group) => new ListTuple<int>(group.x, group.y);
public static implicit operator ValueTuple<int, int>(Int2 group) => (group.x, group.y); public static implicit operator ValueTuple<int, int>(Int2 group) => (group.x, group.y);
} }
} }

View File

@ -1,5 +1,5 @@
using Nerd_STF.Exceptions; using Nerd_STF.Exceptions;
using Nerd_STF.Mathematics.Abstract; using Nerd_STF.Mathematics.Algebra;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
@ -47,9 +47,15 @@ namespace Nerd_STF.Mathematics
{ {
this[index] = item; this[index] = item;
index++; 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] public int this[int index]
{ {
@ -74,21 +80,23 @@ namespace Nerd_STF.Mathematics
} }
} }
} }
public IEnumerable<int> this[string key] public ListTuple<int> this[string key]
{ {
get get
{ {
int[] items = new int[key.Length];
for (int i = 0; i < key.Length; i++) for (int i = 0; i < key.Length; i++)
{ {
char c = key[i]; char c = key[i];
switch (c) switch (c)
{ {
case 'x': yield return x; break; case 'x': items[i] = x; break;
case 'y': yield return y; break; case 'y': items[i] = y; break;
case 'z': yield return z; break; case 'z': items[i] = z; break;
default: throw new ArgumentException("Invalid key.", nameof(key)); default: throw new ArgumentException("Invalid key.", nameof(key));
} }
} }
return new ListTuple<int>(items);
} }
set set
{ {
@ -129,13 +137,13 @@ namespace Nerd_STF.Mathematics
MathE.Clamp(ref value.y, min.y, max.y); MathE.Clamp(ref value.y, min.y, max.y);
MathE.Clamp(ref value.z, min.z, max.z); MathE.Clamp(ref value.z, min.z, max.z);
} }
public static Int3 ClampMagnitude(Int3 value, int minMag, int maxMag) public static Int3 ClampMagnitude(Int3 value, double minMag, double maxMag)
{ {
Int3 copy = value; Int3 copy = value;
ClampMagnitude(ref copy, minMag, maxMag); ClampMagnitude(ref copy, minMag, maxMag);
return copy; return copy;
} }
public static void ClampMagnitude(ref Int3 value, int minMag, int maxMag) public static void ClampMagnitude(ref Int3 value, double minMag, double maxMag)
{ {
if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag)); if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag));
double mag = value.Magnitude; double mag = value.Magnitude;
@ -143,16 +151,16 @@ namespace Nerd_STF.Mathematics
if (mag < minMag) if (mag < minMag)
{ {
double factor = minMag / mag; double factor = minMag / mag;
value.x = (int)(value.x * factor); value.x = MathE.Ceiling(value.x * factor);
value.y = (int)(value.y * factor); value.y = MathE.Ceiling(value.y * factor);
value.z = (int)(value.z * factor); value.z = MathE.Ceiling(value.z * factor);
} }
else if (mag > maxMag) else if (mag > maxMag)
{ {
double factor = maxMag / mag; double factor = maxMag / mag;
value.x = (int)(value.x * factor); value.x = MathE.Floor(value.x * factor);
value.y = (int)(value.y * factor); value.y = MathE.Floor(value.y * factor);
value.z = (int)(value.z * factor); value.z = MathE.Floor(value.z * factor);
} }
} }
public static Int3 Cross(Int3 a, Int3 b) => public static Int3 Cross(Int3 a, Int3 b) =>
@ -171,6 +179,10 @@ namespace Nerd_STF.Mathematics
} }
return x + y + z; return x + y + z;
} }
#if CS11_OR_GREATER
static double IVectorOperations<Int3>.Dot(Int3 a, Int3 b) => Dot(a, b);
static double IVectorOperations<Int3>.Dot(IEnumerable<Int3> vals) => Dot(vals);
#endif
public static Int3 Lerp(Int3 a, Int3 b, double t, bool clamp = true) => public static Int3 Lerp(Int3 a, Int3 b, double t, bool clamp = true) =>
new Int3(MathE.Lerp(a.x, b.x, t, clamp), new Int3(MathE.Lerp(a.x, b.x, t, clamp),
MathE.Lerp(a.y, b.y, t, clamp), MathE.Lerp(a.y, b.y, t, clamp),
@ -240,6 +252,20 @@ namespace Nerd_STF.Mathematics
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})"; public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})";
public int[] ToArray() => new int[] { x, y, z }; 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 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); public static Int3 operator +(Int3 a, Int3 b) => new Int3(a.x + b.x, a.y + b.y, a.z + b.z);
@ -260,8 +286,12 @@ namespace Nerd_STF.Mathematics
public static explicit operator Int3(Float2 floats) => new Int3((int)floats.x, (int)floats.y, 0); public static explicit operator Int3(Float2 floats) => new Int3((int)floats.x, (int)floats.y, 0);
public static explicit operator Int3(Float3 floats) => new Int3((int)floats.x, (int)floats.y, (int)floats.z); public static explicit operator Int3(Float3 floats) => new Int3((int)floats.x, (int)floats.y, (int)floats.z);
public static explicit operator Int3(Float4 floats) => new Int3((int)floats.x, (int)floats.y, (int)floats.z); public static explicit operator Int3(Float4 floats) => new Int3((int)floats.x, (int)floats.y, (int)floats.z);
public static explicit operator Int3(ListTuple<double> tuple) => new Int3((int)tuple[0], (int)tuple[1], (int)tuple[2]);
public static implicit operator Int3(ListTuple<int> tuple) => new Int3(tuple[0], tuple[1], tuple[2]);
public static implicit operator Int3((int, int, int) tuple) => new Int3(tuple.Item1, tuple.Item2, tuple.Item3); public static implicit operator Int3((int, int, int) tuple) => new Int3(tuple.Item1, tuple.Item2, tuple.Item3);
public static implicit operator ListTuple<double>(Int3 group) => new ListTuple<double>(group.x, group.y, group.z);
public static implicit operator ListTuple<int>(Int3 group) => new ListTuple<int>(group.x, group.y, group.z);
public static implicit operator ValueTuple<int, int, int>(Int3 group) => (group.x, group.y, group.z); public static implicit operator ValueTuple<int, int, int>(Int3 group) => (group.x, group.y, group.z);
} }
} }

View File

@ -1,9 +1,9 @@
using System; using Nerd_STF.Exceptions;
using Nerd_STF.Mathematics.Algebra;
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Nerd_STF.Exceptions;
using Nerd_STF.Mathematics.Abstract;
namespace Nerd_STF.Mathematics namespace Nerd_STF.Mathematics
{ {
@ -51,9 +51,16 @@ namespace Nerd_STF.Mathematics
{ {
this[index] = item; this[index] = item;
index++; 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] public int this[int index]
{ {
@ -80,22 +87,24 @@ namespace Nerd_STF.Mathematics
} }
} }
} }
public IEnumerable<int> this[string key] public ListTuple<int> this[string key]
{ {
get get
{ {
int[] items = new int[key.Length];
for (int i = 0; i < key.Length; i++) for (int i = 0; i < key.Length; i++)
{ {
char c = key[i]; char c = key[i];
switch (c) switch (c)
{ {
case 'w': yield return w; break; case 'w': items[i] = w; break;
case 'x': yield return x; break; case 'x': items[i] = x; break;
case 'y': yield return y; break; case 'y': items[i] = y; break;
case 'z': yield return z; break; case 'z': items[i] = z; break;
default: throw new ArgumentException("Invalid key.", nameof(key)); default: throw new ArgumentException("Invalid key.", nameof(key));
} }
} }
return new ListTuple<int>(items);
} }
set set
{ {
@ -152,18 +161,18 @@ namespace Nerd_STF.Mathematics
if (mag < minMag) if (mag < minMag)
{ {
double factor = minMag / mag; double factor = minMag / mag;
value.w = (int)(value.w * factor); value.w = MathE.Ceiling(value.w * factor);
value.x = (int)(value.x * factor); value.x = MathE.Ceiling(value.x * factor);
value.y = (int)(value.y * factor); value.y = MathE.Ceiling(value.y * factor);
value.z = (int)(value.z * factor); value.z = MathE.Ceiling(value.z * factor);
} }
else if (mag > maxMag) else if (mag > maxMag)
{ {
double factor = maxMag / mag; double factor = maxMag / mag;
value.w = (int)(value.w * factor); value.w = MathE.Floor(value.w * factor);
value.x = (int)(value.x * factor); value.x = MathE.Floor(value.x * factor);
value.y = (int)(value.y * factor); value.y = MathE.Floor(value.y * factor);
value.z = (int)(value.z * factor); value.z = MathE.Floor(value.z * factor);
} }
} }
public static int Dot(Int4 a, Int4 b) => a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z; public static int Dot(Int4 a, Int4 b) => a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z;
@ -179,6 +188,10 @@ namespace Nerd_STF.Mathematics
} }
return w + x + y + z; return w + x + y + z;
} }
#if CS11_OR_GREATER
static double IVectorOperations<Int4>.Dot(Int4 a, Int4 b) => Dot(a, b);
static double IVectorOperations<Int4>.Dot(IEnumerable<Int4> vals) => Dot(vals);
#endif
public static Int4 Lerp(Int4 a, Int4 b, double t, bool clamp = true) => public static Int4 Lerp(Int4 a, Int4 b, double t, bool clamp = true) =>
new Int4(MathE.Lerp(a.w, b.w, t, clamp), new Int4(MathE.Lerp(a.w, b.w, t, clamp),
MathE.Lerp(a.x, b.x, t, clamp), MathE.Lerp(a.x, b.x, t, clamp),
@ -252,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 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 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 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); 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);
@ -272,8 +300,13 @@ 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(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(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(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); public static implicit operator Int4((int, int, int, int) tuple) => new Int4(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
public static implicit operator ListTuple<double>(Int4 group) => new ListTuple<double>(group.w, group.x, group.y, group.z);
public static implicit operator ListTuple<int>(Int4 group) => new ListTuple<int>(group.w, group.x, group.y, group.z);
public static implicit operator ValueTuple<int, int, int, int>(Int4 group) => (group.w, group.x, group.y, group.z); public static implicit operator ValueTuple<int, int, int, int>(Int4 group) => (group.w, group.x, group.y, group.z);
} }
} }

View File

@ -10,36 +10,15 @@ namespace Nerd_STF.Mathematics
{ {
public static class MathE public static class MathE
{ {
public static int Absolute(int value) => value < 0 ? -value : value; public static int Abs(int value) => value < 0 ? -value : value;
public static double Absolute(double value) => value < 0 ? -value : value; public static double Abs(double value) => value < 0 ? -value : value;
#if CS11_OR_GREATER #if CS11_OR_GREATER
public static T Absolute<T>(T num) where T : INumber<T> public static T Abs<T>(T num) where T : INumber<T>
{ {
return num < T.Zero ? -num : num; return num < T.Zero ? -num : num;
} }
#endif #endif
public static int AbsoluteMod(int value, int mod)
{
while (value >= mod) value -= mod;
while (value < 0) value += mod;
return value;
}
public static double AbsoluteMod(double value, double mod)
{
while (value >= mod) value -= mod;
while (value < 0) value += mod;
return value;
}
#if CS11_OR_GREATER
public static T AbsoluteMod<T>(T value, T mod) where T : INumber<T>
{
while (value >= mod) value -= mod;
while (value < T.Zero) value += mod;
return value;
}
#endif
public static int Average(IEnumerable<int> values) public static int Average(IEnumerable<int> values)
{ {
int sum = 0; int sum = 0;
@ -78,7 +57,7 @@ namespace Nerd_STF.Mathematics
public static double Average(IEquation equ, double lowerBound, double upperBound, double epsilon = 1e-3) public static double Average(IEquation equ, double lowerBound, double upperBound, double epsilon = 1e-3)
{ {
double sum = 0; double sum = 0;
double steps = Absolute(upperBound - lowerBound) / epsilon; double steps = Abs(upperBound - lowerBound) / epsilon;
for (double x = lowerBound; x <= upperBound; x += epsilon) sum += equ[x]; for (double x = lowerBound; x <= upperBound; x += epsilon) sum += equ[x];
return sum / steps; return sum / steps;
} }
@ -235,6 +214,9 @@ namespace Nerd_STF.Mathematics
public static IEquation DynamicIntegral(IEquation equ, IEquation lower, IEquation upper) => public static IEquation DynamicIntegral(IEquation equ, IEquation lower, IEquation upper) =>
new Equation((double x) => equ.Integrate(lower[x], upper[x])); new Equation((double x) => equ.Integrate(lower[x], upper[x]));
public static double EulersMethod(IEquation equ, double refX, double deltaX) =>
equ.Derive()[refX] * deltaX + equ[refX];
// TODO: Gamma function at some point. // TODO: Gamma function at some point.
public static BigInteger FactorialBig(int num) public static BigInteger FactorialBig(int num)
{ {
@ -360,22 +342,8 @@ namespace Nerd_STF.Mathematics
return -1; // Will only get here if there are negative numbers in the collection. return -1; // Will only get here if there are negative numbers in the collection.
} }
public static unsafe double InverseSqrt(double num) public static double InverseSqrt(double num) => 1 / Sqrt(num);
{ public static unsafe float InverseSqrtFast(float num)
return InverseSqrt((float)num); // temp while I fix this.
// My variation of the method below for doubles.
// Not much has changed, just the funny constant.
long raw = *(long*)&num;
double half = num * 0.5;
raw = 0x5FE6EB3BDFFFFF36L - (raw >> 1);
num = *(double*)&raw;
num *= 1.5 * (half * num * num); // Newton's method.
return num;
}
public static unsafe float InverseSqrt(float num)
{ {
// I think we all know this function. Code structure // I think we all know this function. Code structure
// has changed (ported), but the idea is exactly the // has changed (ported), but the idea is exactly the
@ -406,6 +374,24 @@ namespace Nerd_STF.Mathematics
} }
public static IEquation Lerp(IEquation a, IEquation b, double t, bool clamp = true) => 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)); 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 #if CS11_OR_GREATER
public static T Lerp<T>(T a, T b, T t, bool clamp = true) public static T Lerp<T>(T a, T b, T t, bool clamp = true)
where T : INumber<T> where T : INumber<T>
@ -413,6 +399,20 @@ namespace Nerd_STF.Mathematics
if (clamp) Clamp(ref t, T.Zero, T.One); if (clamp) Clamp(ref t, T.Zero, T.One);
return a + t * (b - a); 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 #endif
public static int Max(int a, int b) => a > b ? a : b; public static int Max(int a, int b) => a > b ? a : b;
@ -435,7 +435,7 @@ namespace Nerd_STF.Mathematics
} }
else if (val > best) best = val; else if (val > best) best = val;
} }
return any ? best : 0; return best;
} }
public static double Max(IEnumerable<double> values) public static double Max(IEnumerable<double> values)
{ {
@ -488,7 +488,7 @@ namespace Nerd_STF.Mathematics
} }
else if (val < best) best = val; else if (val < best) best = val;
} }
return any ? best : 0; return best;
} }
public static double Min(IEnumerable<double> values) public static double Min(IEnumerable<double> values)
{ {
@ -521,6 +521,27 @@ namespace Nerd_STF.Mathematics
return best; return best;
} }
public static int ModAbs(int value, int mod)
{
while (value >= mod) value -= mod;
while (value < 0) value += mod;
return value;
}
public static double ModAbs(double value, double mod)
{
while (value >= mod) value -= mod;
while (value < 0) value += mod;
return value;
}
#if CS11_OR_GREATER
public static T ModAbs<T>(T value, T mod) where T : INumber<T>
{
while (value >= mod) value -= mod;
while (value < T.Zero) value += mod;
return value;
}
#endif
public static BigInteger NprBig(int n, int r) => FactorialBig(n - r + 1, n); public static BigInteger NprBig(int n, int r) => FactorialBig(n - r + 1, n);
public static int Npr(int n, int r) => (int)Factorial(n - r + 1, n); public static int Npr(int n, int r) => (int)Factorial(n - r + 1, n);
@ -623,6 +644,13 @@ namespace Nerd_STF.Mathematics
public static IEquation Round(IEquation equ) => public static IEquation Round(IEquation equ) =>
new Equation((double x) => Round(equ.Get(x))); new Equation((double x) => Round(equ.Get(x)));
#if CS11_OR_GREATER
public static int Sign<T>(T num) where T : INumber<T> =>
num > T.Zero ? 1 : num < T.Zero ? -1 : 0;
#endif
public static int Sign(double num) => num > 0 ? 1 : num < 0 ? -1 : 0;
public static int Sign(int num) => num > 0 ? 1 : num < 0 ? -1 : 0;
public static double Sin(double rad, int terms = 8) public static double Sin(double rad, int terms = 8)
{ {
bool flip = false; bool flip = false;
@ -652,30 +680,46 @@ namespace Nerd_STF.Mathematics
return flip ? result : -result; 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) => public static IEquation Sin(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Sin(inputRad[x], terms)); new Equation((double x) => Sin(inputRad[x], terms));
public static double Cos(double rad, int terms = 8) => public static double Cos(double rad, int terms = 8) =>
Sin(rad + Constants.HalfPi, terms); 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) => public static IEquation Cos(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Cos(inputRad[x], terms)); new Equation((double x) => Cos(inputRad[x], terms));
public static double Tan(double rad, int terms = 8) => public static double Tan(double rad, int terms = 8) =>
Sin(rad + Constants.HalfPi, terms) / Sin(rad, terms); 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) => public static IEquation Tan(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Tan(inputRad[x], terms)); new Equation((double x) => Tan(inputRad[x], terms));
public static double Csc(double rad, int terms = 8) => public static double Csc(double rad, int terms = 8) =>
1 / Sin(rad, terms); 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) => public static IEquation Csc(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Csc(inputRad[x], terms)); new Equation((double x) => Csc(inputRad[x], terms));
public static double Sec(double rad, int terms = 8) => public static double Sec(double rad, int terms = 8) =>
1 / Sin(rad + Constants.HalfPi, terms); 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) => public static IEquation Sec(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Sec(inputRad[x], terms)); new Equation((double x) => Sec(inputRad[x], terms));
public static double Cot(double rad, int terms = 8) => public static double Cot(double rad, int terms = 8) =>
Sin(rad, terms) / Sin(rad + Constants.HalfPi, terms); 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) => public static IEquation Cot(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Cot(inputRad[x], terms)); new Equation((double x) => Cot(inputRad[x], terms));
public static double Sqrt(double num) => 1 / InverseSqrt(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) => public static IEquation Sqrt(IEquation equ) =>
new Equation((double x) => Sqrt(equ.Get(x))); new Equation((double x) => Sqrt(equ.Get(x)));
@ -702,6 +746,12 @@ namespace Nerd_STF.Mathematics
} }
#endif #endif
public static Linear TangentLine(IEquation equ, double x)
{
double slope = equ.Derive()[x];
return new Linear(slope, equ[x] - slope * x);
}
public static Polynomial TaylorSeries(IEquation equ, double x, int terms) public static Polynomial TaylorSeries(IEquation equ, double x, int terms)
{ {
IEquation current = equ; IEquation current = equ;

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

View File

@ -0,0 +1,548 @@
using Nerd_STF.Helpers;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
namespace Nerd_STF.Mathematics.Numbers
{
public readonly struct Fraction : IComparable<Fraction>,
IEquatable<Fraction>,
IFormattable
#if CS11_OR_GREATER
,INumber<Fraction>,
IInterpolable<Fraction>,
IPresets1d<Fraction>,
IRoundable<Fraction>,
ISimpleMathOperations<Fraction>,
ISplittable<Fraction, (int[] nums, int[] dens)>
#endif
{
public static Fraction NaN => new Fraction(0, 0);
public static Fraction NegativeInfinity => new Fraction(-1, 0);
public static Fraction One => new Fraction(1, 1);
public static Fraction PositiveInfinity => new Fraction(1, 0);
public static Fraction Zero => new Fraction(0, 1);
public int Numerator => num;
public int Denominator => den;
public int Whole => num / den;
public Fraction Partial => new Fraction(num % den, den);
public Fraction Simplified
{
get
{
int newNum = num, newDen = den;
List<int> numFactors = new List<int>(MathE.PrimeFactors(MathE.Abs(num))),
denFactors = new List<int>(MathE.PrimeFactors(den));
foreach (int fac in numFactors)
{
if (!denFactors.Contains(fac)) continue;
newNum /= fac;
newDen /= fac;
denFactors.Remove(fac);
}
return new Fraction(newNum, newDen);
}
}
public Fraction Reciprocal => new Fraction(den, num);
private readonly int num, den;
public Fraction(int numerator, int denominator)
{
num = numerator;
den = denominator;
if (den < 0)
{
num = -num;
den = -den;
}
}
public static Fraction Approximate(double number, int iterations = 32)
{
int num, den;
if (number == 0) return Zero;
else if (number == 1) return One;
else if (number % 1 == 0) return new Fraction((int)number, 1);
else if (number < 0)
{
Approximate(-number, iterations, out num, out den, out _);
num = -num;
}
else if (number > 1)
{
Approximate(number % 1, iterations, out num, out den, out _);
int whole = (int)number;
num += whole * den;
}
else Approximate(number, iterations, out num, out den, out _);
return new Fraction(num, den);
}
private static void Approximate(double number, int iterations, out int num, out int den, out double error)
{
// Forget what this algorithm is called. When I remember, I'll put its
// Wikipedia page here.
int minNum = 0, maxNum = 1, newNum = minNum + maxNum,
minDen = 1, maxDen = 1, newDen = minDen + maxDen;
double newVal = (double)newNum / newDen;
for (int i = 0; i < iterations; i++)
{
if (number == newVal) break;
else if (number > newVal)
{
minNum = newNum;
minDen = newDen;
}
else // if (number < newVal)
{
maxNum = newNum;
maxDen = newDen;
}
newNum = minNum + maxNum;
newDen = minDen + maxDen;
newVal = (double)newNum / newDen;
}
num = newNum;
den = newDen;
error = MathE.Abs(newVal - number);
}
public static IEnumerable<Fraction> Egyptian(double number, int maxTerms) =>
Egyptian(Approximate(number, 256), maxTerms);
public static Fraction[] Egyptian(Fraction number, int maxTerms)
{
List<Fraction> parts = new List<Fraction>();
int terms = 0;
foreach (Fraction part in EgyptianE(number))
{
parts.Add(part);
terms++;
if (terms >= maxTerms) break;
}
return parts.ToArray();
}
public static IEnumerable<Fraction> EgyptianE(double number) =>
EgyptianE(Approximate(number, 256));
public static IEnumerable<Fraction> EgyptianE(Fraction number)
{
int wholes = number.Whole;
if (wholes > 0) yield return new Fraction(wholes, 1);
number = number.Partial;
int newDen = 2, curNum = number.num, curDen = number.den;
while (curNum > 0 && newDen <= curDen)
{
if (curNum * newDen >= curDen)
{
yield return new Fraction(1, newDen);
curNum = curNum * newDen - curDen;
curDen *= newDen;
}
else newDen++;
}
}
#if CS8_OR_GREATER
public static Fraction Parse(string? str) =>
#else
public static Fraction Parse(string str) =>
#endif
str is null ? NaN : Parse(str.AsSpan());
public static Fraction Parse(ReadOnlySpan<char> str)
{
if (str.Length == 0) return NaN;
ReadOnlySpan<char> numSpan, denSpan;
str = str.Trim();
char first = str[0];
if (first == '\\')
{
// TeX format.
if (str.Length >= 5 && str.StartsWith("\\frac".AsSpan())) str = str.Slice(5);
else if (str.Length >= 6 && str.Slice(2, 4).Equals("frac".AsSpan(), StringComparison.Ordinal)) str = str.Slice(6); // Allows for \sfrac or things like that.
else goto _fail;
if (!str.StartsWith("{".AsSpan()) || !str.EndsWith("}".AsSpan())) goto _fail;
int separator = str.IndexOf(',');
if (separator == -1 || separator == 1 || separator == str.Length - 2) goto _fail;
numSpan = str.Slice(1, separator - 1);
denSpan = str.Slice(separator + 1, str.Length - separator - 2);
}
else
{
// Standard fraction format.
char[] allowedSeparators = new char[] { '/', ':' };
int separator = -1;
foreach (char c in allowedSeparators)
{
int newSep = str.IndexOf(c);
if (newSep == -1) continue;
else
{
if (separator != -1) goto _fail; // More than one separator.
else separator = newSep;
}
}
if (separator == 0 || separator == str.Length - 1) goto _fail;
numSpan = str.Slice(0, separator);
denSpan = str.Slice(separator + 1, str.Length - separator - 1);
}
int top = ParseHelper.ParseDoubleWholeDecimals(numSpan, out int topPlaces),
bot = ParseHelper.ParseDoubleWholeDecimals(denSpan, out int botPlaces);
int topDen = 1, botDen = 1;
for (int i = 0; i < topPlaces; i++) topDen *= 10;
for (int i = 0; i < botPlaces; i++) botDen *= 10;
Fraction topF = new Fraction(top, topDen), botF = new Fraction(bot, botDen);
return topF / botF;
_fail:
throw new FormatException("Cannot parse fraction from span.");
}
#if CS8_OR_GREATER
public static bool TryParse(string? str, out Fraction frac) =>
#else
public static bool TryParse(string str, out Fraction frac) =>
#endif
TryParse(str.AsSpan(), out frac);
public static bool TryParse(ReadOnlySpan<char> str, out Fraction frac)
{
try
{
frac = Parse(str);
return true;
}
catch
{
frac = NaN;
return false;
}
}
public static Fraction Abs(Fraction val) => new Fraction(MathE.Abs(val.num), val.den);
public static Fraction Ceiling(Fraction val)
{
int newNum = val.num;
if (val.num % val.den != 0)
{
if (val.num > 0) newNum += val.den - (val.num % val.den);
else newNum -= val.num % val.den;
}
return new Fraction(newNum, val.den);
}
public static Fraction Clamp(Fraction val, Fraction min, Fraction max)
{
int lcm = MathE.Lcm(val.den, min.den, max.den);
int valFac = val.den / lcm, minFac = min.den / lcm, maxFac = max.den / lcm;
int trueVal = val.num * valFac, trueMin = min.num * minFac, trueMax = max.num * maxFac;
if (trueVal > trueMax) return max;
else if (trueVal < trueMin) return min;
else return val;
}
public static Fraction Floor(Fraction val)
{
int newNum = val.num;
if (val.num % val.den != 0)
{
if (val.num > 0) newNum -= val.num % val.den;
else newNum -= val.den + (val.num % val.den);
}
return new Fraction(newNum, val.den);
}
public static Fraction Lerp(Fraction a, Fraction b, double t, bool clamp = true, bool fast = false)
{
if (fast)
{
int aNum = a.num * b.den, bNum = b.num * a.den, cDen = a.den * b.den;
int cNum = (int)(aNum + t * (bNum - aNum));
return new Fraction(cNum, cDen);
}
else return Approximate(MathE.Lerp(a, b, t, clamp), 128);
}
#if CS11_OR_GREATER
static Fraction IInterpolable<Fraction>.Lerp(Fraction a, Fraction b, double t, bool clamp) =>
Lerp(a, b, t, clamp, false);
#endif
public static Fraction Product(IEnumerable<Fraction> vals)
{
bool any = false;
int resultNum = 1, resultDen = 1;
foreach (Fraction frac in vals)
{
any = true;
resultNum *= frac.num;
resultDen *= frac.den;
}
return any ? new Fraction(resultNum, resultDen) : Zero;
}
public static Fraction Round(Fraction val)
{
int half = val.den / 2;
int newNum = val.num;
if (val.num > 0)
{
if (val.num % val.den > half) newNum += val.den - (val.num % val.den);
else newNum -= val.num % val.den;
}
else
{
if (-val.num % val.den > half) newNum -= val.den + (val.num % val.den);
else newNum -= val.num % val.den;
}
return new Fraction(newNum, val.den);
}
public static Fraction Sum(IEnumerable<Fraction> vals)
{
bool any = false;
Fraction result = Zero;
foreach (Fraction frac in vals)
{
any = true;
result += frac;
}
return any ? result : NaN;
}
#if CS8_OR_GREATER
private static bool TryConvertFrom(object? value, out Fraction result)
#else
private static bool TryConvertFrom(object value, out Fraction result)
#endif
{
if (value is null)
{
result = NaN;
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
else if (value is int valueInt32) result = new Fraction(valueInt32, 1);
else
{
result = NaN;
return false;
}
return true;
}
public static bool IsCanonical(Fraction val)
{
IEnumerable<int> factorsNum = MathE.PrimeFactorsE(MathE.Abs(val.num)),
factorsDen = MathE.PrimeFactorsE(val.den),
shared = factorsNum.Where(x => factorsDen.Contains(x));
return shared.Any();
}
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;
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;
public static bool IsNegative(Fraction val) => val.num < 0;
public static bool IsNegativeInfinity(Fraction val) => val.den == 0 && val.num < 0;
public static bool IsNormal(Fraction val) => val.den != 0 && val.num != 0;
public static bool IsOddInteger(Fraction val) =>
val.num % val.den == 0 && val.num / val.den % 2 == 1;
public static bool IsPositive(Fraction val) => val.num > 0;
public static bool IsPositiveInfinity(Fraction val) => val.den == 0 && val.num > 0;
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 MinMagnitude(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);
static bool INumberBase<Fraction>.TryParse(ReadOnlySpan<char> str, NumberStyles style, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac);
static Fraction IParsable<Fraction>.Parse(string? str, IFormatProvider? provider) => Parse(str);
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
else if (typeof(T) == typeof(int)) tempValue = (int)frac.GetValue();
else
{
value = default!;
return false;
}
value = (T)tempValue;
return true;
}
static bool INumberBase<Fraction>.TryConvertFromChecked<TOther>(TOther value, out Fraction result) => TryConvertFrom(value, out result);
static bool INumberBase<Fraction>.TryConvertFromSaturating<TOther>(TOther value, out Fraction result) => TryConvertFrom(value, out result);
static bool INumberBase<Fraction>.TryConvertFromTruncating<TOther>(TOther value, out Fraction result) => TryConvertFrom(value, out result);
static bool INumberBase<Fraction>.TryConvertToChecked<TOther>(Fraction value, out TOther result) => TryConvertTo(value, out result);
static bool INumberBase<Fraction>.TryConvertToSaturating<TOther>(Fraction value, out TOther result) => TryConvertTo(value, out result);
static bool INumberBase<Fraction>.TryConvertToTruncating<TOther>(Fraction value, out TOther result) => TryConvertTo(value, out result);
#endif
public static (int[] nums, int[] dens) SplitArray(IEnumerable<Fraction> vals)
{
int count = vals.Count();
int[] nums = new int[count], dens = new int[count];
int index = 0;
foreach (Fraction val in vals)
{
nums[index] = val.num;
dens[index] = val.den;
}
return (nums, dens);
}
public double GetValue() => (double)num / den;
#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 Fraction otherFrac) return CompareTo(otherFrac);
else if (TryConvertFrom(other, out Fraction otherConvert)) return CompareTo(otherConvert);
else return -1;
}
public int CompareTo(Fraction other) => (num * other.den).CompareTo(other.num * den);
public bool Equals(Fraction other) => num * other.den == other.num * den;
#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 Fraction otherFrac) return Equals(otherFrac);
else if (TryConvertFrom(other, out Fraction otherConvert)) return Equals(otherConvert);
else return false;
}
public override int GetHashCode() =>
(int)((uint)num.GetHashCode() & 0xFFFF0000 |
(uint)den.GetHashCode() & 0x0000FFFF);
#if CS8_OR_GREATER
public override string ToString() => ToString(null, null);
public string ToString(string? format) => ToString(format, null);
public string ToString(IFormatProvider? provider) => ToString(null, provider);
public string ToString(string? format, IFormatProvider? provider) => $"{num.ToString(format)}/{den.ToString(format)}";
#else
public override string ToString() => ToString(null, null);
public string ToString(string format) => ToString(format, null);
public string ToString(IFormatProvider provider) => ToString(null, provider);
public string ToString(string format, IFormatProvider provider) => $"{num.ToString(format)}/{den.ToString(format)}";
#endif
#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;
}
#endif
public static Fraction operator +(Fraction a) => a;
public static Fraction operator +(Fraction a, Fraction b)
{
int lcm = MathE.Lcm(a.den, b.den);
int facA = lcm / a.den, facB = lcm / b.den;
return new Fraction(a.num * facA + b.num * facB, lcm);
}
public static Fraction operator +(Fraction a, int b) => new Fraction(a.num + b * a.den, a.den);
public static Fraction operator +(Fraction a, double b) => a * Approximate(b);
public static Fraction operator ++(Fraction a) => new Fraction(a.num + a.den, a.den);
public static Fraction operator -(Fraction a) => new Fraction(-a.num, a.den);
public static Fraction operator -(Fraction a, Fraction b)
{
int lcm = MathE.Lcm(a.den, b.den);
int facA = lcm / a.den, facB = lcm / b.den;
return new Fraction(a.num * facA - b.num * facB, lcm);
}
public static Fraction operator -(Fraction a, int b) => new Fraction(a.num - b * a.den, a.den);
public static Fraction operator -(Fraction a, double b) => a * Approximate(b);
public static Fraction operator --(Fraction a) => new Fraction(a.num - a.den, a.den);
public static Fraction operator *(Fraction a, Fraction b) => new Fraction(a.num * b.num, a.den * b.den);
public static Fraction operator *(Fraction a, int b) => new Fraction(a.num * b, a.den);
public static Fraction operator *(Fraction a, double b) => a * Approximate(b);
public static Fraction operator /(Fraction a, Fraction b) => new Fraction(a.num * b.den, a.den * b.num);
public static Fraction operator /(Fraction a, int b) => new Fraction(a.num, a.den * b);
public static Fraction operator /(Fraction a, double b) => a / Approximate(b);
public static Fraction operator %(Fraction a, Fraction b)
{
// c = a / b
// f = b * mod(c, 1)
int cNum = a.num * b.den, cDen = a.den * b.num;
if (cDen < 0)
{
cNum = -cNum;
cDen = -cDen;
}
cNum = MathE.ModAbs(cNum, cDen); // Fractional portion.
return new Fraction(b.num * cNum, b.den * cDen);
}
public static Fraction operator %(Fraction a, int b)
{
// c = a / b
// f = b * mod(c, 1)
int cNum = a.num, cDen = a.den * b;
if (cDen < 0)
{
cNum = -cNum;
cDen = -cDen;
}
cNum = MathE.ModAbs(cNum, cDen); // Fractional portion.
return new Fraction(b * cNum, cDen);
}
public static Fraction operator %(Fraction a, double b) => a % Approximate(b);
public static Fraction operator ^(Fraction a, Fraction b) => new Fraction(a.num + b.num, a.den + b.den);
public static Fraction operator ~(Fraction a) => a.Reciprocal;
public static bool operator ==(Fraction a, Fraction b) => a.Equals(b);
public static bool operator !=(Fraction a, Fraction b) => !a.Equals(b);
public static bool operator >(Fraction a, Fraction b) => a.CompareTo(b) > 0;
public static bool operator <(Fraction a, Fraction b) => a.CompareTo(b) < 0;
public static bool operator >=(Fraction a, Fraction b) => a.CompareTo(b) >= 0;
public static bool operator <=(Fraction a, Fraction b) => a.CompareTo(b) <= 0;
// TODO: Comparisons with a double on the right (maybe).
public static implicit operator double(Fraction frac) => frac.GetValue();
public static explicit operator Fraction(double num) => Approximate(num);
}
}

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

View File

@ -1,25 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<!-- General stuff -->
<PropertyGroup> <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> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly> <ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<DebugType>embedded</DebugType> <DebugType>portable</DebugType>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<CheckNotRecommendedTargetFramework>false</CheckNotRecommendedTargetFramework>
</PropertyGroup>
<!-- NuGet package customization. -->
<PropertyGroup>
<Title>Nerd_STF</Title> <Title>Nerd_STF</Title>
<Version>3.0.0-beta1</Version> <Version>3.0.0</Version>
<Authors>That_One_Nerd</Authors> <Authors>That_One_Nerd</Authors>
<Description>A general-purpose mathematics library for C#.</Description> <Description>A general-purpose mathematics library for C#.</Description>
<PackageProjectUrl>https://github.com/That-One-Nerd/Nerd_STF</PackageProjectUrl> <PackageProjectUrl>https://github.com/That-One-Nerd/Nerd_STF</PackageProjectUrl>
<PackageIcon>Logo Square.png</PackageIcon> <PackageIcon>Logo Square.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/That-One-Nerd/Nerd_STF</RepositoryUrl> <RepositoryUrl>https://github.com/That-One-Nerd/Nerd_STF</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<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> <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>
<PackageReleaseNotes># Nerd_STF v3.0-beta1
<!-- Sorry this is stupidly long, wish I could have linked a markdown file instead. -->
<PackageReleaseNotes># Nerd_STF Version 3.0
Hi! Pretty much nothing has remained the same from version 2. There are plenty of breaking changes, and the betas will have plenty of missing features from 2.4.1. The betas will continue until every feature from 2.4.1 has been added to 3.0 or scrapped. It's time to get this thing out of beta.
In the mean time, here's what's new. 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 ## More Compatibility
@ -120,81 +137,181 @@ I've tried to use this library when working with Windows Forms a few times. Prob
**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!** **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 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);
IEnumerable&lt;double&gt; vals1 = wxyz["xy"]; // Yields [2, 3]
Float2 vals2 = wxyz["xy"]; // Not allowed!
```
And that kind of sucked. So I created the `ListTuple&lt;T&gt;` 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&lt;T&gt;`, thus allowing conversions to the double and int groups indirectly. Now, all combination indexers return a `ListTuple` instead of an `IEnumerable`.
Under the hood, the `ListTuple` actually uses an array, but you get the idea.
```csharp
Float4 wxyz = (1, 2, 3, 4);
ListTuple&lt;double&gt; vals1 = wxyz["xy"]; // Yields (2, 3)
Float2 vals2 = vals1; // Yields (2, 3)
IEnumerable&lt;double&gt; 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 &lt;- x, y &lt;- y
Float2 wz = wxyz["wz"]; // x &lt;- w, y &lt;- 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 Best of All, Matrices
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 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&lt;T&gt;` and `Fill2d&lt;T&gt;` are back!
I thought the `Fill&lt;T&gt;` 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&lt;TSelf&gt;` 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&lt;ColorRGB&gt; palette = new(8);
// palette[3] is currently set to black.
MethodB(palette[3]);
// palette[3] is now set to blue.
}
void MethodB(IndexedColor&lt;ColorRGB&gt; color)
{
color.Color() = ColorRGB.Blue;
// You could also:
ref ColorRGB val = ref color.Color();
val = ColorRGB.Blue;
}
```
--- ---
Anyway, that's most of the big changes! I don't know if I'll do the full changelog like I have before. It takes a really long time to compile for large updates. We'll see. Thanks for checking out the update and I hope you use it well (or wait for the release version, that's fine too)!</PackageReleaseNotes> Anyway, that's a lot of stuff. It's a big update. You should download it!
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<IncludeSymbols>True</IncludeSymbols> 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!
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
Enjoy the full release!</PackageReleaseNotes>
</PropertyGroup>
<!-- ItemGroup customization based on framework. -->
<!-- Mostly used to reference system packages that are not included in this version of .NET Standard. -->
<!-- TODO: Maybe this isn't good practice, and we should define environment variables for specific features (tuples, drawing, etc) instead? -->
<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 vulnerabilities. -->
<PackageReference Include="System.Memory" Version="4.5.5" /> <!-- Newer versions not supported. -->
<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 vulnerabilities. -->
<PackageReference Include="System.Memory" Version="4.5.5" /> <!-- Newer versions not supported. -->
<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)'=='netstandard1.1'">
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.1'"> <PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
<DefineConstants>$(DefineConstants);CS8_OR_GREATER</DefineConstants> <DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER</DefineConstants>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </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>
<PropertyGroup Condition="'$(TargetFramework)'=='netcoreapp3.0'"> <PropertyGroup Condition="'$(TargetFramework)'=='netcoreapp3.0'">
<DefineConstants>$(DefineConstants);CS8_OR_GREATER</DefineConstants> <DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER</DefineConstants>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net5.0'"> <PropertyGroup Condition="'$(TargetFramework)'=='net5.0'">
<DefineConstants>$(DefineConstants);CS8_OR_GREATER;CS9_OR_GREATER</DefineConstants> <DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER;CS9_OR_GREATER</DefineConstants>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net7.0'"> <PropertyGroup Condition="'$(TargetFramework)'=='net7.0'">
<DefineConstants>$(DefineConstants);CS10_OR_GREATER;CS11_OR_GREATER;CS8_OR_GREATER;CS9_OR_GREATER</DefineConstants> <DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER;CS9_OR_GREATER;CS10_OR_GREATER;CS11_OR_GREATER</DefineConstants>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard1.1|AnyCPU'"> <!-- Pack extra stuff into the NuGet package. -->
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard1.3|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.1|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netcoreapp3.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net5.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net7.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard1.1|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard1.3|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.1|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netcoreapp3.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net5.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="..\Extras\Logo Square.png"> <None Include="..\Extras\Logo Square.png">
@ -205,11 +322,14 @@ Anyway, that's most of the big changes! I don't know if I'll do the full changel
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath>\</PackagePath> <PackagePath>\</PackagePath>
</None> </None>
</ItemGroup> <None Include="..\Changelog.md">
<Pack>True</Pack>
<ItemGroup> <PackagePath>\</PackagePath>
<PackageReference Include="System.Drawing.Primitives" Version="4.3.0" /> </None>
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <None Include="..\LICENSE.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup> </ItemGroup>
</Project> </Project>