Compare commits
No commits in common. "v3.0" and "v3.0.0-beta1" have entirely different histories.
v3.0
...
v3.0.0-bet
119
Changelog.md
119
Changelog.md
@ -1,53 +1,108 @@
|
||||
# Nerd_STF v3.0-beta3
|
||||
# Nerd_STF v3.0-beta1
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Here's what's new:
|
||||
In the mean time, here's what's new.
|
||||
|
||||
## `Fill<T>` and `Fill2d<T>` are back!
|
||||
## More Compatibility
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Slight Matrix Constructor Change
|
||||
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.
|
||||
|
||||
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.
|
||||
## Committed to Doubles
|
||||
|
||||
## And Best of All: Colors!
|
||||
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.
|
||||
|
||||
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.
|
||||
But all math functions are now using doubles (with a few exceptions).
|
||||
|
||||
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.
|
||||
## 'w' Goes in Front Now
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
This means though that casting a `Float3` to a `Float4` will put the extra zero at the start, not the end (because `x` -> `x` in the cast).
|
||||
```csharp
|
||||
Float3 xyz = (5, 6, 7);
|
||||
Float4 wxyz = xyz; // Gives (0, 5, 6, 7)
|
||||
```
|
||||
This also means that truncating a `Float4` removes the front `w` first, giving some odd results.
|
||||
```csharp
|
||||
Float2 xy = (10, 9);
|
||||
Float4 wxyz = xy; // Gives (0, 10, 9, 0)
|
||||
```
|
||||
```csharp
|
||||
Float4 wxyz = (9, 8, 7, 6);
|
||||
Float2 xy = (Float2)wxyz; // Must be explicitly stated. Yields (8, 7)
|
||||
```
|
||||
|
||||
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.
|
||||
But `x` always goes to `x` when casting between groups, same with the other variables. Hopefully that'll make more sense.
|
||||
|
||||
## Combination Indexers
|
||||
|
||||
One thing I've always been envious of was HLSL's ability to easily make a subset of a group.
|
||||
```c++
|
||||
float3 group = float3(1, 2, 3);
|
||||
float2 part = group.yz; // Like this.
|
||||
```
|
||||
And I had a crude version of this in Nerd_STF before, with properties for `XY`, `YZW`, and stuff like that. But you couldn't do things out of order (for example, you could never do `.ZY`). Also, the naming scheme would not make very much sense. `x` was always the first item Now, you can do it with an indexer.
|
||||
|
||||
```csharp
|
||||
void MethodA()
|
||||
{
|
||||
ColorPalette<ColorRGB> palette = new(8);
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
IEnumerable<double> zyx = wxyz["zyx"]; // Yields [ 4, 3, 2 ]
|
||||
```
|
||||
|
||||
// palette[3] is currently set to black.
|
||||
MethodB(palette[3]);
|
||||
// palette[3] is now set to blue.
|
||||
}
|
||||
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.
|
||||
|
||||
void MethodB(IndexedColor<ColorRGB> color)
|
||||
{
|
||||
color.Color() = ColorRGB.Blue;
|
||||
```csharp
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
wxyz["xy"] = [ 9, 8 ]; // Yields (9, 8, 3, 4)
|
||||
```
|
||||
|
||||
// You could also:
|
||||
ref ColorRGB val = ref color.Color();
|
||||
val = ColorRGB.Blue;
|
||||
You can also have duplicates. Why you would want duplicates is beyond me. And the order can be whatever you'd like.
|
||||
```csharp
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
IEnumerable<double> nums = wxyz["wyyxzzwy"]; // Yields [ 1, 3, 3, 2, 4, 4, 1, 3 ]
|
||||
```
|
||||
|
||||
## Better Equations
|
||||
|
||||
The previous equation system was just a delegate to a method. While it worked for unoptimized things, it won't automatically give precise results. So that system has been overhauled.
|
||||
|
||||
Now, every equation derives from the `IEquation` interface, which defines a few operators (most importantly the `Get(double)` method, which is intended to evaluate the equation at the given input). And there are multiple types. There's the base `Equation` type that replicates the method delegate it used to be, but there are also now `Polynomial` equations which specialize in... uh... polynomials, including `Quadratic` and `Linear` along with the dynamic `Polynomial` type.
|
||||
|
||||
The indexer is equivalent to calling the `Get(double)` method.
|
||||
|
||||
Creating your own is easy, simply derive from the interface and implement the methods required. You should never throw an exception if the two equations you are adding (or multiplying or whatever) are not the same type. If they cannot be combined in a nice way, you should default to the delegate-based approach. Here is an example:
|
||||
```csharp
|
||||
public IEquation Add(IEquation other) {
|
||||
if (other is YourEquation yourEqu) {
|
||||
// Properly add your two equations.
|
||||
} else {
|
||||
// Unknown other equation type, do a basic addition system.
|
||||
return new Equation(x => Get(x) + other.Get(x));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Anyway, that's all I've got for now. I'm not sure what will be next up, but here's what's left to do:
|
||||
- Complex numbers and quaternions.
|
||||
- More color types and formats.
|
||||
- Bit-offset compatible streams.
|
||||
- Fix bugs/inconveniences I've noted.
|
||||
And in practice, you should avoid referring to a general equation by its type. Go by the interface operators instead.
|
||||
```csharp
|
||||
double Fun(double x) => 0.5 * MathE.Sin(x);
|
||||
Equation a = (Equation)Fun; // The traditional delegate approach from previous versions.
|
||||
Polynomial b = new Polynomial(1, 5, 4); // x^2 + 5x + 4
|
||||
|
||||
I think the Image type will be completely reworked and might be what version 3.1 is.
|
||||
IEquation c = a.Add(b).Multiply(2); // Result is technically an `Equation`, but we should not cast here.
|
||||
```
|
||||
|
||||
## Renamed the `Mathf` class.
|
||||
|
||||
I chose that name because I thought Unity did it well, but I also intend for this project to be compatible with Unity. So I've renamed it to `MathE`. I'm still iffy on that name. I'll commit to one before this project goes out of beta, but it might change until then. Other ideas I'm considering are `Mathe` and `Math2`. Feel free to give your input!
|
||||
|
||||
## Support for `System.Drawing` types.
|
||||
|
||||
I've tried to use this library when working with Windows Forms a few times. Problem is, it sucks having to manually set the variables from `Point` and `Size`. So Nerd_STF 3.0 now does that for you, with implicit casts to and from both, along with their float variations.
|
||||
|
||||
**It's worth mentioning that `Float2` is a double group, while `PointF` is a float group. Data *will* be lost slightly when implicitly casting. Watch out!**
|
||||
|
||||
---
|
||||
|
||||
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)!
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
namespace Nerd_STF
|
||||
{
|
||||
public delegate T Fill<T>(int index);
|
||||
public delegate T Fill2d<T>(int x, int y);
|
||||
}
|
||||
@ -5,9 +5,7 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Style", "IDE0066:Use 'switch' expression", Justification = "Only available for C#8+.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "Only available for C#9+")]
|
||||
[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Only available for C#9+")]
|
||||
[assembly: SuppressMessage("Style", "IDE0251:Make member 'readonly'", Justification = "Only available for C#8+. Also, what does applying 'readonly' to a method even do?")]
|
||||
[assembly: SuppressMessage("Style", "IDE0057:Use range operator", Justification = "Not supported in .NET Standard 1.1 and 1.3.")]
|
||||
[assembly: SuppressMessage("Performance", "CA1846:Prefer 'AsSpan' over 'Substring'", Justification = "Not supported in .NET Standard 1.1 and 1.3.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0066:Use 'switch' expression", Justification = "Only available for C#8+.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "Only available for C#9+")]
|
||||
[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Only available for C#9+")]
|
||||
[assembly: SuppressMessage("Style", "IDE0251:Make member 'readonly'", Justification = "Only available for C#8+. Also, what does applying 'readonly' to a method even do?")]
|
||||
|
||||
@ -1,365 +0,0 @@
|
||||
using Nerd_STF.Mathematics;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
public struct ColorCMYK : IColor<ColorCMYK>,
|
||||
IEnumerable<double>
|
||||
#if CS11_OR_GREATER
|
||||
,IFromTuple<ColorCMYK, (double, double, double, double)>,
|
||||
IFromTuple<ColorCMYK, (double, double, double, double, double)>,
|
||||
ISplittable<ColorCMYK, (double[] Cs, double[] Ms, double[] Ys, double[] Ks, double[] As)>
|
||||
#endif
|
||||
{
|
||||
public static int ChannelCount => 5;
|
||||
|
||||
public static ColorCMYK Black => new ColorCMYK(0 , 0 , 0 , 1 , 1);
|
||||
public static ColorCMYK Blue => new ColorCMYK(1 , 1 , 0 , 0 , 1);
|
||||
public static ColorCMYK Clear => new ColorCMYK(0 , 0 , 0 , 0 , 0);
|
||||
public static ColorCMYK Cyan => new ColorCMYK(1 , 0 , 0 , 0 , 1);
|
||||
public static ColorCMYK Gray => new ColorCMYK(0 , 0 , 0 , 0.5, 1);
|
||||
public static ColorCMYK Green => new ColorCMYK(1 , 0 , 1 , 0 , 1);
|
||||
public static ColorCMYK Magenta => new ColorCMYK(0 , 1 , 0 , 0 , 1);
|
||||
public static ColorCMYK Orange => new ColorCMYK(0 , 0.5, 1 , 0 , 1);
|
||||
public static ColorCMYK Purple => new ColorCMYK(0.5, 1 , 0 , 0 , 1);
|
||||
public static ColorCMYK Red => new ColorCMYK(0 , 1 , 1 , 0 , 1);
|
||||
public static ColorCMYK White => new ColorCMYK(0 , 0 , 0 , 0 , 1);
|
||||
public static ColorCMYK Yellow => new ColorCMYK(0 , 0 , 1 , 0 , 1);
|
||||
|
||||
public double c, m, y, k, a;
|
||||
|
||||
public ColorCMYK(double cyan, double magenta, double yellow, double black)
|
||||
{
|
||||
c = cyan;
|
||||
m = magenta;
|
||||
y = yellow;
|
||||
k = black;
|
||||
a = 1;
|
||||
}
|
||||
public ColorCMYK(double cyan, double magenta, double yellow, double black, double alpha)
|
||||
{
|
||||
c = cyan;
|
||||
m = magenta;
|
||||
y = yellow;
|
||||
k = black;
|
||||
a = alpha;
|
||||
}
|
||||
public ColorCMYK(IEnumerable<double> nums)
|
||||
{
|
||||
c = 0;
|
||||
m = 0;
|
||||
y = 0;
|
||||
k = 0;
|
||||
a = 1;
|
||||
|
||||
int index = 0;
|
||||
foreach (double item in nums)
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index == 5) break;
|
||||
}
|
||||
}
|
||||
public ColorCMYK(Fill<double> fill)
|
||||
{
|
||||
c = fill(0);
|
||||
m = fill(1);
|
||||
y = fill(2);
|
||||
k = fill(3);
|
||||
a = fill(4);
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return c;
|
||||
case 1: return m;
|
||||
case 2: return y;
|
||||
case 3: return k;
|
||||
case 4: return a;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: c = value; break;
|
||||
case 1: m = value; break;
|
||||
case 2: y = value; break;
|
||||
case 3: k = value; break;
|
||||
case 4: a = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
public ListTuple<double> this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
double[] items = new double[key.Length];
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
{
|
||||
char c = key[i];
|
||||
switch (c)
|
||||
{
|
||||
case 'c': items[i] = c; break;
|
||||
case 'm': items[i] = m; break;
|
||||
case 'y': items[i] = y; break;
|
||||
case 'k': items[i] = k; break;
|
||||
case 'a': items[i] = a; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
return new ListTuple<double>(items);
|
||||
}
|
||||
set
|
||||
{
|
||||
IEnumerator<double> stepper = value.GetEnumerator();
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
{
|
||||
char c = key[i];
|
||||
stepper.MoveNext();
|
||||
switch (c)
|
||||
{
|
||||
case 'c': this.c = stepper.Current; break;
|
||||
case 'm': m = stepper.Current; break;
|
||||
case 'y': y = stepper.Current; break;
|
||||
case 'k': k = stepper.Current; break;
|
||||
case 'a': a = stepper.Current; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ColorCMYK Average(double gamma, IEnumerable<ColorCMYK> colors)
|
||||
{
|
||||
double avgC = 0, avgM = 0, avgY = 0, avgK = 0, avgA = 0;
|
||||
int count = 0;
|
||||
foreach (ColorCMYK color in colors)
|
||||
{
|
||||
double correctC = MathE.Pow(color.c, gamma),
|
||||
correctM = MathE.Pow(color.m, gamma),
|
||||
correctY = MathE.Pow(color.y, gamma),
|
||||
correctK = MathE.Pow(color.k, gamma);
|
||||
// Gamma doesn't apply to the alpha channel.
|
||||
|
||||
avgC += correctC;
|
||||
avgM += correctM;
|
||||
avgY += correctY;
|
||||
avgK += correctK;
|
||||
avgA += color.a;
|
||||
count++;
|
||||
}
|
||||
avgC /= count;
|
||||
avgM /= count;
|
||||
avgY /= count;
|
||||
avgK /= count;
|
||||
avgA /= count;
|
||||
double invGamma = 1 / gamma;
|
||||
return new ColorCMYK(MathE.Pow(avgC, invGamma),
|
||||
MathE.Pow(avgM, invGamma),
|
||||
MathE.Pow(avgY, invGamma),
|
||||
MathE.Pow(avgK, invGamma),
|
||||
avgA);
|
||||
}
|
||||
#if CS11_OR_GREATER
|
||||
static ColorCMYK IColor<ColorCMYK>.Average(IEnumerable<ColorCMYK> colors) => Average(1, colors);
|
||||
#endif
|
||||
public static ColorCMYK Clamp(ColorCMYK color, ColorCMYK min, ColorCMYK max) =>
|
||||
new ColorCMYK(MathE.Clamp(color.c, min.c, max.c),
|
||||
MathE.Clamp(color.m, min.m, max.m),
|
||||
MathE.Clamp(color.y, min.y, max.y),
|
||||
MathE.Clamp(color.k, min.k, max.k),
|
||||
MathE.Clamp(color.a, min.a, max.a));
|
||||
public static ColorCMYK Lerp(double gamma, ColorCMYK a, ColorCMYK b, double t, bool clamp = true)
|
||||
{
|
||||
double aCorrectedC = MathE.Pow(a.c, gamma), bCorrectedC = MathE.Pow(b.c, gamma),
|
||||
aCorrectedM = MathE.Pow(a.m, gamma), bCorrectedM = MathE.Pow(b.m, gamma),
|
||||
aCorrectedY = MathE.Pow(a.y, gamma), bCorrectedY = MathE.Pow(b.y, gamma),
|
||||
aCorrectedK = MathE.Pow(a.k, gamma), bCorrectedK = MathE.Pow(b.k, gamma);
|
||||
|
||||
double newC = MathE.Lerp(aCorrectedC, bCorrectedC, t, clamp),
|
||||
newM = MathE.Lerp(aCorrectedM, bCorrectedM, t, clamp),
|
||||
newY = MathE.Lerp(aCorrectedY, bCorrectedY, t, clamp),
|
||||
newK = MathE.Lerp(aCorrectedK, bCorrectedK, t, clamp),
|
||||
newA = MathE.Lerp(a.a, b.a, t, clamp);
|
||||
|
||||
double invGamma = 1 / gamma;
|
||||
return new ColorCMYK(MathE.Pow(newC, invGamma),
|
||||
MathE.Pow(newM, invGamma),
|
||||
MathE.Pow(newY, invGamma),
|
||||
MathE.Pow(newK, invGamma),
|
||||
newA);
|
||||
}
|
||||
#if CS11_OR_GREATER
|
||||
static ColorCMYK IInterpolable<ColorCMYK>.Lerp(ColorCMYK a, ColorCMYK b, double t, bool clamp) => Lerp(1, a, b, t, clamp);
|
||||
#endif
|
||||
public static ColorCMYK Product(IEnumerable<ColorCMYK> colors)
|
||||
{
|
||||
bool any = false;
|
||||
ColorCMYK result = new ColorCMYK(1, 1, 1, 1, 1);
|
||||
foreach (ColorCMYK color in colors)
|
||||
{
|
||||
any = true;
|
||||
result *= color;
|
||||
}
|
||||
return any ? result : Black;
|
||||
}
|
||||
public static ColorCMYK Sum(IEnumerable<ColorCMYK> colors)
|
||||
{
|
||||
bool any = false;
|
||||
ColorCMYK result = new ColorCMYK(0, 0, 0, 0);
|
||||
foreach (ColorCMYK color in colors)
|
||||
{
|
||||
any = true;
|
||||
result += color;
|
||||
}
|
||||
return any ? result : Black;
|
||||
}
|
||||
public static (double[] Cs, double[] Ms, double[] Ys, double[] Ks, double[] As) SplitArray(IEnumerable<ColorCMYK> colors)
|
||||
{
|
||||
int count = colors.Count();
|
||||
double[] Cs = new double[count], Ms = new double[count], Ys = new double[count], Ks = new double[count], As = new double[count];
|
||||
int index = 0;
|
||||
foreach (ColorCMYK c in colors)
|
||||
{
|
||||
Cs[index] = c.c;
|
||||
Ms[index] = c.m;
|
||||
Ys[index] = c.y;
|
||||
Ks[index] = c.k;
|
||||
As[index] = c.a;
|
||||
index++;
|
||||
}
|
||||
return (Cs, Ms, Ys, Ks, As);
|
||||
}
|
||||
|
||||
public Dictionary<ColorChannel, double> GetChannels() => new Dictionary<ColorChannel, double>()
|
||||
{
|
||||
{ ColorChannel.Cyan, c },
|
||||
{ ColorChannel.Magenta, m },
|
||||
{ ColorChannel.Yellow, y },
|
||||
{ ColorChannel.Key, k },
|
||||
{ ColorChannel.Alpha, a }
|
||||
};
|
||||
|
||||
public TColor AsColor<TColor>() where TColor : struct, IColor<TColor>
|
||||
{
|
||||
Type type = typeof(TColor);
|
||||
if (type == typeof(ColorRGB)) return (TColor)(object)AsRgb();
|
||||
else if (type == typeof(ColorHSV)) return (TColor)(object)AsHsv();
|
||||
else if (type == typeof(ColorCMYK)) return (TColor)(object)this;
|
||||
else throw new InvalidCastException();
|
||||
}
|
||||
public ColorRGB AsRgb()
|
||||
{
|
||||
// Thanks https://www.rapidtables.com/convert/color/cmyk-to-rgb.html
|
||||
double diffK = 1 - k;
|
||||
return new ColorRGB((1 - c) * diffK,
|
||||
(1 - m) * diffK,
|
||||
(1 - y) * diffK);
|
||||
}
|
||||
public ColorHSV AsHsv()
|
||||
{
|
||||
// Inlined version of AsRgb().AsHsv()
|
||||
double diffK = 1 - k;
|
||||
double r = (1 - c) * diffK, g = (1 - m) * diffK, b = (1 - y) * diffK;
|
||||
double[] group = new double[] { r, g, b };
|
||||
double cMax = MathE.Max(group), cMin = MathE.Min(group), delta = cMax - cMin;
|
||||
Angle h;
|
||||
|
||||
if (delta == 0) h = Angle.Zero;
|
||||
else if (cMax == r) h = Angle.FromDegrees(60 * MathE.ModAbs((g - b) / delta, 6));
|
||||
else if (cMax == g) h = Angle.FromDegrees(60 * ((b - r) / delta + 2));
|
||||
else if (cMax == b) h = Angle.FromDegrees(60 * ((r - g) / delta + 4));
|
||||
else h = Angle.Zero;
|
||||
|
||||
double s = cMax == 0 ? 0 : delta / cMax;
|
||||
return new ColorHSV(h, s, cMax);
|
||||
}
|
||||
public ColorCMYK AsCmyk() => this;
|
||||
|
||||
public string HexCode() => AsRgb().HexCode();
|
||||
|
||||
public IEnumerator<double> GetEnumerator()
|
||||
{
|
||||
yield return c;
|
||||
yield return m;
|
||||
yield return y;
|
||||
yield return k;
|
||||
yield return a;
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Deconstruct(out double c, out double m, out double y, out double k)
|
||||
{
|
||||
c = this.c;
|
||||
m = this.m;
|
||||
y = this.y;
|
||||
k = this.k;
|
||||
}
|
||||
public void Deconstruct(out double c, out double m, out double y, out double k, out double a)
|
||||
{
|
||||
c = this.c;
|
||||
m = this.m;
|
||||
y = this.y;
|
||||
k = this.k;
|
||||
a = this.a;
|
||||
}
|
||||
|
||||
public bool Equals(ColorCMYK other)
|
||||
{
|
||||
if (a <= 0 && other.a <= 0) return true;
|
||||
else if (k >= 1 && other.k >= 1) return true;
|
||||
else return c == other.c && m == other.m && y == other.y && k == other.k && a == other.a;
|
||||
}
|
||||
public bool Equals(IColor other) => Equals(other.AsCmyk());
|
||||
#if CS8_OR_GREATER
|
||||
public override bool Equals(object? other)
|
||||
#else
|
||||
public override bool Equals(object other)
|
||||
#endif
|
||||
{
|
||||
if (other is IColor color) return Equals(color.AsRgb());
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
public override string ToString() => $"{{ c={c:0.00}, m={m:0.00}, y={y:0.00}, k={k:0.00}, a={a:0.00} }}";
|
||||
|
||||
public double[] ToArray() => new double[] { c, m, y, k, a };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
ColorCMYK copy = this;
|
||||
return i => copy[i];
|
||||
}
|
||||
public List<double> ToList() => new List<double> { c, m, y, k, a };
|
||||
|
||||
public static ColorCMYK operator +(ColorCMYK a, ColorCMYK b) => new ColorCMYK(a.c + b.c, a.m + b.m, a.y + b.y, a.k + b.k, 1 - (1 - a.a) * (1 - b.a));
|
||||
public static ColorCMYK operator *(ColorCMYK a, ColorCMYK b) => new ColorCMYK(a.c * b.c, a.m * b.m, a.y * b.y, a.k * b.k, a.a * b.a);
|
||||
public static ColorCMYK operator *(ColorCMYK a, double b) => new ColorCMYK(a.c, a.m, a.y, b == 0 ? 1 : MathE.Clamp(a.k / b, 0, 1), a.a);
|
||||
public static bool operator ==(ColorCMYK a, IColor b) => a.Equals(b.AsCmyk());
|
||||
public static bool operator !=(ColorCMYK a, IColor b) => !a.Equals(b.AsCmyk());
|
||||
public static bool operator ==(ColorCMYK a, ColorCMYK b) => a.Equals(b);
|
||||
public static bool operator !=(ColorCMYK a, ColorCMYK b) => !a.Equals(b);
|
||||
|
||||
public static implicit operator ColorCMYK(ListTuple<double> tuple)
|
||||
{
|
||||
if (tuple.Length == 4) return new ColorCMYK(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
else if (tuple.Length == 5) return new ColorCMYK(tuple[0], tuple[1], tuple[2], tuple[3], tuple[4]);
|
||||
else throw new InvalidCastException();
|
||||
}
|
||||
public static implicit operator ColorCMYK((double, double, double, double) tuple) => new ColorCMYK(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||
public static implicit operator ColorCMYK((double, double, double, double, double) tuple) => new ColorCMYK(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5);
|
||||
|
||||
public static implicit operator ListTuple<double>(ColorCMYK color) => new ListTuple<double>(color.c, color.m, color.y, color.k, color.a);
|
||||
public static implicit operator ValueTuple<double, double, double, double>(ColorCMYK color) => (color.c, color.m, color.y, color.k);
|
||||
public static implicit operator ValueTuple<double, double, double, double, double>(ColorCMYK color) => (color.c, color.m, color.y, color.k, color.a);
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
public enum ColorChannel
|
||||
{
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
Alpha,
|
||||
Hue,
|
||||
Saturation,
|
||||
Value,
|
||||
Cyan,
|
||||
Magenta,
|
||||
Yellow,
|
||||
Key,
|
||||
Index
|
||||
}
|
||||
}
|
||||
@ -1,350 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,158 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,387 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
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()}";
|
||||
}
|
||||
}
|
||||
@ -1,141 +0,0 @@
|
||||
using Nerd_STF.Mathematics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Nerd_STF.Graphics.Formats
|
||||
{
|
||||
public class R8G8B8A8 : IColorFormat<R8G8B8A8, ColorRGB>
|
||||
{
|
||||
public static int ChannelCount => 4;
|
||||
public static int BitLength => 32;
|
||||
public static Dictionary<ColorChannel, int> BitsPerChannel { get; } = new Dictionary<ColorChannel, int>()
|
||||
{
|
||||
{ ColorChannel.Red, 8 },
|
||||
{ ColorChannel.Green, 8 },
|
||||
{ ColorChannel.Blue, 8 },
|
||||
{ ColorChannel.Alpha, 8 }
|
||||
};
|
||||
|
||||
int IColorFormat.ChannelCount => ChannelCount;
|
||||
int IColorFormat.BitLength => BitLength;
|
||||
Dictionary<ColorChannel, int> IColorFormat.BitsPerChannel => BitsPerChannel;
|
||||
|
||||
public byte R
|
||||
{
|
||||
get => r;
|
||||
set => r = value;
|
||||
}
|
||||
public byte G
|
||||
{
|
||||
get => g;
|
||||
set => g = value;
|
||||
}
|
||||
public byte B
|
||||
{
|
||||
get => b;
|
||||
set => b = value;
|
||||
}
|
||||
public byte A
|
||||
{
|
||||
get => a;
|
||||
set => a = value;
|
||||
}
|
||||
|
||||
private byte r, g, b, a;
|
||||
|
||||
public R8G8B8A8(ColorRGB color)
|
||||
{
|
||||
r = (byte)MathE.Clamp(color.r * 255, 0, 255);
|
||||
g = (byte)MathE.Clamp(color.g * 255, 0, 255);
|
||||
b = (byte)MathE.Clamp(color.b * 255, 0, 255);
|
||||
a = (byte)MathE.Clamp(color.a * 255, 0, 255);
|
||||
}
|
||||
public R8G8B8A8(byte r, byte g, byte b, byte a)
|
||||
{
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
public static R8G8B8A8 FromColor(IColor color) => new R8G8B8A8(color.AsRgb());
|
||||
public static R8G8B8A8 FromColor(ColorRGB color) => new R8G8B8A8(color);
|
||||
|
||||
public static byte[] GetBitfield(ColorChannel channel)
|
||||
{
|
||||
byte[] buf = new byte[4];
|
||||
switch (channel)
|
||||
{
|
||||
case ColorChannel.Red: buf[0] = 0xFF; break;
|
||||
case ColorChannel.Green: buf[1] = 0xFF; break;
|
||||
case ColorChannel.Blue: buf[2] = 0xFF; break;
|
||||
case ColorChannel.Alpha: buf[3] = 0xFF; break;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
byte[] IColorFormat.GetBitfield(ColorChannel channel) => GetBitfield(channel);
|
||||
|
||||
#if CS8_OR_GREATER
|
||||
public bool Equals(R8G8B8A8? other) =>
|
||||
#else
|
||||
public bool Equals(R8G8B8A8 other) =>
|
||||
#endif
|
||||
!(other is null) && r == other.r && g == other.g && b == other.b && a == other.a;
|
||||
#if CS8_OR_GREATER
|
||||
public override bool Equals(object? obj)
|
||||
#else
|
||||
public override bool Equals(object obj)
|
||||
#endif
|
||||
{
|
||||
if (obj is null) return false;
|
||||
else if (obj is R8G8B8A8 formatObj) return Equals(formatObj);
|
||||
else return false;
|
||||
}
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
public override string ToString() => $"{{ r={r}, g={g}, b={b}, a={a} }}";
|
||||
|
||||
public byte[] GetBits() => new byte[] { r, g, b, a };
|
||||
|
||||
public ColorRGB GetColor()
|
||||
{
|
||||
const double inv255 = 0.00392156862745; // Constant for 1/255
|
||||
return new ColorRGB(r * inv255,
|
||||
g * inv255,
|
||||
b * inv255,
|
||||
a * inv255);
|
||||
}
|
||||
IColor IColorFormat.GetColor() => GetColor();
|
||||
public void SetColor(ColorRGB color)
|
||||
{
|
||||
r = (byte)MathE.Clamp(color.r * 255, 0, 255);
|
||||
g = (byte)MathE.Clamp(color.g * 255, 0, 255);
|
||||
b = (byte)MathE.Clamp(color.b * 255, 0, 255);
|
||||
a = (byte)MathE.Clamp(color.a * 255, 0, 255);
|
||||
}
|
||||
public void SetColor(byte r, byte g, byte b, byte a)
|
||||
{
|
||||
this.r = (byte)MathE.Clamp(r * 255, 0, 255);
|
||||
this.g = (byte)MathE.Clamp(g * 255, 0, 255);
|
||||
this.b = (byte)MathE.Clamp(b * 255, 0, 255);
|
||||
this.a = (byte)MathE.Clamp(a * 255, 0, 255);
|
||||
}
|
||||
|
||||
public static R8G8B8A8 operator +(R8G8B8A8 a, R8G8B8A8 b)
|
||||
{
|
||||
return new R8G8B8A8((byte)MathE.Clamp(a.r + b.r, 0, 255),
|
||||
(byte)MathE.Clamp(a.g + b.g, 0, 255),
|
||||
(byte)MathE.Clamp(a.b + b.b, 0, 255),
|
||||
(byte)MathE.Clamp(a.a + b.a, 0, 255));
|
||||
}
|
||||
public static R8G8B8A8 operator *(R8G8B8A8 a, R8G8B8A8 b)
|
||||
{
|
||||
const double inv255 = 0.00392156862745; // Constant for 1/255
|
||||
return new R8G8B8A8((byte)MathE.Clamp(a.r * b.r * inv255, 0, 255),
|
||||
(byte)MathE.Clamp(a.g * b.g * inv255, 0, 255),
|
||||
(byte)MathE.Clamp(a.b * b.b * inv255, 0, 255),
|
||||
(byte)MathE.Clamp(a.a * b.a * inv255, 0, 255));
|
||||
}
|
||||
public static bool operator ==(R8G8B8A8 a, R8G8B8A8 b) => a.Equals(b);
|
||||
public static bool operator !=(R8G8B8A8 a, R8G8B8A8 b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
#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
|
||||
@ -1,20 +0,0 @@
|
||||
#if CS11_OR_GREATER
|
||||
namespace Nerd_STF.Graphics
|
||||
{
|
||||
public interface IColorPresets<TSelf> where TSelf : IColorPresets<TSelf>
|
||||
{
|
||||
static abstract TSelf Black { get; }
|
||||
static abstract TSelf Blue { get; }
|
||||
static abstract TSelf Clear { get; }
|
||||
static abstract TSelf Cyan { get; }
|
||||
static abstract TSelf Gray { get; }
|
||||
static abstract TSelf Green { get; }
|
||||
static abstract TSelf Magenta { get; }
|
||||
static abstract TSelf Orange { get; }
|
||||
static abstract TSelf Purple { get; }
|
||||
static abstract TSelf Red { get; }
|
||||
static abstract TSelf White { get; }
|
||||
static abstract TSelf Yellow { get; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Nerd_STF.Helpers
|
||||
{
|
||||
internal static class CordicHelper
|
||||
public static class CordicHelper
|
||||
{
|
||||
// Starts at 4 radians. Each index downwards is half that.
|
||||
// Goes from 2^2 to 2^-19.
|
||||
|
||||
@ -1,72 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Nerd_STF.Helpers
|
||||
{
|
||||
internal static class ParseHelper
|
||||
{
|
||||
// TODO: Allow parsing more stuff (hexadecimal).
|
||||
public static double ParseDouble(ReadOnlySpan<char> str)
|
||||
{
|
||||
// Turns out this is less accurate than copying and modifying
|
||||
// the code from ParseDoubleWholeDecimals. I think because applying
|
||||
// 0.1 to the whole number is worse than 0.1 to a each individual
|
||||
// decimal point.
|
||||
int raw = ParseDoubleWholeDecimals(str, out int places);
|
||||
double value = raw;
|
||||
for (int i = 0; i < places; i++) value *= 0.1;
|
||||
return value;
|
||||
}
|
||||
public static int ParseDoubleWholeDecimals(ReadOnlySpan<char> str, out int places)
|
||||
{
|
||||
str = str.Trim();
|
||||
if (str.Length == 0) goto _fail;
|
||||
places = 0;
|
||||
|
||||
bool negative = str.StartsWith("-".AsSpan());
|
||||
|
||||
int result = 0;
|
||||
ReadOnlySpan<char>.Enumerator stepper = str.GetEnumerator();
|
||||
if (negative) stepper.MoveNext();
|
||||
bool decFound = false;
|
||||
while (stepper.MoveNext())
|
||||
{
|
||||
char c = stepper.Current;
|
||||
if (c == ',') continue;
|
||||
else if (c == '.')
|
||||
{
|
||||
decFound = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c < '0' || c > '9') goto _fail;
|
||||
int value = c - '0';
|
||||
|
||||
result = result * 10 + value;
|
||||
if (decFound) places++;
|
||||
}
|
||||
|
||||
return negative ? -result : result;
|
||||
|
||||
_fail:
|
||||
throw new FormatException("Cannot parse double from span.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,41 +1,9 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Nerd_STF.Helpers
|
||||
{
|
||||
internal static class TargetHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsFinite(double d)
|
||||
{
|
||||
#if CS11_OR_GREATER
|
||||
return double.IsFinite(d);
|
||||
#else
|
||||
long bits = BitConverter.DoubleToInt64Bits(d);
|
||||
return (bits & 0x7FFFFFFFFFFFFFFF) < 0x7FF0000000000000;
|
||||
#endif
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsInfinity(double d)
|
||||
{
|
||||
#if CS11_OR_GREATER
|
||||
return double.IsInfinity(d);
|
||||
#else
|
||||
long bits = BitConverter.DoubleToInt64Bits(d);
|
||||
return (bits & 0x7FFFFFFFFFFFFFFF) == 0x7FF0000000000000;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteLine(string content)
|
||||
{
|
||||
#if NETSTANDARD1_1
|
||||
#else
|
||||
Console.WriteLine(content);
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T[] EmptyArray<T>()
|
||||
{
|
||||
#if NETSTANDARD1_1
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
using Nerd_STF.Mathematics;
|
||||
using Nerd_STF.Mathematics.Algebra;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace Nerd_STF.Helpers
|
||||
{
|
||||
@ -52,103 +48,5 @@ namespace Nerd_STF.Helpers
|
||||
}
|
||||
return builder.Remove(builder.Length - 1, 1).ToString();
|
||||
}
|
||||
|
||||
#if CS8_OR_GREATER
|
||||
public static string MatrixToString<T>(T matrix, string? format)
|
||||
#else
|
||||
public static string MatrixToString<T>(T matrix, string format)
|
||||
#endif
|
||||
where T : IMatrix<T>
|
||||
{
|
||||
// First convert all items to their string counterparts,
|
||||
// then measure the lengths and do spacing accordingly.
|
||||
Int2 size = matrix.Size;
|
||||
string[,] items = new string[size.y, size.x];
|
||||
for (int x = 0; x < size.x; x++) for (int y = 0; y < size.y; y++)
|
||||
items[y, x] = matrix[x, y].ToString(format);
|
||||
|
||||
// Then write each line separately.
|
||||
StringBuilder[] lines = new StringBuilder[size.x + 2];
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (i == 0) builder.Append('┌');
|
||||
else if (i == lines.Length - 1) builder.Append('└');
|
||||
else builder.Append('│');
|
||||
|
||||
lines[i] = builder;
|
||||
}
|
||||
int totalLen = 0;
|
||||
for (int x = 0; x < size.y; x++)
|
||||
{
|
||||
int maxLen = 0;
|
||||
for (int y = 0; y < size.x; y++)
|
||||
{
|
||||
string item = items[x, y];
|
||||
if (item.Length > maxLen) maxLen = item.Length;
|
||||
}
|
||||
totalLen += maxLen + 1;
|
||||
for (int y = 0; y < size.x; y++)
|
||||
{
|
||||
StringBuilder builder = lines[y + 1];
|
||||
string item = items[x, y];
|
||||
int spacing = maxLen - item.Length;
|
||||
builder.Append(new string(' ', spacing + 1));
|
||||
builder.Append(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Finish up and merge.
|
||||
StringBuilder total = new StringBuilder();
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
StringBuilder builder = lines[i];
|
||||
if (i == 0) builder.Append(new string(' ', totalLen)).Append(" ┐");
|
||||
else if (i == lines.Length - 1) builder.Append(new string(' ', totalLen)).Append(" ┘");
|
||||
else builder.Append(" │");
|
||||
total.Append(builder);
|
||||
if (i != lines.Length - 1) total.AppendLine();
|
||||
}
|
||||
return total.ToString();
|
||||
}
|
||||
|
||||
private static readonly string dimNumSymbols = " ijk?";
|
||||
#if CS8_OR_GREATER
|
||||
public static string HighDimNumberToString(IEnumerable<double> terms, string? format, IFormatProvider? provider)
|
||||
#else
|
||||
public static string HighDimNumberToString(IEnumerable<double> terms, string format, IFormatProvider provider)
|
||||
#endif
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int index = 0;
|
||||
bool first = true;
|
||||
foreach (double term in terms)
|
||||
{
|
||||
if (term == 0)
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
if (first) builder.Append(term.ToString(format, provider));
|
||||
else
|
||||
{
|
||||
if (term > 0)
|
||||
{
|
||||
builder.Append(" + ");
|
||||
builder.Append(term.ToString(format, provider));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(" - ");
|
||||
builder.Append((-term).ToString(format, provider));
|
||||
}
|
||||
}
|
||||
if (index > 0) builder.Append(dimNumSymbols[MathE.Min(index, dimNumSymbols.Length)]);
|
||||
first = false;
|
||||
index++;
|
||||
}
|
||||
if (first) builder.Append(0.0.ToString(format, provider));
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
namespace Nerd_STF
|
||||
{
|
||||
public interface ICombinationIndexer<TItem>
|
||||
{
|
||||
ListTuple<TItem> this[string key] { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,145 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Nerd_STF/Mathematics/Abstract/ICombinationIndexer.cs
Normal file
9
Nerd_STF/Mathematics/Abstract/ICombinationIndexer.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nerd_STF.Mathematics.Abstract
|
||||
{
|
||||
public interface ICombinationIndexer<TItem>
|
||||
{
|
||||
IEnumerable<TItem> this[string key] { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,14 @@
|
||||
#if CS11_OR_GREATER
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Nerd_STF
|
||||
namespace Nerd_STF.Mathematics.Abstract
|
||||
{
|
||||
public interface IFromTuple<TSelf, TTuple>
|
||||
where TSelf : IFromTuple<TSelf, TTuple>
|
||||
where TTuple : struct, ITuple
|
||||
{
|
||||
static abstract implicit operator TSelf(TTuple tuple);
|
||||
static abstract implicit operator TTuple(TSelf tuple);
|
||||
public static abstract implicit operator TSelf(TTuple tuple);
|
||||
public static abstract implicit operator TTuple(TSelf tuple);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
16
Nerd_STF/Mathematics/Abstract/INumberGroup.cs
Normal file
16
Nerd_STF/Mathematics/Abstract/INumberGroup.cs
Normal file
@ -0,0 +1,16 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
10
Nerd_STF/Mathematics/Abstract/IPresets1D.cs
Normal file
10
Nerd_STF/Mathematics/Abstract/IPresets1D.cs
Normal file
@ -0,0 +1,10 @@
|
||||
#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
|
||||
13
Nerd_STF/Mathematics/Abstract/IPresets2D.cs
Normal file
13
Nerd_STF/Mathematics/Abstract/IPresets2D.cs
Normal file
@ -0,0 +1,13 @@
|
||||
#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
|
||||
11
Nerd_STF/Mathematics/Abstract/IPresets3D.cs
Normal file
11
Nerd_STF/Mathematics/Abstract/IPresets3D.cs
Normal file
@ -0,0 +1,11 @@
|
||||
#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
|
||||
11
Nerd_STF/Mathematics/Abstract/IPresets4D.cs
Normal file
11
Nerd_STF/Mathematics/Abstract/IPresets4D.cs
Normal file
@ -0,0 +1,11 @@
|
||||
#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
|
||||
@ -2,13 +2,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Nerd_STF
|
||||
namespace Nerd_STF.Mathematics.Abstract
|
||||
{
|
||||
public interface ISplittable<TSelf, TTuple>
|
||||
where TSelf : ISplittable<TSelf, TTuple>
|
||||
where TTuple : struct, ITuple
|
||||
{
|
||||
static abstract TTuple SplitArray(IEnumerable<TSelf> values);
|
||||
public static abstract TTuple SplitArray(IEnumerable<TSelf> values);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1,36 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
#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
|
||||
@ -1,8 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Algebra
|
||||
{
|
||||
public interface ISquareMatrix<TSelf> : IMatrix<TSelf>
|
||||
where TSelf : ISquareMatrix<TSelf>
|
||||
{
|
||||
double Trace();
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
#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
|
||||
@ -1,9 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
#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
|
||||
@ -1,424 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,362 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,458 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,608 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
namespace Nerd_STF.Mathematics.Algebra
|
||||
{
|
||||
public enum RowColumn
|
||||
{
|
||||
Row,
|
||||
Column
|
||||
}
|
||||
}
|
||||
@ -1,276 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nerd_STF.Mathematics.Equations
|
||||
{
|
||||
@ -24,17 +23,5 @@ namespace Nerd_STF.Mathematics.Equations
|
||||
double Integrate(double lower, double upper);
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,11 +21,6 @@ namespace Nerd_STF.Mathematics.Equations
|
||||
M = m;
|
||||
B = b;
|
||||
}
|
||||
public Linear(Fill<double> fill)
|
||||
{
|
||||
B = fill(0);
|
||||
M = fill(1);
|
||||
}
|
||||
|
||||
public double this[double x] => M * x + B;
|
||||
public double Get(double x) => M * x + B;
|
||||
|
||||
@ -30,16 +30,6 @@ namespace Nerd_STF.Mathematics.Equations
|
||||
if (reverse) this.terms = TrimExcessTerms(terms.Reverse().ToArray());
|
||||
else this.terms = TrimExcessTerms(terms.ToArray());
|
||||
}
|
||||
public Polynomial(bool reverse, Fill<double> terms, int length)
|
||||
{
|
||||
this.terms = new double[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (reverse) this.terms[i] = terms(length - 1 - i);
|
||||
else this.terms[i] = terms(i);
|
||||
}
|
||||
this.terms = TrimExcessTerms(this.terms);
|
||||
}
|
||||
private static double[] TrimExcessTerms(double[] terms)
|
||||
{
|
||||
int newLength = terms.Length;
|
||||
@ -89,8 +79,6 @@ namespace Nerd_STF.Mathematics.Equations
|
||||
public IEquation Add(IEquation other)
|
||||
{
|
||||
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));
|
||||
}
|
||||
public Polynomial Add(double constant)
|
||||
|
||||
@ -42,12 +42,6 @@ namespace Nerd_STF.Mathematics.Equations
|
||||
B = 0;
|
||||
C = c;
|
||||
}
|
||||
public Quadratic(Fill<double> fill)
|
||||
{
|
||||
C = fill(0);
|
||||
B = fill(1);
|
||||
A = fill(2);
|
||||
}
|
||||
|
||||
public double this[double x] => A * x * x + B * x + C;
|
||||
public double Get(double x) => A * x * x + B * x + C;
|
||||
@ -189,7 +183,6 @@ namespace Nerd_STF.Mathematics.Equations
|
||||
public static Polynomial operator *(Quadratic a, double b) => a.Multiply(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 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, Quadratic b) => !a.Equals(b);
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Mathematics.Abstract;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nerd_STF.Mathematics
|
||||
{
|
||||
@ -12,8 +13,6 @@ namespace Nerd_STF.Mathematics
|
||||
#if CS11_OR_GREATER
|
||||
,IFromTuple<Float2, (double, double)>,
|
||||
IPresets2d<Float2>,
|
||||
IRoundable<Float2, Int2>,
|
||||
IRefRoundable<Float2>,
|
||||
ISplittable<Float2, (double[] Xs, double[] Ys)>
|
||||
#endif
|
||||
{
|
||||
@ -46,14 +45,9 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index == 2) break;
|
||||
if (index >= 2) break;
|
||||
}
|
||||
}
|
||||
public Float2(Fill<double> fill)
|
||||
{
|
||||
x = fill(0);
|
||||
y = fill(1);
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
@ -76,22 +70,20 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
}
|
||||
}
|
||||
public ListTuple<double> this[string key]
|
||||
public IEnumerable<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 'x': items[i] = x; break;
|
||||
case 'y': items[i] = y; break;
|
||||
case 'x': yield return x; break;
|
||||
case 'y': yield return y; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
return new ListTuple<double>(items);
|
||||
}
|
||||
set
|
||||
{
|
||||
@ -261,19 +253,6 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)})";
|
||||
|
||||
public double[] ToArray() => new double[] { x, y };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
Float2 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.x;
|
||||
case 1: return copy.y;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<double> ToList() => new List<double> { x, y };
|
||||
|
||||
public static Float2 operator +(Float2 a, Float2 b) => new Float2(a.x + b.x, a.y + b.y);
|
||||
@ -286,7 +265,6 @@ namespace Nerd_STF.Mathematics
|
||||
public static bool operator ==(Float2 a, Float2 b) => a.Equals(b);
|
||||
public static bool operator !=(Float2 a, Float2 b) => !a.Equals(b);
|
||||
|
||||
public static explicit operator Float2(Complex complex) => new Float2(complex.Real, complex.Imaginary);
|
||||
public static explicit operator Float2(Float3 floats) => new Float2(floats.x, floats.y);
|
||||
public static explicit operator Float2(Float4 floats) => new Float2(floats.x, floats.y);
|
||||
public static implicit operator Float2(Int2 ints) => new Float2(ints.x, ints.y);
|
||||
@ -296,22 +274,12 @@ namespace Nerd_STF.Mathematics
|
||||
public static implicit operator Float2(PointF point) => new Float2(point.X, point.Y);
|
||||
public static implicit operator Float2(Size point) => new Float2(point.Width, point.Height);
|
||||
public static implicit operator Float2(SizeF size) => new Float2(size.Width, size.Height);
|
||||
public static implicit operator Float2(Vector2 vec) => new Float2(vec.X, vec.Y);
|
||||
public static explicit operator Float2(Vector3 vec) => new Float2(vec.X, vec.Y);
|
||||
public static explicit operator Float2(Vector4 vec) => new Float2(vec.X, vec.Y);
|
||||
public static implicit operator Float2(ListTuple<double> tuple) => new Float2(tuple[0], tuple[1]);
|
||||
public static implicit operator Float2(ListTuple<int> tuple) => new Float2(tuple[0], tuple[1]);
|
||||
public static implicit operator Float2((double, double) tuple) => new Float2(tuple.Item1, tuple.Item2);
|
||||
|
||||
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 explicit operator Size(Float2 group) => new Size((int)group.x, (int)group.y);
|
||||
public static implicit operator SizeF(Float2 group) => new SizeF((float)group.x, (float)group.y);
|
||||
public static implicit operator Vector2(Float2 group) => new Vector2((float)group.x, (float)group.y);
|
||||
public static implicit operator Vector3(Float2 group) => new Vector3((float)group.x, (float)group.y, 0);
|
||||
public static implicit operator Vector4(Float2 group) => new Vector4((float)group.x, (float)group.y, 0, 0);
|
||||
public static implicit operator ListTuple<double>(Float2 group) => new ListTuple<double>(group.x, group.y);
|
||||
public static explicit operator ListTuple<int>(Float2 group) => new ListTuple<int>((int)group.x, (int)group.y);
|
||||
public static implicit operator ValueTuple<double, double>(Float2 group) => (group.x, group.y);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Graphics;
|
||||
using Nerd_STF.Mathematics.Abstract;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nerd_STF.Mathematics
|
||||
{
|
||||
@ -12,8 +11,6 @@ namespace Nerd_STF.Mathematics
|
||||
#if CS11_OR_GREATER
|
||||
,IFromTuple<Float3, (double, double, double)>,
|
||||
IPresets2d<Float3>,
|
||||
IRoundable<Float3, Int3>,
|
||||
IRefRoundable<Float3>,
|
||||
ISplittable<Float3, (double[] Xs, double[] Ys, double[] Zs)>
|
||||
#endif
|
||||
{
|
||||
@ -50,15 +47,9 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index == 3) break;
|
||||
if (index >= 2) break;
|
||||
}
|
||||
}
|
||||
public Float3(Fill<double> fill)
|
||||
{
|
||||
x = fill(0);
|
||||
y = fill(1);
|
||||
z = fill(2);
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
@ -83,23 +74,21 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
}
|
||||
}
|
||||
public ListTuple<double> this[string key]
|
||||
public IEnumerable<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 'x': items[i] = x; break;
|
||||
case 'y': items[i] = y; break;
|
||||
case 'z': items[i] = z; break;
|
||||
case 'x': yield return x; break;
|
||||
case 'y': yield return y; break;
|
||||
case 'z': yield return z; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
return new ListTuple<double>(items);
|
||||
}
|
||||
set
|
||||
{
|
||||
@ -128,7 +117,7 @@ namespace Nerd_STF.Mathematics
|
||||
total += val;
|
||||
count++;
|
||||
}
|
||||
return total / count;
|
||||
return total;
|
||||
}
|
||||
public static Int3 Ceiling(Float3 val) =>
|
||||
new Int3(MathE.Ceiling(val.x),
|
||||
@ -289,20 +278,6 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})";
|
||||
|
||||
public double[] ToArray() => new double[] { x, y, z };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
Float3 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.x;
|
||||
case 1: return copy.y;
|
||||
case 2: return copy.z;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<double> ToList() => new List<double> { x, y, z };
|
||||
|
||||
public static Float3 operator +(Float3 a, Float3 b) => new Float3(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
@ -315,24 +290,13 @@ namespace Nerd_STF.Mathematics
|
||||
public static bool operator ==(Float3 a, Float3 b) => a.Equals(b);
|
||||
public static bool operator !=(Float3 a, Float3 b) => !a.Equals(b);
|
||||
|
||||
public static explicit operator Float3(ColorRGB color) => new Float3(color.r, color.g, color.b);
|
||||
public static implicit operator Float3(Float2 floats) => new Float3(floats.x, floats.y, 0);
|
||||
public static explicit operator Float3(Float4 floats) => new Float3(floats.x, floats.y, floats.z);
|
||||
public static implicit operator Float3(Int2 ints) => new Float3(ints.x, ints.y, 0);
|
||||
public static implicit operator Float3(Int3 ints) => new Float3(ints.x, ints.y, ints.z);
|
||||
public static explicit operator Float3(Int4 ints) => new Float3(ints.x, ints.y, ints.z);
|
||||
public static implicit operator Float3(Vector2 vec) => new Float3(vec.X, vec.Y, 0);
|
||||
public static implicit operator Float3(Vector3 vec) => new Float3(vec.X, vec.Y, vec.Z);
|
||||
public static explicit operator Float3(Vector4 vec) => new Float3(vec.X, vec.Y, vec.Z);
|
||||
public static implicit operator Float3(ListTuple<double> tuple) => new Float3(tuple[0], tuple[1], tuple[2]);
|
||||
public static implicit operator Float3(ListTuple<int> tuple) => new Float3(tuple[0], tuple[1], tuple[2]);
|
||||
public static implicit operator Float3((double, double, double) tuple) => new Float3(tuple.Item1, tuple.Item2, tuple.Item3);
|
||||
|
||||
public static explicit operator Vector2(Float3 group) => new Vector2((float)group.x, (float)group.y);
|
||||
public static implicit operator Vector3(Float3 group) => new Vector3((float)group.x, (float)group.y, (float)group.z);
|
||||
public static implicit operator Vector4(Float3 group) => new Vector4((float)group.x, (float)group.y, (float)group.z, 0);
|
||||
public static implicit operator ListTuple<double>(Float3 group) => new ListTuple<double>(group.x, group.y, group.z);
|
||||
public static explicit operator ListTuple<int>(Float3 group) => new ListTuple<int>((int)group.x, (int)group.y, (int)group.z);
|
||||
public static implicit operator ValueTuple<double, double, double>(Float3 group) => (group.x, group.y, group.z);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,9 +2,8 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Graphics;
|
||||
using Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
namespace Nerd_STF.Mathematics
|
||||
{
|
||||
@ -12,8 +11,6 @@ namespace Nerd_STF.Mathematics
|
||||
#if CS11_OR_GREATER
|
||||
,IFromTuple<Float4, (double, double, double, double)>,
|
||||
IPresets4d<Float4>,
|
||||
IRoundable<Float4, Int4>,
|
||||
IRefRoundable<Float4>,
|
||||
ISplittable<Float4, (double[] Ws, double[] Xs, double[] Ys, double[] Zs)>
|
||||
#endif
|
||||
{
|
||||
@ -54,16 +51,9 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index == 4) break;
|
||||
if (index >= 2) break;
|
||||
}
|
||||
}
|
||||
public Float4(Fill<double> fill)
|
||||
{
|
||||
w = fill(0);
|
||||
x = fill(1);
|
||||
y = fill(2);
|
||||
z = fill(3);
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
@ -90,24 +80,22 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
}
|
||||
}
|
||||
public ListTuple<double> this[string key]
|
||||
public IEnumerable<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;
|
||||
case 'w': yield return w; break;
|
||||
case 'x': yield return x; break;
|
||||
case 'y': yield return y; break;
|
||||
case 'z': yield return z; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
return new ListTuple<double>(items);
|
||||
}
|
||||
set
|
||||
{
|
||||
@ -137,7 +125,7 @@ namespace Nerd_STF.Mathematics
|
||||
total += val;
|
||||
count++;
|
||||
}
|
||||
return total / count;
|
||||
return total;
|
||||
}
|
||||
public static Int4 Ceiling(Float4 val) =>
|
||||
new Int4(MathE.Ceiling(val.w),
|
||||
@ -310,21 +298,6 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({w.ToString(format)}, {x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})";
|
||||
|
||||
public double[] ToArray() => new double[] { w, x, y, z };
|
||||
public Fill<double> ToFill()
|
||||
{
|
||||
Float4 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.w;
|
||||
case 1: return copy.x;
|
||||
case 2: return copy.y;
|
||||
case 3: return copy.z;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<double> ToList() => new List<double> { w, x, y, z };
|
||||
|
||||
public static Float4 operator +(Float4 a, Float4 b) => new Float4(a.w + b.w, a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
@ -337,25 +310,13 @@ namespace Nerd_STF.Mathematics
|
||||
public static bool operator ==(Float4 a, Float4 b) => a.Equals(b);
|
||||
public static bool operator !=(Float4 a, Float4 b) => !a.Equals(b);
|
||||
|
||||
public static explicit operator Float4(ColorRGB color) => new Float4(color.a, color.r, color.g, color.b);
|
||||
public static implicit operator Float4(Int2 ints) => new Float4(0, ints.x, ints.y, 0);
|
||||
public static implicit operator Float4(Int3 ints) => new Float4(0, ints.x, ints.y, ints.z);
|
||||
public static implicit operator Float4(Int4 ints) => new Float4(ints.w, ints.x, ints.y, ints.z);
|
||||
public static implicit operator Float4(Float2 floats) => new Float4(0, floats.x, floats.y, 0);
|
||||
public static implicit operator Float4(Float3 floats) => new Float4(0, floats.x, floats.y, floats.z);
|
||||
public static implicit operator Float4(Numbers.Quaternion quat) => new Float4(quat.w, quat.x, quat.y, quat.z);
|
||||
public static implicit operator Float4(Vector2 vec) => new Float4(0, vec.X, vec.Y, 0);
|
||||
public static implicit operator Float4(Vector3 vec) => new Float4(0, vec.X, vec.Y, vec.Z);
|
||||
public static implicit operator Float4(Vector4 vec) => new Float4(vec.W, vec.X, vec.Y, vec.Z);
|
||||
public static implicit operator Float4(ListTuple<double> tuple) => new Float4(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
public static implicit operator Float4(ListTuple<int> tuple) => new Float4(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
public static implicit operator Float4((double, double, double, double) tuple) => new Float4(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||
|
||||
public static explicit operator Vector2(Float4 group) => new Vector2((float)group.x, (float)group.y);
|
||||
public static explicit operator Vector3(Float4 group) => new Vector3((float)group.x, (float)group.y, (float)group.z);
|
||||
public static implicit operator Vector4(Float4 group) => new Vector4((float)group.x, (float)group.y, (float)group.z, (float)group.w);
|
||||
public static implicit operator ListTuple<double>(Float4 group) => new ListTuple<double>(group.w, group.x, group.y, group.z);
|
||||
public static explicit operator ListTuple<int>(Float4 group) => new ListTuple<int>((int)group.w, (int)group.x, (int)group.y, (int)group.z);
|
||||
public static implicit operator ValueTuple<double, double, double, double>(Float4 group) => (group.w, group.x, group.y, group.z);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
#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
|
||||
@ -1,22 +0,0 @@
|
||||
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
|
||||
{ }
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
#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
|
||||
@ -1,13 +0,0 @@
|
||||
#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
|
||||
@ -1,11 +0,0 @@
|
||||
#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
|
||||
@ -1,12 +0,0 @@
|
||||
#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
|
||||
@ -1,12 +0,0 @@
|
||||
#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
|
||||
@ -1,15 +0,0 @@
|
||||
#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
|
||||
@ -1,15 +0,0 @@
|
||||
#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
|
||||
@ -1,6 +1,5 @@
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Mathematics.Algebra;
|
||||
using Nerd_STF.Mathematics.Numbers;
|
||||
using Nerd_STF.Mathematics.Abstract;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@ -45,14 +44,9 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index == 2) break;
|
||||
if (index >= 2) break;
|
||||
}
|
||||
}
|
||||
public Int2(Fill<int> fill)
|
||||
{
|
||||
x = fill(0);
|
||||
y = fill(1);
|
||||
}
|
||||
|
||||
public int this[int index]
|
||||
{
|
||||
@ -75,22 +69,20 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
}
|
||||
}
|
||||
public ListTuple<int> this[string key]
|
||||
public IEnumerable<int> this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
int[] items = new int[key.Length];
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
{
|
||||
char c = key[i];
|
||||
switch (c)
|
||||
{
|
||||
case 'x': items[i] = x; break;
|
||||
case 'y': items[i] = y; break;
|
||||
case 'x': yield return x; break;
|
||||
case 'y': yield return y; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
return new ListTuple<int>(items);
|
||||
}
|
||||
set
|
||||
{
|
||||
@ -128,27 +120,27 @@ namespace Nerd_STF.Mathematics
|
||||
MathE.Clamp(ref value.x, min.x, max.x);
|
||||
MathE.Clamp(ref value.y, min.y, max.y);
|
||||
}
|
||||
public static Int2 ClampMagnitude(Int2 value, double minMag, double maxMag)
|
||||
public static Int2 ClampMagnitude(Int2 value, int minMag, int maxMag)
|
||||
{
|
||||
Int2 copy = value;
|
||||
ClampMagnitude(ref copy, minMag, maxMag);
|
||||
return copy;
|
||||
}
|
||||
public static void ClampMagnitude(ref Int2 value, double minMag, double maxMag)
|
||||
public static void ClampMagnitude(ref Int2 value, int minMag, int maxMag)
|
||||
{
|
||||
if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag));
|
||||
double mag = value.Magnitude;
|
||||
if (mag < minMag)
|
||||
{
|
||||
double factor = minMag / mag;
|
||||
value.x = MathE.Ceiling(value.x * factor);
|
||||
value.y = MathE.Ceiling(value.y * factor);
|
||||
value.x = (int)(value.x * factor);
|
||||
value.y = (int)(value.y * factor);
|
||||
}
|
||||
else if (mag > maxMag)
|
||||
{
|
||||
double factor = maxMag / mag;
|
||||
value.x = MathE.Floor(value.x * factor);
|
||||
value.y = MathE.Floor(value.y * factor);
|
||||
value.x = (int)(value.x * factor);
|
||||
value.y = (int)(value.y * factor);
|
||||
}
|
||||
}
|
||||
public static Int3 Cross(Int2 a, Int2 b) => Int3.Cross(a, b);
|
||||
@ -163,10 +155,6 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
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) =>
|
||||
new Int2(MathE.Lerp(a.x, b.x, t, clamp),
|
||||
MathE.Lerp(a.y, b.y, t, clamp));
|
||||
@ -232,19 +220,6 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)})";
|
||||
|
||||
public int[] ToArray() => new int[] { x, y };
|
||||
public Fill<int> ToFill()
|
||||
{
|
||||
Int2 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.x;
|
||||
case 1: return copy.y;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<int> ToList() => new List<int> { x, y };
|
||||
|
||||
public static Int2 operator +(Int2 a, Int2 b) => new Int2(a.x + b.x, a.y + b.y);
|
||||
@ -260,7 +235,6 @@ namespace Nerd_STF.Mathematics
|
||||
public static bool operator ==(Int2 a, Int2 b) => a.Equals(b);
|
||||
public static bool operator !=(Int2 a, Int2 b) => !a.Equals(b);
|
||||
|
||||
public static explicit operator Int2(Complex complex) => new Int2((int)complex.r, (int)complex.i);
|
||||
public static explicit operator Int2(Float2 floats) => new Int2((int)floats.x, (int)floats.y);
|
||||
public static explicit operator Int2(Float3 floats) => new Int2((int)floats.x, (int)floats.y);
|
||||
public static explicit operator Int2(Float4 floats) => new Int2((int)floats.x, (int)floats.y);
|
||||
@ -270,16 +244,12 @@ namespace Nerd_STF.Mathematics
|
||||
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 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 Point(Int2 group) => new Point(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 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Mathematics.Algebra;
|
||||
using Nerd_STF.Mathematics.Abstract;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@ -47,15 +47,9 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index == 3) break;
|
||||
if (index >= 2) break;
|
||||
}
|
||||
}
|
||||
public Int3(Fill<int> fill)
|
||||
{
|
||||
x = fill(0);
|
||||
y = fill(1);
|
||||
z = fill(2);
|
||||
}
|
||||
|
||||
public int this[int index]
|
||||
{
|
||||
@ -80,23 +74,21 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
}
|
||||
}
|
||||
public ListTuple<int> this[string key]
|
||||
public IEnumerable<int> this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
int[] items = new int[key.Length];
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
{
|
||||
char c = key[i];
|
||||
switch (c)
|
||||
{
|
||||
case 'x': items[i] = x; break;
|
||||
case 'y': items[i] = y; break;
|
||||
case 'z': items[i] = z; break;
|
||||
case 'x': yield return x; break;
|
||||
case 'y': yield return y; break;
|
||||
case 'z': yield return z; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
return new ListTuple<int>(items);
|
||||
}
|
||||
set
|
||||
{
|
||||
@ -137,13 +129,13 @@ namespace Nerd_STF.Mathematics
|
||||
MathE.Clamp(ref value.y, min.y, max.y);
|
||||
MathE.Clamp(ref value.z, min.z, max.z);
|
||||
}
|
||||
public static Int3 ClampMagnitude(Int3 value, double minMag, double maxMag)
|
||||
public static Int3 ClampMagnitude(Int3 value, int minMag, int maxMag)
|
||||
{
|
||||
Int3 copy = value;
|
||||
ClampMagnitude(ref copy, minMag, maxMag);
|
||||
return copy;
|
||||
}
|
||||
public static void ClampMagnitude(ref Int3 value, double minMag, double maxMag)
|
||||
public static void ClampMagnitude(ref Int3 value, int minMag, int maxMag)
|
||||
{
|
||||
if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag));
|
||||
double mag = value.Magnitude;
|
||||
@ -151,16 +143,16 @@ namespace Nerd_STF.Mathematics
|
||||
if (mag < minMag)
|
||||
{
|
||||
double factor = minMag / mag;
|
||||
value.x = MathE.Ceiling(value.x * factor);
|
||||
value.y = MathE.Ceiling(value.y * factor);
|
||||
value.z = MathE.Ceiling(value.z * factor);
|
||||
value.x = (int)(value.x * factor);
|
||||
value.y = (int)(value.y * factor);
|
||||
value.z = (int)(value.z * factor);
|
||||
}
|
||||
else if (mag > maxMag)
|
||||
{
|
||||
double factor = maxMag / mag;
|
||||
value.x = MathE.Floor(value.x * factor);
|
||||
value.y = MathE.Floor(value.y * factor);
|
||||
value.z = MathE.Floor(value.z * factor);
|
||||
value.x = (int)(value.x * factor);
|
||||
value.y = (int)(value.y * factor);
|
||||
value.z = (int)(value.z * factor);
|
||||
}
|
||||
}
|
||||
public static Int3 Cross(Int3 a, Int3 b) =>
|
||||
@ -179,10 +171,6 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
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) =>
|
||||
new Int3(MathE.Lerp(a.x, b.x, t, clamp),
|
||||
MathE.Lerp(a.y, b.y, t, clamp),
|
||||
@ -252,20 +240,6 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})";
|
||||
|
||||
public int[] ToArray() => new int[] { x, y, z };
|
||||
public Fill<int> ToFill()
|
||||
{
|
||||
Int3 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.x;
|
||||
case 1: return copy.y;
|
||||
case 2: return copy.z;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<int> ToList() => new List<int> { x, y, z };
|
||||
|
||||
public static Int3 operator +(Int3 a, Int3 b) => new Int3(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
@ -286,12 +260,8 @@ 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(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(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 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Mathematics.Algebra;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Nerd_STF.Exceptions;
|
||||
using Nerd_STF.Mathematics.Abstract;
|
||||
|
||||
namespace Nerd_STF.Mathematics
|
||||
{
|
||||
@ -51,16 +51,9 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
this[index] = item;
|
||||
index++;
|
||||
if (index == 4) break;
|
||||
if (index >= 2) break;
|
||||
}
|
||||
}
|
||||
public Int4(Fill<int> fill)
|
||||
{
|
||||
w = fill(0);
|
||||
x = fill(1);
|
||||
y = fill(2);
|
||||
z = fill(3);
|
||||
}
|
||||
|
||||
public int this[int index]
|
||||
{
|
||||
@ -87,24 +80,22 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
}
|
||||
}
|
||||
public ListTuple<int> this[string key]
|
||||
public IEnumerable<int> this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
int[] items = new int[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;
|
||||
case 'w': yield return w; break;
|
||||
case 'x': yield return x; break;
|
||||
case 'y': yield return y; break;
|
||||
case 'z': yield return z; break;
|
||||
default: throw new ArgumentException("Invalid key.", nameof(key));
|
||||
}
|
||||
}
|
||||
return new ListTuple<int>(items);
|
||||
}
|
||||
set
|
||||
{
|
||||
@ -161,18 +152,18 @@ namespace Nerd_STF.Mathematics
|
||||
if (mag < minMag)
|
||||
{
|
||||
double factor = minMag / mag;
|
||||
value.w = MathE.Ceiling(value.w * factor);
|
||||
value.x = MathE.Ceiling(value.x * factor);
|
||||
value.y = MathE.Ceiling(value.y * factor);
|
||||
value.z = MathE.Ceiling(value.z * factor);
|
||||
value.w = (int)(value.w * factor);
|
||||
value.x = (int)(value.x * factor);
|
||||
value.y = (int)(value.y * factor);
|
||||
value.z = (int)(value.z * factor);
|
||||
}
|
||||
else if (mag > maxMag)
|
||||
{
|
||||
double factor = maxMag / mag;
|
||||
value.w = MathE.Floor(value.w * factor);
|
||||
value.x = MathE.Floor(value.x * factor);
|
||||
value.y = MathE.Floor(value.y * factor);
|
||||
value.z = MathE.Floor(value.z * factor);
|
||||
value.w = (int)(value.w * factor);
|
||||
value.x = (int)(value.x * factor);
|
||||
value.y = (int)(value.y * factor);
|
||||
value.z = (int)(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;
|
||||
@ -188,10 +179,6 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
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) =>
|
||||
new Int4(MathE.Lerp(a.w, b.w, t, clamp),
|
||||
MathE.Lerp(a.x, b.x, t, clamp),
|
||||
@ -265,21 +252,6 @@ namespace Nerd_STF.Mathematics
|
||||
public string ToString(string format) => $"({w.ToString(format)}, {x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})";
|
||||
|
||||
public int[] ToArray() => new int[] { w, x, y, z };
|
||||
public Fill<int> ToFill()
|
||||
{
|
||||
Int4 copy = this;
|
||||
return delegate (int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return copy.w;
|
||||
case 1: return copy.x;
|
||||
case 2: return copy.y;
|
||||
case 3: return copy.z;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<int> ToList() => new List<int> { w, x, y, z };
|
||||
|
||||
public static Int4 operator +(Int4 a, Int4 b) => new Int4(a.w + b.w, a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
@ -300,13 +272,8 @@ namespace Nerd_STF.Mathematics
|
||||
public static explicit operator Int4(Float2 floats) => new Int4(0, (int)floats.x, (int)floats.y, 0);
|
||||
public static explicit operator Int4(Float3 floats) => new Int4(0, (int)floats.x, (int)floats.y, (int)floats.z);
|
||||
public static explicit operator Int4(Float4 floats) => new Int4((int)floats.w, (int)floats.x, (int)floats.y, (int)floats.z);
|
||||
public static explicit operator Int4(Numbers.Quaternion quat) => new Int4((int)quat.w, (int)quat.x, (int)quat.y, (int)quat.z);
|
||||
public static explicit operator Int4(ListTuple<double> tuple) => new Int4((int)tuple[0], (int)tuple[1], (int)tuple[2], (int)tuple[3]);
|
||||
public static implicit operator Int4(ListTuple<int> tuple) => new Int4(tuple[0], tuple[1], tuple[2], tuple[3]);
|
||||
public static implicit operator Int4((int, int, int, int) tuple) => new Int4(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,15 +10,36 @@ namespace Nerd_STF.Mathematics
|
||||
{
|
||||
public static class MathE
|
||||
{
|
||||
public static int Abs(int value) => value < 0 ? -value : value;
|
||||
public static double Abs(double value) => value < 0 ? -value : value;
|
||||
public static int Absolute(int value) => value < 0 ? -value : value;
|
||||
public static double Absolute(double value) => value < 0 ? -value : value;
|
||||
#if CS11_OR_GREATER
|
||||
public static T Abs<T>(T num) where T : INumber<T>
|
||||
public static T Absolute<T>(T num) where T : INumber<T>
|
||||
{
|
||||
return num < T.Zero ? -num : num;
|
||||
}
|
||||
#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)
|
||||
{
|
||||
int sum = 0;
|
||||
@ -57,7 +78,7 @@ namespace Nerd_STF.Mathematics
|
||||
public static double Average(IEquation equ, double lowerBound, double upperBound, double epsilon = 1e-3)
|
||||
{
|
||||
double sum = 0;
|
||||
double steps = Abs(upperBound - lowerBound) / epsilon;
|
||||
double steps = Absolute(upperBound - lowerBound) / epsilon;
|
||||
for (double x = lowerBound; x <= upperBound; x += epsilon) sum += equ[x];
|
||||
return sum / steps;
|
||||
}
|
||||
@ -214,9 +235,6 @@ namespace Nerd_STF.Mathematics
|
||||
public static IEquation DynamicIntegral(IEquation equ, IEquation lower, IEquation upper) =>
|
||||
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.
|
||||
public static BigInteger FactorialBig(int num)
|
||||
{
|
||||
@ -342,8 +360,22 @@ namespace Nerd_STF.Mathematics
|
||||
return -1; // Will only get here if there are negative numbers in the collection.
|
||||
}
|
||||
|
||||
public static double InverseSqrt(double num) => 1 / Sqrt(num);
|
||||
public static unsafe float InverseSqrtFast(float num)
|
||||
public static unsafe double InverseSqrt(double 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*)#
|
||||
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
|
||||
// has changed (ported), but the idea is exactly the
|
||||
@ -374,24 +406,6 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
public static IEquation Lerp(IEquation a, IEquation b, double t, bool clamp = true) =>
|
||||
new Equation((double x) => Lerp(a.Get(x), b.Get(x), t, clamp));
|
||||
public static Fill<double> Lerp(Fill<double> a, Fill<double> b, double t, bool clamp = true)
|
||||
{
|
||||
if (clamp) Clamp(ref t, 0, 1);
|
||||
return delegate (int index)
|
||||
{
|
||||
double aVal = a(index);
|
||||
return aVal + t * (b(index) - aVal);
|
||||
};
|
||||
}
|
||||
public static Fill<int> Lerp(Fill<int> a, Fill<int> b, double t, bool clamp = true)
|
||||
{
|
||||
if (clamp) Clamp(ref t, 0, 1);
|
||||
return delegate (int index)
|
||||
{
|
||||
int aVal = a(index);
|
||||
return (int)(aVal + t * (b(index) - aVal));
|
||||
};
|
||||
}
|
||||
#if CS11_OR_GREATER
|
||||
public static T Lerp<T>(T a, T b, T t, bool clamp = true)
|
||||
where T : INumber<T>
|
||||
@ -399,20 +413,6 @@ namespace Nerd_STF.Mathematics
|
||||
if (clamp) Clamp(ref t, T.Zero, T.One);
|
||||
return a + t * (b - a);
|
||||
}
|
||||
public static T Lerp<T>(T a, T b, double t, bool clamp = true)
|
||||
where T : IInterpolable<T> => T.Lerp(a, b, t, clamp);
|
||||
public static Fill<T> Lerp<T>(Fill<T> a, Fill<T> b, T t, bool clamp = true)
|
||||
where T : INumber<T>
|
||||
{
|
||||
if (clamp) Clamp(ref t, T.Zero, T.One);
|
||||
return delegate (int index)
|
||||
{
|
||||
T aVal = a(index);
|
||||
return aVal + t * (b(index) - aVal);
|
||||
};
|
||||
}
|
||||
public static Fill<T> Lerp<T>(Fill<T> a, Fill<T> b, double t, bool clamp = true)
|
||||
where T : IInterpolable<T> => (int index) => T.Lerp(a(index), b(index), t, clamp);
|
||||
#endif
|
||||
|
||||
public static int Max(int a, int b) => a > b ? a : b;
|
||||
@ -435,7 +435,7 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
else if (val > best) best = val;
|
||||
}
|
||||
return best;
|
||||
return any ? best : 0;
|
||||
}
|
||||
public static double Max(IEnumerable<double> values)
|
||||
{
|
||||
@ -488,7 +488,7 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
else if (val < best) best = val;
|
||||
}
|
||||
return best;
|
||||
return any ? best : 0;
|
||||
}
|
||||
public static double Min(IEnumerable<double> values)
|
||||
{
|
||||
@ -521,27 +521,6 @@ namespace Nerd_STF.Mathematics
|
||||
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 int Npr(int n, int r) => (int)Factorial(n - r + 1, n);
|
||||
|
||||
@ -644,13 +623,6 @@ namespace Nerd_STF.Mathematics
|
||||
public static IEquation Round(IEquation equ) =>
|
||||
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)
|
||||
{
|
||||
bool flip = false;
|
||||
@ -680,46 +652,30 @@ namespace Nerd_STF.Mathematics
|
||||
|
||||
return flip ? result : -result;
|
||||
}
|
||||
public static double Sin(Angle angle, int terms = 8) =>
|
||||
Sin(angle.Radians, terms);
|
||||
public static IEquation Sin(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Sin(inputRad[x], terms));
|
||||
public static double Cos(double rad, int terms = 8) =>
|
||||
Sin(rad + Constants.HalfPi, terms);
|
||||
public static double Cos(Angle angle, int terms = 8) =>
|
||||
Cos(angle.Radians, terms);
|
||||
public static IEquation Cos(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Cos(inputRad[x], terms));
|
||||
public static double Tan(double rad, int terms = 8) =>
|
||||
Sin(rad + Constants.HalfPi, terms) / Sin(rad, terms);
|
||||
public static double Tan(Angle angle, int terms = 8) =>
|
||||
Tan(angle.Radians, terms);
|
||||
public static IEquation Tan(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Tan(inputRad[x], terms));
|
||||
public static double Csc(double rad, int terms = 8) =>
|
||||
1 / Sin(rad, terms);
|
||||
public static double Csc(Angle angle, int terms = 8) =>
|
||||
Csc(angle.Radians, terms);
|
||||
public static IEquation Csc(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Csc(inputRad[x], terms));
|
||||
public static double Sec(double rad, int terms = 8) =>
|
||||
1 / Sin(rad + Constants.HalfPi, terms);
|
||||
public static double Sec(Angle angle, int terms = 8) =>
|
||||
Sec(angle.Radians, terms);
|
||||
public static IEquation Sec(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Sec(inputRad[x], terms));
|
||||
public static double Cot(double rad, int terms = 8) =>
|
||||
Sin(rad, terms) / Sin(rad + Constants.HalfPi, terms);
|
||||
public static double Cot(Angle angle, int terms = 8) =>
|
||||
Cot(angle.Radians, terms);
|
||||
public static IEquation Cot(IEquation inputRad, int terms = 8) =>
|
||||
new Equation((double x) => Cot(inputRad[x], terms));
|
||||
|
||||
// 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 double Sqrt(double num) => 1 / InverseSqrt(num); // !!TODO!!: Bring back Newton's
|
||||
public static IEquation Sqrt(IEquation equ) =>
|
||||
new Equation((double x) => Sqrt(equ.Get(x)));
|
||||
|
||||
@ -746,12 +702,6 @@ namespace Nerd_STF.Mathematics
|
||||
}
|
||||
#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)
|
||||
{
|
||||
IEquation current = equ;
|
||||
|
||||
@ -1,511 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,548 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,336 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,42 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!-- General stuff -->
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.1;netstandard1.3;netstandard2.1;net46;net462;net47;netcoreapp3.0;net5.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.1;netstandard1.3;netstandard2.1;netcoreapp3.0;net5.0;net7.0</TargetFrameworks>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
|
||||
<DebugType>portable</DebugType>
|
||||
<DebugType>embedded</DebugType>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<CheckNotRecommendedTargetFramework>false</CheckNotRecommendedTargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- NuGet package customization. -->
|
||||
|
||||
<PropertyGroup>
|
||||
<Title>Nerd_STF</Title>
|
||||
<Version>3.0.0</Version>
|
||||
<Version>3.0.0-beta1</Version>
|
||||
<Authors>That_One_Nerd</Authors>
|
||||
<Description>A general-purpose mathematics library for C#.</Description>
|
||||
<PackageProjectUrl>https://github.com/That-One-Nerd/Nerd_STF</PackageProjectUrl>
|
||||
<PackageIcon>Logo Square.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<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>
|
||||
|
||||
<!-- Sorry this is stupidly long, wish I could have linked a markdown file instead. -->
|
||||
<PackageReleaseNotes># Nerd_STF Version 3.0
|
||||
<PackageReleaseNotes># Nerd_STF v3.0-beta1
|
||||
|
||||
It's time to get this thing out of beta.
|
||||
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 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
|
||||
In the mean time, here's what's new.
|
||||
|
||||
## More Compatibility
|
||||
|
||||
@ -137,181 +120,81 @@ 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!**
|
||||
|
||||
## 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<double> vals1 = wxyz["xy"]; // Yields [2, 3]
|
||||
|
||||
Float2 vals2 = wxyz["xy"]; // Not allowed!
|
||||
```
|
||||
|
||||
And that kind of sucked. So I created the `ListTuple<T>` type. It's job is to act like a regular tuple, but be able to impliclty convert to either an `IEnumerable` or a regular `ValueTuple<T>`, thus allowing conversions to the double and int groups indirectly. Now, all combination indexers return a `ListTuple` instead of an `IEnumerable`.
|
||||
|
||||
Under the hood, the `ListTuple` actually uses an array, but you get the idea.
|
||||
|
||||
```csharp
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
ListTuple<double> vals1 = wxyz["xy"]; // Yields (2, 3)
|
||||
|
||||
Float2 vals2 = vals1; // Yields (2, 3)
|
||||
IEnumerable<double> vals3 = vals1; // Yields [2, 3]
|
||||
```
|
||||
|
||||
Problem is, now the names have the potential to make much less sense.
|
||||
```csharp
|
||||
Float4 wxyz = (1, 2, 3, 4);
|
||||
Float2 xy = wxyz["xy"]; // x <- x, y <- y
|
||||
Float2 wz = wxyz["wz"]; // x <- w, y <- z
|
||||
```
|
||||
|
||||
But whatever. You can always stick to using `IEnumerable`s if you want.
|
||||
|
||||
## No More `*.Abstract`
|
||||
|
||||
I got rid of all the `Abstract` namespaces, since they don't really make much sense in the grand scheme of things. They've all been moved to the namespace that applies to them most (eg. `INumberGroup` went to `Nerd_STF.Mathematics`, `ICombinationIndexer` went to `Nerd_STF` since it applies to more than just mathematics).
|
||||
|
||||
## The `Fraction` Type
|
||||
|
||||
This type originally went under the name of `Rational` in Nerd_STF 2.x, but that name is actually incorrect, right? So in the rework, it changed names. But it also can do much more now thanks to the `INumber` interface added in .NET 7.0. If you're using that framework or above, the fraction type is fully compatible with that type, and all the math functions in `MathE` and elsewhere that use `INumber` will work with it.
|
||||
|
||||
Can I just say that the `INumber` interface is really annoying to write a type for? There's so many weird casting functions and a whole lot of methods that even the .NET developers will hide in public declarations. Why have them at all?
|
||||
|
||||
## And 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<T>` and `Fill2d<T>` are back!
|
||||
|
||||
I thought the `Fill<T>` delegate was slightly redundant, since you could probably just pass an `IEnumerable` or something instead, but I've learned that it's really easy to pass a Fill delegate in places where an IEnumerable would be annoying. I might add support for Fill stuff in the future, such as an extension list, but for now I've just re-added the delegate and made most types support it in a constructor.
|
||||
|
||||
## And Best of All: Colors!
|
||||
|
||||
I have had plenty of time to think about how I could have done colors better in the previous iteration of the library, and I've come up with this, which I think is slightly better.
|
||||
|
||||
First of all, colors derive from the `IColor<TSelf>` interface similarly to how they did before, but no more `IColorFloat`. Now, every color has double-precision channels by default. To handle specific bit sizes, the `IColorFormat` interface has been created. It can of course be derived from, and I think it's pretty easy to use and understand, but hopefully there will be enough color formats already defined that you won't even need to touch it directly. At the moment, there's only one real color format created, `R8G8B8A8`, which is what it sounds like: 8 bits for each of the RGBA channels. There will be plenty more to come.
|
||||
|
||||
I have been thinking about writing a stream class that is capable of having a bit-offset. I would use it in tandom with the color formats, as many of them span multiple bytes in ways that don't always align with 8-bit bytes. It seems somewhat out of place, but I think I'll go for it anyway.
|
||||
|
||||
There's also a color palette system now. You give it a certain number of colors and it allocates room to the nearest power of two. If you give it 6 colors, it allocates room for 8. This is to always keep the size of the palette identical to its bit depth. 6 colors needs 3 bits per color, so might as well do as much as you can with those 3 bits.
|
||||
|
||||
There is also an `IndexedColor` "format," which does not store its color directly. Rather, it stores its index and a reference to the color palette it came from. I understand a true "indexed color" wouldn't store a reference to its palette to save memory, but this is mostly for ease of use. Colors are passed through methods with the `ref` keyword, so you can manipulate them directly.
|
||||
|
||||
```csharp
|
||||
void MethodA()
|
||||
{
|
||||
ColorPalette<ColorRGB> palette = new(8);
|
||||
|
||||
// palette[3] is currently set to black.
|
||||
MethodB(palette[3]);
|
||||
// palette[3] is now set to blue.
|
||||
}
|
||||
|
||||
void MethodB(IndexedColor<ColorRGB> color)
|
||||
{
|
||||
color.Color() = ColorRGB.Blue;
|
||||
|
||||
// You could also:
|
||||
ref ColorRGB val = ref color.Color();
|
||||
val = ColorRGB.Blue;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Anyway, that's a lot of stuff. It's a big update. You should download it!
|
||||
|
||||
Sorry my original writing has been a bit off. I kind of forgot about this project again. I can't promise a new update any time soon, but maybe there'll be one!
|
||||
|
||||
Enjoy the full release!</PackageReleaseNotes>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- ItemGroup customization based on framework. -->
|
||||
<!-- 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>
|
||||
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>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<IncludeSymbols>True</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);CS8_OR_GREATER</DefineConstants>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net46'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net47'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='netcoreapp3.0'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);CS8_OR_GREATER</DefineConstants>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net5.0'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER;CS9_OR_GREATER</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);CS8_OR_GREATER;CS9_OR_GREATER</DefineConstants>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net7.0'">
|
||||
<DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER;CS9_OR_GREATER;CS10_OR_GREATER;CS11_OR_GREATER</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);CS10_OR_GREATER;CS11_OR_GREATER;CS8_OR_GREATER;CS9_OR_GREATER</DefineConstants>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Pack extra stuff into the NuGet package. -->
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard1.1|AnyCPU'">
|
||||
<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>
|
||||
<None Include="..\Extras\Logo Square.png">
|
||||
@ -322,14 +205,11 @@ Enjoy the full release!</PackageReleaseNotes>
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Include="..\Changelog.md">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Include="..\LICENSE.md">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Primitives" Version="4.3.0" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user