Compare commits

...

31 Commits

Author SHA1 Message Date
32df5fdd79 Some docs. 2023-11-27 13:37:39 -05:00
eee49b80a6 Setup. 2023-11-16 10:22:34 -05:00
3682aaacf8 Ellipse stuff. Some of this is old and I forgot my train of thought here. 2023-11-16 10:04:55 -05:00
209a310363 Changed the version. 2023-10-26 15:34:30 -04:00
7484195f97 Lots more stuff. Finished Box2d. 2023-10-26 15:08:46 -04:00
6f42afa15e Added quadrilateral code among other things. 2023-09-21 12:59:48 -04:00
593d55ea28 Helpers shouldn't be public. 2023-09-11 12:52:14 -04:00
35f52f494c Fixed a bunch of errors I caused :) 2023-09-11 10:14:46 -04:00
809660d58e Buncha changes. Removed subtract, sum, product, divide, and did other stuff. 2023-09-11 08:50:29 -04:00
c1570b74e1 Isometrics work now. 2023-09-05 13:54:33 -04:00
99b3185f38 All matrix types are no longer records. 2023-09-05 06:52:02 -04:00
8b4e61ce82 Partial commit: Some projection matrix stuff. 2023-09-05 04:59:52 -04:00
d58ad7c94f Finished the triangle type and erased the changelog (I'll do it at the end of the update). 2023-08-28 13:06:16 -04:00
0e0922e236 Pretty sure I'm done with triangle. Changelog next 2023-08-28 12:46:35 -04:00
63631632fe Some improvements to the line and a bunch of changelog additions. 2023-08-24 07:59:07 -04:00
5d691bedb7 More improvements to the triangle. WIP, doesn't compile. 2023-08-22 14:05:10 -04:00
58ac950068 Some more triangle stuff (WIP) 2023-08-22 08:57:25 -04:00
16c067ec3e Made some triangle progress. DOESN'T COMPILE 2023-08-21 14:00:44 -04:00
f685517d9e Wrote one of the final drafts for the Line class. 2023-08-21 10:33:43 -04:00
2278d16292 Much better system in the IPolygon interface now. 2023-08-07 16:03:50 -04:00
2ca17a4a64 Quickie. Some additions to the IPolygon interface. 2023-08-07 15:33:19 -04:00
d4250f83a9 Deleted all the geometry classes. We will be starting over completely. 2023-08-07 15:18:10 -04:00
631ee0ea92 Added coterminal angles. 2023-08-06 19:16:30 -04:00
3ee5f9b811 Using dot product to calculate angle from three verts. 2023-07-26 09:44:32 -04:00
31609253f6 Added the InverseMagnitude property to number group types. 2023-07-26 09:38:11 -04:00
950b67dde1 whoops, forgor to change this 2023-07-24 20:45:06 -04:00
89d52381ab finished the changelog and fixed some blunders 2023-07-24 20:10:44 -04:00
1517f317e7 Partial commit. Code is finished btu I need to complete the changelog. 2023-07-24 18:52:27 -04:00
4093fd6538 Made the 'd' lowercase in Box2D and Box3D 2023-07-24 18:35:25 -04:00
30509e0e7d A quickie. Added the Quake 3 Inverse Sqrt method. 2023-07-18 13:24:22 -04:00
f3db47e47f Found a bug in 2.4.1 :(. Not big enough to call for 2.4.2 though. 2023-07-18 08:42:52 -04:00
70 changed files with 2636 additions and 2180 deletions

View File

@ -1,118 +1,9 @@
# Nerd_STF v2.4.1 # Nerd_STF v2.5.0
Hey everyone! This is one of the larger small updates, and I'm pretty proud of what I got done in a week. TODO:
- added better inv sqrt
Along with adding setters to parts like `Float3.XY` and fixing a few bugs, almost all improvements in this update are related to matricies. First of all, I've added a bunch of new items to the `IMatrix` interface. Now, any deriving matrix has more requirements that fit the regular `Matrix` type. I don't know why one would use the `IMatrix` interface rather than a specific matrix type, but now the options are more sophisticated.
I've added some new stuff to all the matrix types, including row operations. You can now scale a row, add a row to another, and swap two rows. If I become aware of any more commonly-used row operations, I'll add then in a `2.4.2` update. But I think I've got all the good ones. There is also a mutable version of each operation which, rather than returning a new matrix with changes made, instead applies the changes to itself.
Did you know I made two seperate blunders in the `Cofactor()` method? For the `Matrix2x2` version of the `Cofactor()` method, I had the diagonal elements swapped. Whoops. For the `Matrix` version of the `Cofactor()` method, matricies with even column count would break because of the alternating sign pattern I was using. Now, as far as I know, that bug is fixed.
The last thing I did was add the ability to turn a matrix into its equivalent row-echelon form. This is applicable only to the `Matrix` type (the dynamic one), and works with some levels of success. It's a little weird and tends to give results with lots of negative zeroes, but overall it's fine, I think. As far as I know there aren't any obvious bugs. We'll see though.
Anyway, that's everything in this update. Again, pretty small, but meaningful nonetheless. Unless I haven't screwed anything up, the next update I work on will be `2.5`, so I'll see you then!
Here's the full changelog: Here's the full changelog:
``` ```
* Nerd_STF I'm gonna do this towards the end of the update (because it's boring and I think I've accidentally missed stuff already).
* Mathematics
* Abstract
* IMatrix
+ AddRow(int, int, float)
+ AddRowMutable(int, int, float)
+ Cofactor()
+ GetColumn(int)
+ GetRow(int)
+ ScaleRow(int, float)
+ ScaleRowMutable(int, float)
+ SetColumn(int, float[])
+ SetRow(int, float[])
+ Size
+ SwapRows(int, int)
+ SwapRowsMutable(int, int)
+ this[int, int]
+ this[Index, Index]
* Algebra
* Matrix
+ AddRow(int, int, float)
+ AddRowMutable(int, int, float)
+ ScaleRow(int, float)
+ ScaleRowMutable(int, float)
+ SwapRows(int, int)
+ SwapRowsMutable(int, int)
= Fixed a blunder in `SignGrid(Int2)` with signs being incorrectly placed on matrixes with even column count.
* Matrix2x2
+ AddRow(int, int, float)
+ AddRowMutable(int, int, float)
+ GetColumn(int)
+ GetRow(int)
+ ScaleRow(int, float)
+ ScaleRowMutable(int, float)
+ SetColumn(int, float[])
+ SetRow(int, float[])
+ Size
+ SwapRows(int, int)
+ SwapRowsMutable(int, int)
= Fixed a blunder in `Cofactor()` with the position of elements.
* Matrix3x3
+ AddRow(int, int, float)
+ AddRowMutable(int, int, float)
+ GetColumn(int)
+ GetRow(int)
+ ScaleRow(int, float)
+ ScaleRowMutable(int, float)
+ SetColumn(int, float[])
+ SetRow(int, float[])
+ Size
+ SwapRows(int, int)
+ SwapRowsMutable(int, int)
* Matrix4x4
+ AddRow(int, int, float)
+ AddRowMutable(int, int, float)
+ GetColumn(int)
+ GetRow(int)
+ ScaleRow(int, float)
+ ScaleRowMutable(int, float)
+ SetColumn(int, float[])
+ SetRow(int, float[])
+ Size
+ SwapRows(int, int)
+ SwapRowsMutable(int, int)
* NumberSystems
* Complex
+ operator Complex(SystemComplex)
+ operator SystemComplex(Complex)
* Quaternion
+ operator Quaternion(SystemQuaternion)
+ operator SystemQuaternion(Quaternion)
* Float3
= Added a setter to `XY`
= Added a setter to `XZ`
= Added a setter to `YZ`
* Float4
= Added a setter to `XW`
= Added a setter to `XY`
= Added a setter to `XZ`
= Added a setter to `YW`
= Added a setter to `YZ`
= Added a setter to `ZW`
= Added a setter to `XYW`
= Added a setter to `XYZ`
= Added a setter to `XZW`
= Added a setter to `YZW`
* Int3
= Added a setter to `XY`
= Added a setter to `XZ`
= Added a setter to `YZ`
* Int4
= Added a setter to `XW`
= Added a setter to `XY`
= Added a setter to `XZ`
= Added a setter to `YW`
= Added a setter to `YZ`
= Added a setter to `ZW`
= Added a setter to `XYW`
= Added a setter to `XYZ`
= Added a setter to `XZW`
= Added a setter to `YZW`
``` ```

View File

@ -0,0 +1,8 @@
namespace Nerd_STF;
public enum CrossSection2d
{
XY,
YZ,
ZX
}

View File

@ -0,0 +1,21 @@
namespace Nerd_STF.Extensions;
public class AspectLockedException : Nerd_STFException
{
public readonly object? ReferencedObject;
public AspectLockedException() : base("This object has a locked aspect ratio.") { }
public AspectLockedException(string message) : base(message) { }
public AspectLockedException(string message, Exception inner) : base(message, inner) { }
public AspectLockedException(string message, object? obj) : base(message)
{
ReferencedObject = obj;
}
public AspectLockedException(string message, Exception inner, object? obj)
: base(message, inner)
{
ReferencedObject = obj;
}
protected AspectLockedException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
}

View File

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

View File

@ -0,0 +1,10 @@
namespace Nerd_STF.Extensions;
public class NotAlignedException : Nerd_STFException
{
public NotAlignedException() : base("Points are not aligned.") { }
public NotAlignedException(string message) : base(message) { }
public NotAlignedException(string message, Exception inner) : base(message, inner) { }
protected NotAlignedException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
}

View File

@ -20,6 +20,9 @@ public static class EquationExtension
public static Equation Absolute(this Equation equ) => x => Mathf.Absolute(equ(x)); public static Equation Absolute(this Equation equ) => x => Mathf.Absolute(equ(x));
public static Equation AbsoluteMod(this Equation equ, float mod) => x => Mathf.AbsoluteMod(equ(x), mod); public static Equation AbsoluteMod(this Equation equ, float mod) => x => Mathf.AbsoluteMod(equ(x), mod);
public static Equation Add(this Equation equ, float offset) => x => equ(x) + offset;
public static Equation Add(this Equation equ, Equation offset) => x => equ(x) + offset(x);
public static Equation ArcCos(this Equation equ) => x => Mathf.ArcCos(equ(x)).Radians; public static Equation ArcCos(this Equation equ) => x => Mathf.ArcCos(equ(x)).Radians;
public static Equation ArcCot(this Equation equ) => x => Mathf.ArcCot(equ(x)).Radians; public static Equation ArcCot(this Equation equ) => x => Mathf.ArcCot(equ(x)).Radians;
public static Equation ArcCsc(this Equation equ) => x => Mathf.ArcCsc(equ(x)).Radians; public static Equation ArcCsc(this Equation equ) => x => Mathf.ArcCsc(equ(x)).Radians;
@ -65,14 +68,8 @@ public static class EquationExtension
public static Equation Coth(this Equation equ) => x => Mathf.Coth(equ(x)); public static Equation Coth(this Equation equ) => x => Mathf.Coth(equ(x));
public static Equation Csch(this Equation equ) => x => Mathf.Csch(equ(x)); public static Equation Csch(this Equation equ) => x => Mathf.Csch(equ(x));
public static Equation Divide(this Equation equ, params float[] dividends) => public static Equation Divide(this Equation equ, float factor) => x => equ(x) / factor;
x => Mathf.Divide(equ(x), dividends); public static Equation Divide(this Equation equ, Equation factor) => x => equ(x) / factor(x);
public static Equation Divide(this Equation equ, params Equation[] dividends) => delegate (float x)
{
float[] dividendsAtValue = new float[dividends.Length];
for (int i = 0; i < dividends.Length; i++) dividendsAtValue[i] = dividends[i](x);
return Mathf.Divide(equ(x), dividendsAtValue);
};
public static Equation Factorial(this Equation equ) => x => Mathf.Factorial((int)equ(x)); public static Equation Factorial(this Equation equ) => x => Mathf.Factorial((int)equ(x));
@ -107,6 +104,9 @@ public static class EquationExtension
public static float Min(this Equation equ, float min, float max, float step = Calculus.DefaultStep) => public static float Min(this Equation equ, float min, float max, float step = Calculus.DefaultStep) =>
Mathf.Min(equ, min, max, step); Mathf.Min(equ, min, max, step);
public static Equation Multiply(this Equation equ, float factor) => x => equ(x) * factor;
public static Equation Multiply(this Equation equ, Equation factor) => x => equ(x) * factor(x);
public static Equation Permutations(this Equation equ, int size) => public static Equation Permutations(this Equation equ, int size) =>
x => Mathf.Permutations(size, (int)equ(x)); x => Mathf.Permutations(size, (int)equ(x));
public static Equation Permutations(this Equation equ, Equation size) => public static Equation Permutations(this Equation equ, Equation size) =>
@ -115,20 +115,8 @@ public static class EquationExtension
public static Equation Power(this Equation equ, float pow) => x => Mathf.Power(equ(x), pow); public static Equation Power(this Equation equ, float pow) => x => Mathf.Power(equ(x), pow);
public static Equation Power(this Equation equ, Equation pow) => x => Mathf.Power(equ(x), pow(x)); public static Equation Power(this Equation equ, Equation pow) => x => Mathf.Power(equ(x), pow(x));
public static Equation Product(this Equation equ, params float[] vals) => delegate (float x) public static float Product(this Equation equ, float lower, float upper, float step = 1) =>
{ Mathf.Product(equ, lower, upper, step);
float[] valsAtValue = new float[vals.Length + 1];
valsAtValue[0] = equ(x);
for (int i = 0; i < vals.Length; i++) valsAtValue[i + 1] = vals[i];
return Mathf.Product(valsAtValue);
};
public static Equation Product(this Equation equ, params Equation[] vals) => delegate (float x)
{
float[] valsAtValue = new float[vals.Length + 1];
valsAtValue[0] = equ(x);
for (int i = 0; i < vals.Length; i++) valsAtValue[i + 1] = vals[i](x);
return Mathf.Product(valsAtValue);
};
public static Equation Root(this Equation equ, float index) => x => Mathf.Root(equ(x), index); public static Equation Root(this Equation equ, float index) => x => Mathf.Root(equ(x), index);
public static Equation Root(this Equation equ, Equation index) => x => Mathf.Root(equ(x), index(x)); public static Equation Root(this Equation equ, Equation index) => x => Mathf.Root(equ(x), index(x));
@ -153,29 +141,11 @@ public static class EquationExtension
public static Equation Sqrt(this Equation equ) => x => Mathf.Sqrt(equ(x)); public static Equation Sqrt(this Equation equ) => x => Mathf.Sqrt(equ(x));
public static Equation Subtract(this Equation equ, params float[] vals) => public static Equation Subtract(this Equation equ, float offset) => x => equ(x) - offset;
x => Mathf.Subtract(equ(x), vals); public static Equation Subtract(this Equation equ, Equation offset) => x => equ(x) - offset(x);
public static Equation Subtract(this Equation equ, params Equation[] vals) => delegate (float x)
{
float[] valsAtValue = new float[vals.Length];
for (int i = 0; i < vals.Length; i++) valsAtValue[i] = vals[i](x);
return Mathf.Subtract(equ(x), valsAtValue);
};
public static Equation Sum(this Equation equ, params float[] vals) => delegate (float x) public static float Sum(this Equation equ, float lower, float upper, float step = 1) =>
{ Mathf.Sum(equ, lower, upper, step);
float[] valsAtValue = new float[vals.Length + 1];
valsAtValue[0] = equ(x);
for (int i = 0; i < vals.Length; i++) valsAtValue[i + 1] = vals[i];
return Mathf.Sum(valsAtValue);
};
public static Equation Sum(this Equation equ, params Equation[] vals) => delegate (float x)
{
float[] valsAtValue = new float[vals.Length + 1];
valsAtValue[0] = equ(x);
for (int i = 0; i < vals.Length; i++) valsAtValue[i + 1] = vals[i](x);
return Mathf.Sum(valsAtValue);
};
public static Equation Tan(this Equation equ) => x => Mathf.Tan(equ(x)); public static Equation Tan(this Equation equ) => x => Mathf.Tan(equ(x));

View File

@ -11,7 +11,7 @@
// to which we know the values of each part. // to which we know the values of each part.
// Then we just do this iteratively on a bunch // Then we just do this iteratively on a bunch
// of powers of 2. // of powers of 2.
public static class CordicHelper internal static class CordicHelper
{ {
private static readonly float[] p_cosTable = private static readonly float[] p_cosTable =
{ {

View File

@ -0,0 +1,190 @@
using System;
using System.Numerics;
namespace Nerd_STF.Helpers;
internal static class GeometryHelper
{
public static Float2 Box2dAlongRay(in Box2d box, Float2 p)
{
// This is an approximate system. It's good enough for most purposes
// but maybe not all of them. Basically it just makes a ray through the point
// and sees where it intersects the box.
if (box.Contains(p)) return p;
Float2 c = box.center, m1 = box.Max, m2 = box.Min;
// Calculates the coordinates of the ray along the points `p` and `c`
// for both a reference `x` and a reference `y`.
float rayRefX(float x) => c.y - (p.y - c.y) * (c.x - x) / (p.x - c.x);
float rayRefY(float y) => c.x - (p.x - c.x) * (c.y - y) / (p.y - c.y);
// Calculates all possible intersections for the rectangle at once, then
// eliminates invalid options.
float xSol1 = (m1.y - c.y) * (p.x - c.x) / (p.y - c.y) + c.x,
xSol2 = (m2.y - c.y) * (p.x - c.x) / (p.y - c.y) + c.x,
ySol1 = (m1.x - c.x) * (p.y - c.y) / (p.x - c.x) + c.y,
ySol2 = (m2.x - c.x) * (p.y - c.y) / (p.x - c.x) + c.y;
const float tolerance = 0.0005f;
Float2 option1, option2;
float dist1, dist2;
if (float.IsNormal(xSol1) && m1.x - xSol1 >= -tolerance && xSol1 - m2.x >= -tolerance)
{
// The valid solutions are xSol1 and xSol2.
option1 = (xSol1, rayRefX(xSol1));
option2 = (xSol2, rayRefX(xSol2));
}
else
{
// The valid solutions are ySol1 and ySol2.
option1 = (rayRefY(ySol1), ySol1);
option2 = (rayRefY(ySol2), ySol2);
}
dist1 = (p - option1).Magnitude;
dist2 = (p - option2).Magnitude;
// Pick the closest option.
if (dist1 < dist2) return option1;
else return option2;
}
public static bool LineIntersects(in Line a, in Line b) =>
LineIntersects2d(a, b, CrossSection2d.XY) &&
LineIntersects2d(a, b, CrossSection2d.YZ) &&
LineIntersects2d(a, b, CrossSection2d.ZX);
public static bool LineIntersects2d(in Line a, in Line b, CrossSection2d plane)
{
Float2 p1 = a.a.GetCrossSection(plane), q1 = a.b.GetCrossSection(plane),
p2 = b.a.GetCrossSection(plane), q2 = b.b.GetCrossSection(plane);
OrientationType o1 = GetOrientation(p1, q1, p2),
o2 = GetOrientation(p1, q1, q2),
o3 = GetOrientation(p2, q2, p1),
o4 = GetOrientation(p2, q2, q1);
return (o1 != o2 && o3 != o4) ||
(o1 == OrientationType.Colinear && PointOnSegmentCo(p1, p2, q1)) ||
(o2 == OrientationType.Colinear && PointOnSegmentCo(p1, q2, q1)) ||
(o3 == OrientationType.Colinear && PointOnSegmentCo(p2, p1, q2)) ||
(o4 == OrientationType.Colinear && PointOnSegmentCo(p2, q1, q2));
}
public static float EllipsePerimeterInfiniteSeries(in Ellipse ellipse, int steps)
{
// Not gonna write out the infinite series because it's pretty big.
float a = ellipse.Radius.x,
e = ellipse.Eccentricity;
decimal sumPart = 0;
for (int i = 1; i <= steps; i++)
{
BigInteger num = Mathf.Factorial(2 * i);
num = num * num;
BigInteger den = Mathf.Power(2, i);
den *= Mathf.Factorial(i);
den = den * den * den * den;
den *= 2 * i - 1;
Rational ePart = Rational.FromFloat(Mathf.Power(e, 2 * i));
num *= ePart.numerator;
den *= ePart.denominator;
num *= 1_000_000_000;
sumPart += (decimal)(num / den) / 1_000_000_000;
}
return 2 * a * Constants.Pi * (1 - (float)sumPart);
}
public static float EllipsePerimeterParker1(in Ellipse ellipse)
{
// pi*( 53a/3 + 717b/35 - sqrt( 269a^2 + 667ab + 371b^2 ) )
// a must be larger than b
float a = float.Max(ellipse.Radius.x, ellipse.Radius.y),
b = float.Min(ellipse.Radius.x, ellipse.Radius.y);
float part1 = 53 * a / 3,
part2 = 717 * b / 35,
part3 = Mathf.Sqrt(269 * a * a + 667 * a * b + 371 * b * b);
return Constants.Pi * (part1 + part2 - part3);
}
public static float EllipsePerimeterParker2(in Ellipse ellipse)
{
// pi*( 6a/5 + 3b/4 )
// a must be larger than b
float a = float.Max(ellipse.Radius.x, ellipse.Radius.y),
b = float.Min(ellipse.Radius.x, ellipse.Radius.y);
return Constants.Pi * ((6 * a / 5) + (3 * b / 4));
}
public static float EllipsePerimeterRamanujan1(in Ellipse ellipse)
{
// pi*( 3*(a+b) - sqrt( (3a + b)(a + 3b) ) )
float a = ellipse.Radius.x,
b = ellipse.Radius.y;
float sqrtPart = Mathf.Sqrt((3 * a + b) * (a + 3 * b));
return Constants.Pi * (3 * (a + b) - sqrtPart);
}
public static float EllipsePerimeterRamanujan2(in Ellipse ellipse)
{
// pi*(a+b)*( 1 + 3h/(10 + sqrt(4 - 3h)) )
float a = ellipse.Radius.x,
b = ellipse.Radius.y,
h = ellipse.H;
float part1 = a + b,
part2a = Mathf.Sqrt(4 - 3 * h),
part2b = 10 + part2a,
part2 = 3 * h / part2b;
return Constants.Pi * part1 * (1 + part2);
}
private static bool PointOnSegmentCo(Float2 a, Float2 r, Float2 b)
{
// Just checks the box. Points must be colinear.
return r.x <= Mathf.Max(a.x, b.x) && r.x >= Mathf.Min(a.x, b.x) &&
r.y <= Mathf.Max(a.y, b.y) && r.y >= Mathf.Min(a.y, b.y);
}
private static OrientationType GetOrientation(Float2 a, Float2 b, Float2 c)
{
// Rotation type between the three points.
float rot = (b.y - a.y) * (c.x - b.x) -
(b.x - a.x) * (c.y - b.y);
if (rot > 0) return OrientationType.Clockwise;
else if (rot < 0) return OrientationType.CounterClockwise;
else return OrientationType.Colinear;
}
private enum OrientationType
{
Colinear,
Clockwise,
CounterClockwise
}
public static Triangle[] EllipseTriangulateFan(in Ellipse ellipse, float step)
{
Float2[] points = new Float2[(int)(1 / step)];
float position = 0;
for (int i = 0; i < points.Length; i++)
{
points[i] = ellipse.LerpAcrossOutline(position);
position += step;
}
Triangle[] tris = new Triangle[points.Length];
for (int i = 0; i < tris.Length; i++)
{
int i1 = i, i2 = (i + 1) % points.Length;
tris[i] = (ellipse.Position, points[i1], points[i2]);
}
return tris;
}
}

View File

@ -10,4 +10,23 @@ internal static unsafe class UnsafeHelper
where CT : unmanaged where CT : unmanaged
where NT : unmanaged where NT : unmanaged
=> *(NT*)&obj; => *(NT*)&obj;
// Fast square root thing. Uses the classic Quake 3 algorithm.
// I'll be preserving variable names and comments for funzies.
public static float Q_rsqrt(float number)
{
int i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( int * ) &y; // evil floating point bit hack
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, can be removed.
return y;
}
} }

View File

@ -1,6 +1,20 @@
namespace Nerd_STF.Mathematics.Abstract; namespace Nerd_STF.Mathematics.Abstract;
/// <summary>
/// An <see langword="interface"/> that can be derived from to implement
/// absolute value functionality. This interface includes one method:
/// <list type="bullet">
/// <see cref="Absolute(T)"/>
/// </list>
/// </summary>
/// <typeparam name="T">This type.</typeparam>
public interface IAbsolute<T> where T : IAbsolute<T> public interface IAbsolute<T> where T : IAbsolute<T>
{ {
/// <summary>
/// Calculate the positive value of <typeparamref name="T"/>.
/// I know, this isn't technically the "absolute" value but whatever.
/// </summary>
/// <param name="val">The value of <typeparamref name="T"/> to calculate the absolute value of.</param>
/// <returns>The positive vlaue of <typeparamref name="T"/>.</returns>
public static abstract T Absolute(T val); public static abstract T Absolute(T val);
} }

View File

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

View File

@ -0,0 +1,8 @@
namespace Nerd_STF.Mathematics.Abstract;
public interface IFloatArray<T> where T : IFloatArray<T>
{
public static abstract float[] ToFloatArrayAll(params T[] vals);
public float[] ToFloatArray();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,12 @@
using System.Numerics;
namespace Nerd_STF.Mathematics.Abstract;
public interface IWithinRange<TSub>
{
public bool WithinRange<TNumber>(TSub obj, TNumber range) where TNumber : INumber<TNumber>;
}
public interface IWithinRange<TSub, TNumber> where TNumber : INumber<TNumber>
{
public bool WithinRange(TSub obj, TNumber range);
}

View File

@ -1,8 +1,7 @@
namespace Nerd_STF.Mathematics.Abstract; namespace Nerd_STF.Mathematics.Algebra.Abstract;
public interface IMatrix<T> : IAbsolute<T>, ICeiling<T>, IClamp<T>, IDivide<T>, public interface IMatrix<T> : IAbsolute<T>, ICeiling<T>, IClamp<T>,
IEquatable<T>, IFloor<T>, IGroup2d<float>, ILerp<T, float>, IProduct<T>, IRound<T>, IEquatable<T>, IFloor<T>, IGroup2d<float>, ILerp<T, float>, IRound<T>
ISubtract<T>, ISum<T>
where T : IMatrix<T> where T : IMatrix<T>
{ {
public Int2 Size { get; } public Int2 Size { get; }
@ -28,6 +27,15 @@ public interface IMatrix<T> : IAbsolute<T>, ICeiling<T>, IClamp<T>, IDivide<T>,
public void ScaleRowMutable(int rowIndex, float value); public void ScaleRowMutable(int rowIndex, float value);
public T SwapRows(int rowA, int rowB); public T SwapRows(int rowA, int rowB);
public void SwapRowsMutable(int rowA, int rowB); public void SwapRowsMutable(int rowA, int rowB);
public static abstract T operator +(T a, T b);
public static abstract T? operator -(T m);
public static abstract T operator -(T a, T b);
public static abstract T operator *(T a, T b);
public static abstract T operator /(T a, T b);
public static abstract T operator ^(T a, T b);
public static abstract bool operator ==(T a, T b);
public static abstract bool operator !=(T a, T b);
} }
public interface IMatrix<This, TMinor> : IMatrix<This> where This : IMatrix<This, TMinor> public interface IMatrix<This, TMinor> : IMatrix<This> where This : IMatrix<This, TMinor>

View File

@ -1,4 +1,4 @@
namespace Nerd_STF.Mathematics.Abstract; namespace Nerd_STF.Mathematics.Algebra.Abstract;
public interface IMatrixPresets<T> where T : IMatrix<T>, IMatrixPresets<T> public interface IMatrixPresets<T> where T : IMatrix<T>, IMatrixPresets<T>
{ {

View File

@ -0,0 +1,9 @@
namespace Nerd_STF.Mathematics.Algebra.Abstract;
public interface IProjectionMatrix<TThis, TBaseMatrix, TDim> : IMatrix<TThis>
where TThis : IProjectionMatrix<TThis, TBaseMatrix, TDim>
where TBaseMatrix : IMatrix<TBaseMatrix>
where TDim : IGroup<float>
{
public Fill<TDim> Project(Fill<TDim> toProject);
}

View File

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

View File

@ -1,4 +1,6 @@
namespace Nerd_STF.Mathematics.Algebra; using Nerd_STF.Mathematics.Algebra.Abstract;
namespace Nerd_STF.Mathematics.Algebra;
public class Matrix : IMatrix<Matrix, Matrix> public class Matrix : IMatrix<Matrix, Matrix>
{ {
@ -157,39 +159,19 @@ public class Matrix : IMatrix<Matrix, Matrix>
} }
} }
public static Matrix Get2dRotationMatrix(Angle rot) =>
Matrix2x2.GenerateRotationMatrix(rot);
public static Matrix Get3dRotationMatrix(Angle yaw, Angle pitch, Angle roll) =>
Matrix3x3.GenerateRotationMatrix(yaw, pitch, roll);
public static Matrix Absolute(Matrix val) => new(val.Size, (r, c) => Mathf.Absolute(val[r, c])); public static Matrix Absolute(Matrix val) => new(val.Size, (r, c) => Mathf.Absolute(val[r, c]));
public static Matrix Ceiling(Matrix val) => new(val.Size, (r, c) => Mathf.Ceiling(val[r, c])); public static Matrix Ceiling(Matrix val) => new(val.Size, (r, c) => Mathf.Ceiling(val[r, c]));
public static Matrix Clamp(Matrix val, Matrix min, Matrix max) => public static Matrix Clamp(Matrix val, Matrix min, Matrix max) =>
new(val.Size, (r, c) => Mathf.Clamp(val[r, c], min[r, c], max[r, c])); new(val.Size, (r, c) => Mathf.Clamp(val[r, c], min[r, c], max[r, c]));
public static Matrix Divide(Matrix num, params Matrix[] vals)
{
foreach (Matrix m in vals) num /= m;
return num;
}
public static Matrix Floor(Matrix val) => new(val.Size, (r, c) => Mathf.Floor(val[r, c])); public static Matrix Floor(Matrix val) => new(val.Size, (r, c) => Mathf.Floor(val[r, c]));
public static Matrix Lerp(Matrix a, Matrix b, float t, bool clamp = true) => public static Matrix Lerp(Matrix a, Matrix b, float t, bool clamp = true) =>
new(a.Size, (r, c) => Mathf.Lerp(a[r, c], b[r, c], t, clamp)); new(a.Size, (r, c) => Mathf.Lerp(a[r, c], b[r, c], t, clamp));
public static Matrix Product(params Matrix[] vals)
{
if (vals.Length < 1) throw new InvalidSizeException("Array must contain at least one matrix.");
if (!CheckSize(vals)) throw new InvalidSizeException("All matricies must be the same size.");
Matrix val = Identity(vals[0].Size);
foreach (Matrix m in vals) val *= m;
return val;
}
public static Matrix Round(Matrix val) => new(val.Size, (r, c) => Mathf.Round(val[r, c])); public static Matrix Round(Matrix val) => new(val.Size, (r, c) => Mathf.Round(val[r, c]));
public static Matrix Subtract(Matrix num, params Matrix[] vals)
{
foreach (Matrix m in vals) num -= m;
return num;
}
public static Matrix Sum(params Matrix[] vals)
{
if (!CheckSize(vals)) throw new InvalidSizeException("All matricies must be the same size.");
Matrix val = Zero(vals[0].Size);
foreach (Matrix m in vals) val += m;
return val;
}
public void Apply(Modifier2d modifier) public void Apply(Modifier2d modifier)
{ {
@ -338,16 +320,14 @@ public class Matrix : IMatrix<Matrix, Matrix>
return result; return result;
} }
public override bool Equals([NotNullWhen(true)] object? obj) public override bool Equals(object? obj)
{ {
if (obj == null) return base.Equals(obj); if (obj is null) return base.Equals(obj);
Type t = obj.GetType(); else if (obj is Matrix m) return Equals(m);
if (t == typeof(Matrix)) return Equals((Matrix)obj); else if (obj is Matrix2x2 m2x2) return Equals(m2x2);
else if (t == typeof(Matrix2x2)) return Equals((Matrix)obj); else if (obj is Matrix3x3 m3x3) return Equals(m3x3);
else if (t == typeof(Matrix3x3)) return Equals((Matrix)obj); else if (obj is Matrix4x4 m4x4) return Equals(m4x4);
else if (t == typeof(Matrix4x4)) return Equals((Matrix)obj); return false;
return base.Equals(obj);
} }
public bool Equals(Matrix? other) public bool Equals(Matrix? other)
{ {

View File

@ -1,6 +1,8 @@
namespace Nerd_STF.Mathematics.Algebra; using Nerd_STF.Mathematics.Algebra.Abstract;
public record class Matrix2x2 : IStaticMatrix<Matrix2x2> namespace Nerd_STF.Mathematics.Algebra;
public class Matrix2x2 : ICloneable, IStaticMatrix<Matrix2x2>
{ {
public static Matrix2x2 Identity => new(new[,] public static Matrix2x2 Identity => new(new[,]
{ {
@ -161,19 +163,25 @@ public record class Matrix2x2 : IStaticMatrix<Matrix2x2>
} }
} }
public static Matrix2x2 GenerateRotationMatrix(Angle rot) => new(new[,]
{
{ Mathf.Cos(rot), -Mathf.Sin(rot) },
{ Mathf.Sin(rot), Mathf.Cos(rot) }
});
public static Matrix2x2 Absolute(Matrix2x2 val) => new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r1c2), public static Matrix2x2 Absolute(Matrix2x2 val) => new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r1c2),
Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r2c2)); Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r2c2));
public static Matrix2x2 Average(params Matrix2x2[] vals) => Sum(vals) / vals.Length; public static Matrix2x2 Average(params Matrix2x2[] vals)
{
Matrix2x2 sum = Zero;
foreach (Matrix2x2 m in vals) sum += m;
return sum / vals.Length;
}
public static Matrix2x2 Ceiling(Matrix2x2 val) => new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r1c2), public static Matrix2x2 Ceiling(Matrix2x2 val) => new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r1c2),
Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r2c2)); Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r2c2));
public static Matrix2x2 Clamp(Matrix2x2 val, Matrix2x2 min, Matrix2x2 max) => public static Matrix2x2 Clamp(Matrix2x2 val, Matrix2x2 min, Matrix2x2 max) =>
new(Mathf.Clamp(val.r1c1, min.r1c1, max.r1c1), Mathf.Clamp(val.r1c2, min.r1c2, max.r1c2), new(Mathf.Clamp(val.r1c1, min.r1c1, max.r1c1), Mathf.Clamp(val.r1c2, min.r1c2, max.r1c2),
Mathf.Clamp(val.r2c1, min.r2c1, max.r2c1), Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2)); Mathf.Clamp(val.r2c1, min.r2c1, max.r2c1), Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2));
public static Matrix2x2 Divide(Matrix2x2 num, params Matrix2x2[] vals)
{
foreach (Matrix2x2 m in vals) num /= m;
return num;
}
public static Matrix2x2 Floor(Matrix2x2 val) => new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r1c2), public static Matrix2x2 Floor(Matrix2x2 val) => new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r1c2),
Mathf.Floor(val.r2c1), Mathf.Floor(val.r2c2)); Mathf.Floor(val.r2c1), Mathf.Floor(val.r2c2));
public static Matrix2x2 Lerp(Matrix2x2 a, Matrix2x2 b, float t, bool clamp = true) => public static Matrix2x2 Lerp(Matrix2x2 a, Matrix2x2 b, float t, bool clamp = true) =>
@ -185,26 +193,8 @@ public record class Matrix2x2 : IStaticMatrix<Matrix2x2>
Matrix2x2 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; Matrix2x2 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB); return Average(valA, valB);
} }
public static Matrix2x2 Product(params Matrix2x2[] vals)
{
if (vals.Length < 1) return Zero;
Matrix2x2 val = Identity;
foreach (Matrix2x2 m in vals) val *= m;
return val;
}
public static Matrix2x2 Round(Matrix2x2 val) => new(Mathf.Round(val.r1c1), Mathf.Round(val.r1c2), public static Matrix2x2 Round(Matrix2x2 val) => new(Mathf.Round(val.r1c1), Mathf.Round(val.r1c2),
Mathf.Round(val.r2c1), Mathf.Round(val.r2c2)); Mathf.Round(val.r2c1), Mathf.Round(val.r2c2));
public static Matrix2x2 Subtract(Matrix2x2 num, params Matrix2x2[] vals)
{
foreach (Matrix2x2 m in vals) num -= m;
return num;
}
public static Matrix2x2 Sum(params Matrix2x2[] vals)
{
Matrix2x2 val = Zero;
foreach (Matrix2x2 m in vals) val += m;
return val;
}
public static (float[] r1c1s, float[] r1c2s, float[] r2c1s, float[] r2c2s) SplitArray(params Matrix2x2[] vals) public static (float[] r1c1s, float[] r1c2s, float[] r2c1s, float[] r2c2s) SplitArray(params Matrix2x2[] vals)
{ {
@ -306,7 +296,13 @@ public record class Matrix2x2 : IStaticMatrix<Matrix2x2>
SetRow(rowB, dataA); SetRow(rowB, dataA);
} }
public virtual bool Equals(Matrix2x2? other) public override bool Equals(object? obj)
{
if (obj is null) return false;
else if (obj is Matrix2x2 m2x2) return Equals(m2x2);
return false;
}
public bool Equals(Matrix2x2? other)
{ {
if (other is null) return false; if (other is null) return false;
return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r2c1 == other.r2c1 && r2c2 == other.r2c2; return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r2c1 == other.r2c1 && r2c2 == other.r2c2;
@ -323,6 +319,8 @@ public record class Matrix2x2 : IStaticMatrix<Matrix2x2>
yield return r2c2; yield return r2c2;
} }
public object Clone() => new Matrix2x2(r1c1, r1c2, r2c1, r2c2);
public float[] ToArray() => new[] { r1c1, r1c2, r2c1, r2c2 }; public float[] ToArray() => new[] { r1c1, r1c2, r2c1, r2c2 };
public float[,] ToArray2D() => new[,] public float[,] ToArray2D() => new[,]
{ {
@ -359,6 +357,8 @@ public record class Matrix2x2 : IStaticMatrix<Matrix2x2>
public static Float2 operator /(Matrix2x2 a, Float2 b) => (Matrix)a / b; public static Float2 operator /(Matrix2x2 a, Float2 b) => (Matrix)a / b;
public static Matrix2x2 operator ^(Matrix2x2 a, Matrix2x2 b) => // Single number multiplication. public static Matrix2x2 operator ^(Matrix2x2 a, Matrix2x2 b) => // Single number multiplication.
new(a.r1c1 * b.r1c1, a.r1c2 * b.r1c2, a.r2c1 * b.r2c1, a.r2c2 * b.r2c2); new(a.r1c1 * b.r1c1, a.r1c2 * b.r1c2, a.r2c1 * b.r2c1, a.r2c2 * b.r2c2);
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 m) public static explicit operator Matrix2x2(Matrix m)
{ {

View File

@ -1,6 +1,8 @@
namespace Nerd_STF.Mathematics.Algebra; using Nerd_STF.Mathematics.Algebra.Abstract;
public record class Matrix3x3 : IStaticMatrix<Matrix3x3> namespace Nerd_STF.Mathematics.Algebra;
public class Matrix3x3 : ICloneable, IStaticMatrix<Matrix3x3>
{ {
public static Matrix3x3 Identity => new(new[,] public static Matrix3x3 Identity => new(new[,]
{ {
@ -225,11 +227,44 @@ public record class Matrix3x3 : IStaticMatrix<Matrix3x3>
} }
} }
public static Matrix3x3 GenerateRotationMatrix(Angle yaw, Angle pitch, Angle roll)
{
// Could be optimized by merging all of the matrices by hand
// but that's a huge matrix and, as said before like a gajillion
// times, this ain't the optimization update.
Matrix3x3 yawMatrix = new(new[,]
{
{ Mathf.Cos(yaw), -Mathf.Sin(yaw), 0 },
{ Mathf.Sin(yaw), Mathf.Cos(yaw), 0 },
{ 0, 0, 1 }
});
Matrix3x3 pitchMatrix = new(new[,]
{
{ Mathf.Cos(pitch), 0, Mathf.Sin(pitch) },
{ 0, 1, 0 },
{ -Mathf.Sin(pitch), 0, Mathf.Cos(pitch) }
});
Matrix3x3 rollMatrix = new(new[,]
{
{ 1, 0, 0 },
{ 0, Mathf.Cos(roll), -Mathf.Sin(roll) },
{ 0, Mathf.Sin(roll), Mathf.Cos(roll) }
});
return yawMatrix * pitchMatrix * rollMatrix;
}
public static Matrix3x3 Absolute(Matrix3x3 val) => public static Matrix3x3 Absolute(Matrix3x3 val) =>
new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r1c2), Mathf.Absolute(val.r1c3), new(Mathf.Absolute(val.r1c1), Mathf.Absolute(val.r1c2), Mathf.Absolute(val.r1c3),
Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r2c2), Mathf.Absolute(val.r2c3), Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r2c2), Mathf.Absolute(val.r2c3),
Mathf.Absolute(val.r3c1), Mathf.Absolute(val.r3c2), Mathf.Absolute(val.r3c3)); Mathf.Absolute(val.r3c1), Mathf.Absolute(val.r3c2), Mathf.Absolute(val.r3c3));
public static Matrix3x3 Average(params Matrix3x3[] vals) => Sum(vals) / vals.Length; public static Matrix3x3 Average(params Matrix3x3[] vals)
{
Matrix3x3 sum = Zero;
foreach (Matrix3x3 m in vals) sum += m;
return sum / vals.Length;
}
public static Matrix3x3 Ceiling(Matrix3x3 val) => public static Matrix3x3 Ceiling(Matrix3x3 val) =>
new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r1c2), Mathf.Ceiling(val.r1c3), new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r1c2), Mathf.Ceiling(val.r1c3),
Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r2c2), Mathf.Ceiling(val.r2c3), Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r2c2), Mathf.Ceiling(val.r2c3),
@ -240,11 +275,6 @@ public record class Matrix3x3 : IStaticMatrix<Matrix3x3>
Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2), Mathf.Clamp(val.r2c3, min.r2c3, max.r2c3), Mathf.Clamp(val.r2c2, min.r2c2, max.r2c2), Mathf.Clamp(val.r2c3, min.r2c3, max.r2c3),
Mathf.Clamp(val.r3c1, min.r3c1, max.r3c1), Mathf.Clamp(val.r3c2, min.r3c2, max.r3c2), Mathf.Clamp(val.r3c1, min.r3c1, max.r3c1), Mathf.Clamp(val.r3c2, min.r3c2, max.r3c2),
Mathf.Clamp(val.r3c3, min.r3c3, max.r3c3)); Mathf.Clamp(val.r3c3, min.r3c3, max.r3c3));
public static Matrix3x3 Divide(Matrix3x3 num, params Matrix3x3[] vals)
{
foreach (Matrix3x3 m in vals) num /= m;
return num;
}
public static Matrix3x3 Floor(Matrix3x3 val) => public static Matrix3x3 Floor(Matrix3x3 val) =>
new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r1c2), Mathf.Floor(val.r1c3), new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r1c2), Mathf.Floor(val.r1c3),
Mathf.Floor(val.r2c1), Mathf.Floor(val.r2c2), Mathf.Floor(val.r2c3), Mathf.Floor(val.r2c1), Mathf.Floor(val.r2c2), Mathf.Floor(val.r2c3),
@ -261,28 +291,10 @@ public record class Matrix3x3 : IStaticMatrix<Matrix3x3>
Matrix3x3 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; Matrix3x3 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB); return Average(valA, valB);
} }
public static Matrix3x3 Product(params Matrix3x3[] vals)
{
if (vals.Length < 1) return Zero;
Matrix3x3 val = Identity;
foreach (Matrix3x3 m in vals) val *= m;
return val;
}
public static Matrix3x3 Round(Matrix3x3 val) => public static Matrix3x3 Round(Matrix3x3 val) =>
new(Mathf.Round(val.r1c1), Mathf.Round(val.r1c2), Mathf.Round(val.r1c3), new(Mathf.Round(val.r1c1), Mathf.Round(val.r1c2), Mathf.Round(val.r1c3),
Mathf.Round(val.r2c1), Mathf.Round(val.r2c2), Mathf.Round(val.r2c3), Mathf.Round(val.r2c1), Mathf.Round(val.r2c2), Mathf.Round(val.r2c3),
Mathf.Round(val.r3c1), Mathf.Round(val.r3c2), Mathf.Round(val.r3c3)); Mathf.Round(val.r3c1), Mathf.Round(val.r3c2), Mathf.Round(val.r3c3));
public static Matrix3x3 Subtract(Matrix3x3 num, params Matrix3x3[] vals)
{
foreach (Matrix3x3 m in vals) num -= m;
return num;
}
public static Matrix3x3 Sum(params Matrix3x3[] vals)
{
Matrix3x3 val = Zero;
foreach (Matrix3x3 m in vals) val += m;
return val;
}
public static (float[] r1c1s, float[] r1c2s, float[] r1c3s, float[] r2c1s, float[] r2c2s, public static (float[] r1c1s, float[] r1c2s, float[] r1c3s, float[] r2c1s, float[] r2c2s,
float[] r2c3s, float[] r3c1s, float[] r3c2s, float[] r3c3s) SplitArray(params Matrix3x3[] vals) float[] r2c3s, float[] r3c1s, float[] r3c2s, float[] r3c3s) SplitArray(params Matrix3x3[] vals)
@ -310,7 +322,8 @@ public record class Matrix3x3 : IStaticMatrix<Matrix3x3>
{ {
Matrix3x3 dets = Zero; Matrix3x3 dets = Zero;
Matrix2x2[,] minors = Minors(); Matrix2x2[,] minors = Minors();
for (int r = 0; r < 3; r++) for (int c = 0; c < 3; c++) dets[r, c] = minors[r, c].Determinant(); for (int r = 0; r < 3; r++) for (int c = 0; c < 3; c++)
dets[r, c] = minors[r, c].Determinant();
return dets ^ SignGrid; return dets ^ SignGrid;
} }
public float Determinant() public float Determinant()
@ -407,7 +420,13 @@ public record class Matrix3x3 : IStaticMatrix<Matrix3x3>
SetRow(rowB, dataA); SetRow(rowB, dataA);
} }
public virtual bool Equals(Matrix3x3? other) public override bool Equals(object? obj)
{
if (obj is null) return false;
else if (obj is Matrix3x3 m3x3) return Equals(m3x3);
return false;
}
public bool Equals(Matrix3x3? other)
{ {
if (other is null) return false; if (other is null) return false;
return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r1c3 == other.r1c3 && return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r1c3 == other.r1c3 &&
@ -434,6 +453,8 @@ public record class Matrix3x3 : IStaticMatrix<Matrix3x3>
yield return r3c3; yield return r3c3;
} }
public object Clone() => new Matrix3x3(r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3);
public float[] ToArray() => new[] { r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3 }; public float[] ToArray() => new[] { r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3 };
public float[,] ToArray2D() => new[,] public float[,] ToArray2D() => new[,]
{ {
@ -484,6 +505,8 @@ public record class Matrix3x3 : IStaticMatrix<Matrix3x3>
new(a.r1c1 * b.r1c1, a.r1c2 * b.r1c2, a.r1c3 * b.r1c3, new(a.r1c1 * b.r1c1, a.r1c2 * b.r1c2, a.r1c3 * b.r1c3,
a.r2c1 * b.r2c1, a.r2c2 * b.r2c2, a.r2c3 * b.r2c3, a.r2c1 * b.r2c1, a.r2c2 * b.r2c2, a.r2c3 * b.r2c3,
a.r3c1 * b.r3c1, a.r3c2 * b.r3c2, a.r3c3 * b.r3c3); a.r3c1 * b.r3c1, a.r3c2 * b.r3c2, a.r3c3 * b.r3c3);
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 m) public static explicit operator Matrix3x3(Matrix m)
{ {

View File

@ -1,8 +1,9 @@
using System.Data.Common; using System.Data.Common;
using Nerd_STF.Mathematics.Algebra.Abstract;
namespace Nerd_STF.Mathematics.Algebra; namespace Nerd_STF.Mathematics.Algebra;
public record class Matrix4x4 : IStaticMatrix<Matrix4x4> public class Matrix4x4 : ICloneable, IStaticMatrix<Matrix4x4>
{ {
public static Matrix4x4 Identity => new(new[,] public static Matrix4x4 Identity => new(new[,]
{ {
@ -303,7 +304,12 @@ public record class Matrix4x4 : IStaticMatrix<Matrix4x4>
Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r2c2), Mathf.Absolute(val.r2c3), Mathf.Absolute(val.r2c4), Mathf.Absolute(val.r2c1), Mathf.Absolute(val.r2c2), Mathf.Absolute(val.r2c3), Mathf.Absolute(val.r2c4),
Mathf.Absolute(val.r3c1), Mathf.Absolute(val.r3c2), Mathf.Absolute(val.r3c3), Mathf.Absolute(val.r3c4), Mathf.Absolute(val.r3c1), Mathf.Absolute(val.r3c2), Mathf.Absolute(val.r3c3), Mathf.Absolute(val.r3c4),
Mathf.Absolute(val.r4c1), Mathf.Absolute(val.r4c2), Mathf.Absolute(val.r4c3), Mathf.Absolute(val.r4c4)); Mathf.Absolute(val.r4c1), Mathf.Absolute(val.r4c2), Mathf.Absolute(val.r4c3), Mathf.Absolute(val.r4c4));
public static Matrix4x4 Average(params Matrix4x4[] vals) => Sum(vals) / vals.Length; public static Matrix4x4 Average(params Matrix4x4[] vals)
{
Matrix4x4 sum = Zero;
foreach (Matrix4x4 m in vals) sum += m;
return sum / vals.Length;
}
public static Matrix4x4 Ceiling(Matrix4x4 val) => public static Matrix4x4 Ceiling(Matrix4x4 val) =>
new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r1c2), Mathf.Ceiling(val.r1c3), Mathf.Ceiling(val.r1c4), new(Mathf.Ceiling(val.r1c1), Mathf.Ceiling(val.r1c2), Mathf.Ceiling(val.r1c3), Mathf.Ceiling(val.r1c4),
Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r2c2), Mathf.Ceiling(val.r2c3), Mathf.Ceiling(val.r2c4), Mathf.Ceiling(val.r2c1), Mathf.Ceiling(val.r2c2), Mathf.Ceiling(val.r2c3), Mathf.Ceiling(val.r2c4),
@ -318,11 +324,6 @@ public record class Matrix4x4 : IStaticMatrix<Matrix4x4>
Mathf.Clamp(val.r3c3, min.r3c3, max.r3c3), Mathf.Clamp(val.r3c4, min.r3c4, max.r3c4), Mathf.Clamp(val.r3c3, min.r3c3, max.r3c3), Mathf.Clamp(val.r3c4, min.r3c4, max.r3c4),
Mathf.Clamp(val.r4c1, min.r4c1, max.r4c1), Mathf.Clamp(val.r4c2, min.r4c2, max.r4c2), Mathf.Clamp(val.r4c1, min.r4c1, max.r4c1), Mathf.Clamp(val.r4c2, min.r4c2, max.r4c2),
Mathf.Clamp(val.r4c3, min.r4c3, max.r4c3), Mathf.Clamp(val.r4c4, min.r4c4, max.r4c4)); Mathf.Clamp(val.r4c3, min.r4c3, max.r4c3), Mathf.Clamp(val.r4c4, min.r4c4, max.r4c4));
public static Matrix4x4 Divide(Matrix4x4 num, params Matrix4x4[] vals)
{
foreach (Matrix4x4 m in vals) num /= m;
return num;
}
public static Matrix4x4 Floor(Matrix4x4 val) => public static Matrix4x4 Floor(Matrix4x4 val) =>
new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r1c2), Mathf.Floor(val.r1c3), Mathf.Floor(val.r1c4), new(Mathf.Floor(val.r1c1), Mathf.Floor(val.r1c2), Mathf.Floor(val.r1c3), Mathf.Floor(val.r1c4),
Mathf.Floor(val.r2c1), Mathf.Floor(val.r2c2), Mathf.Floor(val.r2c3), Mathf.Floor(val.r2c4), Mathf.Floor(val.r2c1), Mathf.Floor(val.r2c2), Mathf.Floor(val.r2c3), Mathf.Floor(val.r2c4),
@ -343,29 +344,11 @@ public record class Matrix4x4 : IStaticMatrix<Matrix4x4>
Matrix4x4 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; Matrix4x4 valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)];
return Average(valA, valB); return Average(valA, valB);
} }
public static Matrix4x4 Product(params Matrix4x4[] vals)
{
if (vals.Length < 1) return Zero;
Matrix4x4 val = Identity;
foreach (Matrix4x4 m in vals) val *= m;
return val;
}
public static Matrix4x4 Round(Matrix4x4 val) => public static Matrix4x4 Round(Matrix4x4 val) =>
new(Mathf.Round(val.r1c1), Mathf.Round(val.r1c2), Mathf.Round(val.r1c3), Mathf.Round(val.r1c4), new(Mathf.Round(val.r1c1), Mathf.Round(val.r1c2), Mathf.Round(val.r1c3), Mathf.Round(val.r1c4),
Mathf.Round(val.r2c1), Mathf.Round(val.r2c2), Mathf.Round(val.r2c3), Mathf.Round(val.r2c4), Mathf.Round(val.r2c1), Mathf.Round(val.r2c2), Mathf.Round(val.r2c3), Mathf.Round(val.r2c4),
Mathf.Round(val.r3c1), Mathf.Round(val.r3c2), Mathf.Round(val.r3c3), Mathf.Round(val.r3c4), Mathf.Round(val.r3c1), Mathf.Round(val.r3c2), Mathf.Round(val.r3c3), Mathf.Round(val.r3c4),
Mathf.Round(val.r4c1), Mathf.Round(val.r4c2), Mathf.Round(val.r4c3), Mathf.Round(val.r4c4)); Mathf.Round(val.r4c1), Mathf.Round(val.r4c2), Mathf.Round(val.r4c3), Mathf.Round(val.r4c4));
public static Matrix4x4 Subtract(Matrix4x4 num, params Matrix4x4[] vals)
{
foreach (Matrix4x4 m in vals) num -= m;
return num;
}
public static Matrix4x4 Sum(params Matrix4x4[] vals)
{
Matrix4x4 val = Zero;
foreach (Matrix4x4 m in vals) val += m;
return val;
}
public static (float[] r1c1s, float[] r1c2, float[] r1c3, float[] r1c4, float[] r2c1, float[] r2c2s, public static (float[] r1c1s, float[] r1c2, float[] r1c3, float[] r1c4, float[] r2c1, float[] r2c2s,
float[] r2c3, float[] r2c4, float[] r3c1, float[] r3c2, float[] r3c3s, float[] r3c4, float[] r4c1, float[] r2c3, float[] r2c4, float[] r3c1, float[] r3c2, float[] r3c3s, float[] r3c4, float[] r4c1,
@ -530,7 +513,13 @@ public record class Matrix4x4 : IStaticMatrix<Matrix4x4>
SetRow(rowB, dataA); SetRow(rowB, dataA);
} }
public virtual bool Equals(Matrix4x4? other) public override bool Equals(object? obj)
{
if (obj is null) return false;
else if (obj is Matrix4x4 m4x4) return Equals(m4x4);
return false;
}
public bool Equals(Matrix4x4? other)
{ {
if (other is null) return false; if (other is null) return false;
return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r1c3 == other.r1c3 && r1c4 == other.r1c4 && return r1c1 == other.r1c1 && r1c2 == other.r1c2 && r1c3 == other.r1c3 && r1c4 == other.r1c4 &&
@ -566,6 +555,11 @@ public record class Matrix4x4 : IStaticMatrix<Matrix4x4>
yield return r4c4; yield return r4c4;
} }
public object Clone() => new Matrix4x4(r1c1, r1c2, r1c3, r1c4,
r2c1, r2c2, r2c3, r2c4,
r3c1, r3c2, r3c3, r3c4,
r4c1, r4c2, r4c3, r4c4);
public float[] ToArray() => new[] public float[] ToArray() => new[]
{ {
r1c1, r2c1, r3c1, r4c1, r1c1, r2c1, r3c1, r4c1,
@ -639,6 +633,8 @@ public record class Matrix4x4 : IStaticMatrix<Matrix4x4>
a.r2c1 * b.r2c1, a.r2c2 * b.r2c2, a.r2c3 * b.r2c3, a.r2c4 * b.r2c4, a.r2c1 * b.r2c1, a.r2c2 * b.r2c2, a.r2c3 * b.r2c3, a.r2c4 * b.r2c4,
a.r3c1 * b.r3c1, a.r3c2 * b.r3c2, a.r3c3 * b.r3c3, a.r3c4 * b.r3c4, a.r3c1 * b.r3c1, a.r3c2 * b.r3c2, a.r3c3 * b.r3c3, a.r3c4 * b.r3c4,
a.r4c1 * b.r4c1, a.r4c2 * b.r4c2, a.r4c3 * b.r4c3, a.r4c4 * b.r4c4); a.r4c1 * b.r4c1, a.r4c2 * b.r4c2, a.r4c3 * b.r4c3, a.r4c4 * b.r4c4);
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 m) public static explicit operator Matrix4x4(Matrix m)
{ {

View File

@ -0,0 +1,159 @@
using Nerd_STF.Mathematics.Algebra.Abstract;
namespace Nerd_STF.Mathematics.Algebra;
public class SimpleProjectionMatrix : Matrix3x3,
IProjectionMatrix<SimpleProjectionMatrix, Matrix3x3, Float3>
{
public SimpleProjectionMatrix(Matrix3x3 nonProjection) : this(nonProjection.ToFill2D()) { }
public SimpleProjectionMatrix(float all) : this(all, all, all, all, all, all, all, all, all) { }
public SimpleProjectionMatrix(float r1c1, float r1c2, float r1c3, float r2c1,
float r2c2, float r2c3, float r3c1, float r3c2, float r3c3) :
base(r1c1, r1c2, r1c3, r2c1, r2c2, r2c3, r3c1, r3c2, r3c3) { }
public SimpleProjectionMatrix(float[] nums) : this(nums[0], nums[1], nums[2],
nums[3], nums[4], nums[5], nums[6], nums[7], nums[8]) { }
public SimpleProjectionMatrix(int[] nums) : this(nums[0], nums[1], nums[2],
nums[3], nums[4], nums[5], nums[6], nums[7], nums[8]) { }
public SimpleProjectionMatrix(Fill<float> fill) : this(fill(0), fill(1), fill(2),
fill(3), fill(4), fill(5), fill(6), fill(7), fill(8)) { }
public SimpleProjectionMatrix(Fill<int> fill) : this(fill(0), fill(1), fill(2),
fill(3), fill(4), fill(5), fill(6), fill(7), fill(8)) { }
public SimpleProjectionMatrix(float[,] nums) : this(nums[0, 0], nums[0, 1], nums[0, 2],
nums[1, 0], nums[1, 1], nums[1, 2], nums[2, 0], nums[2, 1], nums[2, 2]) { }
public SimpleProjectionMatrix(int[,] nums) : this(nums[0, 0], nums[0, 1], nums[0, 2],
nums[1, 0], nums[1, 1], nums[1, 2], nums[2, 0], nums[2, 1], nums[2, 2]) { }
public SimpleProjectionMatrix(Fill2d<float> fill) : this(fill(0, 0), fill(0, 1), fill(0, 2),
fill(1, 0), fill(1, 1), fill(1, 2), fill(2, 0), fill(2, 1), fill(2, 2)) { }
public SimpleProjectionMatrix(Fill2d<int> fill) : this(fill(0, 0), fill(0, 1), fill(0, 2),
fill(1, 0), fill(1, 1), fill(1, 2), fill(2, 0), fill(2, 1), fill(2, 2)) { }
public SimpleProjectionMatrix(Float3 r1, Float3 r2, Float3 r3) : this(r1.x, r1.y, r1.z, r2.x, r2.y, r2.z, r3.x, r3.y, r3.z) { }
public SimpleProjectionMatrix(Fill<Float3> fill) : this(fill(0), fill(1), fill(2)) { }
public SimpleProjectionMatrix(Fill<Int3> fill) : this((IEnumerable<int>)fill(0), fill(1), fill(2)) { }
public SimpleProjectionMatrix(IEnumerable<float> r1, IEnumerable<float> r2, IEnumerable<float> r3)
: this(r1.ToFill(), r2.ToFill(), r3.ToFill()) { }
public SimpleProjectionMatrix(IEnumerable<int> r1, IEnumerable<int> r2, IEnumerable<int> r3)
: this(r1.ToFill(), r2.ToFill(), r3.ToFill()) { }
public SimpleProjectionMatrix(Fill<float> r1, Fill<float> r2, Fill<float> r3)
: this(r1(0), r1(1), r1(2), r2(0), r2(1), r2(2), r3(0), r3(1), r3(2)) { }
public SimpleProjectionMatrix(Fill<int> r1, Fill<int> r2, Fill<int> r3)
: this(r1(0), r1(1), r1(2), r2(0), r2(1), r2(2), r3(0), r3(1), r3(2)) { }
public static SimpleProjectionMatrix SingleView(CrossSection2d section) => new(new[,]
{
{ section == CrossSection2d.XY || section == CrossSection2d.ZX ? 1 : 0, 0, 0 },
{ 0, section == CrossSection2d.XY || section == CrossSection2d.YZ ? 1 : 0, 0 },
{ 0, 0, section == CrossSection2d.YZ || section == CrossSection2d.ZX ? 1 : 0 }
});
public static SimpleProjectionMatrix Isometric()
{
// Hand-calculated optimization of an axonometric projection
// with alpha set to arcsin(tan(30deg)) and beta set to 45deg.
const float invSqrt2 = 0.707106781187f,
invSqrt6 = 0.408248290464f,
invSqrt6_2 = 0.816496580928f;
return new(new[,]
{
{ invSqrt2, 0, -invSqrt2 },
{ invSqrt6, invSqrt6_2, invSqrt6 },
{ 0, 0, 0 },
});
}
public static SimpleProjectionMatrix Axonometric(Angle alpha, Angle beta)
{
Matrix3x3 alphaMat = new(new[,]
{
{ 1, 0, 0 },
{ 0, Mathf.Cos(alpha), Mathf.Sin(alpha) },
{ 0, -Mathf.Sin(alpha), Mathf.Cos(alpha) }
});
Matrix3x3 betaMat = new(new[,]
{
{ Mathf.Cos(beta), 0, -Mathf.Sin(beta) },
{ 0, 1, 0 },
{ Mathf.Sin(beta), 0, Mathf.Cos(beta) }
});
Matrix3x3 flatten = new(new[,]
{
{ 1, 0, 0 },
{ 0, 1, 0 },
{ 0, 0, 0 }
});
Matrix3x3 result = (alphaMat * betaMat).Transpose() * flatten;
return new(result.Transpose().ToFill2D());
}
public Fill<Float3> Project(Fill<Float3> toProject) => i => this * toProject(i);
public static SimpleProjectionMatrix Absolute(SimpleProjectionMatrix val) =>
new(Matrix3x3.Absolute(val));
public static SimpleProjectionMatrix Average(SimpleProjectionMatrix val) =>
new(Matrix3x3.Average(val));
public static SimpleProjectionMatrix Ceiling(SimpleProjectionMatrix val) =>
new(Matrix3x3.Ceiling(val));
public static SimpleProjectionMatrix Clamp(SimpleProjectionMatrix val,
SimpleProjectionMatrix min, SimpleProjectionMatrix max) =>
new(Matrix3x3.Clamp(val, min, max));
public static SimpleProjectionMatrix Floor(SimpleProjectionMatrix val) =>
new(Matrix3x3.Floor(val));
public static SimpleProjectionMatrix Lerp(SimpleProjectionMatrix a, SimpleProjectionMatrix b,
float t, bool clamp = true) =>
new(Matrix3x3.Lerp(a, b, t, clamp));
public static SimpleProjectionMatrix Median(params SimpleProjectionMatrix[] vals) =>
new(Matrix3x3.Median(vals));
public static SimpleProjectionMatrix Round(SimpleProjectionMatrix val) =>
new(Matrix3x3.Round(val));
public static (float[] r1c1s, float[] r1c2s, float[] r1c3s, float[] r2c1s, float[] r2c2s,
float[] r2c3s, float[] r3c1s, float[] r3c2s, float[] r3c3s)
SplitArray(params SimpleProjectionMatrix[] vals) => Matrix3x3.SplitArray(vals);
new public SimpleProjectionMatrix Adjugate() => new(base.Adjugate());
new public SimpleProjectionMatrix Cofactor() => new(base.Cofactor());
new public SimpleProjectionMatrix? Inverse()
{
Matrix3x3? mInverse = base.Inverse();
if (mInverse is null) return null;
return new(mInverse);
}
new public Matrix2x2[,] Minors() => base.Minors();
new public SimpleProjectionMatrix Transpose() => new(base.Transpose());
new public SimpleProjectionMatrix AddRow(int rowToChange, int referenceRow, float factor = 1) =>
new(base.AddRow(rowToChange, referenceRow, factor));
new public SimpleProjectionMatrix ScaleRow(int rowIndex, float factor) =>
new(base.ScaleRow(rowIndex, factor));
new public SimpleProjectionMatrix SwapRows(int rowA, int rowB) =>
new(base.SwapRows(rowA, rowB));
public override bool Equals(object? obj) => base.Equals(obj);
public override int GetHashCode() => base.GetHashCode();
public bool Equals(SimpleProjectionMatrix? other) => base.Equals(other);
new public object Clone() => new SimpleProjectionMatrix(r1c1, r1c2, r1c3,
r2c1, r2c2, r2c3,
r3c1, r3c2, r3c3);
public static SimpleProjectionMatrix operator +(SimpleProjectionMatrix a,
SimpleProjectionMatrix b) => new((Matrix3x3)a + b);
public static SimpleProjectionMatrix? operator -(SimpleProjectionMatrix m) => m.Inverse();
public static SimpleProjectionMatrix operator -(SimpleProjectionMatrix a,
SimpleProjectionMatrix b) => new((Matrix3x3)a - b);
public static SimpleProjectionMatrix operator *(SimpleProjectionMatrix a, float b) =>
new((Matrix3x3)a * b);
public static SimpleProjectionMatrix operator *(SimpleProjectionMatrix a,
SimpleProjectionMatrix b) => new((Matrix3x3)a * b);
public static Float3 operator *(SimpleProjectionMatrix a, Float3 b) => (Matrix3x3)a * b;
public static SimpleProjectionMatrix operator /(SimpleProjectionMatrix a, float b) =>
new((Matrix3x3)a / b);
public static SimpleProjectionMatrix operator /(SimpleProjectionMatrix a,
SimpleProjectionMatrix b) => new((Matrix3x3)a / b);
public static Float3 operator /(SimpleProjectionMatrix a, Float3 b) => (Matrix3x3)a / b;
public static SimpleProjectionMatrix operator ^(SimpleProjectionMatrix a,
SimpleProjectionMatrix b) => new((Matrix3x3)a ^ b);
public static bool operator ==(SimpleProjectionMatrix a, SimpleProjectionMatrix b) =>
a.Equals(b);
public static bool operator !=(SimpleProjectionMatrix a, SimpleProjectionMatrix b) =>
!a.Equals(b);
}

View File

@ -4,8 +4,7 @@ public record struct Vector2d : IAbsolute<Vector2d>, IAverage<Vector2d>,
IClampMagnitude<Vector2d, float>, IComparable<Vector2d>, ICross<Vector2d, Vector3d>, IClampMagnitude<Vector2d, float>, IComparable<Vector2d>, ICross<Vector2d, Vector3d>,
IDot<Vector2d, float>, IEquatable<Vector2d>, IFromTuple<Vector2d, (Angle angle, float mag)>, IDot<Vector2d, float>, IEquatable<Vector2d>, IFromTuple<Vector2d, (Angle angle, float mag)>,
ILerp<Vector2d, float>, IMax<Vector2d>, IMagnitude<float>, IMedian<Vector2d>, IMin<Vector2d>, ILerp<Vector2d, float>, IMax<Vector2d>, IMagnitude<float>, IMedian<Vector2d>, IMin<Vector2d>,
IPresets2d<Vector2d>, ISplittable<Vector2d, (Angle[] rots, float[] mags)>, ISubtract<Vector2d>, IPresets2d<Vector2d>, ISplittable<Vector2d, (Angle[] rots, float[] mags)>
ISum<Vector2d>
{ {
public static Vector2d Down => new(Angle.Down); public static Vector2d Down => new(Angle.Down);
public static Vector2d Left => new(Angle.Left); public static Vector2d Left => new(Angle.Left);
@ -50,7 +49,7 @@ public record struct Vector2d : IAbsolute<Vector2d>, IAverage<Vector2d>,
return new(val.theta, mag); return new(val.theta, mag);
} }
public static Vector3d Cross(Vector2d a, Vector2d b, bool normalized = false) => public static Vector3d Cross(Vector2d a, Vector2d b, bool normalized = false) =>
Float2.Cross(a.ToXYZ(), b.ToXYZ(), normalized).ToVector(); new Float3(0, 0, Float2.Cross(a.ToXYZ(), b.ToXYZ(), normalized)).ToVector();
public static float Dot(Vector2d a, Vector2d b) => Float2.Dot(a.ToXYZ(), b.ToXYZ()); public static float Dot(Vector2d a, Vector2d b) => Float2.Dot(a.ToXYZ(), b.ToXYZ());
public static float Dot(params Vector2d[] vals) public static float Dot(params Vector2d[] vals)
{ {
@ -84,18 +83,6 @@ public record struct Vector2d : IAbsolute<Vector2d>, IAverage<Vector2d>,
} }
public static Vector2d Round(Vector2d val, Angle.Type angleRound = Angle.Type.Degrees) => public static Vector2d Round(Vector2d val, Angle.Type angleRound = Angle.Type.Degrees) =>
new(Angle.Round(val.theta, angleRound), Mathf.Round(val.magnitude)); new(Angle.Round(val.theta, angleRound), Mathf.Round(val.magnitude));
public static Vector2d Subtract(Vector2d num, params Vector2d[] vals)
{
foreach (Vector2d v in vals) num -= v;
return num;
}
public static Vector2d Sum(params Vector2d[] vals)
{
if (vals.Length < 1) return Zero;
Vector2d val = One;
foreach (Vector2d v in vals) val += v;
return val;
}
public static (Angle[] rots, float[] mags) SplitArray(params Vector2d[] vals) public static (Angle[] rots, float[] mags) SplitArray(params Vector2d[] vals)
{ {
@ -114,7 +101,7 @@ public record struct Vector2d : IAbsolute<Vector2d>, IAverage<Vector2d>,
public override int GetHashCode() => base.GetHashCode(); public override int GetHashCode() => base.GetHashCode();
public override string ToString() => ToString(Angle.Type.Degrees); public override string ToString() => ToString(Angle.Type.Degrees);
public string ToString(Angle.Type outputType) => public string ToString(Angle.Type outputType) =>
nameof(Vector2d) + " { Mag = " + magnitude + ", Rot = " + theta.ToString(outputType) + " }"; $"{magnitude:0.000} @ {theta.ToString(outputType)}";
public Float2 ToXYZ() => new Float2(Mathf.Cos(theta), Mathf.Sin(theta)) * magnitude; public Float2 ToXYZ() => new Float2(Mathf.Cos(theta), Mathf.Sin(theta)) * magnitude;
@ -136,7 +123,6 @@ public record struct Vector2d : IAbsolute<Vector2d>, IAverage<Vector2d>,
public static explicit operator Vector2d(Int2 val) => val.ToVector(); public static explicit operator Vector2d(Int2 val) => val.ToVector();
public static explicit operator Vector2d(Int3 val) => (Vector2d)val.ToVector(); public static explicit operator Vector2d(Int3 val) => (Vector2d)val.ToVector();
public static explicit operator Vector2d(Matrix m) => ((Float2)m).ToVector(); public static explicit operator Vector2d(Matrix m) => ((Float2)m).ToVector();
public static explicit operator Vector2d(Vert val) => (Vector2d)val.ToVector();
public static explicit operator Vector2d(Vector3d val) => new(val.yaw, val.magnitude); public static explicit operator Vector2d(Vector3d val) => new(val.yaw, val.magnitude);
public static implicit operator Vector2d((Angle angle, float mag) val) => new(val.angle, val.mag); public static implicit operator Vector2d((Angle angle, float mag) val) => new(val.angle, val.mag);
} }

View File

@ -4,7 +4,7 @@ public record struct Vector3d : IAbsolute<Vector3d>, IAverage<Vector3d>, IClampM
IComparable<Vector3d>, ICross<Vector3d>, IDot<Vector3d, float>, IEquatable<Vector3d>, IComparable<Vector3d>, ICross<Vector3d>, IDot<Vector3d, float>, IEquatable<Vector3d>,
IFromTuple<Vector3d, (Angle yaw, Angle pitch, float mag)>, IIndexAll<Angle>, IIndexRangeAll<Angle>, IFromTuple<Vector3d, (Angle yaw, Angle pitch, float mag)>, IIndexAll<Angle>, IIndexRangeAll<Angle>,
ILerp<Vector3d, float>, IMagnitude<float>, IMax<Vector3d>, IMedian<Vector3d>, IMin<Vector3d>, ILerp<Vector3d, float>, IMagnitude<float>, IMax<Vector3d>, IMedian<Vector3d>, IMin<Vector3d>,
IPresets3d<Vector3d>, ISubtract<Vector3d>, ISum<Vector3d> IPresets3d<Vector3d>
{ {
public static Vector3d Back => new(Angle.Zero, Angle.Up); public static Vector3d Back => new(Angle.Zero, Angle.Up);
public static Vector3d Down => new(Angle.Down, Angle.Zero); public static Vector3d Down => new(Angle.Down, Angle.Zero);
@ -142,18 +142,6 @@ public record struct Vector3d : IAbsolute<Vector3d>, IAverage<Vector3d>, IClampM
} }
public static Vector3d Round(Vector3d val, Angle.Type angleRound = Angle.Type.Degrees) => public static Vector3d Round(Vector3d val, Angle.Type angleRound = Angle.Type.Degrees) =>
new(Angle.Round(val.yaw, angleRound), Angle.Round(val.pitch, angleRound), Mathf.Round(val.magnitude)); new(Angle.Round(val.yaw, angleRound), Angle.Round(val.pitch, angleRound), Mathf.Round(val.magnitude));
public static Vector3d Subtract(Vector3d num, params Vector3d[] vals)
{
foreach (Vector3d v in vals) num -= v;
return num;
}
public static Vector3d Sum(params Vector3d[] vals)
{
if (vals.Length < 1) return Zero;
Vector3d val = One;
foreach (Vector3d v in vals) val += v;
return val;
}
public static (Angle[] yaws, Angle[] pitches, float[] mags) SplitArray(params Vector3d[] vals) public static (Angle[] yaws, Angle[] pitches, float[] mags) SplitArray(params Vector3d[] vals)
{ {
@ -201,7 +189,6 @@ public record struct Vector3d : IAbsolute<Vector3d>, IAverage<Vector3d>, IClampM
public static explicit operator Vector3d(Int2 val) => val.ToVector(); public static explicit operator Vector3d(Int2 val) => val.ToVector();
public static explicit operator Vector3d(Int3 val) => val.ToVector(); public static explicit operator Vector3d(Int3 val) => val.ToVector();
public static explicit operator Vector3d(Matrix m) => ((Float3)m).ToVector(); public static explicit operator Vector3d(Matrix m) => ((Float3)m).ToVector();
public static explicit operator Vector3d(Vert val) => val.ToVector();
public static implicit operator Vector3d(Vector2d v) => new(v.theta, Angle.Zero, v.magnitude); public static implicit operator Vector3d(Vector2d v) => new(v.theta, Angle.Zero, v.magnitude);
public static implicit operator Vector3d((Angle yaw, Angle pitch, float mag) val) => public static implicit operator Vector3d((Angle yaw, Angle pitch, float mag) val) =>
new(val.yaw, val.pitch, val.mag); new(val.yaw, val.pitch, val.mag);

View File

@ -22,7 +22,7 @@ public struct Angle : IAbsolute<Angle>, IAverage<Angle>, IClamp<Angle>, ICloneab
} }
public float Gradians public float Gradians
{ {
get => p_deg * 1.11111111111f; // Reciprocal of 9/10 as a constant (10/9) get => p_deg * 1.11111111111f;
set => p_deg = value * 0.9f; set => p_deg = value * 0.9f;
} }
public float Normalized public float Normalized
@ -52,6 +52,19 @@ public struct Angle : IAbsolute<Angle>, IAverage<Angle>, IClamp<Angle>, ICloneab
_ => throw new ArgumentException("Unknown type.", nameof(valueType)), _ => throw new ArgumentException("Unknown type.", nameof(valueType)),
}; };
public static Angle FromLines(Line ab, Line bc)
{
if (ab.b != bc.a) throw new DisconnectedLinesException(ab, bc);
return FromPoints(ab.a, ab.b, bc.b);
}
public static Angle FromPoints(Float3 endA, Float3 middleB, Float3 endC)
{
endA -= middleB;
endC -= middleB;
float dot = Float3.Dot(endA, endC);
return Mathf.ArcCos(dot * endA.InverseMagnitude * endC.InverseMagnitude);
}
public static Angle Absolute(Angle val) => new(Mathf.Absolute(val.p_deg)); public static Angle Absolute(Angle val) => new(Mathf.Absolute(val.p_deg));
public static Angle Average(params Angle[] vals) => new(Mathf.Average(SplitArray(Type.Degrees, vals))); public static Angle Average(params Angle[] vals) => new(Mathf.Average(SplitArray(Type.Degrees, vals)));
public static Angle Ceiling(Angle val, Type type = Type.Degrees) => public static Angle Ceiling(Angle val, Type type = Type.Degrees) =>
@ -110,6 +123,31 @@ public struct Angle : IAbsolute<Angle>, IAverage<Angle>, IClamp<Angle>, ICloneab
public object Clone() => new Angle(p_deg); public object Clone() => new Angle(p_deg);
public Angle[] GetCoterminalAngles() => GetCoterminalAngles(Zero, Full);
public Angle[] GetCoterminalAngles(Angle lowerBound, Angle upperBound)
{
List<Angle> values = new();
Angle active = this;
// A little bit redundant but it's a fairly easy way to guarentee
// all coterminal angles are between the lower and upper bounds.
// Definitely not the most efficient approach.
while (active < upperBound) active += Full;
active -= Full;
while (active > lowerBound) active -= Full;
active += Full;
while (active < upperBound)
{
values.Add(active);
active += Full;
}
return values.ToArray();
}
public float ValueFromType(Type type) => type switch public float ValueFromType(Type type) => type switch
{ {
Type.Degrees => Degrees, Type.Degrees => Degrees,

View File

@ -2,11 +2,11 @@
public record struct Float2 : IAbsolute<Float2>, IAverage<Float2>, ICeiling<Float2, Int2>, public record struct Float2 : IAbsolute<Float2>, IAverage<Float2>, ICeiling<Float2, Int2>,
IClamp<Float2>, IClampMagnitude<Float2, float>, IComparable<Float2>, IClamp<Float2>, IClampMagnitude<Float2, float>, IComparable<Float2>,
ICross<Float2, Float3>, IDivide<Float2>, IDot<Float2, float>, IEquatable<Float2>, ICross<Float2, float>, IDot<Float2, float>, IEquatable<Float2>,
IFloor<Float2, Int2>, IFromTuple<Float2, (float x, float y)>, IGroup<float>, IFloor<Float2, Int2>, IFromTuple<Float2, (float x, float y)>, IGroup<float>,
ILerp<Float2, float>, IMathOperators<Float2>, IMax<Float2>, IMedian<Float2>, IMin<Float2>, ILerp<Float2, float>, IMathOperators<Float2>, IMax<Float2>, IMedian<Float2>, IMin<Float2>,
IIndexAll<float>, IIndexRangeAll<float>, IPresets2d<Float2>, IProduct<Float2>, IRound<Float2, Int2>, IIndexAll<float>, IIndexRangeAll<float>, IPresets2d<Float2>, IRound<Float2, Int2>,
ISplittable<Float2, (float[] Xs, float[] Ys)>, ISubtract<Float2>, ISum<Float2> ISplittable<Float2, (float[] Xs, float[] Ys)>
{ {
public static Float2 Down => new(0, -1); public static Float2 Down => new(0, -1);
public static Float2 Left => new(-1, 0); public static Float2 Left => new(-1, 0);
@ -16,6 +16,7 @@ public record struct Float2 : IAbsolute<Float2>, IAverage<Float2>, ICeiling<Floa
public static Float2 One => new(1, 1); public static Float2 One => new(1, 1);
public static Float2 Zero => new(0, 0); public static Float2 Zero => new(0, 0);
public float InverseMagnitude => Mathf.InverseSqrt(x * x + y * y);
public float Magnitude => Mathf.Sqrt(x * x + y * y); public float Magnitude => Mathf.Sqrt(x * x + y * y);
public Float2 Normalized => this * Mathf.InverseSqrt(x * x + y * y); public Float2 Normalized => this * Mathf.InverseSqrt(x * x + y * y);
@ -78,7 +79,12 @@ public record struct Float2 : IAbsolute<Float2>, IAverage<Float2>, ICeiling<Floa
public static Float2 Absolute(Float2 val) => public static Float2 Absolute(Float2 val) =>
new(Mathf.Absolute(val.x), Mathf.Absolute(val.y)); new(Mathf.Absolute(val.x), Mathf.Absolute(val.y));
public static Float2 Average(params Float2[] vals) => Sum(vals) / vals.Length; public static Float2 Average(params Float2[] vals)
{
Float2 sum = Zero;
foreach (Float2 f in vals) sum += f;
return sum / vals.Length;
}
public static Int2 Ceiling(Float2 val) => public static Int2 Ceiling(Float2 val) =>
new(Mathf.Ceiling(val.x), Mathf.Ceiling(val.y)); new(Mathf.Ceiling(val.x), Mathf.Ceiling(val.y));
public static Float2 Clamp(Float2 val, Float2 min, Float2 max) => public static Float2 Clamp(Float2 val, Float2 min, Float2 max) =>
@ -95,9 +101,8 @@ public record struct Float2 : IAbsolute<Float2>, IAverage<Float2>, ICeiling<Floa
else if (mag > maxMag) val *= maxMag; else if (mag > maxMag) val *= maxMag;
return val; return val;
} }
public static Float3 Cross(Float2 a, Float2 b, bool normalized = false) => public static float Cross(Float2 a, Float2 b, bool normalized = false) =>
Float3.Cross(a, b, normalized); Float3.Cross(a, b, normalized).z;
public static Float2 Divide(Float2 num, params Float2[] vals) => num / Product(vals);
public static float Dot(Float2 a, Float2 b) => a.x * b.x + a.y * b.y; public static float Dot(Float2 a, Float2 b) => a.x * b.x + a.y * b.y;
public static float Dot(params Float2[] vals) public static float Dot(params Float2[] vals)
{ {
@ -134,22 +139,8 @@ public record struct Float2 : IAbsolute<Float2>, IAverage<Float2>, ICeiling<Floa
foreach (Float2 f in vals) val = f.Magnitude < val.Magnitude ? f : val; foreach (Float2 f in vals) val = f.Magnitude < val.Magnitude ? f : val;
return val; return val;
} }
public static Float2 Product(params Float2[] vals)
{
if (vals.Length < 1) return Zero;
Float2 val = One;
foreach (Float2 f in vals) val *= f;
return val;
}
public static Int2 Round(Float2 val) => public static Int2 Round(Float2 val) =>
new(Mathf.RoundInt(val.x), Mathf.RoundInt(val.y)); new(Mathf.RoundInt(val.x), Mathf.RoundInt(val.y));
public static Float2 Subtract(Float2 num, params Float2[] vals) => num - Sum(vals);
public static Float2 Sum(params Float2[] vals)
{
Float2 val = Zero;
foreach (Float2 f in vals) val += f;
return val;
}
public static (float[] Xs, float[] Ys) SplitArray(params Float2[] vals) public static (float[] Xs, float[] Ys) SplitArray(params Float2[] vals)
{ {
@ -212,7 +203,6 @@ public record struct Float2 : IAbsolute<Float2>, IAverage<Float2>, ICeiling<Floa
public static explicit operator Float2(Int4 val) => new(val.x, val.y); public static explicit operator Float2(Int4 val) => new(val.x, val.y);
public static explicit operator Float2(Matrix m) => new(m[0, 0], m[1, 0]); public static explicit operator Float2(Matrix m) => new(m[0, 0], m[1, 0]);
public static explicit operator Float2(Vector2d val) => val.ToXYZ(); public static explicit operator Float2(Vector2d val) => val.ToXYZ();
public static explicit operator Float2(Vert val) => new(val.position.x, val.position.y);
public static implicit operator Float2(Fill<float> fill) => new(fill); public static implicit operator Float2(Fill<float> fill) => new(fill);
public static implicit operator Float2(Fill<int> fill) => new(fill); public static implicit operator Float2(Fill<int> fill) => new(fill);
public static implicit operator Float2((float x, float y) val) => new(val.x, val.y); public static implicit operator Float2((float x, float y) val) => new(val.x, val.y);

View File

@ -4,11 +4,11 @@ namespace Nerd_STF.Mathematics;
public record struct Float3 : IAbsolute<Float3>, IAverage<Float3>, public record struct Float3 : IAbsolute<Float3>, IAverage<Float3>,
ICeiling<Float3, Int3>, IClamp<Float3>, IClampMagnitude<Float3, float>, IComparable<Float3>, ICeiling<Float3, Int3>, IClamp<Float3>, IClampMagnitude<Float3, float>, IComparable<Float3>,
ICross<Float3>, IDivide<Float3>, IDot<Float3, float>, IEquatable<Float3>, ICross<Float3>, IDot<Float3, float>, IEquatable<Float3>,
IFloor<Float3, Int3>, IFromTuple<Float3, (float x, float y, float z)>, IGroup<float>, IFloor<Float3, Int3>, IFromTuple<Float3, (float x, float y, float z)>, IGroup<float>,
IIndexAll<float>, IIndexRangeAll<float>, ILerp<Float3, float>, IMathOperators<Float3>, IMax<Float3>, IIndexAll<float>, IIndexRangeAll<float>, ILerp<Float3, float>, IMathOperators<Float3>, IMax<Float3>,
IMedian<Float3>, IMin<Float3>, IPresets3d<Float3>, IProduct<Float3>, IRound<Float3, Int3>, IMedian<Float3>, IMin<Float3>, IPresets3d<Float3>, IRound<Float3, Int3>,
ISplittable<Float3, (float[] Xs, float[] Ys, float[] Zs)>, ISubtract<Float3>, ISum<Float3> ISplittable<Float3, (float[] Xs, float[] Ys, float[] Zs)>
{ {
public static Float3 Back => new(0, 0, -1); public static Float3 Back => new(0, 0, -1);
public static Float3 Down => new(0, -1, 0); public static Float3 Down => new(0, -1, 0);
@ -20,6 +20,7 @@ public record struct Float3 : IAbsolute<Float3>, IAverage<Float3>,
public static Float3 One => new(1, 1, 1); public static Float3 One => new(1, 1, 1);
public static Float3 Zero => new(0, 0, 0); public static Float3 Zero => new(0, 0, 0);
public float InverseMagnitude => Mathf.InverseSqrt(x * x + y * y + z * z);
public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z); public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z);
public Float3 Normalized => this * Mathf.InverseSqrt(x * x + y * y + z * z); public Float3 Normalized => this * Mathf.InverseSqrt(x * x + y * y + z * z);
@ -118,7 +119,12 @@ public record struct Float3 : IAbsolute<Float3>, IAverage<Float3>,
public static Float3 Absolute(Float3 val) => public static Float3 Absolute(Float3 val) =>
new(Mathf.Absolute(val.x), Mathf.Absolute(val.y), Mathf.Absolute(val.z)); new(Mathf.Absolute(val.x), Mathf.Absolute(val.y), Mathf.Absolute(val.z));
public static Float3 Average(params Float3[] vals) => Sum(vals) / vals.Length; public static Float3 Average(params Float3[] vals)
{
Float3 sum = Zero;
foreach (Float3 f in vals) sum += f;
return sum / vals.Length;
}
public static Int3 Ceiling(Float3 val) => public static Int3 Ceiling(Float3 val) =>
new(Mathf.Ceiling(val.x), Mathf.Ceiling(val.y), Mathf.Ceiling(val.z)); new(Mathf.Ceiling(val.x), Mathf.Ceiling(val.y), Mathf.Ceiling(val.z));
public static Float3 Clamp(Float3 val, Float3 min, Float3 max) => public static Float3 Clamp(Float3 val, Float3 min, Float3 max) =>
@ -143,7 +149,6 @@ public record struct Float3 : IAbsolute<Float3>, IAverage<Float3>,
a.x * b.y - b.x * a.y); a.x * b.y - b.x * a.y);
return normalized ? val.Normalized : val; return normalized ? val.Normalized : val;
} }
public static Float3 Divide(Float3 num, params Float3[] vals) => num / Product(vals);
public static float Dot(Float3 a, Float3 b) => a.x * b.x + a.y * b.y + a.z * b.z; public static float Dot(Float3 a, Float3 b) => a.x * b.x + a.y * b.y + a.z * b.z;
public static float Dot(params Float3[] vals) public static float Dot(params Float3[] vals)
{ {
@ -181,22 +186,8 @@ public record struct Float3 : IAbsolute<Float3>, IAverage<Float3>,
foreach (Float3 d in vals) val = d.Magnitude < val.Magnitude ? d : val; foreach (Float3 d in vals) val = d.Magnitude < val.Magnitude ? d : val;
return val; return val;
} }
public static Float3 Product(params Float3[] vals)
{
if (vals.Length < 1) return Zero;
Float3 val = One;
foreach (Float3 d in vals) val *= d;
return val;
}
public static Int3 Round(Float3 val) => public static Int3 Round(Float3 val) =>
new(Mathf.RoundInt(val.x), Mathf.RoundInt(val.y), Mathf.RoundInt(val.z)); new(Mathf.RoundInt(val.x), Mathf.RoundInt(val.y), Mathf.RoundInt(val.z));
public static Float3 Subtract(Float3 num, params Float3[] vals) => num - Sum(vals);
public static Float3 Sum(params Float3[] vals)
{
Float3 val = Zero;
foreach (Float3 d in vals) val += d;
return val;
}
public static (float[] Xs, float[] Ys, float[] Zs) SplitArray(params Float3[] vals) public static (float[] Xs, float[] Ys, float[] Zs) SplitArray(params Float3[] vals)
{ {
@ -239,6 +230,14 @@ public record struct Float3 : IAbsolute<Float3>, IAverage<Float3>,
return new(yaw, pitch, mag); return new(yaw, pitch, mag);
} }
public Float2 GetCrossSection(CrossSection2d plane) => plane switch
{
CrossSection2d.XY => XY,
CrossSection2d.YZ => YZ,
CrossSection2d.ZX => XZ,
_ => throw new ArgumentException("Unknown cross section type.")
};
private bool PrintMembers(StringBuilder builder) private bool PrintMembers(StringBuilder builder)
{ {
builder.Append("x = "); builder.Append("x = ");
@ -270,7 +269,6 @@ public record struct Float3 : IAbsolute<Float3>, IAverage<Float3>,
public static explicit operator Float3(Int4 val) => new(val.x, val.y, val.z); public static explicit operator Float3(Int4 val) => new(val.x, val.y, val.z);
public static explicit operator Float3(Matrix m) => new(m[0, 0], m[1, 0], m[2, 0]); public static explicit operator Float3(Matrix m) => new(m[0, 0], m[1, 0], m[2, 0]);
public static explicit operator Float3(Vector2d val) => val.ToXYZ(); public static explicit operator Float3(Vector2d val) => val.ToXYZ();
public static implicit operator Float3(Vert val) => new(val.position.x, val.position.y, val.position.z);
public static explicit operator Float3(RGBA val) => new(val.R, val.G, val.B); public static explicit operator Float3(RGBA val) => new(val.R, val.G, val.B);
public static explicit operator Float3(HSVA val) => new(val.H.Normalized, val.S, val.V); public static explicit operator Float3(HSVA val) => new(val.H.Normalized, val.S, val.V);
public static explicit operator Float3(RGBAByte val) => (Float3)val.ToRGBA(); public static explicit operator Float3(RGBAByte val) => (Float3)val.ToRGBA();

View File

@ -2,12 +2,11 @@
public record struct Float4 : IAbsolute<Float4>, public record struct Float4 : IAbsolute<Float4>,
IAverage<Float4>, ICeiling<Float4, Int4>, IClamp<Float4>, IClampMagnitude<Float4, float>, IAverage<Float4>, ICeiling<Float4, Int4>, IClamp<Float4>, IClampMagnitude<Float4, float>,
IComparable<Float4>, IDivide<Float4>, IDot<Float4, float>, IEquatable<Float4>, IComparable<Float4>, IDot<Float4, float>, IEquatable<Float4>,
IFloor<Float4, Int4>, IFromTuple<Float4, (float x, float y, float z, float w)>, IFloor<Float4, Int4>, IFromTuple<Float4, (float x, float y, float z, float w)>,
IGroup<float>, IIndexAll<float>, IIndexRangeAll<float>, ILerp<Float4, float>, IMathOperators<Float4>, IGroup<float>, IIndexAll<float>, IIndexRangeAll<float>, ILerp<Float4, float>, IMathOperators<Float4>,
IMax<Float4>, IMedian<Float4>, IMin<Float4>, IPresets4d<Float4>, IProduct<Float4>, IRound<Float4, Int4>, IMax<Float4>, IMedian<Float4>, IMin<Float4>, IPresets4d<Float4>, IRound<Float4, Int4>,
ISplittable<Float4, (float[] Xs, float[] Ys, float[] Zs, float[] Ws)>, ISubtract<Float4>, ISplittable<Float4, (float[] Xs, float[] Ys, float[] Zs, float[] Ws)>
ISum<Float4>
{ {
public static Float4 Back => new(0, 0, -1, 0); public static Float4 Back => new(0, 0, -1, 0);
public static Float4 Down => new(0, -1, 0, 0); public static Float4 Down => new(0, -1, 0, 0);
@ -21,6 +20,7 @@ public record struct Float4 : IAbsolute<Float4>,
public static Float4 One => new(1, 1, 1, 1); public static Float4 One => new(1, 1, 1, 1);
public static Float4 Zero => new(0, 0, 0, 0); public static Float4 Zero => new(0, 0, 0, 0);
public float InverseMagnitude => Mathf.InverseSqrt(x * x + y * y + z * z + w * w);
public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z + w * w); public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z + w * w);
public Float4 Normalized => this * Mathf.InverseSqrt(x * x + y * y + z * z + w * w); public Float4 Normalized => this * Mathf.InverseSqrt(x * x + y * y + z * z + w * w);
@ -194,7 +194,12 @@ public record struct Float4 : IAbsolute<Float4>,
public static Float4 Absolute(Float4 val) => public static Float4 Absolute(Float4 val) =>
new(Mathf.Absolute(val.x), Mathf.Absolute(val.y), Mathf.Absolute(val.z), Mathf.Absolute(val.w)); new(Mathf.Absolute(val.x), Mathf.Absolute(val.y), Mathf.Absolute(val.z), Mathf.Absolute(val.w));
public static Float4 Average(params Float4[] vals) => Sum(vals) / vals.Length; public static Float4 Average(params Float4[] vals)
{
Float4 sum = Zero;
foreach (Float4 f in vals) sum += f;
return sum / vals.Length;
}
public static Int4 Ceiling(Float4 val) => public static Int4 Ceiling(Float4 val) =>
new(Mathf.Ceiling(val.x), Mathf.Ceiling(val.y), Mathf.Ceiling(val.z), Mathf.Ceiling(val.w)); new(Mathf.Ceiling(val.x), Mathf.Ceiling(val.y), Mathf.Ceiling(val.z), Mathf.Ceiling(val.w));
public static Float4 Clamp(Float4 val, Float4 min, Float4 max) => public static Float4 Clamp(Float4 val, Float4 min, Float4 max) =>
@ -213,7 +218,6 @@ public record struct Float4 : IAbsolute<Float4>,
else if (mag > maxMag) val *= maxMag; else if (mag > maxMag) val *= maxMag;
return val; return val;
} }
public static Float4 Divide(Float4 num, params Float4[] vals) => num / Product(vals);
public static float Dot(Float4 a, Float4 b) => a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; public static float Dot(Float4 a, Float4 b) => a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
public static float Dot(params Float4[] vals) public static float Dot(params Float4[] vals)
{ {
@ -256,20 +260,6 @@ public record struct Float4 : IAbsolute<Float4>,
public static Int4 Round(Float4 val) => public static Int4 Round(Float4 val) =>
new(Mathf.RoundInt(val.x), Mathf.RoundInt(val.y), Mathf.RoundInt(val.z), new(Mathf.RoundInt(val.x), Mathf.RoundInt(val.y), Mathf.RoundInt(val.z),
Mathf.RoundInt(val.w)); Mathf.RoundInt(val.w));
public static Float4 Product(params Float4[] vals)
{
if (vals.Length < 1) return Zero;
Float4 val = One;
foreach (Float4 d in vals) val *= d;
return val;
}
public static Float4 Subtract(Float4 num, params Float4[] vals) => num - Sum(vals);
public static Float4 Sum(params Float4[] vals)
{
Float4 val = Zero;
foreach (Float4 d in vals) val += d;
return val;
}
public static (float[] Xs, float[] Ys, float[] Zs, float[] Ws) SplitArray(params Float4[] vals) public static (float[] Xs, float[] Ys, float[] Zs, float[] Ws) SplitArray(params Float4[] vals)
{ {
@ -338,7 +328,6 @@ public record struct Float4 : IAbsolute<Float4>,
public static implicit operator Float4(Int4 val) => new(val.x, val.y, val.z, val.w); public static implicit operator Float4(Int4 val) => new(val.x, val.y, val.z, val.w);
public static explicit operator Float4(Matrix m) => new(m[0, 0], m[1, 0], m[2, 0], m[3, 0]); public static explicit operator Float4(Matrix m) => new(m[0, 0], m[1, 0], m[2, 0], m[3, 0]);
public static explicit operator Float4(Vector2d val) => val.ToXYZ(); public static explicit operator Float4(Vector2d val) => val.ToXYZ();
public static implicit operator Float4(Vert val) => new(val.position.x, val.position.y, val.position.z, 0);
public static implicit operator Float4(RGBA val) => new(val.R, val.G, val.B, val.A); public static implicit operator Float4(RGBA val) => new(val.R, val.G, val.B, val.A);
public static explicit operator Float4(CMYKA val) => new(val.C, val.M, val.Y, val.K); public static explicit operator Float4(CMYKA val) => new(val.C, val.M, val.Y, val.K);
public static explicit operator Float4(HSVA val) => new(val.H.Normalized, val.S, val.V, val.A); public static explicit operator Float4(HSVA val) => new(val.H.Normalized, val.S, val.V, val.A);

View File

@ -1,4 +1,4 @@
namespace Nerd_STF.Mathematics.Abstract; namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IClosestTo<T> where T : IEquatable<T> public interface IClosestTo<T> where T : IEquatable<T>
{ {

View File

@ -1,4 +1,4 @@
namespace Nerd_STF.Mathematics.Abstract; namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IContains<T> where T : IEquatable<T> public interface IContains<T> where T : IEquatable<T>
{ {

View File

@ -0,0 +1,17 @@
namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IContainsGeometry2d<T> : IContains<Float2>, IContains<Box2d>, IContains<Line>,
IContains<Triangle>, IIntersect<Box2d>, IIntersect<Line>, IIntersect<Triangle>
where T : IContainsGeometry2d<T>, IEquatable<T>
{
public bool Contains(T obj);
public bool Intersects(T obj);
public bool Contains(IEnumerable<Float2> points);
public bool Contains(Fill<Float2> points, int count);
public bool Intersects(IEnumerable<Line> lines);
public bool Intersects(Fill<Line> lines, int count);
public bool Contains<TOther>(TOther obj) where TOther : IPolygon<TOther>;
public bool Intersects<TOther>(TOther obj) where TOther : IPolygon<TOther>;
}

View File

@ -0,0 +1,17 @@
namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IContainsGeometry3d<T> : IContains<Float3>, IContains<Box2d>, IContains<Line>,
IContains<Triangle>, IIntersect<Box2d>, IIntersect<Line>, IIntersect<Triangle>
where T : IContainsGeometry3d<T>, IEquatable<T>
{
public bool Contains(T obj);
public bool Intersects(T obj);
public bool Contains(IEnumerable<Float3> points);
public bool Contains(Fill<Float3> points, int count);
public bool Intersects(IEnumerable<Line> lines);
public bool Intersects(Fill<Line> lines, int count);
public bool Contains<TOther>(TOther obj) where TOther : IPolygon<TOther>;
public bool Intersects<TOther>(TOther obj) where TOther : IPolygon<TOther>;
}

View File

@ -1,4 +1,4 @@
namespace Nerd_STF.Mathematics.Abstract; namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IEncapsulate<T, TE> : IContains<TE> where T : IEquatable<T> where TE : IEquatable<TE> public interface IEncapsulate<T, TE> : IContains<TE> where T : IEquatable<T> where TE : IEquatable<TE>
{ {

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IGeometricModifiers2d<T> : IGeometricRotate2d<T>, IGeometricScale2d<T>,
IGeometricTranslate2d<T>
where T : IGeometricModifiers2d<T>
{ }

View File

@ -0,0 +1,12 @@
namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IGeometricModifiers3d<T> where T : IGeometricModifiers3d<T>
{
public void Scale(float factor);
public void Scale(Float3 factor);
public void Translate(Float3 offset);
public T ScaleImmutable(float factor);
public T ScaleImmutable(Float3 factor);
public T TranslateImmutable(Float3 offset);
}

View File

@ -0,0 +1,12 @@
namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IGeometricRotate2d<T> where T : IGeometricRotate2d<T>
{
public void Rotate(Angle rot);
public void Rotate(Complex rot);
public void Rotate(Matrix2x2 rotMatrix);
public T RotateImmutable(Angle rot);
public T RotateImmutable(Complex rot);
public T RotateImmutable(Matrix2x2 rotMatrix);
}

View File

@ -0,0 +1,10 @@
namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IGeometricScale2d<T> where T : IGeometricScale2d<T>
{
public void Scale(float factor);
public void Scale(Float2 factor);
public T ScaleImmutable(float factor);
public T ScaleImmutable(Float2 factor);
}

View File

@ -0,0 +1,7 @@
namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IGeometricTranslate2d<T> where T : IGeometricTranslate2d<T>
{
public void Translate(Float2 offset);
public T TranslateImmutable(Float2 offset);
}

View File

@ -0,0 +1,6 @@
namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IIntersect<T> where T : IEquatable<T>
{
public bool Intersects(T other);
}

View File

@ -0,0 +1,32 @@
using Nerd_STF.Mathematics.Geometry.Abstract;
namespace Nerd_STF.Mathematics.Abstract;
public interface IPolygon<T> : IAverage<T>, IContainsGeometry3d<T>, IEquatable<T>,
IFloatArray<T>, IGeometricModifiers3d<T>, IGroup<Float3>, IIndexAll<Float3>,
IIndexRangeAll<Float3>, ILerp<T, float>, IMedian<T>, ITriangulate
where T : IPolygon<T>
{
public float Area { get; }
public Float3 Midpoint { get; }
public float Perimeter { get; }
public Float3[] GetAllVerts();
public Line[] GetOutlines();
public static abstract T operator +(T poly, Float3 offset);
public static abstract T operator -(T poly, Float3 offset);
public static abstract T operator *(T poly, float scale);
public static abstract T operator *(T poly, Float3 scale3);
public static abstract T operator /(T poly, float scale);
public static abstract T operator /(T poly, Float3 scale3);
public static abstract bool operator ==(T a, T b);
public static abstract bool operator !=(T a, T b);
public static abstract implicit operator T(Fill<Float3> fill);
public static abstract implicit operator T(Fill<Int3> fill);
public static abstract implicit operator T(Fill<Line> fill);
public static abstract implicit operator T(Fill<float> fill);
public static abstract implicit operator T(Fill<int> fill);
}

View File

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

View File

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

View File

@ -1,115 +0,0 @@
namespace Nerd_STF.Mathematics.Geometry;
public record class Box2D : IAbsolute<Box2D>, IAverage<Box2D>, ICeiling<Box2D>, IClamp<Box2D>, IContains<Vert>,
IEquatable<Box2D>, IFloor<Box2D>, ILerp<Box2D, float>, IMedian<Box2D>, IRound<Box2D>, IShape2d<float>,
ISplittable<Box2D, (Vert[] centers, Float2[] sizes)>
{
public static Box2D Unit => new(Vert.Zero, Float2.One);
public Vert MaxVert
{
get => center + (size / 2);
set
{
Vert diff = center - value;
size = (Float2)diff.position * 2f;
}
}
public Vert MinVert
{
get => center - (size / 2);
set
{
Vert diff = center + value;
size = (Float2)diff.position * 2f;
}
}
public float Area => size.x * size.y;
public float Perimeter => 2 * (size.x + size.y);
public Vert center;
public Float2 size;
public Box2D(Vert min, Vert max) : this(Vert.Average(min, max), (Float2)(min - max)) { }
public Box2D(Vert center, Float2 size)
{
this.center = center;
this.size = size;
}
public Box2D(Fill<float> fill) : this(fill, new Float2(fill(3), fill(4))) { }
public float this[int index]
{
get => size[index];
set => size[index] = value;
}
public static Box2D Absolute(Box2D val) => new(Vert.Absolute(val.MinVert), Vert.Absolute(val.MaxVert));
public static Box2D Average(params Box2D[] vals)
{
(Vert[] centers, Float2[] sizes) = SplitArray(vals);
return new(Vert.Average(centers), Float2.Average(sizes));
}
public static Box2D Ceiling(Box2D val) => new(Vert.Ceiling(val.center), Float2.Ceiling(val.size));
public static Box2D Clamp(Box2D val, Box2D min, Box2D max) =>
new(Vert.Clamp(val.center, min.center, max.center), Float2.Clamp(val.size, min.size, max.size));
public static Box2D Floor(Box2D val) => new(Vert.Floor(val.center), Float2.Floor(val.size));
public static Box2D Lerp(Box2D a, Box2D b, float t, bool clamp = true) =>
new(Vert.Lerp(a.center, b.center, t, clamp), Float2.Lerp(a.size, b.size, t, clamp));
public static Box2D Median(params Box2D[] vals)
{
(Vert[] verts, Float2[] sizes) = SplitArray(vals);
return new(Vert.Median(verts), Float2.Median(sizes));
}
public static Box2D Round(Box2D val) => new(Vert.Round(val.center), Float2.Round(val.size));
public static (Vert[] centers, Float2[] sizes) SplitArray(params Box2D[] vals)
{
Vert[] centers = new Vert[vals.Length];
Float2[] sizes = new Float2[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
centers[i] = vals[i].center;
sizes[i] = vals[i].size;
}
return (centers, sizes);
}
public virtual bool Equals(Box2D? other)
{
if (other is null) return false;
return center == other.center && size == other.size;
}
public override int GetHashCode() => base.GetHashCode();
public bool Contains(Vert vert)
{
Float2 diff = Float2.Absolute((Float2)(center - vert));
return diff.x <= size.x && diff.y <= size.y;
}
protected virtual bool PrintMembers(StringBuilder builder)
{
builder.Append("Min = ");
builder.Append(MinVert);
builder.Append(", Max = ");
builder.Append(MaxVert);
return true;
}
public static Box2D operator +(Box2D a, Vert b) => new(a.center + b, a.size);
public static Box2D operator +(Box2D a, Float2 b) => new(a.center, a.size + b);
public static Box2D operator -(Box2D b) => new(-b.MaxVert, -b.MinVert);
public static Box2D operator -(Box2D a, Vert b) => new(a.center - b, a.size);
public static Box2D operator -(Box2D a, Float2 b) => new(a.center, a.size - b);
public static Box2D operator *(Box2D a, float b) => new(a.center * b, a.size * b);
public static Box2D operator *(Box2D a, Float2 b) => new(a.center, a.size * b);
public static Box2D operator /(Box2D a, float b) => new(a.center / b, a.size / b);
public static Box2D operator /(Box2D a, Float2 b) => new(a.center, a.size / b);
public static implicit operator Box2D(Fill<float> fill) => new(fill);
public static explicit operator Box2D(Box3D box) => new(box.center, (Float2)box.size);
}

View File

@ -0,0 +1,335 @@
using Nerd_STF.Mathematics.Geometry.Abstract;
namespace Nerd_STF.Mathematics.Geometry;
public class Box2d : IAverage<Box2d>, IContainsGeometry2d<Box2d>, IEquatable<Box2d>,
ILerp<Box2d, float>, IMedian<Box2d>,
ISplittable<Box2d, (Float2[] centers, Float2[] extents)>, ISubdivide<Box2d>, ITriangulate,
IWithinRange<Float2, float>
{
public float Area => Size.x * Size.y;
public float Perimeter => 2 * Size.x + 2 * Size.y;
public Float2 Midpoint => center;
public float Height
{
get => extents.y * 2;
set => extents.y = value / 2;
}
public Float2 Max
{
get => center + extents;
set => extents = value - center;
}
public Float2 Min
{
get => center - extents;
set => extents = center - value;
}
public Float2 Size
{
get => extents * 2;
set => extents = value / 2;
}
public float Width
{
get => extents.x * 2;
set => extents.x = value / 2;
}
public Float2 center, extents;
public Box2d()
{
center = Float2.Zero;
extents = Float2.Zero;
}
public Box2d(Float2 center, Float2 extents)
{
this.center = center;
this.extents = extents;
}
public Box2d(Float2 center, float width, float height)
{
this.center = center;
extents = (width, height);
}
public Box2d(float centerX, float centerY, float width, float height)
{
center = (centerX, centerY);
extents = (width, height);
}
public Box2d(Fill<Float2> fill) : this(fill(0), fill(1)) { }
public Box2d(Fill<Int2> fill) : this(fill(0), fill(1)) { }
public Box2d(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public Box2d(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public static Box2d FromRange(Float2 min, Float2 max)
{
if (min.x > max.x) (max.x, min.x) = (min.x, max.x);
if (min.y > max.y) (max.y, min.y) = (min.y, max.y);
return new((max + min) / 2, (max - min) / 2);
}
public static Box2d Average(params Box2d[] vals)
{
(Float2[] centers, Float2[] extents) = SplitArray(vals);
return new(Float2.Average(centers), Float2.Average(extents));
}
public static Box2d Lerp(Box2d a, Box2d b, float t, bool clamp = true) =>
FromRange(Float2.Lerp(a.Min, b.Min, t, clamp), Float2.Lerp(a.Max, b.Max, t, clamp));
public static (Float2[] centers, Float2[] extents) SplitArray(params Box2d[] vals)
{
Float2[] centers = new Float2[vals.Length],
extents = new Float2[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
centers[i] = vals[i].center;
extents[i] = vals[i].extents;
}
return (centers, extents);
}
public Float2 AlongRay(Float2 p) => GeometryHelper.Box2dAlongRay(this, p);
public Float2 ClosestTo(Float2 p) => Float2.Clamp(p, Min, Max);
public bool Contains(Float2 point)
{
Float2 diff = Float2.Absolute(point - center);
return diff.x <= extents.x && diff.y <= extents.y;
}
public bool Contains<T>(T poly) where T : IPolygon<T>
{
Float3[] verts = poly.GetAllVerts();
if (verts.Length < 1) return false;
foreach (Float3 v in verts) if (!Contains((Float2)v)) return false;
return true;
}
public bool Contains(Box2d box) => Contains(box.Min) && Contains(box.Max);
public bool Contains(Line line) => Contains((Float2)line.a) && Contains((Float2)line.b);
public bool Contains(Triangle tri) =>
Contains((Float2)tri.a) && Contains((Float2)tri.b) & Contains((Float2)tri.c);
public bool Contains(IEnumerable<Float2> points)
{
foreach (Float3 p in points) if (!Contains((Float2)p)) return false;
return true;
}
public bool Contains(Fill<Float2> points, int count)
{
for (int i = 0; i < count; i++) if (!Contains(points(i))) return false;
return true;
}
public void Encapsulate(Float2 point)
{
if (Contains(point)) return;
// Pick which of the corners to extend.
Float2 min = Min, max = Max;
if (point.x > max.x) max.x = point.x;
else if (point.x < min.x) min.x = point.x;
if (point.y > max.y) max.y = point.y;
else if (point.y < min.y) min.y = point.y;
center = Float2.Average(min, max);
extents = (max - min) / 2;
}
public void Encapsulate<T>(T poly) where T : IPolygon<T>
{
foreach (Float3 p in poly.GetAllVerts()) Encapsulate((Float2)p);
}
public void Encapsulate(Box2d box)
{
Encapsulate(box.Min);
Encapsulate(box.Max);
}
public void Encapsulate(Line line)
{
Encapsulate((Float2)line.a);
Encapsulate((Float2)line.b);
}
public void Encapsulate(Triangle tri)
{
Encapsulate((Float2)tri.a);
Encapsulate((Float2)tri.b);
Encapsulate((Float2)tri.c);
}
public void Encapsulate(IEnumerable<Float2> points)
{
foreach (Float2 p in points) Encapsulate(p);
}
public void Encapsulate(Fill<Float2> points, int count)
{
for (int i = 0; i < count; i++) Encapsulate(points(i));
}
public void Encapsulate(IEnumerable<Line> lines)
{
foreach (Line l in lines)
{
Encapsulate((Float2)l.a);
Encapsulate((Float2)l.b);
}
}
public void Encapsulate(Fill<Line> lines, int count)
{
for (int i = 0; i < count; i++)
{
Line l = lines(i);
Encapsulate((Float2)l.a);
Encapsulate((Float2)l.b);
}
}
public bool Intersects<T>(T poly) where T : IPolygon<T>
{
// For some odd reason, I have to cast backwards to IContains<Box2d>
// in order to use the Contains(Box2d) method, despite it being inherited by
// IContainsGeometry3d<IPolygon<T>> which is inherited by IPolygon<T>.
// Weird.
if (Contains(poly) || ((IContains<Box2d>)poly).Contains(this)) return true;
Line[] lines = poly.GetOutlines();
foreach (Line l in lines) if (Contains(l)) return true;
return false;
}
public bool Intersects(Box2d box)
{
if (Contains(box) || box.Contains(this)) return true;
// A bunch of brute force work but it's still decently fast.
(Line top, Line right, Line bottom, Line left) = box.GetOutlines();
return Intersects(top) || Intersects(right) || Intersects(bottom) || Intersects(left);
}
public bool Intersects(Line line)
{
if (Contains(line)) return true;
(Line top, Line right, Line bottom, Line left) = GetOutlines();
return GeometryHelper.LineIntersects2d(line, top, CrossSection2d.XY) ||
GeometryHelper.LineIntersects2d(line, right, CrossSection2d.XY) ||
GeometryHelper.LineIntersects2d(line, bottom, CrossSection2d.XY) ||
GeometryHelper.LineIntersects2d(line, left, CrossSection2d.XY);
}
public bool Intersects(Triangle tri)
{
if (Contains(tri) || tri.Contains(this)) return true;
return Intersects(tri.AB) || Intersects(tri.BC) || Intersects(tri.CA);
}
public bool Intersects(IEnumerable<Line> lines)
{
foreach (Line l in lines) if (Intersects(l)) return true;
return false;
}
public bool Intersects(Fill<Line> lines, int count)
{
for (int i = 0; i < count; i++) if (Intersects(lines(i))) return true;
return false;
}
public Float2 LerpAcrossOutline(float t, bool clamp = true)
{
if (clamp) t = Mathf.Clamp(t, 0, 1);
else t = Mathf.AbsoluteMod(t, 1);
(Line top, Line right, Line bottom, Line left) = GetOutlines();
float weightTB = top.Length / Perimeter, weightLR = left.Length / Perimeter;
if (t < weightTB) return (Float2)top.LerpAcross(t / weightTB);
else if (t < 0.5f) return (Float2)right.LerpAcross((t - weightTB) / weightLR);
else if (t < 0.5f + weightTB) return (Float2)bottom.LerpAcross((t - 0.5f) / weightTB);
else return (Float2)left.LerpAcross((t - 0.5f - weightTB) / weightLR);
}
public override bool Equals(object? obj)
{
if (obj is null) return false;
else if (obj is Box2d box2d) return Equals(box2d);
return false;
}
public bool Equals(Box2d? other) => other is not null &&
center == other.center && extents == other.extents;
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => $"{nameof(Box2d)} {{ " +
$"Min = {Min}, Max = {Max} }}";
public (Float2 topLeft, Float2 topRight, Float2 bottomRight, Float2 bottomLeft) GetCorners()
{
float top = center.y + extents.y, bottom = center.y - extents.y,
left = center.x - extents.x, right = center.x + extents.x;
return ((top, left), (top, right), (bottom, right), (bottom, left));
}
public (Line top, Line right, Line bottom, Line left) GetOutlines()
{
(Float2 topLeft, Float2 topRight, Float2 bottomRight, Float2 bottomLeft) = GetCorners();
return ((topLeft, topRight), (topRight, bottomRight),
(bottomRight, bottomLeft), (bottomLeft, topLeft));
}
public Box2d[] Subdivide()
{
Float2 halfExtents = extents / 2;
(Float2 topLeft, Float2 topRight, Float2 bottomRight, Float2 bottomLeft) = GetCorners();
return new Box2d[]
{
new(Float2.Lerp(center, topLeft, 0.5f), halfExtents),
new(Float2.Lerp(center, topRight, 0.5f), halfExtents),
new(Float2.Lerp(center, bottomRight, 0.5f), halfExtents),
new(Float2.Lerp(center, bottomLeft, 0.5f), halfExtents)
};
}
public Box2d[] Subdivide(int iterations)
{
Box2d[] active = new[] { this };
for (int i = 0; i < iterations; i++)
{
List<Box2d> newBoxes = new();
foreach (Box2d box in active) newBoxes.AddRange(box.Subdivide());
active = newBoxes.ToArray();
}
return active;
}
public Triangle[] Triangulate()
{
(Float2 topLeft, Float2 topRight, Float2 bottomRight, Float2 bottomLeft) = GetCorners();
return new Triangle[]
{
(bottomLeft, topLeft, topRight),
(topRight, bottomRight, bottomLeft)
};
}
public bool WithinRange(Float2 point, float range)
{
// First, get the distance to each edge.
float top = center.y + extents.y, bottom = center.y - extents.y,
left = center.x - extents.x, right = center.x + extents.x;
(Float2 topLeft, Float2 topRight, Float2 bottomRight, Float2 bottomLeft) = GetCorners();
// Positive if inside the box, but that doesn't matter.
float toTop = Mathf.Absolute(top - point.y),
toBottom = Mathf.Absolute(point.y - bottom),
toLeft = Mathf.Absolute(left - point.x),
toRight = Mathf.Absolute(point.x - right);
// Then get the distance to each corner.
float toTL = (topLeft - point).Magnitude,
toTR = (topRight - point).Magnitude,
toBR = (bottomRight - point).Magnitude,
toBL = (bottomLeft - point).Magnitude;
// Get the minimum of them all and compare.
return Mathf.Min(toTop, toBottom, toRight, toLeft,
toTL, toTR, toBL, toBR) <= range;
}
public enum ClosestToMethod
{
Iterative,
RayCalculations
}
}

View File

@ -1,119 +0,0 @@
namespace Nerd_STF.Mathematics.Geometry;
public record class Box3D : IAbsolute<Box3D>, IAverage<Box3D>, ICeiling<Box3D>, IClamp<Box3D>,
IContains<Vert>, IEquatable<Box3D>, IFloor<Box3D>, ILerp<Box3D, float>, IMedian<Box3D>,
IRound<Box3D>, IShape3d<float>, ISplittable<Box3D, (Vert[] centers, Float3[] sizes)>
{
public static Box3D Unit => new(Vert.Zero, Float3.One);
public Vert MaxVert
{
get => center + (Vert)(size / 2);
set
{
Vert diff = center - value;
size = diff.position * 2;
}
}
public Vert MinVert
{
get => center - (Vert)(size / 2);
set
{
Vert diff = center + value;
size = diff.position * 2;
}
}
public float Perimeter => 2 * (size.x + size.y + size.z);
public float SurfaceArea => 2 * (size.x * size.y + size.y * size.z + size.x * size.z);
public float Volume => size.x * size.y * size.z;
public Vert center;
public Float3 size;
public Box3D(Box2D box) : this(box.center, (Float3)box.size) { }
public Box3D(Vert min, Vert max) : this(Vert.Average(min, max), (Float3)(min - max)) { }
public Box3D(Vert center, Float3 size)
{
this.center = center;
this.size = size;
}
public Box3D(Fill<float> fill) : this(fill, new Float3(fill(3), fill(4), fill(5))) { }
public float this[int index]
{
get => size[index];
set => size[index] = value;
}
public static Box3D Absolute(Box3D val) => new(Vert.Absolute(val.MinVert), Vert.Absolute(val.MaxVert));
public static Box3D Average(params Box3D[] vals)
{
(Vert[] centers, Float3[] sizes) = SplitArray(vals);
return new(Vert.Average(centers), Float3.Average(sizes));
}
public static Box3D Ceiling(Box3D val) =>
new(Vert.Ceiling(val.center), (Float3)Float3.Ceiling(val.size));
public static Box3D Clamp(Box3D val, Box3D min, Box3D max) =>
new(Vert.Clamp(val.center, min.center, max.center), Float3.Clamp(val.size, min.size, max.size));
public static Box3D Floor(Box3D val) =>
new(Vert.Floor(val.center), (Float3)Float3.Floor(val.size));
public static Box3D Lerp(Box3D a, Box3D b, float t, bool clamp = true) =>
new(Vert.Lerp(a.center, b.center, t, clamp), Float3.Lerp(a.size, b.size, t, clamp));
public static Box3D Median(params Box3D[] vals)
{
(Vert[] verts, Float3[] sizes) = SplitArray(vals);
return new(Vert.Median(verts), Float3.Median(sizes));
}
public static Box3D Round(Box3D val) => new(Vert.Ceiling(val.center), (Float3)Float3.Ceiling(val.size));
public static (Vert[] centers, Float3[] sizes) SplitArray(params Box3D[] vals)
{
Vert[] centers = new Vert[vals.Length];
Float3[] sizes = new Float3[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
centers[i] = vals[i].center;
sizes[i] = vals[i].size;
}
return (centers, sizes);
}
public virtual bool Equals(Box3D? other)
{
if (other is null) return false;
return center == other.center && size == other.size;
}
public override int GetHashCode() => base.GetHashCode();
public bool Contains(Vert vert)
{
Float3 diff = Float3.Absolute(center - vert);
return diff.x <= size.x && diff.y <= size.y && diff.z <= size.z;
}
protected virtual bool PrintMembers(StringBuilder builder)
{
builder.Append("Min = ");
builder.Append(MinVert);
builder.Append(", Max = ");
builder.Append(MaxVert);
return true;
}
public static Box3D operator +(Box3D a, Vert b) => new(a.center + b, a.size);
public static Box3D operator +(Box3D a, Float3 b) => new(a.center, a.size + b);
public static Box3D operator -(Box3D b) => new(-b.MaxVert, -b.MinVert);
public static Box3D operator -(Box3D a, Vert b) => new(a.center - b, a.size);
public static Box3D operator -(Box3D a, Float3 b) => new(a.center, a.size - b);
public static Box3D operator *(Box3D a, float b) => new(a.center * b, a.size * b);
public static Box3D operator *(Box3D a, Float3 b) => new(a.center, a.size * b);
public static Box3D operator /(Box3D a, float b) => new(a.center / b, a.size / b);
public static Box3D operator /(Box3D a, Float3 b) => new(a.center, a.size / b);
public static implicit operator Box3D(Fill<float> fill) => new(fill);
public static implicit operator Box3D(Box2D box) => new(box);
}

View File

@ -0,0 +1,321 @@
using Nerd_STF.Mathematics.Geometry.Abstract;
namespace Nerd_STF.Mathematics.Geometry;
public class Ellipse : IAverage<Ellipse>, IEquatable<Ellipse>,
IGeometricScale2d<Ellipse>, IGeometricTranslate2d<Ellipse>, ILerp<Ellipse, float>,
IMedian<Ellipse>, IPresets0d<Ellipse>,
ISplittable<Ellipse, (Float2[] positions, Float2[] radii)>, ITriangulate
{
public static Ellipse Unit => new(Float2.Zero, Float2.One, true);
public float Area => Constants.Pi * Radius.x * Radius.y;
public float Perimeter => GeometryHelper.EllipsePerimeterRamanujan2(this);
public float Eccentricity =>
Mathf.Sqrt(Radius.x * Radius.x - Radius.y * Radius.y) / Radius.x;
public float H =>
((Radius.x - Radius.y) * (Radius.x - Radius.y))
/ ((Radius.x + Radius.y) * (Radius.x + Radius.y));
public Float2 Bottom
{
get => Position + Float2.Down * Radius;
set
{
Float2 pos = Position;
Float2 rad = Radius;
pos.x = value.x;
float offset = (pos + Float2.Down * rad).y - value.y;
rad.y *= offset / 2;
pos.y += offset / 2;
Position = pos;
Radius = rad;
}
}
public Float2 Left
{
get => Position + Float2.Left * Radius;
set
{
Float2 pos = Position;
Float2 rad = Radius;
pos.x = value.x;
float offset = (pos + Float2.Left * rad).y - value.y;
rad.y *= offset / 2;
pos.y += offset / 2;
Position = pos;
Radius = rad;
}
}
public Float2 Right
{
get => Position + Float2.Right * Radius;
set
{
Float2 pos = Position;
Float2 rad = Radius;
pos.x = value.x;
float offset = (pos + Float2.Right * rad).y - value.y;
rad.y *= offset / 2;
pos.y += offset / 2;
Position = pos;
Radius = rad;
}
}
public Float2 Top
{
get => Position + Float2.Up * Radius;
set
{
Float2 pos = Position;
Float2 rad = Radius;
pos.x = value.x;
float offset = (pos + Float2.Up * rad).y - value.y;
rad.y *= offset / 2;
pos.y += offset / 2;
Position = pos;
Radius = rad;
}
}
public Float2 Position
{
get => p_position;
set => p_position = value;
}
public Float2 Radius
{
get => p_radius;
set
{
float ogRadiusAspect = p_radius.y / p_radius.x,
newRadiusAspect = value.y / value.x;
if (LockAspect && ogRadiusAspect != newRadiusAspect) ThrowLockedAspect();
p_radius = Float2.Absolute(value);
}
}
private Float2 p_position;
private Float2 p_radius;
public bool LockAspect { get; init; }
public Ellipse(Float2 position, Float2 radius, bool lockAspect = false)
{
p_position = position;
p_radius = Float2.Absolute(radius);
this.LockAspect = lockAspect;
}
public Ellipse(Float2 position, float radius, bool lockAspect = false)
{
p_position = position;
p_radius = Float2.Absolute((radius, radius));
this.LockAspect = lockAspect;
}
public Ellipse(Float2 position, float radiusX, float radiusY, bool lockAspect = false)
{
p_position = position;
p_radius = Float2.Absolute((radiusX, radiusY));
this.LockAspect = lockAspect;
}
public Ellipse(float x, float y, Float2 radius, bool lockAspect = false)
{
p_position = (x, y);
p_radius = Float2.Absolute(radius);
this.LockAspect = lockAspect;
}
public Ellipse(float x, float y, float radius, bool lockAspect = false)
{
p_position = (x, y);
p_radius = Float2.Absolute((radius, radius));
this.LockAspect = lockAspect;
}
public Ellipse(float x, float y, float radiusX, float radiusY, bool lockAspect = false)
{
p_position = (x, y);
p_radius = Float2.Absolute((radiusX, radiusY));
this.LockAspect = lockAspect;
}
public Ellipse(Fill<Float2> fill, bool lockAspect = false)
: this(fill(0), fill(1), lockAspect) { }
public Ellipse(Fill<Int2> fill, bool lockAspect = false)
: this(fill(0), fill(1), lockAspect) { }
public Ellipse(Fill<float> fill, bool lockAspect = false)
: this(fill(0), fill(1), fill(2), fill(3), lockAspect) { }
public Ellipse(Fill<int> fill, bool lockAspect = false)
: this(fill(0), fill(1), fill(2), fill(3), lockAspect) { }
// TODO
public static Ellipse FromFocalPoints(Float2 left, Float2 right, float length, bool lockAspect = false)
{
if (left.y != right.y)
throw new NotAlignedException("Focal points must be aligned on the Y-axis.");
if (left.x < right.x)
throw new ArgumentException("The left focal point must be on the left.");
// TODO: E = c / a
// c = |focal - center|
// c = sqrt(a^2 - b^2) / a
Float2 center = Float2.Average(left, right);
float c = right.x - center.x;
return null!; // TODO
}
public static Ellipse FromBounds(Box2d box, bool lockAspect) =>
new(box.center, box.extents, lockAspect);
public static Ellipse FromBounds(Float2 min, Float2 max, bool lockAspect = false)
{
float a = (max.x - min.x) / 2,
b = (max.y - min.y) / 2;
Float2 center = (min + max) / 2;
return new(center, (a, b), lockAspect);
}
public static Ellipse Average(params Ellipse[] vals)
{
(Float2[] positions, Float2[] radii) = SplitArray(vals);
return new(Float2.Average(positions), Float2.Average(radii));
}
public static Ellipse Lerp(Ellipse a, Ellipse b, float t, bool clamp = true) =>
new(Float2.Lerp(a.Position, b.Position, t, clamp),
Float2.Lerp(a.Radius, b.Radius, t, clamp));
public static (Float2[] positions, Float2[] radii) SplitArray(params Ellipse[] vals)
{
Float2[] positions = new Float2[vals.Length],
radii = new Float2[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
positions[i] = vals[i].Position;
radii[i] = vals[i].Radius;
}
return (positions, radii);
}
public Float2 ClosestTo(Float2 point)
{
point = (point - Position) / Radius;
point = point.Normalized;
point = (point * Radius) + Position;
return point;
}
public bool Contains(Float2 point) =>
((point.x - Position.x) / Radius.x) * ((point.x - Position.x) / Radius.x) +
((point.y - Position.y) / Radius.y) * ((point.y - Position.y) / Radius.y) <= 1;
public bool Contains<T>(T poly) where T : IPolygon<T>
{
Float3[] verts = poly.GetAllVerts();
if (verts.Length < 1) return false;
foreach (Float3 v in verts) if (!Contains((Float2)v)) return false;
return true;
}
public bool Contains(Box2d box) => Contains(box.Min) && Contains(box.Max);
public bool Contains(Line line) => Contains((Float2)line.a) && Contains((Float2)line.b);
public bool Contains(Triangle tri) =>
Contains((Float2)tri.a) && Contains((Float2)tri.b) & Contains((Float2)tri.c);
public bool Contains(IEnumerable<Float2> points)
{
foreach (Float3 p in points) if (!Contains((Float2)p)) return false;
return true;
}
public bool Contains(Fill<Float2> points, int count)
{
for (int i = 0; i < count; i++) if (!Contains(points(i))) return false;
return true;
}
public (Float2 left, Float2 right) GetFocalPoints()
{
float c = Eccentricity * Radius.x;
return (Position - (c, 0), Position + (c, 0));
}
public Float2 LerpAcrossOutline(float t, bool clamp = true)
{
if (clamp) t = Mathf.Clamp(t, 0, 1);
else t = Mathf.AbsoluteMod(t, 1);
float rot = 2 * Constants.Pi * t;
Float2 point = (Mathf.Cos(rot), Mathf.Sin(rot));
point.x *= Radius.x;
point.y *= Radius.y;
point += Position;
return point;
}
public void Scale(float factor)
{
Radius *= factor;
}
public void Scale(Float2 factor)
{
Radius *= factor;
}
public Ellipse ScaleImmutable(float factor)
{
Ellipse clone = new(Position, Radius, LockAspect);
clone.Scale(factor);
return clone;
}
public Ellipse ScaleImmutable(Float2 factor)
{
Ellipse clone = new(Position, Radius, LockAspect);
clone.Scale(factor);
return clone;
}
public void Translate(Float2 offset)
{
Position += offset;
}
public Ellipse TranslateImmutable(Float2 offset)
{
Ellipse clone = new(Position, Radius, LockAspect);
clone.Translate(offset);
return clone;
}
public override bool Equals(object? obj)
{
if (obj is null) return false;
else if (obj is Ellipse ell) return Equals(ell);
return false;
}
public bool Equals(Ellipse? other) => other is not null && Position == other.Position &&
Radius == other.Radius;
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => $"{nameof(Ellipse)} {{ Position: {Position}, Radius: {Radius} }}";
// TODO: public Polygon ToPolygon()
public Triangle[] Triangulate() => Triangulate(TriangulationMode.TriangleFan, 32);
public Triangle[] Triangulate(int detail) => Triangulate(TriangulationMode.TriangleFan, detail);
public Triangle[] Triangulate(TriangulationMode mode) => Triangulate(mode, 32);
public Triangle[] Triangulate(TriangulationMode mode, int detail) => mode switch
{
TriangulationMode.TriangleFan => GeometryHelper.EllipseTriangulateFan(this, 1f / detail),
_ => throw new ArgumentException("Unknown triangulation mode \"" + mode + "\"")
};
private void ThrowLockedAspect() => throw new AspectLockedException(
"Ellipse has a locked aspect ratio which cannot be changed.", this);
public enum TriangulationMode
{
TriangleFan
}
}

View File

@ -1,42 +1,54 @@
using Nerd_STF.Mathematics.Abstract; using Nerd_STF.Mathematics.Geometry.Abstract;
namespace Nerd_STF.Mathematics.Geometry; namespace Nerd_STF.Mathematics.Geometry;
public record class Line : IAbsolute<Line>, IAverage<Line>, ICeiling<Line>, IClamp<Line>, IClosestTo<Vert>, public class Line : IAverage<Line>, IClosestTo<Float3>, IContains<Float3>, IEquatable<Line>,
IComparable<Line>, IContains<Vert>, IEquatable<Line>, IFloor<Line>, IFromTuple<Line, (Vert start, Vert end)>, IFloatArray<Line>, IFromTuple<Line, (Float3 a, Float3 b)>, IGroup<Float3>,
IGroup<Vert>, IIndexAll<Vert>, IIndexRangeAll<Vert>, ILerp<Line, float>, IMedian<Line>, IPresets3d<Line>, IIndexAll<Float3>, IIndexRangeAll<Float3>, ILerp<Line, float>, IIntersect<Line>,
IRound<Line>, ISplittable<Line, (Vert[] starts, Vert[] ends)>, ISubdivide<Line[]> IMedian<Line>, IPresets3d<Line>, ISplittable<Line, (Float3[] As, Float3[] Bs)>,
ISubdivide<Line>, IWithinRange<Float3, float>
{ {
public static Line Back => new(Vert.Zero, Vert.Back); public static Line Back => (Float3.Zero, Float3.Back);
public static Line Down => new(Vert.Zero, Vert.Down); public static Line Down => (Float3.Zero, Float3.Down);
public static Line Forward => new(Vert.Zero, Vert.Forward); public static Line Forward => (Float3.Zero, Float3.Forward);
public static Line Left => new(Vert.Zero, Vert.Left); public static Line Left => (Float3.Zero, Float3.Left);
public static Line Right => new(Vert.Zero, Vert.Right); public static Line Right => (Float3.Zero, Float3.Right);
public static Line Up => new(Vert.Zero, Vert.Up); public static Line Up => (Float3.Zero, Float3.Up);
public static Line One => new(Vert.Zero, Vert.One); public static Line One => (Float3.Zero, Float3.One);
public static Line Zero => new(Vert.Zero, Vert.Zero); public static Line Zero => (Float3.Zero, Float3.Zero);
public Angle Angle => Mathf.ArcTan(Slope);
public float Length => (b - a).Magnitude; public float Length => (b - a).Magnitude;
public Vert Midpoint => Vert.Average(a, b); public Float3 Midpoint => (a + b) / 2;
public float Slope => (b.y - a.y) / (b.x - a.x);
public Vert a, b; public Float3 a, b;
public Line(Vert a, Vert b) public Line() : this(Float3.Zero, Float3.Zero) { }
public Line(Float3 a, Float3 b)
{ {
this.a = a; this.a = a;
this.b = b; this.b = b;
} }
public Line(float x1, float y1, float x2, float y2) : this(new(x1, y1), new(x2, y2)) { } public Line(float x1, float y1, float x2, float y2)
{
a = (x1, y1, 0);
b = (x2, y2, 0);
}
public Line(float x1, float y1, float z1, float x2, float y2, float z2) public Line(float x1, float y1, float z1, float x2, float y2, float z2)
: this(new(x1, y1, z1), new(x2, y2, z2)) { } {
public Line(Fill<Vert> fill) : this(fill(0), fill(1)) { } a = (x1, y1, z1);
public Line(Fill<Float3> fill) : this(new(fill(0)), new(fill(1))) { } b = (x2, y2, z2);
public Line(Fill<Int3> fill) : this(new(fill(0)), new(fill(1))) { } }
public Line(Fill<float> fill) : this(new(fill(0), fill(1), fill(2)), new(fill(3), fill(4), fill(5))) { } public Line(Fill<Float3> fill) : this(fill(0), fill(1)) { }
public Line(Fill<int> fill) : this(new(fill(0), fill(1), fill(2)), new(fill(3), fill(4), fill(5))) { } public Line(Fill<Int3> fill) : this(fill(0), fill(1)) { }
public Line(Fill<float> fill) : this(fill(0), fill(1), fill(2),
fill(3), fill(4), fill(5)) { }
public Line(Fill<int> fill) : this(fill(0), fill(1), fill(2),
fill(3), fill(4), fill(5)) { }
public Vert this[int index] public Float3 this[int index]
{ {
get => index switch get => index switch
{ {
@ -60,18 +72,18 @@ public record class Line : IAbsolute<Line>, IAverage<Line>, ICeiling<Line>, ICla
} }
} }
} }
public Vert this[Index index] public Float3 this[Index index]
{ {
get => this[index.IsFromEnd ? 2 - index.Value : index.Value]; get => this[index.IsFromEnd ? 2 - index.Value : index.Value];
set => this[index.IsFromEnd ? 2 - index.Value : index.Value] = value; set => this[index.IsFromEnd ? 2 - index.Value : index.Value] = value;
} }
public Vert[] this[Range range] public Float3[] this[Range range]
{ {
get get
{ {
int start = range.Start.IsFromEnd ? 2 - range.Start.Value : range.Start.Value; int start = range.Start.IsFromEnd ? 2 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 2 - range.End.Value : range.End.Value; int end = range.End.IsFromEnd ? 2 - range.End.Value : range.End.Value;
List<Vert> res = new(); List<Float3> res = new();
for (int i = start; i < end; i++) res.Add(this[i]); for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray(); return res.ToArray();
} }
@ -83,148 +95,171 @@ public record class Line : IAbsolute<Line>, IAverage<Line>, ICeiling<Line>, ICla
} }
} }
public static Line Absolute(Line val) => new(Vert.Absolute(val.a), Vert.Absolute(val.b)); public static Line Average(params Line[] lines)
public static Line Average(params Line[] vals)
{ {
(Vert[] starts, Vert[] ends) = SplitArray(vals); (Float3[] As, Float3[] Bs) = SplitArray(lines);
return new(Vert.Average(starts), Vert.Average(ends)); return (Float3.Average(As), Float3.Average(Bs));
} }
public static Line Ceiling(Line val) => new(Vert.Ceiling(val.a), Vert.Ceiling(val.b));
public static Line Clamp(Line val, Line min, Line max) =>
new(Vert.Clamp(val.a, min.a, max.a), Vert.Clamp(val.b, min.b, max.b));
public static Line Floor(Line val) => new(Vert.Floor(val.a), Vert.Floor(val.b));
public static Line Lerp(Line a, Line b, float t, bool clamp = true) => public static Line Lerp(Line a, Line b, float t, bool clamp = true) =>
new(Vert.Lerp(a.a, b.a, t, clamp), Vert.Lerp(a.b, b.b, t, clamp)); (Float3.Lerp(a.a, b.a, t, clamp), Float3.Lerp(a.b, b.b, t, clamp));
public static Line Median(params Line[] vals) public static Line Median(params Line[] lines)
{ {
(Vert[] starts, Vert[] ends) = SplitArray(vals); (Float3[] As, Float3[] Bs) = SplitArray(lines);
return new(Vert.Median(starts), Vert.Median(ends)); return (Float3.Median(As), Float3.Median(Bs));
} }
public static Line Round(Line val) => new(Vert.Round(val.a), Vert.Round(val.b)); public static (Float3[] As, Float3[] Bs) SplitArray(params Line[] lines)
public static (Vert[] starts, Vert[] ends) SplitArray(params Line[] lines)
{ {
Vert[] starts = new Vert[lines.Length], ends = new Vert[lines.Length]; Float3[] As = new Float3[lines.Length],
Bs = new Float3[lines.Length];
for (int i = 0; i < lines.Length; i++) for (int i = 0; i < lines.Length; i++)
{ {
starts[i] = lines[i].a; As[i] = lines[i].a;
ends[i] = lines[i].b; Bs[i] = lines[i].b;
} }
return (starts, ends);
return (As, Bs);
} }
public virtual bool Equals(Line? other) public static float[] ToFloatArrayAll(params Line[] vals)
{ {
if (other is null) return false; float[] result = new float[6 * vals.Length];
return a == other.a && b == other.b; for (int i = 0; i < vals.Length; i++)
}
public override int GetHashCode() => base.GetHashCode();
[Obsolete("This method is a bit ambiguous. You should instead compare " +
nameof(Length) + "s directly.")]
public int CompareTo(Line? line)
{
if (line is null) return -1;
return Length.CompareTo(line.Length);
}
public bool Contains(Vert vert)
{
Float3 diffA = a - vert, diffB = a - b;
float lerpVal = diffA.Magnitude / diffB.Magnitude;
return Vert.Lerp(a, b, lerpVal) == vert;
}
public Vert ClosestTo(Vert vert) => ClosestTo(vert, Calculus.DefaultStep);
public Vert ClosestTo(Vert vert, float step)
{
Vert closestA = a, closestB = b;
for (float t = 0; t <= 1; t += step)
{ {
Vert valA = Vert.Lerp(a, b, t); int p = i * 6;
Vert valB = Vert.Lerp(b, a, t); result[p + 0] = vals[i].a.x;
closestA = (valA - vert).Magnitude < (closestA - vert).Magnitude ? valA : closestA; result[p + 1] = vals[i].a.y;
closestB = (valB - vert).Magnitude < (closestB - vert).Magnitude ? valB : closestB; result[p + 2] = vals[i].a.z;
result[p + 3] = vals[i].b.x;
result[p + 4] = vals[i].b.y;
result[p + 5] = vals[i].b.z;
} }
return result;
return (closestA - vert).Magnitude >= (closestB - vert).Magnitude ? closestA : closestB;
} }
public Float3[] ToArray() => new[] { a, b };
public Fill<Float3> ToFill()
{
Line @this = this;
return i => @this[i];
}
public List<Float3> ToList() => new() { a, b };
public float[] ToFloatArray() => new[] { a.x, a.y, a.z, b.x, b.y, b.z };
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<Vert> GetEnumerator() public IEnumerator<Float3> GetEnumerator()
{ {
yield return a; yield return a;
yield return b; yield return b;
} }
public Float3 LerpAcross(float t, bool clamp = true) =>
Float3.Lerp(a, b, t, clamp);
public bool Equals(Line? other) => other is not null && a == other.a && b == other.b;
public override bool Equals(object? obj)
{
if (obj is null) return false;
else if (obj is Line line) return Equals(line);
else return false;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString()
{
StringBuilder builder = new();
builder.Append(nameof(Line));
builder.Append(" { ");
builder.Append(a);
builder.Append(", ");
builder.Append(b);
builder.Append(" }");
return builder.ToString();
}
public Float3 ClosestTo(Float3 point) => ClosestTo(point, Calculus.DefaultStep);
public Float3 ClosestTo(Float3 point, float step)
{
// Probably could optimize this with some weird formula but whatever.
// This isn't the optimization update.
(Float3 point, float dist) min = (a, float.MaxValue);
for (float f = 0; f < 1; f += step)
{
Float3 check = Float3.Lerp(a, b, f);
float dist = (check - point).Magnitude;
if (dist < min.dist) min = (check, dist);
}
return min.point;
}
public bool Contains(Float3 point)
{
float left = (point.y - a.y) / (b.y - a.y),
right = (point.x - a.x) / (b.x - a.x);
return left == right && point.x >= float.Min(a.x, b.x)
&& point.x <= float.Max(a.x, b.x);
}
public bool Intersects(Line line) => GeometryHelper.LineIntersects(this, line);
public Line[] Subdivide() public Line[] Subdivide()
{ {
Vert middle = Vert.Lerp(a, b, 0.5f); Float3 midPoint = Float3.Lerp(a, b, 0.5f);
return new Line[] { new(a, middle), new(middle, b) }; return new Line[] { (a, midPoint), (midPoint, b) };
} }
public Line[] Subdivide(int iterations) public Line[] Subdivide(int iterations)
{ {
if (iterations < 1) return Array.Empty<Line>(); Line[] result = new Line[iterations + 4];
List<Line> lines = new(Subdivide()); float step = 1f / (iterations + 1);
for (int i = 1; i < iterations; i++)
int i = 0;
float prev = 0;
for (float f = step; f <= 1; f += step)
{ {
List<Line> add = new(); result[i] = (Float3.Lerp(a, b, prev), Float3.Lerp(a, b, f));
for (int j = 0; j < lines.Count; j++) add.AddRange(lines[j].Subdivide()); prev = f;
lines = add; i++;
} }
return lines.ToArray();
return result;
} }
public Vert[] ToArray() => new Vert[] { a, b }; public bool WithinRange(Float3 point, float range) =>
public Fill<Vert> ToFill() WithinRange(point, range, Calculus.DefaultStep);
public bool WithinRange(Float3 point, float range, float step)
{ {
Line @this = this; // I could probably replace this with a more optimized solution (such as a
return i => @this[i]; // modified version of `Contains(Float3)`), but hey, this isn't the optimization
} // update, is it?
public List<Vert> ToList() => new() { a, b };
public float[] ToFloatArray() => new float[] { a.position.x, a.position.y, a.position.z, for (float f = 0; f <= 1; f += step)
b.position.x, b.position.y, b.position.z }; {
public List<float> ToFloatList() => new() { a.position.x, a.position.y, a.position.z, Float3 check = Float3.Lerp(a, b, f);
b.position.x, b.position.y, b.position.z };
protected virtual bool PrintMembers(StringBuilder builder) // I could make a new line but that seems wasteful.
{ float dist = (check - point).Magnitude;
builder.Append("A = "); if (dist <= range) return true;
builder.Append(a); }
builder.Append(", B = ");
builder.Append(b); return false;
return true;
} }
public static Line operator +(Line a, Line b) => new(a.a + b.a, a.b + b.b); public static Line operator +(Line l, Float3 offset) => (l.a + offset, l.b + offset);
public static Line operator +(Line a, Vert b) => new(a.a + b, a.b + b); public static Line operator -(Line l, Float3 offset) => (l.a + offset, l.b + offset);
public static Line operator -(Line l) => new(-l.a, -l.b); public static Line operator *(Line l, float factor) => (l.a * factor, l.b * factor);
public static Line operator -(Line a, Line b) => new(a.a - b.a, a.b - b.b); public static Line operator *(Line l, Float3 factor) => (l.a * factor, l.b * factor);
public static Line operator -(Line a, Vert b) => new(a.a - b, a.b - b); public static Line operator /(Line l, float factor) => (l.a / factor, l.b / factor);
public static Line operator *(Line a, Line b) => new(a.a * b.a, a.b * b.b); public static Line operator /(Line l, Float3 factor) => (l.a / factor, l.b / factor);
public static Line operator *(Line a, Vert b) => new(a.a * b, a.b * b); public static bool operator ==(Line a, Line b) => a.Equals(b);
public static Line operator *(Line a, float b) => new(a.a * b, a.b * b); public static bool operator !=(Line a, Line b) => !a.Equals(b);
public static Line operator /(Line a, Line b) => new(a.a / b.a, a.b / b.b);
public static Line operator /(Line a, Vert b) => new(a.a / b, a.b / b);
public static Line operator /(Line a, float b) => new(a.a / b, a.b / b);
[Obsolete("This operator is a bit ambiguous. You should instead compare " +
nameof(Length) + "s directly.")]
public static bool operator >(Line a, Line b) => a.CompareTo(b) > 0;
[Obsolete("This operator is a bit ambiguous. You should instead compare " +
nameof(Length) + "s directly.")]
public static bool operator <(Line a, Line b) => a.CompareTo(b) < 0;
[Obsolete("This operator is a bit ambiguous (and misleading at times). " +
"You should instead compare " + nameof(Length) + "s directly.")]
public static bool operator >=(Line a, Line b) => a > b || a == b;
[Obsolete("This operator is a bit ambiguous (and misleading at times). " +
"You should instead compare " + nameof(Length) + "s directly.")]
public static bool operator <=(Line a, Line b) => a < b || a == b;
public static implicit operator Line(Fill<Vert> fill) => new(fill);
public static implicit operator Line(Fill<Float3> fill) => new(fill); public static implicit operator Line(Fill<Float3> fill) => new(fill);
public static implicit operator Line(Fill<Int3> fill) => new(fill); public static implicit operator Line(Fill<Int3> fill) => new(fill);
public static implicit operator Line(Fill<float> fill) => new(fill); public static implicit operator Line(Fill<float> fill) => new(fill);
public static implicit operator Line(Fill<int> fill) => new(fill); public static implicit operator Line(Fill<int> fill) => new(fill);
public static implicit operator Line((Vert start, Vert end) val) => new(val.start, val.end); public static implicit operator Line((Float3 a, Float3 b) tuple) => new(tuple.a, tuple.b);
} }

View File

@ -1,477 +1,12 @@
namespace Nerd_STF.Mathematics.Geometry; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
[Obsolete("This struct is a garbage fire. This will be completely redesigned in v2.5.0")] namespace Nerd_STF.Mathematics.Geometry
public struct Polygon : ICloneable, IEquatable<Polygon>, IGroup<Vert>, ISubdivide<Polygon>, ITriangulate
{ {
public Line[] Lines public class Polygon
{ {
get => p_lines;
set
{
p_lines = value;
p_verts = GenerateVerts(value);
}
} }
public Vert Midpoint => Vert.Average(Verts);
public Vert[] Verts
{
get => p_verts;
set
{
p_verts = value;
p_lines = GenerateLines(value);
}
}
private Line[] p_lines;
private Vert[] p_verts;
[Obsolete("This method uses the Polygon.Triangulate() function, which has issues. It will be fixed in a " +
"future update.")]
public float Area
{
get
{
float val = 0;
foreach (Triangle t in Triangulate()) val += t.Area;
return val;
}
}
public float Perimeter
{
get
{
float val = 0;
foreach (Line l in Lines) val += l.Length;
return val;
}
}
public Polygon()
{
p_lines = Array.Empty<Line>();
p_verts = Array.Empty<Vert>();
}
public Polygon(Fill<Vert?> fill)
{
List<Vert> verts = new();
int i = 0;
while (true)
{
Vert? v = fill(i);
if (!v.HasValue) break;
verts.Add(v.Value);
}
this = new(verts.ToArray());
}
public Polygon(Fill<Float3?> fill)
{
List<Vert> verts = new();
int i = 0;
while (true)
{
Vert? v = fill(i);
if (!v.HasValue) break;
verts.Add(v.Value);
}
this = new(verts.ToArray());
}
public Polygon(Fill<Line?> fill)
{
List<Line> lines = new();
int i = 0;
while (true)
{
Line? v = fill(i);
if (v is null) break;
lines.Add(v);
}
this = new(lines.ToArray());
}
public Polygon(Fill<Vert> fill, int length)
{
List<Vert> verts = new();
for (int i = 0; i < length; i++) verts.Add(fill(i));
this = new(verts.ToArray());
}
public Polygon(Fill<Float3> fill, int length)
{
List<Vert> verts = new();
for (int i = 0; i < length; i++) verts.Add(fill(i));
this = new(verts.ToArray());
}
public Polygon(Fill<Line> fill, int length)
{
List<Line> lines = new();
for (int i = 0; i < length; i++) lines.Add(fill(i));
this = new(lines.ToArray());
}
public Polygon(params Float3[] verts)
{
p_verts = new Vert[verts.Length];
for (int i = 0; i < verts.Length; i++) p_verts[i] = verts[i];
p_lines = GenerateLines(p_verts);
}
public Polygon(params Vert[] verts)
{
p_verts = verts;
p_lines = GenerateLines(verts);
}
public Polygon(params Line[] lines)
{
p_lines = lines;
p_verts = GenerateVerts(lines);
}
public Vert this[int index]
{
get => Verts[index];
set => Verts[index] = value;
}
public static Polygon CreateCircle(int vertCount)
{
List<Vert> parts = new();
for (int i = 0; i < vertCount; i++)
{
float val = Constants.Tau * i / vertCount;
parts.Add(new(Mathf.Cos(val), Mathf.Sin(val)));
}
return new(parts.ToArray());
}
public static Polygon Absolute(Polygon val)
{
Vert[] v = val.Verts;
for (int i = 0; i < v.Length; i++) v[i] = Vert.Absolute(v[i]);
return new(v);
}
public static Polygon Average(params Polygon[] vals)
{
if (!CheckVerts(vals)) throw new DifferingVertCountException(nameof(vals), vals);
if (vals.Length < 1) return default;
Line[][] lines = new Line[vals.Length][];
for (int i = 0; i < vals.Length; i++) lines[i] = vals[i].Lines;
Line[] res = new Line[vals[0].Lines.Length];
for (int i = 0; i < res.Length; i++)
{
Line[] row = new Line[vals.Length];
for (int j = 0; j < vals[0].Lines.Length; j++) row[j] = vals[j].Lines[i];
res[i] = Line.Average(row);
}
return new(res);
}
public static Polygon Ceiling(Polygon val)
{
Vert[] v = val.Verts;
for (int i = 0; i < v.Length; i++) v[i] = Vert.Ceiling(v[i]);
return new(v);
}
public static Polygon Clamp(Polygon val, Polygon min, Polygon max)
{
if (!CheckVerts(val, min, max)) throw new DifferingVertCountException(val, min, max);
Line[][] lines = new Line[3][] { val.Lines, min.Lines, max.Lines };
Line[] res = new Line[val.Lines.Length];
for (int i = 0; i < res.Length; i++) res[i] = Line.Clamp(lines[0][i], lines[1][i], lines[2][i]);
return new(res);
}
public static Polygon Floor(Polygon val)
{
Vert[] v = val.Verts;
for (int i = 0; i < v.Length; i++) v[i] = Vert.Floor(v[i]);
return new(v);
}
public static Polygon Lerp(Polygon a, Polygon b, float t, bool clamp = true)
{
if (!CheckVerts(a, b)) throw new DifferingVertCountException(a, b);
Line[][] lines = new Line[2][] { a.Lines, b.Lines };
Line[] res = new Line[a.Lines.Length];
for (int i = 0; i < res.Length; i++) res[i] = Line.Lerp(lines[0][i], lines[1][i], t, clamp);
return new(res);
}
public static Polygon Median(params Polygon[] vals)
{
if (!CheckVerts(vals)) throw new DifferingVertCountException(nameof(vals), vals);
if (vals.Length < 1) return default;
Line[][] lines = new Line[vals.Length][];
for (int i = 0; i < vals.Length; i++) lines[i] = vals[i].Lines;
Line[] res = new Line[vals[0].Lines.Length];
for (int i = 0; i < res.Length; i++)
{
Line[] row = new Line[vals.Length];
for (int j = 0; j < vals[0].Lines.Length; j++) row[j] = vals[j].Lines[i];
res[i] = Line.Median(row);
}
return new(res);
}
public static float[] ToFloatArrayAll(params Polygon[] polys) => ToFloatListAll(polys).ToArray();
public static List<float> ToFloatListAll(params Polygon[] polys)
{
List<float> vals = new();
foreach (Polygon poly in polys) vals.AddRange(poly.ToFloatArray());
return vals;
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (obj == null || obj.GetType() != typeof(Polygon)) return base.Equals(obj);
return Equals((Polygon)obj);
}
public bool Equals(Polygon other)
{
if (!CheckVerts(this, other)) return false;
return Lines == other.Lines;
}
public override int GetHashCode() => Lines.GetHashCode();
public override string ToString()
{
string s = "";
for (int i = 0; i < Lines.Length; i++) s += "L" + i + ": " + Lines[i] + " ";
return s;
}
public object Clone() => new Polygon(Lines);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<Vert> GetEnumerator() { foreach (Vert v in Verts) yield return v; }
public Vert[] ToArray() => Verts;
public Fill<Vert> ToFill()
{
Polygon @this = this;
return i => @this[i];
}
public List<Vert> ToList() => new(Verts);
public float[] ToFloatArray()
{
float[] vals = new float[Verts.Length * 3];
for (int i = 0; i < Verts.Length; i++)
{
int pos = i * 3;
vals[pos + 0] = Verts[i].position.x;
vals[pos + 1] = Verts[i].position.y;
vals[pos + 2] = Verts[i].position.z;
}
return vals;
}
public List<float> ToFloatList() => new(ToFloatArray());
public Polygon Subdivide()
{
Polygon poly = new();
List<Line> lines = new();
for (int i = 0; i < Lines.Length; i++) lines.AddRange(Lines[i].Subdivide());
return poly;
}
public Polygon Subdivide(int iterations)
{
if (iterations < 1) return new();
Polygon poly = this;
for (int i = 0; i < iterations; i++) poly = poly.Subdivide();
return poly;
}
public Polygon SubdivideCatmullClark(int segments)
{
// Thanks Saalty for making this accidentally.
List<Vert> newVerts = new();
for (int i = 0; i < Verts.Length; i++)
{
for (int factor = 0; factor < segments; factor++)
{
float unit = factor / (float)(segments * 2), unit2 = unit + 0.5f, lastUnit = unit * 2;
Vert p1, p2;
if (i == Verts.Length - 1)
{
p1 = Verts[^1] + (Verts[0] - Verts[^1]) * unit2;
p2 = Verts[0] + (Verts[1] - Verts[0]) * unit;
}
else if (i == Verts.Length - 2)
{
p1 = Verts[^2] + (Verts[^1] - Verts[^2]) * unit2;
p2 = Verts[^1] + (Verts[0] - Verts[^1]) * unit;
}
else
{
p1 = Verts[i] + (Verts[i + 1] - Verts[i]) * unit2;
p2 = Verts[i + 1] + (Verts[i + 2] - Verts[i + 1]) * unit;
}
newVerts.Add(p1 + (p2 - p1) * lastUnit);
}
}
return new(newVerts.ToArray());
}
[Obsolete("This method doesn't work very well, and will give very weird results in certain cases. " +
"This will be fixed in a future update.")]
public Triangle[] Triangulate()
{
// This may cause issues. FIXME
// Tbh, not even sure if this works. This was a bit confusing.
if (Verts.Length == 3) return new Triangle[] { new(Verts[0], Verts[1], Verts[2]) };
(int posA, int posB, Line line)? closest = null;
for (int i = 0; i < Verts.Length; i++)
{
for (int j = 0; j < Verts.Length; j++)
{
if (i == j) continue;
Line l = new(Verts[i], Verts[j]);
if (Lines.Contains(l)) continue;
if (!closest.HasValue || closest.Value.line.Length > l.Length) closest = (i, j, l);
}
}
if (closest == null)
throw new Nerd_STFException("Unknown error triangulating the polygon.");
if (closest.Value.posB > closest.Value.posA)
closest = (closest.Value.posB, closest.Value.posA, closest.Value.line);
List<Line> partA = new(Lines[closest.Value.posA..(closest.Value.posB - 1)])
{
closest.Value.line
};
Polygon pA = new(partA.ToArray());
List<Line> partB = new(Lines[0..(closest.Value.posA - 1)]);
partB.AddRange(Lines[closest.Value.posB..(Lines.Length - 1)]);
partB.Add(closest.Value.line);
Polygon pB = new(partB.ToArray());
List<Triangle> tris = new(pA.Triangulate());
tris.AddRange(pB.Triangulate());
return tris.ToArray();
}
private static bool CheckVerts(params Polygon[] polys)
{
int len = -1;
foreach (Polygon poly in polys)
{
if (len == -1)
{
len = poly.Verts.Length;
continue;
}
if (poly.Verts.Length != len) return false;
}
return true;
}
private static Line[] GenerateLines(Vert[] verts)
{
Line[] lines = new Line[verts.Length];
for (int i = 0; i < lines.Length; i++)
lines[i] = new(verts[i], verts[i == lines.Length - 1 ? 0 : i + 1]);
return lines;
}
private static Vert[] GenerateVerts(Line[] lines)
{
Vert[] verts = new Vert[lines.Length];
for (int i = 0; i < verts.Length; i++)
{
verts[i] = lines[i].a;
if (lines[i].b != lines[i == verts.Length - 1 ? 0 : i + 1].a)
throw new DisconnectedLinesException(nameof(lines), lines);
}
return verts;
}
public static Polygon operator +(Polygon a, Polygon b)
{
if (!CheckVerts(a, b)) throw new DifferingVertCountException(a, b);
Line[][] lines = new Line[2][] { a.Lines, b.Lines };
Line[] res = new Line[a.Lines.Length];
for (int i = 0; i < res.Length; i++) res[i] = lines[0][i] + lines[1][i];
return new(res);
}
public static Polygon operator +(Polygon a, Vert b)
{
Line[] lines = a.Lines;
for (int i = 0; i < lines.Length; i++) lines[i] += b;
return new(lines);
}
public static Polygon operator -(Polygon p)
{
Line[] lines = p.Lines;
for (int i = 0; i < lines.Length; i++) lines[i] = -lines[i];
return new(lines);
}
public static Polygon operator -(Polygon a, Polygon b)
{
if (!CheckVerts(a, b)) throw new DifferingVertCountException(a, b);
Line[][] lines = new Line[2][] { a.Lines, b.Lines };
Line[] res = new Line[a.Lines.Length];
for (int i = 0; i < res.Length; i++) res[i] = lines[0][i] - lines[1][i];
return new(res);
}
public static Polygon operator -(Polygon a, Vert b)
{
Line[] lines = a.Lines;
for (int i = 0; i < lines.Length; i++) lines[i] -= b;
return new(lines);
}
public static Polygon operator *(Polygon a, Polygon b)
{
if (!CheckVerts(a, b)) throw new DifferingVertCountException(a, b);
Line[][] lines = new Line[2][] { a.Lines, b.Lines };
Line[] res = new Line[a.Lines.Length];
for (int i = 0; i < res.Length; i++) res[i] = lines[0][i] * lines[1][i];
return new(res);
}
public static Polygon operator *(Polygon a, Vert b)
{
Line[] lines = a.Lines;
for (int i = 0; i < lines.Length; i++) lines[i] *= b;
return new(lines);
}
public static Polygon operator *(Polygon a, float b)
{
Line[] lines = a.Lines;
for (int i = 0; i < lines.Length; i++) lines[i] *= b;
return new(lines);
}
public static Polygon operator /(Polygon a, Polygon b)
{
if (!CheckVerts(a, b)) throw new DifferingVertCountException(a, b);
Line[][] lines = new Line[2][] { a.Lines, b.Lines };
Line[] res = new Line[a.Lines.Length];
for (int i = 0; i < res.Length; i++) res[i] = lines[0][i] / lines[1][i];
return new(res);
}
public static Polygon operator /(Polygon a, Vert b)
{
Line[] lines = a.Lines;
for (int i = 0; i < lines.Length; i++) lines[i] /= b;
return new(lines);
}
public static Polygon operator /(Polygon a, float b)
{
Line[] lines = a.Lines;
for (int i = 0; i < lines.Length; i++) lines[i] /= b;
return new(lines);
}
public static bool operator ==(Polygon a, Polygon b) => a.Equals(b);
public static bool operator !=(Polygon a, Polygon b) => !a.Equals(b);
public static implicit operator Polygon(Fill<Vert?> fill) => new(fill);
public static implicit operator Polygon(Fill<Float3?> fill) => new(fill);
public static implicit operator Polygon(Fill<Line?> fill) => new(fill);
public static implicit operator Polygon(Vert[] verts) => new(verts);
public static implicit operator Polygon(Float3[] verts) => new(verts);
public static implicit operator Polygon(Line[] lines) => new(lines);
public static implicit operator Polygon(Triangle tri) => new(tri.AB, tri.BC, tri.CA);
public static implicit operator Polygon(Quadrilateral quad) => new(quad.AB, quad.BC, quad.CD, quad.DA);
} }

View File

@ -1,199 +1,222 @@
namespace Nerd_STF.Mathematics.Geometry; using Nerd_STF.Mathematics.Geometry.Abstract;
public record class Quadrilateral : IAbsolute<Quadrilateral>, IAverage<Quadrilateral>, ICeiling<Quadrilateral>, namespace Nerd_STF.Mathematics.Geometry;
IClamp<Quadrilateral>, IEquatable<Quadrilateral>, IFloor<Quadrilateral>,
IFromTuple<Quadrilateral, (Vert a, Vert b, Vert c, Vert d)>, IGroup<Vert>, IIndexAll<Vert>, IIndexRangeAll<Vert>, public class Quadrilateral : IClosestTo<Float3>,
ILerp<Quadrilateral, float>, IRound<Quadrilateral>, IShape2d<float>, ITriangulate IFromTuple<Quadrilateral, (Float3 a, Float3 b, Float3 c, Float3 d)>, IPolygon<Quadrilateral>,
ISplittable<Quadrilateral, (Float3[] As, Float3[] Bs, Float3[] Cs, Float3[] Ds)>,
ISubdivide<Quadrilateral>, ITriangulate, IWithinRange<Float3, float>
{ {
public Vert A public float Area
{ {
get => p_a; get // Modification of Heron's Formula to work with quadrilaterals.
set
{ {
p_a = value; float a = AB.Length, b = BC.Length, c = CD.Length, d = DA.Length,
p_ab.a = value; s = (a + b + c + d) / 2;
p_da.b = value;
} Angle theta1 = Angle.FromPoints(this.a, this.b, this.c),
} theta2 = Angle.FromPoints(this.c, this.d, this.a);
public Vert B
{ float cos = Mathf.Cos((theta1 + theta2) / 2);
get => p_b;
set return Mathf.Sqrt((s - a) * (s - b) * (s - c) * (s - d) - (a * b * c * d * cos * cos));
{
p_b = value;
p_ab.b = value;
p_bc.a = value;
}
}
public Vert C
{
get => p_c;
set
{
p_c = value;
p_bc.b = value;
p_cd.a = value;
}
}
public Vert D
{
get => p_d;
set
{
p_d = value;
p_cd.b = value;
p_da.a = value;
} }
} }
public Float3 Midpoint => Float3.Average(a, b, c, d);
public float Perimeter => AB.Length + BC.Length + CD.Length + DA.Length;
public Line AB public Line AB
{ {
get => p_ab; get => (a, b);
set set
{ {
p_ab = value; a = value.a;
p_a = value.a; b = value.b;
p_b = value.b; }
p_bc.a = value.b; }
p_da.b = value.a; public Line AC
{
get => (a, c);
set
{
a = value.a;
c = value.b;
} }
} }
public Line BC public Line BC
{ {
get => p_bc; get => (b, c);
set set
{ {
p_bc = value; b = value.a;
p_b = value.a; c = value.b;
p_c = value.b; }
p_cd.a = value.b; }
p_ab.b = value.a; public Line BD
{
get => (b, d);
set
{
b = value.a;
d = value.b;
} }
} }
public Line CD public Line CD
{ {
get => p_cd; get => (c, d);
set set
{ {
p_cd = value; c = value.a;
p_c = value.a; d = value.b;
p_d = value.b;
p_da.a = value.b;
p_bc.b = value.a;
} }
} }
public Line DA public Line DA
{ {
get => p_da; get => (d, a);
set set
{ {
p_da = value; d = value.a;
p_d = value.a; a = value.b;
p_a = value.b;
p_ab.a = value.b;
p_cd.b = value.a;
} }
} }
private Vert p_a, p_b, p_c, p_d; public Triangle ABC
private Line p_ab, p_bc, p_cd, p_da;
[Obsolete("This field doesn't account for the Z-axis. This will be fixed in v2.4.0")]
public float Area
{ {
get get => (a, b, c);
set
{ {
float val = 0; a = value.a;
foreach (Triangle t in Triangulate()) val += t.Area; b = value.b;
return val; c = value.c;
} }
} }
public Vert Midpoint => Vert.Average(A, B, C, D); public Triangle BCD
public float Perimeter => AB.Length + BC.Length + CD.Length + DA.Length;
public Quadrilateral(Vert a, Vert b, Vert c, Vert d)
{ {
p_a = a; get => (b, c, d);
p_b = b; set
p_c = c; {
p_d = d; b = value.a;
p_ab = new(a, b); c = value.b;
p_bc = new(b, c); d = value.c;
p_cd = new(c, d); }
p_da = new(d, a); }
public Triangle CDA
{
get => (c, d, a);
set
{
c = value.a;
d = value.b;
a = value.c;
}
}
public Triangle DAB
{
get => (d, a, b);
set
{
d = value.a;
a = value.b;
b = value.c;
}
}
public Angle AngleABC => Angle.FromPoints(a, b, c);
public Angle AngleBCD => Angle.FromPoints(b, c, d);
public Angle AngleCDA => Angle.FromPoints(c, d, a);
public Angle AngleDAB => Angle.FromPoints(d, a, b);
public Float3 a, b, c, d;
public Quadrilateral() : this(Float3.Zero, Float3.Zero, Float3.Zero, Float3.Zero) { }
public Quadrilateral(Float3 a, Float3 b, Float3 c, Float3 d)
{
this.a = a;
this.b = b;
this.c = c;
this.d = d;
} }
public Quadrilateral(Line ab, Line bc, Line cd, Line da) public Quadrilateral(Line ab, Line bc, Line cd, Line da)
{ {
if (ab.a != da.b || ab.b != bc.a || bc.b != cd.a || cd.b != da.a) if (ab.b != bc.a || bc.b != cd.a || cd.b != da.a || da.b != ab.a)
throw new DisconnectedLinesException(ab, bc, cd, da); throw new DisconnectedLinesException(ab, bc, cd, da);
p_a = ab.a;
p_b = bc.a; a = ab.a;
p_c = cd.a; b = bc.a;
p_d = da.a; c = cd.a;
p_ab = ab; d = da.a;
p_bc = bc; }
p_cd = cd; public Quadrilateral(float x1, float y1, float x2, float y2, float x3, float y3,
p_da = da; float x4, float y4)
{
a = (x1, y1, 0);
b = (x2, y2, 0);
c = (x3, y3, 0);
d = (x4, y4, 0);
}
public Quadrilateral(float x1, float y1, float z1, float x2, float y2, float z2,
float x3, float y3, float z3, float x4, float y4, float z4)
{
a = (x1, y1, z1);
b = (x2, y2, z2);
c = (x3, y3, z3);
d = (x4, y4, z4);
} }
public Quadrilateral(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4)
: this(new Vert(x1, y1), new(x2, y2), new(x3, y3), new(x4, y4)) { }
public Quadrilateral(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3,
float z3, float x4, float y4, float z4)
: this(new Vert(x1, y1, z1), new(x2, y2, z2), new(x3, y3, z3), new(x4, y4, z4)) { }
public Quadrilateral(Fill<Float3> fill) : this(fill(0), fill(1), fill(2), fill(3)) { } public Quadrilateral(Fill<Float3> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public Quadrilateral(Fill<Int3> fill) : this(fill(0), fill(1), fill(2), fill(3)) { } public Quadrilateral(Fill<Int3> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public Quadrilateral(Fill<Vert> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public Quadrilateral(Fill<Line> fill) : this(fill(0), fill(1), fill(2), fill(3)) { } public Quadrilateral(Fill<Line> fill) : this(fill(0), fill(1), fill(2), fill(3)) { }
public Quadrilateral(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4), fill(5), fill(6), public Quadrilateral(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3),
fill(7), fill(8), fill(9), fill(10), fill(11)) { } fill(4), fill(5), fill(6), fill(7), fill(8), fill(9), fill(10), fill(11)) { }
public Quadrilateral(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4), fill(5), fill(6), public Quadrilateral(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3),
fill(7), fill(8), fill(9), fill(10), fill(11)) { } fill(4), fill(5), fill(6), fill(7), fill(8), fill(9), fill(10), fill(11)) { }
public Vert this[int index] public Float3 this[int index]
{ {
get => index switch get => index switch
{ {
0 => A, 0 => a,
1 => B, 1 => b,
2 => C, 2 => c,
3 => D, 3 => d,
_ => throw new IndexOutOfRangeException(nameof(index)), _ => throw new IndexOutOfRangeException(nameof(index))
}; };
set set
{ {
switch (index) switch (index)
{ {
case 0: case 0:
A = value; a = value;
break; break;
case 1: case 1:
B = value; b = value;
break; break;
case 2: case 2:
C = value; c = value;
break; break;
case 3: case 3:
D = value; d = value;
break; break;
default: throw new IndexOutOfRangeException(nameof(index)); default: throw new IndexOutOfRangeException(nameof(index));
} }
} }
} }
public Vert this[Index index]
public Float3 this[Index index]
{ {
get => this[index.IsFromEnd ? 4 - index.Value : index.Value]; get => this[index.IsFromEnd ? 4 - index.Value : index.Value];
set => this[index.IsFromEnd ? 4 - index.Value : index.Value] = value; set => this[index.IsFromEnd ? 4 - index.Value : index.Value] = value;
} }
public Vert[] this[Range range] public Float3[] this[Range range]
{ {
get get
{ {
int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value; int start = range.Start.IsFromEnd ? 4 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value; int end = range.End.IsFromEnd ? 4 - range.End.Value : range.End.Value;
List<Vert> res = new(); List<Float3> res = new();
for (int i = start; i < end; i++) res.Add(this[i]); for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray(); return res.ToArray();
} }
@ -205,164 +228,259 @@ public record class Quadrilateral : IAbsolute<Quadrilateral>, IAverage<Quadrilat
} }
} }
public static Quadrilateral Absolute(Quadrilateral val) =>
new(Vert.Absolute(val.A), Vert.Absolute(val.B), Vert.Absolute(val.C), Vert.Absolute(val.D));
public static Quadrilateral Average(params Quadrilateral[] vals) public static Quadrilateral Average(params Quadrilateral[] vals)
{ {
(Vert[] As, Vert[] Bs, Vert[] Cs, Vert[] Ds) = SplitVertArray(vals); (Float3[] As, Float3[] Bs, Float3[] Cs, Float3[] Ds) = SplitArray(vals);
return new(Vert.Average(As), Vert.Average(Bs), Vert.Average(Cs), Vert.Average(Ds)); return (Float3.Average(As), Float3.Average(Bs), Float3.Average(Cs), Float3.Average(Ds));
} }
public static Quadrilateral Ceiling(Quadrilateral val) => public static Quadrilateral Lerp(Quadrilateral a, Quadrilateral b, float t,
new(Vert.Ceiling(val.A), Vert.Ceiling(val.B), Vert.Ceiling(val.C), Vert.Ceiling(val.D)); bool clamp = true) => (Float3.Lerp(a.a, b.a, t, clamp), Float3.Lerp(a.b, b.b, t, clamp),
public static Quadrilateral Clamp(Quadrilateral val, Quadrilateral min, Quadrilateral max) => Float3.Lerp(a.c, b.c, t, clamp), Float3.Lerp(a.d, b.d, t, clamp));
new(Vert.Clamp(val.A, min.A, max.A), Vert.Clamp(val.B, min.B, max.B), Vert.Clamp(val.C, min.C, max.C), public static (Float3[] As, Float3[] Bs, Float3[] Cs, Float3[] Ds) SplitArray(
Vert.Clamp(val.D, min.D, max.D)); params Quadrilateral[] vals)
public static Quadrilateral Floor(Quadrilateral val) =>
new(Vert.Floor(val.A), Vert.Floor(val.B), Vert.Floor(val.C), Vert.Floor(val.D));
public static Quadrilateral Lerp(Quadrilateral a, Quadrilateral b, float t, bool clamp = true) =>
new(Vert.Lerp(a.A, b.A, t, clamp), Vert.Lerp(a.B, b.B, t, clamp), Vert.Lerp(a.C, b.C, t, clamp),
Vert.Lerp(a.D, b.D, t, clamp));
public static Quadrilateral Max(params Quadrilateral[] vals)
{ {
(Vert[] As, Vert[] Bs, Vert[] Cs, Vert[] Ds) = SplitVertArray(vals); Float3[] As = new Float3[vals.Length],
return new(Vert.Max(As), Vert.Max(Bs), Vert.Max(Cs), Vert.Max(Ds)); Bs = new Float3[vals.Length],
} Cs = new Float3[vals.Length],
public static Quadrilateral Median(params Quadrilateral[] vals) Ds = new Float3[vals.Length];
{ for (int i = 0; i < vals.Length; i++)
(Vert[] As, Vert[] Bs, Vert[] Cs, Vert[] Ds) = SplitVertArray(vals);
return new(Vert.Median(As), Vert.Median(Bs), Vert.Median(Cs), Vert.Median(Ds));
}
public static Quadrilateral Min(params Quadrilateral[] vals)
{
(Vert[] As, Vert[] Bs, Vert[] Cs, Vert[] Ds) = SplitVertArray(vals);
return new(Vert.Min(As), Vert.Min(Bs), Vert.Min(Cs), Vert.Min(Ds));
}
public static Quadrilateral Round(Quadrilateral val) =>
new(Vert.Round(val.A), Vert.Round(val.B), Vert.Round(val.C), Vert.Round(val.D));
public static (Vert[] As, Vert[] Bs, Vert[] Cs, Vert[] Ds) SplitVertArray(params Quadrilateral[] quads)
{
Vert[] a = new Vert[quads.Length], b = new Vert[quads.Length],
c = new Vert[quads.Length], d = new Vert[quads.Length];
for (int i = 0; i < quads.Length; i++)
{ {
a[i] = quads[i].A; As[i] = vals[i].a;
b[i] = quads[i].B; Bs[i] = vals[i].b;
c[i] = quads[i].C; Cs[i] = vals[i].c;
d[i] = quads[i].D; Ds[i] = vals[i].d;
} }
return (As, Bs, Cs, Ds);
return (a, b, c, d);
} }
public static (Line[] ABs, Line[] BCs, Line[] CDs, Line[] DAs) SplitLineArray(params Quadrilateral[] quads)
public static float[] ToFloatArrayAll(params Quadrilateral[] vals)
{ {
Line[] ab = new Line[quads.Length], bc = new Line[quads.Length], float[] result = new float[12 * vals.Length];
cd = new Line[quads.Length], da = new Line[quads.Length]; for (int i = 0; i < vals.Length; i++)
for (int i = 0; i < quads.Length; i++)
{ {
ab[i] = quads[i].AB; int p = i * 12;
bc[i] = quads[i].BC; result[p + 0] = vals[i].a.x;
cd[i] = quads[i].CD; result[p + 1] = vals[i].a.y;
da[i] = quads[i].DA; result[p + 2] = vals[i].a.z;
result[p + 3] = vals[i].b.x;
result[p + 4] = vals[i].b.y;
result[p + 5] = vals[i].b.z;
result[p + 6] = vals[i].c.x;
result[p + 7] = vals[i].c.y;
result[p + 8] = vals[i].c.z;
result[p + 9] = vals[i].d.x;
result[p + 10] = vals[i].d.y;
result[p + 11] = vals[i].d.y;
} }
return result;
return (ab, bc, cd, da);
} }
public static float[] ToFloatArrayAll(params Quadrilateral[] quads) public Float3 ClosestTo(Float3 point)
{ {
float[] vals = new float[quads.Length * 12]; Float3 abClosest = AB.ClosestTo(point),
for (int i = 0; i < quads.Length; i++) bcClosest = BC.ClosestTo(point),
{ cdClosest = CD.ClosestTo(point),
int pos = i * 12; daClosest = DA.ClosestTo(point);
vals[pos + 0] = quads[i].A.position.x;
vals[pos + 1] = quads[i].A.position.y;
vals[pos + 2] = quads[i].A.position.z;
vals[pos + 3] = quads[i].B.position.x;
vals[pos + 4] = quads[i].B.position.y;
vals[pos + 5] = quads[i].B.position.z;
vals[pos + 6] = quads[i].C.position.x;
vals[pos + 7] = quads[i].C.position.y;
vals[pos + 8] = quads[i].C.position.z;
vals[pos + 9] = quads[i].D.position.x;
vals[pos + 10] = quads[i].D.position.y;
vals[pos + 11] = quads[i].D.position.z;
}
return vals;
}
public static List<float> ToFloatListAll(params Quadrilateral[] quads) => new(ToFloatArrayAll(quads));
public virtual bool Equals(Quadrilateral? other) // Very inefficient way to select the closest point.
float abDist = (abClosest - point).Magnitude,
bcDist = (bcClosest - point).Magnitude,
cdDist = (cdClosest - point).Magnitude,
daDist = (daClosest - point).Magnitude;
float min = Mathf.Min(abDist, bcDist, cdDist, daDist);
if (min == abDist) return abClosest;
else if (min == bcDist) return bcClosest;
else if (min == cdDist) return cdClosest;
else return daClosest;
}
public bool Equals(Quadrilateral? other) => other is not null &&
a == other.a && b == other.b && c == other.c && d == other.d;
public override bool Equals(object? obj)
{ {
if (other is null) return false; if (obj is null) return false;
return A == other.A && B == other.B && C == other.C && D == other.D; else if (obj is Quadrilateral quad) return Equals(quad);
return false;
} }
public override int GetHashCode() => base.GetHashCode(); public override int GetHashCode() => base.GetHashCode();
public override string ToString() => $"{nameof(Quadrilateral)} {{ a: {a}, b: {b}, c: {c}, d: {d} }}";
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public bool Contains(Float3 point) => Contains(point, 0.05f);
public IEnumerator<Vert> GetEnumerator() public bool Contains(Float3 point, float tolerance)
{ {
yield return A; Triangle pab = (point, a, b),
yield return B; pbc = (point, b, c),
yield return C; pcd = (point, c, d),
yield return D; pda = (point, d, a);
return Mathf.Absolute(Area - (pab.Area + pbc.Area + pcd.Area + pda.Area)) < tolerance;
}
public bool Contains(Line line) => Contains(line, 0.05f);
public bool Contains(Line line, float tolerance, float step = Calculus.DefaultStep)
{
for (float t = 0; t <= 1; t += step)
if (!Contains(Float3.Lerp(line.a, line.b, t), tolerance)) return false;
return true;
}
public bool Contains(Triangle tri) => Contains(tri.a) && Contains(tri.b) && Contains(tri.c);
public bool Contains(Quadrilateral quad) => Contains(quad.a) && Contains(quad.b) &&
Contains(quad.c) && Contains(quad.d);
public bool Contains(Box2d box)
{
(Float2 topLeft, Float2 topRight, Float2 bottomRight, Float2 bottomLeft) = box.GetCorners();
return Contains(topLeft) || Contains(topRight) ||
Contains(bottomRight) || Contains(bottomLeft);
}
public bool Contains(IEnumerable<Float3> points)
{
foreach (Float3 point in points) if (!Contains(point)) return false;
return true;
}
public bool Contains(Fill<Float3> points, int count)
{
for (int i = 0; i < count; i++) if (!Contains(points(i))) return false;
return true;
} }
public Vert[] ToArray() => new Vert[] { A, B, C, D }; public bool Intersects(Line line) => Intersects(line, 0.05f);
public Fill<Vert> ToFill() public bool Intersects(Line line, float tolerance, float step = Calculus.DefaultStep)
{
for (float t = 0; t <= 1; t += step)
if (Contains(Float3.Lerp(line.a, line.b, t), tolerance))
return true;
return false;
}
public bool Intersects(Quadrilateral quad)
{
if (Contains(quad) || quad.Contains(this)) return true;
return Intersects(quad.AB) || Intersects(quad.BC) ||
Intersects(quad.CD) || Intersects(quad.DA);
}
public bool Intersects(Box2d box)
{
if (Contains(box) || box.Contains(this)) return true;
(Line top, Line right, Line bottom, Line left) = box.GetOutlines();
return Intersects(top) || Intersects(right) ||
Intersects(bottom) || Intersects(left);
}
public bool Intersects(Triangle tri)
{
if (Contains(tri) || tri.Contains(this)) return true;
return Intersects(tri.AB) || Intersects(tri.BC) || Intersects(tri.CA);
}
public bool Intersects(IEnumerable<Line> lines)
{
foreach (Line l in lines) if (Contains(l)) return true;
return false;
}
public bool Intersects(Fill<Line> lines, int count)
{
for (int i = 0; i < count; i++) if (Contains(lines(i))) return true;
return false;
}
public Float3[] GetAllVerts() => new[] { a, b, c, d };
public Line[] GetOutlines() => new[] { AB, BC, CD, DA };
public Quadrilateral[] Subdivide()
{
Float3 abMid = AB.Midpoint,
bcMid = BC.Midpoint,
cdMid = CD.Midpoint,
daMid = DA.Midpoint,
abcdMid = Midpoint;
return new Quadrilateral[4]
{
(a, abMid, abcdMid, daMid),
(b, bcMid, abcdMid, abMid),
(c, cdMid, abcdMid, bcMid),
(d, daMid, abcdMid, cdMid)
};
}
public Quadrilateral[] Subdivide(int iterations)
{
Quadrilateral[] active = new[] { this };
for (int i = 0; i < iterations; i++)
{
List<Quadrilateral> newQuads = new();
foreach (Quadrilateral quad in active) newQuads.AddRange(quad.Subdivide());
active = newQuads.ToArray();
}
return active;
}
public Triangle[] Triangulate()
{
if (AC.Length > BD.Length) return new Triangle[]
{
(a, b, c),
(c, d, a)
};
else return new Triangle[]
{
(b, c, d),
(d, a, b)
};
}
public bool WithinRange(Float3 point, float range) =>
WithinRange(point, range, Calculus.DefaultStep);
public bool WithinRange(Float3 point, float range, float step)
{
// Just like line, this is probably optimizable but who cares?
return AB.WithinRange(point, range, step) ||
BC.WithinRange(point, range, step) ||
CD.WithinRange(point, range, step) ||
DA.WithinRange(point, range, step);
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<Float3> GetEnumerator()
{
yield return a;
yield return b;
yield return c;
yield return d;
}
public Float3[] ToArray() => new[] { a, b, c, d };
public Fill<Float3> ToFill()
{ {
Quadrilateral @this = this; Quadrilateral @this = this;
return i => @this[i]; return i => @this[i];
} }
public List<Vert> ToList() => new() { A, B, C, D }; public List<Float3> ToList() => new() { a, b, c, d };
public float[] ToFloatArray() => new float[] { A.position.x, A.position.y, A.position.z, public float[] ToFloatArray() => new[] { a.x, a.y, a.z,
B.position.x, B.position.y, B.position.z, b.x, b.y, b.z,
C.position.x, C.position.y, C.position.z, c.x, c.y, c.z,
D.position.x, D.position.y, D.position.z }; d.x, d.y, d.z };
public List<float> ToFloatList() => new() { A.position.x, A.position.y, A.position.z,
B.position.x, B.position.y, B.position.z,
C.position.x, C.position.y, C.position.z,
D.position.x, D.position.y, D.position.z };
public Triangle[] Triangulate() => new Line(A, C).Length > new Line(B, D).Length ? public static Quadrilateral operator +(Quadrilateral q, Float3 offset) =>
new Triangle[] { new(A, B, C), new(C, D, A) } : new Triangle[] { new(B, C, D), new(D, A, B) }; new(q.a + offset, q.b + offset, q.c + offset, q.d + offset);
public static Quadrilateral operator -(Quadrilateral q, Float3 offset) =>
new(q.a - offset, q.b - offset, q.c - offset, q.d - offset);
public static Quadrilateral operator *(Quadrilateral q, float factor) =>
new(q.a * factor, q.b * factor, q.c * factor, q.d * factor);
public static Quadrilateral operator *(Quadrilateral q, Float3 factor) =>
new(q.a * factor, q.b * factor, q.c * factor, q.d * factor);
public static Quadrilateral operator /(Quadrilateral q, float factor) =>
new(q.a / factor, q.b / factor, q.c / factor, q.d / factor);
public static Quadrilateral operator /(Quadrilateral q, Float3 factor) =>
new(q.a / factor, q.b / factor, q.c / factor, q.d / factor);
protected virtual bool PrintMembers(StringBuilder builder) public static bool operator ==(Quadrilateral a, Quadrilateral b) => a.Equals(b);
{ public static bool operator !=(Quadrilateral a, Quadrilateral b) => !a.Equals(b);
builder.Append("A = ");
builder.Append(A);
builder.Append(", B = ");
builder.Append(B);
builder.Append(", C = ");
builder.Append(C);
builder.Append(", D = ");
builder.Append(D);
return true;
}
public static Quadrilateral operator +(Quadrilateral a, Quadrilateral b) => new(a.A + b.A, a.B + b.B, // TODO: explicit conversion from polygon, check if exactly four verts, then cast.
a.C + b.C, a.D + b.D);
public static Quadrilateral operator +(Quadrilateral a, Vert b) => new(a.A + b, a.B + b, a.C + b, a.D + b);
public static Quadrilateral operator -(Quadrilateral q) => new(-q.A, -q.B, -q.C, -q.D);
public static Quadrilateral operator -(Quadrilateral a, Quadrilateral b) => new(a.A - b.A, a.B - b.B,
a.C - b.C, a.D - b.D);
public static Quadrilateral operator -(Quadrilateral a, Vert b) => new(a.A - b, a.B - b, a.C - b, a.D - b);
public static Quadrilateral operator *(Quadrilateral a, Quadrilateral b) => new(a.A * b.A, a.B * b.B,
a.C * b.C, a.D * b.D);
public static Quadrilateral operator *(Quadrilateral a, Vert b) => new(a.A * b, a.B * b, a.C * b, a.D * b);
public static Quadrilateral operator *(Quadrilateral a, float b) => new(a.A * b, a.B * b, a.C * b, a.D * b);
public static Quadrilateral operator /(Quadrilateral a, Quadrilateral b) => new(a.A / b.A, a.B / b.B,
a.C / b.C, a.D / b.D);
public static Quadrilateral operator /(Quadrilateral a, Vert b) => new(a.A / b, a.B / b, a.C / b, a.D / b);
public static Quadrilateral operator /(Quadrilateral a, float b) => new(a.A / b, a.B / b, a.C / b, a.D / b);
public static implicit operator Quadrilateral(Fill<Vert> fill) => new(fill);
public static implicit operator Quadrilateral(Fill<Float3> fill) => new(fill); public static implicit operator Quadrilateral(Fill<Float3> fill) => new(fill);
public static implicit operator Quadrilateral(Fill<Int3> fill) => new(fill); public static implicit operator Quadrilateral(Fill<Int3> fill) => new(fill);
public static implicit operator Quadrilateral(Fill<Line> fill) => new(fill); public static implicit operator Quadrilateral(Fill<Line> fill) => new(fill);
public static implicit operator Quadrilateral(Fill<float> fill) => new(fill); public static implicit operator Quadrilateral(Fill<float> fill) => new(fill);
public static implicit operator Quadrilateral(Fill<int> fill) => new(fill); public static implicit operator Quadrilateral(Fill<int> fill) => new(fill);
public static implicit operator Quadrilateral((Vert a, Vert b, Vert c, Vert d) val) => public static implicit operator Quadrilateral((Float3 a, Float3 b, Float3 c, Float3 d) val) =>
new(val.a, val.b, val.c, val.d); new(val.a, val.b, val.c, val.d);
} }

View File

@ -1,148 +0,0 @@
namespace Nerd_STF.Mathematics.Geometry;
public record class Sphere : IAverage<Sphere>, ICeiling<Sphere>, IClamp<Sphere>, IClosestTo<Vert>,
IComparable<Sphere>, IComparable<float>, IContains<Vert>, IEquatable<Sphere>, IEquatable<float>, IFloor<Sphere>,
IFromTuple<Sphere, (Vert center, float radius)>, ILerp<Sphere, float>, IMax<Sphere>, IMedian<Sphere>,
IMin<Sphere>, IRound<Sphere>, ISplittable<Sphere, (Vert[] centers, float[] radii)>
{
public static Sphere Unit => new(Vert.Zero, 1);
public Vert center;
public float radius;
public float SurfaceArea => 4 * Constants.Pi * radius * radius;
public float Volume => 4 / 3 * (Constants.Pi * radius * radius * radius);
public static Sphere FromDiameter(Vert a, Vert b) => new(Vert.Average(a, b), (a - b).Magnitude / 2);
public static Sphere FromRadius(Vert center, Vert radius) => new(center, (center - radius).Magnitude);
public Sphere(Vert center, float radius)
{
this.center = center;
this.radius = radius;
}
public Sphere(float cX, float cY, float radius) : this(new Vert(cX, cY), radius) { }
public Sphere(float cX, float cY, float cZ, float radius) : this(new Vert(cX, cY, cZ), radius) { }
public Sphere(Fill<float> fill, float radius) : this(new Vert(fill), radius) { }
public Sphere(Fill<float> fill) : this(new Vert(fill), fill(3)) { }
public Sphere(Fill<int> fill, float radius) : this(new Vert(fill), radius) { }
public Sphere(Fill<int> fill) : this(new Vert(fill), fill(3)) { }
public Sphere(Fill<Vert> fill, float radius) : this(fill(0), radius) { }
public Sphere(Fill<Vert> fillA, Fill<float> fillB) : this(fillA(0), fillB(0)) { }
public static Sphere Average(params Sphere[] vals)
{
(Vert[] centers, float[] radii) = SplitArray(vals);
return new(Vert.Average(centers), Mathf.Average(radii));
}
public static Sphere Ceiling(Sphere val) => new(Vert.Ceiling(val.center), Mathf.Ceiling(val.radius));
public static Sphere Clamp(Sphere val, Sphere min, Sphere max) =>
new(Vert.Clamp(val.center, min.center, max.center), Mathf.Clamp(val.radius, min.radius, max.radius));
public static Sphere Floor(Sphere val) => new(Vert.Floor(val.center), Mathf.Floor(val.radius));
public static Sphere Lerp(Sphere a, Sphere b, float t, bool clamp = true) =>
new(Vert.Lerp(a.center, b.center, t, clamp), Mathf.Lerp(a.radius, b.radius, t, clamp));
public static Sphere Max(params Sphere[] vals)
{
(Vert[] centers, float[] radii) = SplitArray(vals);
return new(Vert.Max(centers), Mathf.Max(radii));
}
public static Sphere Median(params Sphere[] vals)
{
(Vert[] centers, float[] radii) = SplitArray(vals);
return new(Vert.Median(centers), Mathf.Median(radii));
}
public static Sphere Min(params Sphere[] vals)
{
(Vert[] centers, float[] radii) = SplitArray(vals);
return new(Vert.Min(centers), Mathf.Min(radii));
}
public static Sphere Round(Sphere val) => new(Vert.Round(val.center), Mathf.Round(val.radius));
public static (Vert[] centers, float[] radii) SplitArray(params Sphere[] spheres)
{
Vert[] centers = new Vert[spheres.Length];
float[] radii = new float[spheres.Length];
for (int i = 0; i < spheres.Length; i++)
{
centers[i] = spheres[i].center;
radii[i] = spheres[i].radius;
}
return (centers, radii);
}
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public bool Equals(float other) => Volume == other;
public virtual bool Equals(Sphere? other)
{
if (other is null) return false;
return center == other.center && radius == other.radius;
}
public override int GetHashCode() => base.GetHashCode();
public int CompareTo(Sphere? other)
{
if (other is null) return -1;
return Volume.CompareTo(other.Volume);
}
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public int CompareTo(float volume) => Volume.CompareTo(volume);
public bool Contains(Vert vert) => (center - vert).Magnitude <= radius;
public Vert ClosestTo(Vert vert) => Contains(vert) ? vert : ((vert - center).Normalized * radius) + center;
protected virtual bool PrintMembers(StringBuilder builder)
{
builder.Append("Center = ");
builder.Append(builder);
builder.Append(", Radius = ");
builder.Append(radius);
return true;
}
public static Sphere operator +(Sphere a, Sphere b) => new(a.center + b.center, a.radius + b.radius);
public static Sphere operator +(Sphere a, Vert b) => new(a.center + b, a.radius);
public static Sphere operator +(Sphere a, float b) => new(a.center, a.radius + b);
public static Sphere operator -(Sphere a, Sphere b) => new(a.center + b.center, a.radius + b.radius);
public static Sphere operator -(Sphere a, Vert b) => new(a.center + b, a.radius);
public static Sphere operator -(Sphere a, float b) => new(a.center, a.radius + b);
public static Sphere operator *(Sphere a, Sphere b) => new(a.center * b.center, a.radius * b.radius);
public static Sphere operator *(Sphere a, float b) => new(a.center * b, a.radius * b);
public static Sphere operator /(Sphere a, Sphere b) => new(a.center * b.center, a.radius * b.radius);
public static Sphere operator /(Sphere a, float b) => new(a.center * b, a.radius * b);
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public static bool operator ==(Sphere a, float b) => a.Equals(b);
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public static bool operator !=(Sphere a, float b) => !a.Equals(b);
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public static bool operator >(Sphere a, Sphere b) => a.CompareTo(b) > 0;
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public static bool operator <(Sphere a, Sphere b) => a.CompareTo(b) < 0;
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public static bool operator >(Sphere a, float b) => a.CompareTo(b) > 0;
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public static bool operator <(Sphere a, float b) => a.CompareTo(b) < 0;
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public static bool operator >=(Sphere a, Sphere b) => a > b || a == b;
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public static bool operator <=(Sphere a, Sphere b) => a < b || a == b;
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public static bool operator >=(Sphere a, float b) => a > b || a == b;
[Obsolete("This method is a bit ambiguous. You should instead compare " + nameof(radius) + "es directly. " +
"This method will be removed in Nerd_STF 2.5.0.")]
public static bool operator <=(Sphere a, float b) => a < b || a == b;
public static implicit operator Sphere((Vert center, float radius) val) =>
new(val.center, val.radius);
}

View File

@ -1,129 +1,102 @@
using System.Net.Security; using Nerd_STF.Mathematics.Geometry.Abstract;
namespace Nerd_STF.Mathematics.Geometry; namespace Nerd_STF.Mathematics.Geometry;
public record class Triangle : IAbsolute<Triangle>, IAverage<Triangle>, ICeiling<Triangle>, IClamp<Triangle>, public class Triangle : IClosestTo<Float3>,
IEquatable<Triangle>, IFloor<Triangle>, IFromTuple<Triangle, (Vert a, Vert b, Vert c)>, IGroup<Vert>, IFromTuple<Triangle, (Float3 a, Float3 b, Float3 c)>, IPolygon<Triangle>,
IIndexAll<Vert>, IIndexRangeAll<Vert>, ILerp<Triangle, float>, IRound<Triangle>, IShape2d<float> ISplittable<Triangle, (Float3[] As, Float3[] Bs, Float3[] Cs)>,
ISubdivide<Triangle>, IWithinRange<Float3, float>
{ {
public Vert A public float Area
{ {
get => p_a; get // Heron's Formula
set
{ {
p_a = value; float a = AB.Length, b = BC.Length, c = CA.Length,
p_ab.a = value; s = (a + b + c) / 2;
p_ca.b = value; return Mathf.Sqrt(s * (s - a) * (s - b) * (s - c));
}
}
public Vert B
{
get => p_b;
set
{
p_b = value;
p_ab.b = value;
p_bc.a = value;
}
}
public Vert C
{
get => p_c;
set
{
p_c = value;
p_bc.b = value;
p_ca.a = value;
} }
} }
public Float3 Midpoint => Float3.Average(a, b, c);
public float Perimeter => AB.Length + BC.Length + CA.Length;
public Line AB public Line AB
{ {
get => p_ab; get => (a, b);
set set
{ {
p_ab = value; a = value.a;
p_a = value.a; b = value.b;
p_b = value.b;
p_bc.a = value.b;
p_ca.b = value.a;
} }
} }
public Line BC public Line BC
{ {
get => p_bc; get => (b, c);
set set
{ {
p_bc = value; b = value.a;
p_b = value.a; c = value.b;
p_c = value.b;
p_ca.a = value.b;
p_ab.b = value.a;
} }
} }
public Line CA public Line CA
{ {
get => p_ca; get => (c, a);
set set
{ {
p_ca = value; c = value.a;
p_a = value.b; a = value.b;
p_c = value.a;
p_ab.a = value.b;
p_bc.b = value.a;
} }
} }
private Vert p_a, p_b, p_c; public Angle AngleABC => Angle.FromPoints(a, b, c);
private Line p_ab, p_bc, p_ca; public Angle AngleBCA => Angle.FromPoints(b, c, a);
public Angle AngleCAB => Angle.FromPoints(c, a, b);
[Obsolete("This field doesn't account for the Z-axis. This will be fixed in v2.4.0")] public Float3 a, b, c;
public float Area => (float)Mathf.Absolute((A.position.x * B.position.y) + (B.position.x * C.position.y) +
(C.position.x * A.position.y) - ((B.position.x * A.position.y) + (C.position.x * B.position.y) +
(A.position.x * C.position.y))) * 0.5f;
public Vert Midpoint => Vert.Average(A, B, C);
public float Perimeter => AB.Length + BC.Length + CA.Length;
public Triangle(Vert a, Vert b, Vert c) public Triangle() : this(Float3.Zero, Float3.Zero, Float3.Zero) { }
public Triangle(Float3 a, Float3 b, Float3 c)
{ {
p_a = a; this.a = a;
p_b = b; this.b = b;
p_c = c; this.c = c;
p_ab = new(a, b);
p_bc = new(b, c);
p_ca = new(c, a);
} }
public Triangle(Line ab, Line bc, Line ca) public Triangle(Line ab, Line bc, Line ca)
{ {
if (ab.a != ca.b || ab.b != bc.a || bc.b != ca.a) if (ab.b != bc.a || bc.b != ca.a || ca.b != ab.a)
throw new DisconnectedLinesException(ab, bc, ca); throw new DisconnectedLinesException(ab, bc, ca);
p_a = ab.a; a = ab.a;
p_b = bc.a; b = bc.a;
p_c = ca.a; c = ca.a;
p_ab = ab;
p_bc = bc;
p_ca = ca;
} }
public Triangle(float x1, float y1, float x2, float y2, float x3, float y3) public Triangle(float x1, float y1, float x2, float y2, float x3, float y3)
: this(new Vert(x1, y1), new Vert(x2, y2), new Vert(x3, y3)) { } {
public Triangle(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, a = (x1, y1, 0);
float z3) : this(new Vert(x1, y1, z1), new Vert(x2, y2, z2), new Vert(x3, y3, z3)) { } b = (x2, y2, 0);
c = (x3, y3, 0);
}
public Triangle(float x1, float y1, float z1, float x2, float y2, float z2, float x3,
float y3, float z3)
{
a = (x1, y1, z1);
b = (x2, y2, z2);
c = (x3, y3, z3);
}
public Triangle(Fill<Float3> fill) : this(fill(0), fill(1), fill(2)) { } public Triangle(Fill<Float3> fill) : this(fill(0), fill(1), fill(2)) { }
public Triangle(Fill<Int3> fill) : this(fill(0), fill(1), fill(2)) { } public Triangle(Fill<Int3> fill) : this(fill(0), fill(1), fill(2)) { }
public Triangle(Fill<Vert> fill) : this(fill(0), fill(1), fill(2)) { }
public Triangle(Fill<Line> fill) : this(fill(0), fill(1), fill(2)) { } public Triangle(Fill<Line> fill) : this(fill(0), fill(1), fill(2)) { }
public Triangle(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4), fill(5), fill(6), public Triangle(Fill<float> fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4),
fill(7), fill(8)) { } fill(5), fill(6), fill(7), fill(8)) { }
public Triangle(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4), fill(5), fill(6), public Triangle(Fill<int> fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4),
fill(7), fill(8)) { } fill(5), fill(6), fill(7), fill(8)) { }
public Vert this[int index] public Float3 this[int index]
{ {
get => index switch get => index switch
{ {
0 => A, 0 => a,
1 => B, 1 => b,
2 => C, 2 => c,
_ => throw new IndexOutOfRangeException(nameof(index)), _ => throw new IndexOutOfRangeException(nameof(index)),
}; };
set set
@ -131,33 +104,33 @@ public record class Triangle : IAbsolute<Triangle>, IAverage<Triangle>, ICeiling
switch (index) switch (index)
{ {
case 0: case 0:
A = value; a = value;
break; break;
case 1: case 1:
B = value; b = value;
break; break;
case 2: case 2:
C = value; c = value;
break; break;
default: throw new IndexOutOfRangeException(nameof(index)); default: throw new IndexOutOfRangeException(nameof(index));
} }
} }
} }
public Vert this[Index index] public Float3 this[Index index]
{ {
get => this[index.IsFromEnd ? 3 - index.Value : index.Value]; get => this[index.IsFromEnd ? 3 - index.Value : index.Value];
set => this[index.IsFromEnd ? 3 - index.Value : index.Value] = value; set => this[index.IsFromEnd ? 3 - index.Value : index.Value] = value;
} }
public Vert[] this[Range range] public Float3[] this[Range range]
{ {
get get
{ {
int start = range.Start.IsFromEnd ? 3 - range.Start.Value : range.Start.Value; int start = range.Start.IsFromEnd ? 3 - range.Start.Value : range.Start.Value;
int end = range.End.IsFromEnd ? 3 - range.End.Value : range.End.Value; int end = range.End.IsFromEnd ? 3 - range.End.Value : range.End.Value;
List<Vert> res = new(); List<Float3> res = new();
for (int i = start; i < end; i++) res.Add(this[i]); for (int i = start; i < end; i++) res.Add(this[i]);
return res.ToArray(); return res.ToArray();
} }
@ -169,141 +142,223 @@ public record class Triangle : IAbsolute<Triangle>, IAverage<Triangle>, ICeiling
} }
} }
public static Triangle Absolute(Triangle val) =>
new(Vert.Absolute(val.A), Vert.Absolute(val.B), Vert.Absolute(val.C));
public static Triangle Average(params Triangle[] vals) public static Triangle Average(params Triangle[] vals)
{ {
(Vert[] As, Vert[] Bs, Vert[] Cs) = SplitVertArray(vals); (Float3[] As, Float3[] Bs, Float3[] Cs) = SplitArray(vals);
return new(Vert.Average(As), Vert.Average(Bs), Vert.Average(Cs)); return (Float3.Average(As), Float3.Average(Bs), Float3.Average(Cs));
} }
public static Triangle Ceiling(Triangle val) =>
new(Vert.Ceiling(val.A), Vert.Ceiling(val.B), Vert.Ceiling(val.C));
public static Triangle Clamp(Triangle val, Triangle min, Triangle max) =>
new(Vert.Clamp(val.A, min.A, max.A), Vert.Clamp(val.B, min.B, max.B), Vert.Clamp(val.C, min.C, max.C));
public static Triangle Floor(Triangle val) =>
new(Vert.Floor(val.A), Vert.Floor(val.B), Vert.Floor(val.C));
public static Triangle Lerp(Triangle a, Triangle b, float t, bool clamp = true) => public static Triangle Lerp(Triangle a, Triangle b, float t, bool clamp = true) =>
new(Vert.Lerp(a.A, b.A, t, clamp), Vert.Lerp(a.B, b.B, t, clamp), Vert.Lerp(a.C, b.C, t, clamp)); (Float3.Lerp(a.a, b.a, t, clamp), Float3.Lerp(a.b, b.b, t, clamp), Float3.Lerp(a.c, a.c, t, clamp));
public static Triangle Max(params Triangle[] vals) public static (Float3[] As, Float3[] Bs, Float3[] Cs) SplitArray(params Triangle[] vals)
{ {
(Vert[] As, Vert[] Bs, Vert[] Cs) = SplitVertArray(vals); Float3[] As = new Float3[vals.Length],
return new(Vert.Max(As), Vert.Max(Bs), Vert.Max(Cs)); Bs = new Float3[vals.Length],
} Cs = new Float3[vals.Length];
public static Triangle Median(params Triangle[] vals) for (int i = 0; i < vals.Length; i++)
{
(Vert[] As, Vert[] Bs, Vert[] Cs) = SplitVertArray(vals);
return new(Vert.Median(As), Vert.Median(Bs), Vert.Median(Cs));
}
public static Triangle Min(params Triangle[] vals)
{
(Vert[] As, Vert[] Bs, Vert[] Cs) = SplitVertArray(vals);
return new(Vert.Min(As), Vert.Min(Bs), Vert.Min(Cs));
}
public static Triangle Round(Triangle val) =>
new(Vert.Round(val.A), Vert.Round(val.B), Vert.Round(val.C));
public static (Vert[] As, Vert[] Bs, Vert[] Cs) SplitVertArray(params Triangle[] tris)
{
Vert[] a = new Vert[tris.Length], b = new Vert[tris.Length], c = new Vert[tris.Length];
for (int i = 0; i < tris.Length; i++)
{ {
a[i] = tris[i].A; As[i] = vals[i].a;
b[i] = tris[i].B; Bs[i] = vals[i].b;
c[i] = tris[i].C; Cs[i] = vals[i].c;
} }
return (a, b, c); return (As, Bs, Cs);
}
public static (Line[] ABs, Line[] BCs, Line[] CAs) SplitLineArray(params Triangle[] tris)
{
Line[] ab = new Line[tris.Length], bc = new Line[tris.Length], ca = new Line[tris.Length];
for (int i = 0; i < tris.Length; i++)
{
ab[i] = tris[i].AB;
bc[i] = tris[i].BC;
ca[i] = tris[i].CA;
}
return (ab, bc, ca);
} }
public static float[] ToFloatArrayAll(params Triangle[] tris) public static float[] ToFloatArrayAll(params Triangle[] vals)
{ {
float[] vals = new float[tris.Length * 9]; float[] result = new float[9 * vals.Length];
for (int i = 0; i < tris.Length; i++) for (int i = 0; i < vals.Length; i++)
{ {
int pos = i * 9; int p = i * 9;
vals[pos + 0] = tris[i].A.position.x; result[p + 0] = vals[i].a.x;
vals[pos + 1] = tris[i].A.position.y; result[p + 1] = vals[i].a.y;
vals[pos + 2] = tris[i].A.position.z; result[p + 2] = vals[i].a.z;
vals[pos + 3] = tris[i].B.position.x; result[p + 3] = vals[i].b.x;
vals[pos + 4] = tris[i].B.position.y; result[p + 4] = vals[i].b.y;
vals[pos + 5] = tris[i].B.position.z; result[p + 5] = vals[i].b.z;
vals[pos + 6] = tris[i].C.position.x; result[p + 6] = vals[i].c.x;
vals[pos + 7] = tris[i].C.position.y; result[p + 7] = vals[i].c.y;
vals[pos + 8] = tris[i].C.position.z; result[p + 8] = vals[i].c.z;
} }
return vals; return result;
} }
public static List<float> ToFloatListAll(params Triangle[] tris) => new(ToFloatArrayAll(tris));
public virtual bool Equals(Triangle? other) public Float3 ClosestTo(Float3 point)
{ {
if (other is null) return false; Float3 abClosest = AB.ClosestTo(point),
return A == other.A && B == other.B && C == other.C; bcClosest = BC.ClosestTo(point),
caClosest = CA.ClosestTo(point);
// Very inefficient way to select the closest point.
float abDist = (abClosest - point).Magnitude,
bcDist = (bcClosest - point).Magnitude,
caDist = (caClosest - point).Magnitude;
float min = Mathf.Min(abDist, bcDist, caDist);
if (min == abDist) return abClosest;
else if (min == bcDist) return bcClosest;
else return caClosest;
}
public bool Equals(Triangle? other) => other is not null && a == other.a
&& b == other.b && c == other.c;
public override bool Equals(object? obj)
{
if (obj is null) return false;
else if (obj is Triangle tri) return Equals(tri);
return false;
} }
public override int GetHashCode() => base.GetHashCode(); public override int GetHashCode() => base.GetHashCode();
public override string ToString() => $"{nameof(Triangle)} {{ a: {a}, b: {b}, c: {c} }}";
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public bool Contains(Float3 point) => Contains(point, 0.05f);
public IEnumerator<Vert> GetEnumerator() public bool Contains(Float3 point, float tolerance)
{ {
yield return A; Triangle pab = (point, a, b),
yield return B; pbc = (point, b, c),
yield return C; pca = (point, c, a);
return Mathf.Absolute(Area - (pab.Area + pbc.Area + pca.Area)) < tolerance;
}
public bool Contains(Line line) => Contains(line, 0.05f);
public bool Contains(Line line, float tolerance, float step = Calculus.DefaultStep)
{
for (float t = 0; t <= 1; t += step)
if (!Contains(Float3.Lerp(line.a, line.b, t), tolerance)) return false;
return true;
}
public bool Contains(Box2d box)
{
(Float2 topLeft, Float2 topRight, Float2 bottomRight, Float2 bottomLeft) = box.GetCorners();
return Contains(topLeft) || Contains(topRight) ||
Contains(bottomRight) || Contains(bottomLeft);
}
public bool Contains(Triangle tri) => Contains(tri.a) && Contains(tri.b) && Contains(tri.c);
public bool Contains(IEnumerable<Float3> points)
{
foreach (Float3 point in points) if (!Contains(point)) return false;
return true;
}
public bool Contains(Fill<Float3> points, int count)
{
for (int i = 0; i < count; i++) if (!Contains(points(i))) return false;
return true;
} }
public Vert[] ToArray() => new Vert[] { A, B, C }; public bool Intersects(Line line) => Intersects(line, 0.05f);
public Fill<Vert> ToFill() public bool Intersects(Line line, float tolerance, float step = Calculus.DefaultStep)
{
for (float t = 0; t <= 1; t += step)
if (Contains(Float3.Lerp(line.a, line.b, t), tolerance))
return true;
return false;
}
public bool Intersects(Box2d box)
{
if (Contains(box) || box.Contains(this)) return true;
(Line top, Line right, Line bottom, Line left) = box.GetOutlines();
return Intersects(top) || Intersects(right) ||
Intersects(bottom) || Intersects(left);
}
public bool Intersects(Triangle tri)
{
if (Contains(tri) || tri.Contains(this)) return true;
return Intersects(tri.AB) || Intersects(tri.BC) || Intersects(tri.CA);
}
public bool Intersects(IEnumerable<Line> lines)
{
foreach (Line l in lines) if (Contains(l)) return true;
return false;
}
public bool Intersects(Fill<Line> lines, int count)
{
for (int i = 0; i < count; i++) if (Contains(lines(i))) return true;
return false;
}
public Float3[] GetAllVerts() => new[] { a, b, c };
public Line[] GetOutlines() => new[] { AB, BC, CA };
public Triangle[] Subdivide()
{
Float3 abMid = AB.Midpoint,
bcMid = BC.Midpoint,
caMid = CA.Midpoint;
return new Triangle[4]
{
(a, abMid, caMid),
(abMid, b, bcMid),
(caMid, bcMid, c),
(abMid, bcMid, caMid)
};
}
public Triangle[] Subdivide(int iterations)
{
Triangle[] active = new[] { this };
for (int i = 0; i < iterations; i++)
{
List<Triangle> newTris = new();
foreach (Triangle tri in active) newTris.AddRange(tri.Subdivide());
active = newTris.ToArray();
}
return active;
}
public Triangle[] Triangulate() => new[] { this };
public bool WithinRange(Float3 point, float range) =>
WithinRange(point, range, Calculus.DefaultStep);
public bool WithinRange(Float3 point, float range, float step)
{
// Just like line, this is probably optimizable but who cares?
return AB.WithinRange(point, range, step) ||
BC.WithinRange(point, range, step) ||
CA.WithinRange(point, range, step);
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<Float3> GetEnumerator()
{
yield return a;
yield return b;
yield return c;
}
public Float3[] ToArray() => new[] { a, b, c };
public Fill<Float3> ToFill()
{ {
Triangle @this = this; Triangle @this = this;
return i => @this[i]; return i => @this[i];
} }
public List<Vert> ToList() => new() { A, B, C }; public List<Float3> ToList() => new() { a, b, c };
public float[] ToFloatArray() => new float[] { A.position.x, A.position.y, A.position.z, public float[] ToFloatArray() => new[] { a.x, a.y, a.z,
B.position.x, B.position.y, B.position.z, b.x, b.y, b.z,
C.position.x, C.position.y, C.position.z }; c.x, c.y, c.z };
public List<float> ToFloatList() => new() { A.position.x, A.position.y, A.position.z,
B.position.x, B.position.y, B.position.z,
C.position.x, C.position.y, C.position.z };
protected virtual bool PrintMembers(StringBuilder builder) public static Triangle operator +(Triangle t, Float3 offset) =>
{ new(t.a + offset, t.b + offset, t.c + offset);
builder.Append("A = "); public static Triangle operator -(Triangle t, Float3 offset) =>
builder.Append(A); new(t.a - offset, t.b - offset, t.c - offset);
builder.Append(", B = "); public static Triangle operator *(Triangle t, float factor) =>
builder.Append(B); new(t.a * factor, t.b * factor, t.c * factor);
builder.Append(", C = "); public static Triangle operator *(Triangle t, Float3 factor) =>
builder.Append(C); new(t.a * factor, t.b * factor, t.c * factor);
return true; public static Triangle operator /(Triangle t, float factor) =>
} new(t.a / factor, t.b / factor, t.c / factor);
public static Triangle operator /(Triangle t, Float3 factor) =>
new(t.a / factor, t.b / factor, t.c / factor);
public static Triangle operator +(Triangle a, Triangle b) => new(a.A + b.A, a.B + b.B, a.C + b.C); public static bool operator ==(Triangle a, Triangle b) => a.Equals(b);
public static Triangle operator +(Triangle a, Vert b) => new(a.A + b, a.B + b, a.C + b); public static bool operator !=(Triangle a, Triangle b) => !a.Equals(b);
public static Triangle operator -(Triangle t) => new(-t.A, -t.B, -t.C);
public static Triangle operator -(Triangle a, Triangle b) => new(a.A - b.A, a.B - b.B, a.C - b.C);
public static Triangle operator -(Triangle a, Vert b) => new(a.A - b, a.B - b, a.C - b);
public static Triangle operator *(Triangle a, Triangle b) => new(a.A * b.A, a.B * b.B, a.C * b.C);
public static Triangle operator *(Triangle a, Vert b) => new(a.A * b, a.B * b, a.C * b);
public static Triangle operator *(Triangle a, float b) => new(a.A * b, a.B * b, a.C * b);
public static Triangle operator /(Triangle a, Triangle b) => new(a.A / b.A, a.B / b.B, a.C / b.C);
public static Triangle operator /(Triangle a, Vert b) => new(a.A / b, a.B / b, a.C / b);
public static Triangle operator /(Triangle a, float b) => new(a.A / b, a.B / b, a.C / b);
public static implicit operator Triangle(Fill<Vert> fill) => new(fill); // TODO: explicit conversion from polygon, check if exactly three verts, then cast.
public static implicit operator Triangle(Fill<Float3> fill) => new(fill); public static implicit operator Triangle(Fill<Float3> fill) => new(fill);
public static implicit operator Triangle(Fill<Int3> fill) => new(fill); public static implicit operator Triangle(Fill<Int3> fill) => new(fill);
public static implicit operator Triangle(Fill<Line> fill) => new(fill); public static implicit operator Triangle(Fill<Line> fill) => new(fill);
public static implicit operator Triangle(Fill<float> fill) => new(fill); public static implicit operator Triangle(Fill<float> fill) => new(fill);
public static implicit operator Triangle(Fill<int> fill) => new(fill); public static implicit operator Triangle(Fill<int> fill) => new(fill);
public static implicit operator Triangle((Vert a, Vert b, Vert c) val) => public static implicit operator Triangle((Float3 a, Float3 b, Float3 c) val) =>
new(val.a, val.b, val.c); new(val.a, val.b, val.c);
} }

View File

@ -1,106 +0,0 @@
namespace Nerd_STF.Mathematics.Geometry;
public struct Vert : ICloneable, IEquatable<Vert>, IGroup<float>
{
public static Vert Back => new(0, 0, -1);
public static Vert Down => new(0, -1, 0);
public static Vert Forward => new(0, 0, 1);
public static Vert Left => new(-1, 0, 0);
public static Vert Right => new(1, 0, 0);
public static Vert Up => new(0, 1, 0);
public static Vert One => new(1, 1, 1);
public static Vert Zero => new(0, 0, 0);
public float Magnitude => position.Magnitude;
public Vert Normalized => this / Magnitude;
public Float3 position;
public Vert(Float2 pos) : this((Float3)pos) { }
public Vert(Float3 pos) => position = pos;
public Vert(float x, float y) : this(new Float2(x, y)) { }
public Vert(float x, float y, float z) : this(new Float3(x, y, z)) { }
public Vert(Fill<float> fill) : this(new Float3(fill)) { }
public Vert(Fill<int> fill) : this(new Float3(fill)) { }
public float this[int index]
{
get => position[index];
set => position[index] = value;
}
public static Vert Absolute(Vert val) => new(Float3.Absolute(val.position));
public static Vert Average(params Vert[] vals) => Float3.Average(ToFloat3Array(vals));
public static Vert Ceiling(Vert val) => new(Float3.Ceiling(val.position));
public static Vert Clamp(Vert val, Vert min, Vert max) =>
new(Float3.Clamp(val.position, min.position, max.position));
public static Vert ClampMagnitude(Vert val, float minMag, float maxMag) =>
new(Float3.ClampMagnitude(val.position, minMag, maxMag));
public static Vert Cross(Vert a, Vert b, bool normalized = false) =>
new(Float3.Cross(a.position, b.position, normalized));
public static float Dot(Vert a, Vert b) => Float3.Dot(a.position, b.position);
public static float Dot(params Vert[] vals) => Float3.Dot(ToFloat3Array(vals));
public static Vert Floor(Vert val) => new(Float3.Floor(val.position));
public static Vert Lerp(Vert a, Vert b, float t, bool clamp = true) =>
new(Float3.Lerp(a.position, b.position, t, clamp));
public static Vert Median(params Vert[] vals) =>
Float3.Median(ToFloat3Array(vals));
public static Vert Max(params Vert[] vals) =>
Float3.Max(ToFloat3Array(vals));
public static Vert Min(params Vert[] vals) =>
Float3.Min(ToFloat3Array(vals));
public static Vert Round(Vert val) =>
Float3.Round(val);
public static Float3[] ToFloat3Array(params Vert[] vals)
{
Float3[] floats = new Float3[vals.Length];
for (int i = 0; i < vals.Length; i++) floats[i] = vals[i].position;
return floats;
}
public static List<Float3> ToFloat3List(params Vert[] vals) => ToFloat3Array(vals).ToList();
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (obj == null || obj.GetType() != typeof(Vert)) return base.Equals(obj);
return Equals((Vert)obj);
}
public bool Equals(Vert other) => position == other.position;
public override int GetHashCode() => position.GetHashCode();
public override string ToString() => position.ToString();
public object Clone() => new Vert(position);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<float> GetEnumerator() => position.GetEnumerator();
public float[] ToArray() => position.ToArray();
public Fill<float> ToFill()
{
Vert @this = this;
return i => @this[i];
}
public List<float> ToList() => position.ToList();
public Vector3d ToVector() => ((Float3)this).ToVector();
public static Vert operator +(Vert a, Vert b) => new(a.position + b.position);
public static Vert operator -(Vert d) => new(-d.position);
public static Vert operator -(Vert a, Vert b) => new(a.position - b.position);
public static Vert operator *(Vert a, Vert b) => new(a.position * b.position);
public static Vert operator *(Vert a, float b) => new(a.position * b);
public static Vert operator /(Vert a, Vert b) => new(a.position / b.position);
public static Vert operator /(Vert a, float b) => new(a.position / b);
public static bool operator ==(Vert a, Vert b) => a.Equals(b);
public static bool operator !=(Vert a, Vert b) => !a.Equals(b);
public static implicit operator Vert(Float2 val) => new(val);
public static implicit operator Vert(Float3 val) => new(val);
public static explicit operator Vert(Float4 val) => new(val.XYZ);
public static implicit operator Vert(Int2 val) => new(val);
public static implicit operator Vert(Int3 val) => new(val);
public static explicit operator Vert(Int4 val) => new(val.XYZ);
public static implicit operator Vert(Fill<float> fill) => new(fill);
public static implicit operator Vert(Fill<int> fill) => new(fill);
}

View File

@ -1,10 +1,10 @@
namespace Nerd_STF.Mathematics; namespace Nerd_STF.Mathematics;
public record struct Int2 : IAbsolute<Int2>, IAverage<Int2>, IClamp<Int2>, IClampMagnitude<Int2, int>, public record struct Int2 : IAbsolute<Int2>, IAverage<Int2>, IClamp<Int2>, IClampMagnitude<Int2, int>,
IComparable<Int2>, ICross<Int2, Int3>, IDivide<Int2>, IDot<Int2, int>, IEquatable<Int2>, IComparable<Int2>, ICross<Int2, Int3>, IDot<Int2, int>, IEquatable<Int2>,
IFromTuple<Int2, (int x, int y)>, IGroup<int>, IIndexAll<int>, IIndexRangeAll<int>, ILerp<Int2, float>, IFromTuple<Int2, (int x, int y)>, IGroup<int>, IIndexAll<int>, IIndexRangeAll<int>, ILerp<Int2, float>,
IMathOperators<Int2>, IMax<Int2>, IMedian<Int2>, IMin<Int2>, IPresets2d<Int2>, IProduct<Int2>, IMathOperators<Int2>, IMax<Int2>, IMedian<Int2>, IMin<Int2>, IPresets2d<Int2>,
ISplittable<Int2, (int[] Xs, int[] Ys)>, ISubtract<Int2>, ISum<Int2> ISplittable<Int2, (int[] Xs, int[] Ys)>
{ {
public static Int2 Down => new(0, -1); public static Int2 Down => new(0, -1);
public static Int2 Left => new(-1, 0); public static Int2 Left => new(-1, 0);
@ -14,6 +14,7 @@ public record struct Int2 : IAbsolute<Int2>, IAverage<Int2>, IClamp<Int2>, IClam
public static Int2 One => new(1, 1); public static Int2 One => new(1, 1);
public static Int2 Zero => new(0, 0); public static Int2 Zero => new(0, 0);
public float InverseMagnitude => Mathf.InverseSqrt(x * x + y * y);
public float Magnitude => Mathf.Sqrt(x * x + y * y); public float Magnitude => Mathf.Sqrt(x * x + y * y);
public Int2 Normalized => (Int2)((Float2)this * Mathf.InverseSqrt(x * x + y * y)); public Int2 Normalized => (Int2)((Float2)this * Mathf.InverseSqrt(x * x + y * y));
@ -76,7 +77,12 @@ public record struct Int2 : IAbsolute<Int2>, IAverage<Int2>, IClamp<Int2>, IClam
public static Int2 Absolute(Int2 val) => public static Int2 Absolute(Int2 val) =>
new(Mathf.Absolute(val.x), Mathf.Absolute(val.y)); new(Mathf.Absolute(val.x), Mathf.Absolute(val.y));
public static Int2 Average(params Int2[] vals) => Sum(vals) / vals.Length; public static Int2 Average(params Int2[] vals)
{
Int2 sum = Zero;
foreach (Int2 i in vals) sum += i;
return sum / vals.Length;
}
public static Int2 Clamp(Int2 val, Int2 min, Int2 max) => public static Int2 Clamp(Int2 val, Int2 min, Int2 max) =>
new(Mathf.Clamp(val.x, min.x, max.x), new(Mathf.Clamp(val.x, min.x, max.x),
Mathf.Clamp(val.y, min.y, max.y)); Mathf.Clamp(val.y, min.y, max.y));
@ -93,7 +99,6 @@ public record struct Int2 : IAbsolute<Int2>, IAverage<Int2>, IClamp<Int2>, IClam
} }
public static Int3 Cross(Int2 a, Int2 b, bool normalized = false) => public static Int3 Cross(Int2 a, Int2 b, bool normalized = false) =>
Int3.Cross(a, b, normalized); Int3.Cross(a, b, normalized);
public static Int2 Divide(Int2 num, params Int2[] vals) => num / Product(vals);
public static int Dot(Int2 a, Int2 b) => a.x * b.x + a.y * b.y; public static int Dot(Int2 a, Int2 b) => a.x * b.x + a.y * b.y;
public static int Dot(params Int2[] vals) public static int Dot(params Int2[] vals)
{ {
@ -128,20 +133,6 @@ public record struct Int2 : IAbsolute<Int2>, IAverage<Int2>, IClamp<Int2>, IClam
foreach (Int2 d in vals) val = d.Magnitude < val.Magnitude ? d : val; foreach (Int2 d in vals) val = d.Magnitude < val.Magnitude ? d : val;
return val; return val;
} }
public static Int2 Product(params Int2[] vals)
{
if (vals.Length < 1) return Zero;
Int2 val = One;
foreach (Int2 d in vals) val *= d;
return val;
}
public static Int2 Subtract(Int2 num, params Int2[] vals) => num - Sum(vals);
public static Int2 Sum(params Int2[] vals)
{
Int2 val = Zero;
foreach (Int2 d in vals) val += d;
return val;
}
public static (int[] Xs, int[] Ys) SplitArray(params Int2[] vals) public static (int[] Xs, int[] Ys) SplitArray(params Int2[] vals)
{ {
@ -206,7 +197,6 @@ public record struct Int2 : IAbsolute<Int2>, IAverage<Int2>, IClamp<Int2>, IClam
public static explicit operator Int2(Vector2d val) => (Int2)val.ToXYZ(); public static explicit operator Int2(Vector2d val) => (Int2)val.ToXYZ();
public static explicit operator Int2(Int3 val) => new(val.x, val.y); public static explicit operator Int2(Int3 val) => new(val.x, val.y);
public static explicit operator Int2(Int4 val) => new(val.x, val.y); public static explicit operator Int2(Int4 val) => new(val.x, val.y);
public static explicit operator Int2(Vert val) => new((int)val.position.x, (int)val.position.y);
public static implicit operator Int2(Fill<int> fill) => new(fill); public static implicit operator Int2(Fill<int> fill) => new(fill);
public static implicit operator Int2((int x, int y) val) => new(val.x, val.y); public static implicit operator Int2((int x, int y) val) => new(val.x, val.y);
} }

View File

@ -3,10 +3,10 @@
namespace Nerd_STF.Mathematics; namespace Nerd_STF.Mathematics;
public record struct Int3 : IAbsolute<Int3>, IAverage<Int3>, IClamp<Int3>, IClampMagnitude<Int3, int>, public record struct Int3 : IAbsolute<Int3>, IAverage<Int3>, IClamp<Int3>, IClampMagnitude<Int3, int>,
IComparable<Int3>, ICross<Int3>, IDivide<Int3>, IDot<Int3, int>, IEquatable<Int3>, IComparable<Int3>, ICross<Int3>, IDot<Int3, int>, IEquatable<Int3>,
IFromTuple<Int3, (int x, int y, int z)>, IGroup<int>, IIndexAll<int>, IIndexRangeAll<int>, ILerp<Int3, float>, IFromTuple<Int3, (int x, int y, int z)>, IGroup<int>, IIndexAll<int>, IIndexRangeAll<int>, ILerp<Int3, float>,
IMathOperators<Int3>, IMax<Int3>, IMedian<Int3>, IMin<Int3>, IPresets3d<Int3>, IProduct<Int3>, IMathOperators<Int3>, IMax<Int3>, IMedian<Int3>, IMin<Int3>, IPresets3d<Int3>,
ISplittable<Int3, (int[] Xs, int[] Ys, int[] Zs)>, ISubtract<Int3>, ISum<Int3> ISplittable<Int3, (int[] Xs, int[] Ys, int[] Zs)>
{ {
public static Int3 Back => new(0, 0, -1); public static Int3 Back => new(0, 0, -1);
public static Int3 Down => new(0, -1, 0); public static Int3 Down => new(0, -1, 0);
@ -18,6 +18,7 @@ public record struct Int3 : IAbsolute<Int3>, IAverage<Int3>, IClamp<Int3>, IClam
public static Int3 One => new(1, 1, 1); public static Int3 One => new(1, 1, 1);
public static Int3 Zero => new(0, 0, 0); public static Int3 Zero => new(0, 0, 0);
public float InverseMagnitude => Mathf.InverseSqrt(x * x + y * y + z * z);
public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z); public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z);
public Int3 Normalized => (Int3)((Float3)this * Mathf.InverseSqrt(x * x + y * y + z * z)); public Int3 Normalized => (Int3)((Float3)this * Mathf.InverseSqrt(x * x + y * y + z * z));
@ -115,7 +116,12 @@ public record struct Int3 : IAbsolute<Int3>, IAverage<Int3>, IClamp<Int3>, IClam
public static Int3 Absolute(Int3 val) => public static Int3 Absolute(Int3 val) =>
new(Mathf.Absolute(val.x), Mathf.Absolute(val.y), Mathf.Absolute(val.z)); new(Mathf.Absolute(val.x), Mathf.Absolute(val.y), Mathf.Absolute(val.z));
public static Int3 Average(params Int3[] vals) => Sum(vals) / vals.Length; public static Int3 Average(params Int3[] vals)
{
Int3 sum = Zero;
foreach (Int3 i in vals) sum += i;
return sum / vals.Length;
}
public static Int3 Clamp(Int3 val, Int3 min, Int3 max) => public static Int3 Clamp(Int3 val, Int3 min, Int3 max) =>
new(Mathf.Clamp(val.x, min.x, max.x), new(Mathf.Clamp(val.x, min.x, max.x),
Mathf.Clamp(val.y, min.y, max.y), Mathf.Clamp(val.y, min.y, max.y),
@ -138,7 +144,6 @@ public record struct Int3 : IAbsolute<Int3>, IAverage<Int3>, IClamp<Int3>, IClam
a.x * b.y - b.x * a.y); a.x * b.y - b.x * a.y);
return normalized ? val.Normalized : val; return normalized ? val.Normalized : val;
} }
public static Int3 Divide(Int3 num, params Int3[] vals) => num / Product(vals);
public static int Dot(Int3 a, Int3 b) => a.x * b.x + a.y * b.y + a.z * b.z; public static int Dot(Int3 a, Int3 b) => a.x * b.x + a.y * b.y + a.z * b.z;
public static int Dot(params Int3[] vals) public static int Dot(params Int3[] vals)
{ {
@ -174,20 +179,6 @@ public record struct Int3 : IAbsolute<Int3>, IAverage<Int3>, IClamp<Int3>, IClam
foreach (Int3 d in vals) val = d.Magnitude < val.Magnitude ? d : val; foreach (Int3 d in vals) val = d.Magnitude < val.Magnitude ? d : val;
return val; return val;
} }
public static Int3 Product(params Int3[] vals)
{
if (vals.Length < 1) return Zero;
Int3 val = One;
foreach (Int3 d in vals) val *= d;
return val;
}
public static Int3 Subtract(Int3 num, params Int3[] vals) => num - Sum(vals);
public static Int3 Sum(params Int3[] vals)
{
Int3 val = Zero;
foreach (Int3 d in vals) val += d;
return val;
}
public static (int[] Xs, int[] Ys, int[] Zs) SplitArray(params Int3[] vals) public static (int[] Xs, int[] Ys, int[] Zs) SplitArray(params Int3[] vals)
{ {
@ -256,8 +247,6 @@ public record struct Int3 : IAbsolute<Int3>, IAverage<Int3>, IClamp<Int3>, IClam
public static explicit operator Int3(Int4 val) => new(val.x, val.y, val.z); public static explicit operator Int3(Int4 val) => new(val.x, val.y, val.z);
public static explicit operator Int3(Matrix m) => new((int)m[0, 0], (int)m[1, 0], (int)m[2, 0]); public static explicit operator Int3(Matrix m) => new((int)m[0, 0], (int)m[1, 0], (int)m[2, 0]);
public static explicit operator Int3(Vector2d val) => (Int3)val.ToXYZ(); public static explicit operator Int3(Vector2d val) => (Int3)val.ToXYZ();
public static explicit operator Int3(Vert val) => new((int)val.position.x, (int)val.position.y,
(int)val.position.z);
public static explicit operator Int3(RGBA val) => (Int3)val.ToRGBAByte(); public static explicit operator Int3(RGBA val) => (Int3)val.ToRGBAByte();
public static explicit operator Int3(HSVA val) => (Int3)val.ToHSVAByte(); public static explicit operator Int3(HSVA val) => (Int3)val.ToHSVAByte();
public static explicit operator Int3(RGBAByte val) => new(val.R, val.G, val.B); public static explicit operator Int3(RGBAByte val) => new(val.R, val.G, val.B);

View File

@ -1,10 +1,10 @@
namespace Nerd_STF.Mathematics; namespace Nerd_STF.Mathematics;
public record struct Int4 : IAbsolute<Int4>, IAverage<Int4>, IClamp<Int4>, IClampMagnitude<Int4, int>, public record struct Int4 : IAbsolute<Int4>, IAverage<Int4>, IClamp<Int4>, IClampMagnitude<Int4, int>,
IComparable<Int4>, IDivide<Int4>, IDot<Int4, int>, IEquatable<Int4>, IComparable<Int4>, IDot<Int4, int>, IEquatable<Int4>,
IFromTuple<Int4, (int x, int y, int z, int w)>, IGroup<int>, IIndexAll<int>, IIndexRangeAll<int>, IFromTuple<Int4, (int x, int y, int z, int w)>, IGroup<int>, IIndexAll<int>, IIndexRangeAll<int>,
ILerp<Int4, float>, IMathOperators<Int4>, IMax<Int4>, IMedian<Int4>, IMin<Int4>, IPresets4d<Int4>, ILerp<Int4, float>, IMathOperators<Int4>, IMax<Int4>, IMedian<Int4>, IMin<Int4>, IPresets4d<Int4>,
IProduct<Int4>, ISplittable<Int4, (int[] Xs, int[] Ys, int[] Zs, int[] Ws)>, ISubtract<Int4>, ISum<Int4> ISplittable<Int4, (int[] Xs, int[] Ys, int[] Zs, int[] Ws)>
{ {
public static Int4 Back => new(0, 0, -1, 0); public static Int4 Back => new(0, 0, -1, 0);
public static Int4 Down => new(0, -1, 0, 0); public static Int4 Down => new(0, -1, 0, 0);
@ -18,6 +18,7 @@ public record struct Int4 : IAbsolute<Int4>, IAverage<Int4>, IClamp<Int4>, IClam
public static Int4 One => new(1, 1, 1, 1); public static Int4 One => new(1, 1, 1, 1);
public static Int4 Zero => new(0, 0, 0, 0); public static Int4 Zero => new(0, 0, 0, 0);
public float InverseMagnitude => Mathf.InverseSqrt(x * x + y * y + z * z + w * w);
public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z + w * w); public float Magnitude => Mathf.Sqrt(x * x + y * y + z * z + w * w);
public Int4 Normalized => (Int4)((Float4)this * Mathf.InverseSqrt(x * x + y * y + z * z + w * w)); public Int4 Normalized => (Int4)((Float4)this * Mathf.InverseSqrt(x * x + y * y + z * z + w * w));
@ -190,7 +191,12 @@ public record struct Int4 : IAbsolute<Int4>, IAverage<Int4>, IClamp<Int4>, IClam
public static Int4 Absolute(Int4 val) => public static Int4 Absolute(Int4 val) =>
new(Mathf.Absolute(val.x), Mathf.Absolute(val.y), Mathf.Absolute(val.z), Mathf.Absolute(val.w)); new(Mathf.Absolute(val.x), Mathf.Absolute(val.y), Mathf.Absolute(val.z), Mathf.Absolute(val.w));
public static Int4 Average(params Int4[] vals) => Sum(vals) / vals.Length; public static Int4 Average(params Int4[] vals)
{
Int4 sum = Zero;
foreach (Int4 i in vals) sum += i;
return sum / vals.Length;
}
public static Int4 Clamp(Int4 val, Int4 min, Int4 max) => public static Int4 Clamp(Int4 val, Int4 min, Int4 max) =>
new(Mathf.Clamp(val.x, min.x, max.x), new(Mathf.Clamp(val.x, min.x, max.x),
Mathf.Clamp(val.y, min.y, max.y), Mathf.Clamp(val.y, min.y, max.y),
@ -207,7 +213,6 @@ public record struct Int4 : IAbsolute<Int4>, IAverage<Int4>, IClamp<Int4>, IClam
else if (mag > maxMag) val *= maxMag; else if (mag > maxMag) val *= maxMag;
return val; return val;
} }
public static Int4 Divide(Int4 num, params Int4[] vals) => num / Product(vals);
public static int Dot(Int4 a, Int4 b) => a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; public static int Dot(Int4 a, Int4 b) => a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
public static int Dot(params Int4[] vals) public static int Dot(params Int4[] vals)
{ {
@ -245,20 +250,6 @@ public record struct Int4 : IAbsolute<Int4>, IAverage<Int4>, IClamp<Int4>, IClam
foreach (Int4 d in vals) val = d.Magnitude < val.Magnitude ? d : val; foreach (Int4 d in vals) val = d.Magnitude < val.Magnitude ? d : val;
return val; return val;
} }
public static Int4 Product(params Int4[] vals)
{
if (vals.Length < 1) return Zero;
Int4 val = One;
foreach (Int4 d in vals) val *= d;
return val;
}
public static Int4 Subtract(Int4 num, params Int4[] vals) => num - Sum(vals);
public static Int4 Sum(params Int4[] vals)
{
Int4 val = Zero;
foreach (Int4 d in vals) val += d;
return val;
}
public static (int[] Xs, int[] Ys, int[] Zs, int[] Ws) SplitArray(params Int4[] vals) public static (int[] Xs, int[] Ys, int[] Zs, int[] Ws) SplitArray(params Int4[] vals)
{ {
@ -330,8 +321,6 @@ public record struct Int4 : IAbsolute<Int4>, IAverage<Int4>, IClamp<Int4>, IClam
public static implicit operator Int4(Int3 val) => new(val.x, val.y, val.z, 0); public static implicit operator Int4(Int3 val) => new(val.x, val.y, val.z, 0);
public static explicit operator Int4(Matrix m) => new((int)m[0, 0], (int)m[1, 0], (int)m[2, 0], (int)m[3, 0]); public static explicit operator Int4(Matrix m) => new((int)m[0, 0], (int)m[1, 0], (int)m[2, 0], (int)m[3, 0]);
public static explicit operator Int4(Vector2d val) => (Int4)val.ToXYZ(); public static explicit operator Int4(Vector2d val) => (Int4)val.ToXYZ();
public static explicit operator Int4(Vert val) => new((int)val.position.x, (int)val.position.y,
(int)val.position.z, 0);
public static explicit operator Int4(RGBA val) => val.ToRGBAByte(); public static explicit operator Int4(RGBA val) => val.ToRGBAByte();
public static explicit operator Int4(CMYKA val) => (Int4)val.ToCMYKAByte(); public static explicit operator Int4(CMYKA val) => (Int4)val.ToCMYKAByte();
public static explicit operator Int4(HSVA val) => val.ToHSVAByte(); public static explicit operator Int4(HSVA val) => val.ToHSVAByte();

View File

@ -1,16 +1,49 @@
namespace Nerd_STF.Mathematics; namespace Nerd_STF.Mathematics;
/// <summary>
/// A class that contains various mathematical methods using <see cref="float"/>s.
/// </summary>
public static class Mathf public static class Mathf
{ {
/// <summary>
/// Calculate the absolute value of a <see cref="float"/>.
/// </summary>
/// <param name="val">The <see cref="float"/> to calculate the absolute value of.</param>
/// <returns></returns>
/// <remarks>Runs in <c>O(1)</c> time.</remarks>
public static float Absolute(float val) => val < 0 ? -val : val; public static float Absolute(float val) => val < 0 ? -val : val;
/// <summary>
/// Calculate the absolute value of a <see cref="int"/>.
/// </summary>
/// <param name="val">The <see cref="int"/> to calculate the absolute value of.</param>
/// <returns></returns>
/// <remarks>Runs in <c>O(1)</c> time.</remarks>
public static int Absolute(int val) => val < 0 ? -val : val; public static int Absolute(int val) => val < 0 ? -val : val;
/// <summary>
/// Calculates the absolute modulus value of a number. In C# by default, getting
/// the modulus of a negative number returns a negative value, while in math
/// that number is usually positive. This method ensures the number is positive.
/// </summary>
/// <param name="val">The value to apply the modulus to.</param>
/// <param name="mod">The number to modulus by.</param>
/// <returns>The smallest positive number that can be added to <paramref name="val"/> to create a multiple of <paramref name="mod"/>.</returns>
/// <remarks>Runs in <c>O(n)</c> time.</remarks>
public static float AbsoluteMod(float val, float mod) public static float AbsoluteMod(float val, float mod)
{ {
while (val >= mod) val -= mod; while (val >= mod) val -= mod;
while (val < 0) val += mod; while (val < 0) val += mod;
return val; return val;
} }
/// <summary>
/// Calculates the absolute modulus value of a number. In C# by default, getting
/// the modulus of a negative number returns a negative value, while in math
/// that number is usually positive. This method ensures the number is positive.
/// </summary>
/// <param name="val">The value to apply the modulus to.</param>
/// <param name="mod">The number to modulus by.</param>
/// <returns>The smallest positive number that can be added to <paramref name="val"/> to create a multiple of <paramref name="mod"/>.</returns>
/// <remarks>Runs in <c>O(n)</c> time.</remarks>
public static int AbsoluteMod(int val, int mod) public static int AbsoluteMod(int val, int mod)
{ {
while (val >= mod) val -= mod; while (val >= mod) val -= mod;
@ -18,15 +51,76 @@ public static class Mathf
return val; return val;
} }
/// <summary>
/// Calculates the inverse cosine of the value <paramref name="value"/>, such that
/// <c>cos(arccos(<paramref name="value"/>)) = <paramref name="value"/></c>.
///
/// Since <c>cos(x)</c> only returns a value between -1 and 1, the domain of
/// <c>arccos(x)</c> is [-1, 1], and its range is [0, π].
/// </summary>
/// <param name="value">The value to calculate the inverse cosine of.</param>
/// <returns>The angle θ representing <c>cos(θ) = <paramref name="value"/></c>.</returns>
/// <remarks>Runs in <c>O(n)</c> time.</remarks>
public static Angle ArcCos(float value) => ArcSin(-value) + Angle.Quarter; public static Angle ArcCos(float value) => ArcSin(-value) + Angle.Quarter;
/// <summary>
/// Calculates the inverse cotangent of the value <paramref name="value"/>, such that
/// <c>cot(arccot(<paramref name="value"/>)) = <paramref name="value"/></c>.
///
/// Since <c>cot(x)</c> returns any real number value, the domain of <c>arccot(x)</c>
/// is (-∞, ∞), and its range is (0, π).
/// </summary>
/// <param name="value">The value to calculate the inverse cotangent of.</param>
/// <returns>The angle θ representing <c>cot(θ) = <paramref name="value"/></c>.</returns>
/// <remarks>Runs in <c>O(n)</c> time.</remarks>
public static Angle ArcCot(float value) => ArcCos(value / Sqrt(1 + value * value)); public static Angle ArcCot(float value) => ArcCos(value / Sqrt(1 + value * value));
/// <summary>
/// Calculates the invese cosecant of the value <paramref name="value"/>, such that
/// <c>csc(arccsc(<paramref name="value"/>)) = <paramref name="value"/></c>.
///
/// Since <c>csc(x)</c> returns any real number value such that <c>|csc(x)| >= 1</c>,
/// the domain of <c>arccsc(x)</c> is (-∞, 1] [1, ∞) and its range is [-π/2, π/2].
/// </summary>
/// <param name="value">The value to calculate the inverse cosecant of.</param>
/// <returns>The angle θ representing <c>csc(θ) = <paramref name="value"/></c>.</returns>
/// <remarks>Runs in <c>O(n)</c> time.</remarks>
public static Angle ArcCsc(float value) => ArcSin(1 / value); public static Angle ArcCsc(float value) => ArcSin(1 / value);
/// <summary>
/// Calculates the inverse secant of the value <paramref name="value"/>, such that
/// <c>sec(arcsec(<paramref name="value"/>)) = <paramref name="value"/></c>.
///
/// Since <c>sec(x)</c> returns any real number value such that <c>|sec(x)| >= 1</c>,
/// the domain of <c>arcsec(x)</c> is (-∞, 1] [1, ∞) and its range is [0, π].
/// </summary>
/// <param name="value">The value to calculate the inverse secant of.</param>
/// <returns>The angle θ representing <c>sec(θ) = <paramref name="value"/></c>.</returns>
/// <remarks>Runs in <c>O(n)</c> time.</remarks>
public static Angle ArcSec(float value) => ArcCos(1 / value); public static Angle ArcSec(float value) => ArcCos(1 / value);
/// <summary>
/// Calculates the inverse sine of the value <paramref name="value"/>, such that
/// <c>sin(arcsin(<paramref name="value"/>)) = <paramref name="value"/></c>.
///
/// Since <c>sin(x)</c> returns a value between [-1, 1], the domain of <c>arcsin(x)</c>
/// is [-1, 1] and its range is [-π/2, π/2].
/// </summary>
/// <param name="value">The value to calculate the inverse sine of.</param>
/// <returns>The angle θ representing <c>sin(θ) = <paramref name="value"/></c>.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the input is out of the domain of [-1, 1].</exception>
/// <remarks>Runs in <c>O(n)</c> time.</remarks>
public static Angle ArcSin(float value) public static Angle ArcSin(float value)
{ {
if (value > 1 || value < -1) throw new ArgumentOutOfRangeException(nameof(value)); if (value > 1 || value < -1) throw new ArgumentOutOfRangeException(nameof(value));
return (SolveNewton(x => Sin(x) - value, 0), Angle.Type.Degrees); return (SolveNewton(x => Sin(x) - value, 0), Angle.Type.Radians);
} }
/// <summary>
/// Calculates the inverse tangent of the value <paramref name="value"/>, such that
/// <c>tan(arctan(<paramref name="value"/>)) = <paramref name="value"/></c>.
///
/// Since <c>tan(x)</c> returns any real number value, the domain of <c>arctan(x)</c>
/// is (-∞, ∞), and its range is (-π/2, π/2).
/// </summary>
/// <param name="value">The value to calculate the inverse cotangent of.</param>
/// <returns>The angle θ representing <c>tan(θ) = <paramref name="value"/></c>.</returns>
/// <remarks>Runs in <c>O(n)</c> time.</remarks>
public static Angle ArcTan(float value) => ArcSin(value / Sqrt(1 + value * value)); public static Angle ArcTan(float value) => ArcSin(value / Sqrt(1 + value * value));
public static Angle ArcTan2(float a, float b) => ArcTan(a / b); public static Angle ArcTan2(float a, float b) => ArcTan(a / b);
@ -47,12 +141,42 @@ public static class Mathf
for (float x = min; x <= max; x += step) vals.Add(equ(x)); for (float x = min; x <= max; x += step) vals.Add(equ(x));
return Average(vals.ToArray()); return Average(vals.ToArray());
} }
public static float Average(params float[] vals) => Sum(vals) / vals.Length; /// <summary>
public static int Average(params int[] vals) => Sum(vals) / vals.Length; /// Calculates the average of an array of <see cref="float"/>s.
/// </summary>
/// <param name="vals">The array to calculate the average of.</param>
/// <returns>The average of the array.</returns>
/// <remarks>Runs in O(n) time.</remarks>
public static float Average(params float[] vals)
{
float sum = 0;
foreach (float f in vals) sum += f;
return sum / vals.Length;
}
/// <summary>
/// Calculates the average of an array of <see cref="int"/>s.
/// Because the return type is also an <see cref="int"/>, the average
/// will be rounded down to the nearest whole number.
/// </summary>
/// <param name="vals">The array to calculate the average of.</param>
/// <returns>The average of the array, rounded down to the nearest <see cref="int"/>.</returns>
/// <remarks>Runs in <c>O(n)</c> time.</remarks>
public static int Average(params int[] vals)
{
int sum = 0;
foreach (int i in vals) sum += i;
return sum / vals.Length;
}
public static float Binomial(int n, int total, float successRate) => public static float Binomial(int n, int total, float successRate) =>
Combinations(total, n) * Power(successRate, n) * Power(1 - successRate, total - n); Combinations(total, n) * Power(successRate, n) * Power(1 - successRate, total - n);
/// <summary>
/// Calculates the cube root of a number.
/// </summary>
/// <param name="value">The number to calculate the cube root of.</param>
/// <returns>The cube root of <paramref name="value"/>.</returns>
/// <remarks>Runs in O(n) time.</remarks>
public static float Cbrt(float value) => SolveNewton(x => x * x * x - value, 1); public static float Cbrt(float value) => SolveNewton(x => x * x * x - value, 1);
public static int Ceiling(float val) public static int Ceiling(float val)
@ -95,26 +219,23 @@ public static class Mathf
public static float Csch(float value) => 1 / Sinh(value); public static float Csch(float value) => 1 / Sinh(value);
public static float Divide(float val, params float[] dividends) => val / Product(dividends);
public static int Divide(int val, params int[] dividends) => val / Product(dividends);
public static float Dot(float[] a, float[] b) public static float Dot(float[] a, float[] b)
{ {
if (a.Length != b.Length) throw new InvalidSizeException("Both arrays must have the same length"); if (a.Length != b.Length) throw new InvalidSizeException("Both arrays must have the same length");
float[] vals = new float[a.Length]; float sum = 0;
for (int i = 0; i < a.Length; i++) vals[i] = a[i] * b[i]; for (int i = 0; i < a.Length; i++) sum += a[i] * b[i];
return Sum(vals); return sum;
} }
public static float Dot(params float[][] vals) public static float Dot(params float[][] vals)
{ {
float[] res = new float[vals[0].Length]; float sum = 0;
for (int i = 0; i < res.Length; i++) for (int i = 0; i < vals[0].Length; i++)
{ {
float m = 1; float m = 1;
for (int j = 0; j < vals.Length; j++) m *= vals[j][i]; for (int j = 0; j < vals.Length; j++) m *= vals[j][i];
res[i] = m; sum += m;
} }
return Sum(res); return sum;
} }
public static int Factorial(int amount) public static int Factorial(int amount)
@ -155,7 +276,7 @@ public static class Mathf
return -1; return -1;
} }
public static float InverseSqrt(float val) => 1 / Sqrt(val); public static float InverseSqrt(float val) => UnsafeHelper.Q_rsqrt(val);
public static bool IsPrime(int num, PrimeCheckMethod method = PrimeCheckMethod.Classic) => public static bool IsPrime(int num, PrimeCheckMethod method = PrimeCheckMethod.Classic) =>
method switch method switch
@ -165,7 +286,12 @@ public static class Mathf
_ => throw new ArgumentException("Unknown prime check method.", nameof(method)) _ => throw new ArgumentException("Unknown prime check method.", nameof(method))
}; };
public static int LeastCommonMultiple(params int[] vals) => Product(vals) / GreatestCommonFactor(vals); public static int LeastCommonMultiple(params int[] vals)
{
int product = 1;
foreach (int i in vals) product *= i;
return product / GreatestCommonFactor(vals);
}
public static float Lerp(float a, float b, float t, bool clamp = true) public static float Lerp(float a, float b, float t, bool clamp = true)
{ {
@ -188,6 +314,15 @@ public static class Mathf
else return CordicHelper.LogAnyBase(@base, val, 16, 16); else return CordicHelper.LogAnyBase(@base, val, 16, 16);
} }
/// <summary>
/// Creates an <see cref="Equation"/> out of a <see cref="Dictionary{TKey, TValue}"/>
/// of XY points. This equation has a domain of all real numbers and linearly
/// interpolates between points.
/// </summary>
/// <param name="vals">The <see cref="Dictionary{TKey, TValue}"/> of XY points to include in the <see cref="Equation"/>.</param>
/// <returns>An equation that interpolates between each point in <paramref name="vals"/>.</returns>
/// <exception cref="UndefinedException">Thrown when no points are contained in <paramref name="vals"/> or when two points in the dictionary cannot be joined together.</exception>
/// <remarks>Generates the <see cref="Equation"/> in <c>O(1)</c> time, however invoking that equation returns a result in <c>O(n)</c> time where <c>n</c> is the amount of points in <paramref name="vals"/>.</remarks>
public static Equation MakeEquation(Dictionary<float, float> vals) => delegate (float x) public static Equation MakeEquation(Dictionary<float, float> vals) => delegate (float x)
{ {
if (vals.Count < 1) throw new UndefinedException(); if (vals.Count < 1) throw new UndefinedException();
@ -323,6 +458,15 @@ public static class Mathf
// nPr (n = total, r = size) // nPr (n = total, r = size)
public static int Permutations(int total, int size) => Factorial(total) / Factorial(total - size); public static int Permutations(int total, int size) => Factorial(total) / Factorial(total - size);
/// <summary>
/// Returns an <see cref="Array"/> that contains all prime factors of the number
/// <paramref name="num"/>. A prime factor is defined as a prime number that
/// <paramref name="num"/> is divisible by at least once. Any counting number can
/// be represented as a unique collection of prime factors. Duplicate factors are
/// included.
/// </summary>
/// <param name="num">The number to calculate the prime factors of.</param>
/// <returns>An <see cref="Array"/> of prime factors.</returns>
public static int[] PrimeFactors(int num) public static int[] PrimeFactors(int num)
{ {
List<int> factors = new(); List<int> factors = new();
@ -337,27 +481,6 @@ public static class Mathf
return factors.ToArray(); return factors.ToArray();
} }
public static float Product(params float[] vals)
{
if (vals.Length < 1) return 0;
float val = 1;
foreach (float d in vals) val *= d;
return val;
}
public static int Product(params int[] vals)
{
if (vals.Length < 1) return 0;
int val = 1;
foreach (int i in vals) val *= i;
return val;
}
public static float Product(Equation equ, float min, float max, float step = 1)
{
float total = 1;
for (float f = min; f <= max; f += step) total *= equ(f);
return total;
}
public static float Power(float num, float pow) public static float Power(float num, float pow)
{ {
if (pow == 0) return 1; if (pow == 0) return 1;
@ -400,6 +523,13 @@ public static class Mathf
return val; return val;
} }
public static float Product(Equation equ, float lower, float upper, float step = 1)
{
float result = 0;
for (float f = lower; f < upper; f += step) result *= equ(f);
return result;
}
public static float Root(float value, float index) => (float)Math.Exp(Math.Log(value) / index); public static float Root(float value, float index) => (float)Math.Exp(Math.Log(value) / index);
public static float Round(float num) => num % 1 >= 0.5 ? Ceiling(num) : Floor(num); public static float Round(float num) => num % 1 >= 0.5 ? Ceiling(num) : Floor(num);
@ -450,7 +580,7 @@ public static class Mathf
else return CordicHelper.CalculateHyperTrig(value, 16).sinh; else return CordicHelper.CalculateHyperTrig(value, 16).sinh;
} }
public static float SolveBisection(Equation equ, float initialA, float initialB, float tolerance = 1e-5f, public static float SolveBisection(Equation equ, float initialA, float initialB, float tolerance = 1e-3f,
int maxIterations = 1000) int maxIterations = 1000)
{ {
if (equ(initialA) == 0) return initialA; if (equ(initialA) == 0) return initialA;
@ -486,10 +616,10 @@ public static class Mathf
return guessMid; return guessMid;
} }
public static float SolveEquation(Equation equ, float initial, float tolerance = 1e-5f, public static float SolveEquation(Equation equ, float initial, float tolerance = 1e-3f,
float step = Calculus.DefaultStep, int maxIterations = 1000) => float step = Calculus.DefaultStep, int maxIterations = 1000) =>
SolveNewton(equ, initial, tolerance, step, maxIterations); SolveNewton(equ, initial, tolerance, step, maxIterations);
public static float SolveNewton(Equation equ, float initial, float tolerance = 1e-5f, public static float SolveNewton(Equation equ, float initial, float tolerance = 1e-3f,
float step = Calculus.DefaultStep, int maxIterations = 1000) float step = Calculus.DefaultStep, int maxIterations = 1000)
{ {
if (equ(initial) == 0) return initial; if (equ(initial) == 0) return initial;
@ -513,33 +643,18 @@ public static class Mathf
return result; return result;
} }
public static float Sqrt(float value) => SolveNewton(x => x * x - value, 1); public static float Sqrt(float value, float tolerance = 1e-3f) => SolveNewton(x => x * x - value, value / 2, tolerance);
public static float Subtract(float num, params float[] vals) => num - Sum(vals);
public static int Subtract(int num, params int[] vals) => num - Sum(vals);
public static float Sum(params float[] vals)
{
float val = 0;
foreach (float d in vals) val += d;
return val;
}
public static int Sum(params int[] vals)
{
int val = 0;
foreach (int i in vals) val += i;
return val;
}
public static float Sum(Equation equ, float min, float max, float step = 1)
{
float total = 0;
for (float f = min; f <= max; f += step) total += equ(f);
return total;
}
// Known as stdev // Known as stdev
public static float StandardDeviation(params float[] vals) => Sqrt(Variance(vals)); public static float StandardDeviation(params float[] vals) => Sqrt(Variance(vals));
public static float Sum(Equation equ, float lower, float upper, float step = 1)
{
float result = 0;
for (float f = lower; f < upper; f += step) result += equ(f);
return result;
}
public static float Tan(Angle angle) => Tan(angle.Radians); public static float Tan(Angle angle) => Tan(angle.Radians);
public static float Tan(float radians) => Sin(radians) / Cos(radians); public static float Tan(float radians) => Sin(radians) / Cos(radians);

View File

@ -3,10 +3,10 @@
namespace Nerd_STF.Mathematics.NumberSystems; namespace Nerd_STF.Mathematics.NumberSystems;
public record struct Complex(float u, float i) : IAbsolute<Complex>, IAverage<Complex>, ICeiling<Complex>, public record struct Complex(float u, float i) : IAbsolute<Complex>, IAverage<Complex>, ICeiling<Complex>,
IClampMagnitude<Complex, float>, IComparable<Complex>, IDivide<Complex>, IDot<Complex, float>, IClampMagnitude<Complex, float>, IComparable<Complex>, IDot<Complex, float>,
IEquatable<Complex>, IFloor<Complex>, IGroup<float>, IIndexAll<float>, IIndexRangeAll<float>, IEquatable<Complex>, IFloor<Complex>, IGroup<float>, IIndexAll<float>, IIndexRangeAll<float>,
ILerp<Complex, float>, IMax<Complex>, IMedian<Complex>, IMin<Complex>, IPresets2d<Complex>, IProduct<Complex>, ILerp<Complex, float>, IMax<Complex>, IMedian<Complex>, IMin<Complex>, IPresets2d<Complex>,
IRound<Complex>, ISplittable<Complex, (float[] Us, float[] Is)>, ISum<Complex> IRound<Complex>, ISplittable<Complex, (float[] Us, float[] Is)>
{ {
public static Complex Down => new(0, -1); public static Complex Down => new(0, -1);
public static Complex Left => new(-1, 0); public static Complex Left => new(-1, 0);
@ -86,12 +86,6 @@ public record struct Complex(float u, float i) : IAbsolute<Complex>, IAverage<Co
public static Complex Clamp(Complex val, Complex min, Complex max) => Float2.Clamp(val, min, max); public static Complex Clamp(Complex val, Complex min, Complex max) => Float2.Clamp(val, min, max);
public static Complex ClampMagnitude(Complex val, float minMag, float maxMag) => public static Complex ClampMagnitude(Complex val, float minMag, float maxMag) =>
Float2.ClampMagnitude(val, minMag, maxMag); Float2.ClampMagnitude(val, minMag, maxMag);
public static Complex Divide(Complex num, params Complex[] vals)
{
List<Float2> floats = new();
foreach (Complex c in vals) floats.Add(c);
return Float2.Divide(num, floats.ToArray());
}
public static float Dot(Complex a, Complex b) => Float2.Dot(a, b); public static float Dot(Complex a, Complex b) => Float2.Dot(a, b);
public static float Dot(params Complex[] vals) public static float Dot(params Complex[] vals)
{ {
@ -119,25 +113,7 @@ public record struct Complex(float u, float i) : IAbsolute<Complex>, IAverage<Co
foreach (Complex c in vals) floats.Add(c); foreach (Complex c in vals) floats.Add(c);
return Float2.Min(floats.ToArray()); return Float2.Min(floats.ToArray());
} }
public static Complex Product(params Complex[] vals)
{
List<Float2> floats = new();
foreach (Complex c in vals) floats.Add(c);
return Float2.Product(floats.ToArray());
}
public static Complex Round(Complex val) => Float2.Round(val); public static Complex Round(Complex val) => Float2.Round(val);
public static Complex Subtract(Complex num, params Complex[] vals)
{
List<Float2> floats = new();
foreach (Complex c in vals) floats.Add(c);
return Float2.Subtract(num, floats.ToArray());
}
public static Complex Sum(params Complex[] vals)
{
List<Float2> floats = new();
foreach (Complex c in vals) floats.Add(c);
return Float2.Sum(floats.ToArray());
}
public static (float[] Us, float[] Is) SplitArray(params Complex[] vals) public static (float[] Us, float[] Is) SplitArray(params Complex[] vals)
{ {
@ -202,7 +178,6 @@ public record struct Complex(float u, float i) : IAbsolute<Complex>, IAverage<Co
public static explicit operator Complex(Int4 val) => new(val.x, val.y); public static explicit operator Complex(Int4 val) => new(val.x, val.y);
public static explicit operator Complex(Matrix m) => new(m[0, 0], m[1, 0]); public static explicit operator Complex(Matrix m) => new(m[0, 0], m[1, 0]);
public static explicit operator Complex(Vector2d val) => val.ToXYZ(); public static explicit operator Complex(Vector2d val) => val.ToXYZ();
public static explicit operator Complex(Vert val) => new(val.position.x, val.position.y);
public static implicit operator Complex(Fill<float> fill) => new(fill); public static implicit operator Complex(Fill<float> fill) => new(fill);
public static implicit operator Complex(Fill<int> fill) => new(fill); public static implicit operator Complex(Fill<int> fill) => new(fill);
public static implicit operator Complex((float u, float i) val) => new(val.u, val.i); public static implicit operator Complex((float u, float i) val) => new(val.u, val.i);

View File

@ -4,10 +4,10 @@ namespace Nerd_STF.Mathematics.NumberSystems;
public record struct Quaternion(float u, float i, float j, float k) : IAbsolute<Quaternion>, IAverage<Quaternion>, public record struct Quaternion(float u, float i, float j, float k) : IAbsolute<Quaternion>, IAverage<Quaternion>,
ICeiling<Quaternion>, IClamp<Quaternion>, IClampMagnitude<Quaternion, float>, IComparable<Quaternion>, ICeiling<Quaternion>, IClamp<Quaternion>, IClampMagnitude<Quaternion, float>, IComparable<Quaternion>,
IDivide<Quaternion>, IDot<Quaternion, float>, IEquatable<Quaternion>, IFloor<Quaternion>, IGroup<float>, IDot<Quaternion, float>, IEquatable<Quaternion>, IFloor<Quaternion>, IGroup<float>,
IIndexAll<float>, IIndexRangeAll<float>, ILerp<Quaternion, float>, IMax<Quaternion>, IMedian<Quaternion>, IIndexAll<float>, IIndexRangeAll<float>, ILerp<Quaternion, float>, IMax<Quaternion>, IMedian<Quaternion>,
IMin<Quaternion>, IPresets4d<Quaternion>, IProduct<Quaternion>, IRound<Quaternion>, IMin<Quaternion>, IPresets4d<Quaternion>, IRound<Quaternion>,
ISplittable<Quaternion, (float[] Us, float[] Is, float[] Js, float[] Ks)>, ISum<Quaternion> ISplittable<Quaternion, (float[] Us, float[] Is, float[] Js, float[] Ks)>
{ {
public static Quaternion Back => new(0, 0, -1, 0); public static Quaternion Back => new(0, 0, -1, 0);
public static Quaternion Down => new(0, -1, 0, 0); public static Quaternion Down => new(0, -1, 0, 0);
@ -117,12 +117,6 @@ public record struct Quaternion(float u, float i, float j, float k) : IAbsolute<
public static Quaternion Clamp(Quaternion val, Quaternion min, Quaternion max) => Float4.Clamp(val, min, max); public static Quaternion Clamp(Quaternion val, Quaternion min, Quaternion max) => Float4.Clamp(val, min, max);
public static Quaternion ClampMagnitude(Quaternion val, float minMag, float maxMag) => public static Quaternion ClampMagnitude(Quaternion val, float minMag, float maxMag) =>
Float4.ClampMagnitude(val, minMag, maxMag); Float4.ClampMagnitude(val, minMag, maxMag);
public static Quaternion Divide(Quaternion num, params Quaternion[] vals)
{
List<Float4> floats = new();
foreach (Quaternion q in vals) floats.Add(q);
return Float4.Divide(num, floats.ToArray());
}
public static float Dot(Quaternion a, Quaternion b) => a.u * b.u + a.i * b.i + a.j * b.j + a.k * b.k; public static float Dot(Quaternion a, Quaternion b) => a.u * b.u + a.i * b.i + a.j * b.j + a.k * b.k;
public static float Dot(params Quaternion[] vals) public static float Dot(params Quaternion[] vals)
{ {
@ -150,25 +144,7 @@ public record struct Quaternion(float u, float i, float j, float k) : IAbsolute<
foreach (Quaternion q in vals) floats.Add(q); foreach (Quaternion q in vals) floats.Add(q);
return Float4.Min(floats.ToArray()); return Float4.Min(floats.ToArray());
} }
public static Quaternion Product(params Quaternion[] vals)
{
List<Float4> floats = new();
foreach (Quaternion q in vals) floats.Add(q);
return Float4.Product(floats.ToArray());
}
public static Quaternion Round(Quaternion val) => Float4.Round(val); public static Quaternion Round(Quaternion val) => Float4.Round(val);
public static Quaternion Subtract(Quaternion num, params Quaternion[] vals)
{
List<Float4> floats = new();
foreach (Quaternion q in vals) floats.Add(q);
return Float4.Subtract(num, floats.ToArray());
}
public static Quaternion Sum(params Quaternion[] vals)
{
List<Float4> floats = new();
foreach (Quaternion q in vals) floats.Add(q);
return Float4.Sum(floats.ToArray());
}
public static Quaternion FromAngles(Angle yaw, Angle pitch, Angle? roll = null) public static Quaternion FromAngles(Angle yaw, Angle pitch, Angle? roll = null)
{ {
@ -329,7 +305,6 @@ public record struct Quaternion(float u, float i, float j, float k) : IAbsolute<
public static implicit operator Quaternion(Float4 val) => new(val.x, val.y, val.z, val.w); public static implicit operator Quaternion(Float4 val) => new(val.x, val.y, val.z, val.w);
public static explicit operator Quaternion(Matrix m) => new(m[0, 0], m[1, 0], m[2, 0], m[3, 0]); public static explicit operator Quaternion(Matrix m) => new(m[0, 0], m[1, 0], m[2, 0], m[3, 0]);
public static explicit operator Quaternion(Vector2d val) => (Quaternion)val.ToXYZ(); public static explicit operator Quaternion(Vector2d val) => (Quaternion)val.ToXYZ();
public static implicit operator Quaternion(Vert val) => new(val);
public static implicit operator Quaternion(Fill<float> fill) => new(fill); public static implicit operator Quaternion(Fill<float> fill) => new(fill);
public static implicit operator Quaternion(Fill<int> fill) => new(fill); public static implicit operator Quaternion(Fill<int> fill) => new(fill);
public static implicit operator Quaternion((float u, float i, float j, float k) val) => public static implicit operator Quaternion((float u, float i, float j, float k) val) =>

View File

@ -1,11 +1,10 @@
namespace Nerd_STF.Mathematics; namespace Nerd_STF.Mathematics;
public readonly record struct Rational : IAbsolute<Rational>, IAverage<Rational>, ICeiling<Rational, int>, IClamp<Rational>, public readonly record struct Rational : IAbsolute<Rational>, IAverage<Rational>, ICeiling<Rational, int>, IClamp<Rational>,
IComparable<Rational>, IComparable<float>, IDivide<Rational>, IEquatable<Rational>, IEquatable<float>, IComparable<Rational>, IComparable<float>, IEquatable<Rational>, IEquatable<float>,
IFloor<Rational, int>, IIndexGet<int>, IIndexRangeGet<int>, ILerp<Rational, float>, IMathOperators<Rational>, IFloor<Rational, int>, IIndexGet<int>, IIndexRangeGet<int>, ILerp<Rational, float>, IMathOperators<Rational>,
IMax<Rational>, IMedian<Rational>, IMin<Rational>, IPresets1d<Rational>, IProduct<Rational>, IMax<Rational>, IMedian<Rational>, IMin<Rational>, IPresets1d<Rational>,
IRound<Rational, int>, ISplittable<Rational, (int[] nums, int[] dens)>, ISubtract<Rational>, IRound<Rational, int>, ISplittable<Rational, (int[] nums, int[] dens)>
ISum<Rational>
{ {
public static Rational One => new(1, 1); public static Rational One => new(1, 1);
public static Rational Zero => new(0, 1); public static Rational Zero => new(0, 1);
@ -80,7 +79,24 @@ public readonly record struct Rational : IAbsolute<Rational>, IAverage<Rational>
public static Rational Absolute(Rational value) => public static Rational Absolute(Rational value) =>
new(Mathf.Absolute(value.numerator), value.denominator); new(Mathf.Absolute(value.numerator), value.denominator);
public static Rational Average(params Rational[] vals) => Sum(vals) / (float)vals.Length; public static Rational Average(params Rational[] vals)
{
// TODO: this doesn't work.
int[] denominators = new int[vals.Length];
for (int i = 0; i < vals.Length; i++) denominators[i] = vals[i].denominator;
int lcm = Mathf.LeastCommonMultiple(denominators);
int sumNum = 0, sumDen = lcm * vals.Length;
foreach (Rational r in vals)
{
int scale = lcm / r.denominator;
sumNum += r.numerator * scale;
}
return new(sumNum / vals.Length, sumDen);
}
public static int Ceiling(Rational r) public static int Ceiling(Rational r)
{ {
int mod = r.numerator % r.denominator; int mod = r.numerator % r.denominator;
@ -90,26 +106,10 @@ public readonly record struct Rational : IAbsolute<Rational>, IAverage<Rational>
} }
public static Rational Clamp(Rational val, Rational min, Rational max) public static Rational Clamp(Rational val, Rational min, Rational max)
=> FromFloat(Mathf.Clamp(val.GetValue(), min.GetValue(), max.GetValue())); => FromFloat(Mathf.Clamp(val.GetValue(), min.GetValue(), max.GetValue()));
public static Rational Divide(Rational val, params Rational[] vals) =>
val / Product(vals);
public static int Floor(Rational val) => val.numerator / val.denominator; public static int Floor(Rational val) => val.numerator / val.denominator;
public static Rational Lerp(Rational a, Rational b, float t, bool clamp = true) => public static Rational Lerp(Rational a, Rational b, float t, bool clamp = true) =>
FromFloat(Mathf.Lerp(a.GetValue(), b.GetValue(), t, clamp)); FromFloat(Mathf.Lerp(a.GetValue(), b.GetValue(), t, clamp));
public static Rational Product(params Rational[] vals)
{
Rational res = One;
foreach (Rational r in vals) res *= r;
return res;
}
public static int Round(Rational r) => (int)Mathf.Round(r.numerator, r.denominator) / r.denominator; public static int Round(Rational r) => (int)Mathf.Round(r.numerator, r.denominator) / r.denominator;
public static Rational Subtract(Rational val, params Rational[] vals) =>
val - Sum(vals);
public static Rational Sum(params Rational[] vals)
{
Rational sum = Zero;
foreach (Rational r in vals) sum += r;
return sum;
}
public static (int[] nums, int[] dens) SplitArray(params Rational[] vals) public static (int[] nums, int[] dens) SplitArray(params Rational[] vals)
{ {

View File

@ -1,25 +1,101 @@
namespace Nerd_STF.Mathematics.Samples; namespace Nerd_STF.Mathematics.Samples;
/// <summary>
/// A container of various mathematical constants.
/// </summary>
public static class Constants public static class Constants
{ {
/// <summary>
/// The ratio between a degree and a radian. This constant is intended to be used
/// as follows:
/// <code>degrees = radians * <see cref="DegToRad"/></code>
/// This is the reciprocal of <see cref="RadToDeg"/>.
/// </summary>
public const float DegToRad = Pi / 180; public const float DegToRad = Pi / 180;
/// <summary>
/// Exactly one half of the constant <see cref="Pi"/>. While this constant has many
/// uses, most of them are just a shorthand for <c><see cref="Pi"/> / 2</c>, unlike
/// <see cref="Tau"/>.
/// </summary>
/// <remarks>See also: <see href="https://en.wikipedia.org/wiki/Pi">Pi - Wikipedia</see></remarks>
public const float HalfPi = Pi / 2; public const float HalfPi = Pi / 2;
/// <summary>
/// The ratio between a circle's circumference and its diameter. This constant has
/// many uses.
/// </summary>
/// <remarks>See also: <see href="https://en.wikipedia.org/wiki/Pi">Pi - Wikipedia</see></remarks>
public const float Pi = 3.14159265359f; public const float Pi = 3.14159265359f;
/// <summary>
/// The ratio between a radian and a degree. This constant is intended to be used
/// as follows:
/// <code>radians = degrees * <see cref="RadToDeg"/></code>
/// This is the reciprocal of <see cref="DegToRad"/>.
/// </summary>
public const float RadToDeg = 180 / Pi; public const float RadToDeg = 180 / Pi;
/// <summary>
/// Exactly double the value of the constant <see cref="Pi"/>. Unlike
/// <see cref="HalfPi"/>, there are circumstances where this constant is preferrable
/// over its actual value of <c><see cref="Pi"/> * 2</c>.
/// </summary>
/// <remarks>See also: <see href="https://en.wikipedia.org/wiki/Pi">Pi - Wikipedia</see></remarks>
public const float Tau = Pi * 2; public const float Tau = Pi * 2;
/// <summary>
/// <c>E</c> is also known as Euler's number or the natural number. This constant
/// has a very large number of interpretations and uses.
/// </summary>
/// <remarks>See also: <see href="https://en.wikipedia.org/wiki/E_(mathematical_constant)">e (mathematical constant) - Wikipedia</see></remarks>
public const float E = 2.71828182846f; public const float E = 2.71828182846f;
/// <summary>
/// The limit of the summed error between the harmonic series and the natural logarithm.
/// </summary>
/// <remarks>See also: <see href="https://en.wikipedia.org/wiki/Euler%27s_constant">Euler's constant - Wikipedia</see></remarks>
public const float EulerConstant = 0.5772156649f; public const float EulerConstant = 0.5772156649f;
/// <summary>
/// The natural logarithm of 2. It is a trancendental number.
/// </summary>
/// <remarks>
/// See also: <see href="https://en.wikipedia.org/wiki/Natural_logarithm_of_2">Natural logarithm of 2 - Wikipedia</see><br/>
/// See also: <see href="https://en.wikipedia.org/wiki/Natural_logarithm">Natural logarithm - Wikipedia</see>
/// </remarks>
public const float Ln2 = 0.69314718056f; public const float Ln2 = 0.69314718056f;
/// <summary>
/// The natural logarithm of 3. It is a trancendental number.
/// </summary>
/// <remarks>See also: <see href="https://en.wikipedia.org/wiki/Natural_logarithm">Natural logarithm - Wikipedia</see></remarks>
public const float Ln3 = 1.09861228867f; public const float Ln3 = 1.09861228867f;
/// <summary>
/// The natural logarithm of 5. It is a trancendental number.
/// </summary>
/// <remarks>See also: <see href="https://en.wikipedia.org/wiki/Natural_logarithm">Natural logarithm - Wikipedia</see></remarks>
public const float Ln5 = 1.60943791243f; public const float Ln5 = 1.60943791243f;
/// <summary>
/// The natural logarithm of 10. It is a trancendental number.
/// </summary>
/// <remarks>See also: <see href="https://en.wikipedia.org/wiki/Natural_logarithm">Natural logarithm - Wikipedia</see></remarks>
public const float Ln10 = 2.30258509299f; public const float Ln10 = 2.30258509299f;
/// <summary>
/// The logarithm (base 10) of 2.
/// </summary>
public const float Log2 = 0.301029995664f; public const float Log2 = 0.301029995664f;
/// <summary>
/// The logarithm (base 10) of 3.
/// </summary>
public const float Log3 = 0.47712125472f; public const float Log3 = 0.47712125472f;
/// <summary>
/// The logarithm (base 10) of 5.
/// </summary>
public const float Log5 = 0.698970004336f; public const float Log5 = 0.698970004336f;
/// <summary>
/// The logarithm (base 10) of 10.
/// </summary>
public const float Log10 = 1; public const float Log10 = 1;
/// <summary>
/// The resulting smallest angle when you apply the golden ratio to a circle (in degrees).
/// </summary>
/// <remarks>See also: <see href="https://en.wikipedia.org/wiki/Golden_angle">Golden angle - Wikipedia</see></remarks>
public const float GoldenAngle = 180 * (3 - Sqrt5); public const float GoldenAngle = 180 * (3 - Sqrt5);
public const float GoldenRatio = (1 + Sqrt5) / 2; public const float GoldenRatio = (1 + Sqrt5) / 2;
public const float SilverRatio = Sqrt2 + 1; public const float SilverRatio = Sqrt2 + 1;
@ -29,6 +105,7 @@ public static class Constants
public const float Cbrt3 = 1.44224957031f; public const float Cbrt3 = 1.44224957031f;
public const float Cbrt5 = 1.70997594668f; public const float Cbrt5 = 1.70997594668f;
public const float Cbrt10 = 2.15443469003f; public const float Cbrt10 = 2.15443469003f;
public const float HalfSqrt2 = 0.707106781187f;
public const float Sqrt2 = 1.4142135624f; public const float Sqrt2 = 1.4142135624f;
public const float Sqrt3 = 1.7320508076f; public const float Sqrt3 = 1.7320508076f;
public const float Sqrt5 = 2.2360679775f; public const float Sqrt5 = 2.2360679775f;
@ -67,6 +144,10 @@ public static class Constants
public const float Tan60Deg = Sqrt3; public const float Tan60Deg = Sqrt3;
public const float Tan90Deg = float.PositiveInfinity; public const float Tan90Deg = float.PositiveInfinity;
public static readonly Angle IsometricAngle = (35.2643896828f, Angle.Type.Degrees);
public const float IsometricCos = 0.816496580928f;
public const float IsometricSin = 0.57737026919f;
public const float AperyConstant = 1.2020569031f; public const float AperyConstant = 1.2020569031f;
public const float ArtinConstant = 0.3739558136f; public const float ArtinConstant = 0.3739558136f;
public const float AsymptoticLebesgueConstant = 0.9894312738f; public const float AsymptoticLebesgueConstant = 0.9894312738f;

View File

@ -38,21 +38,32 @@ public class NDArray<T> : IEnumerable<T>, IEquatable<NDArray<T>>
sizes = new int[dimensions]; sizes = new int[dimensions];
Array.Fill(sizes, allLengths); Array.Fill(sizes, allLengths);
arr = new T[Mathf.Product(sizes)]; long allSizes = 1;
foreach (int i in sizes) allSizes *= i;
arr = new T[allSizes];
} }
public NDArray(int[] lengths) public NDArray(int[] lengths)
{ {
arr = new T[Mathf.Product(lengths)];
dimensions = lengths.Length; dimensions = lengths.Length;
sizes = lengths; sizes = lengths;
long allSizes = 1;
foreach (int i in sizes) allSizes *= i;
arr = new T[allSizes];
} }
public NDArray(int dimensions, int[] lengths) public NDArray(int dimensions, int[] lengths)
{ {
if (dimensions != lengths.Length) throw new InvalidSizeException("Dimension count doesn't match length count."); if (dimensions != lengths.Length) throw new InvalidSizeException("Dimension count doesn't match length count.");
arr = new T[Mathf.Product(lengths)];
this.dimensions = lengths.Length; this.dimensions = lengths.Length;
sizes = lengths; sizes = lengths;
long allSizes = 1;
foreach (int i in sizes) allSizes *= i;
arr = new T[allSizes];
} }
public NDArray(T[] items, int[] lengths) public NDArray(T[] items, int[] lengths)
{ {
@ -60,17 +71,30 @@ public class NDArray<T> : IEnumerable<T>, IEquatable<NDArray<T>>
dimensions = lengths.Length; dimensions = lengths.Length;
sizes = lengths; sizes = lengths;
if (arr.Length != Mathf.Product(lengths)) throw new InvalidSizeException("Too many or too few items were provided."); long allSizes = 1;
foreach (int i in sizes) allSizes *= i;
arr = new T[allSizes];
if (arr.Length != allSizes)
throw new InvalidSizeException("Too many or too few items were provided.");
} }
public NDArray(T[] items, int dimensions, int[] lengths) public NDArray(T[] items, int dimensions, int[] lengths)
{ {
if (dimensions != lengths.Length) throw new InvalidSizeException("Dimension count doesn't match length count."); if (dimensions != lengths.Length)
throw new InvalidSizeException("Dimension count doesn't match length count.");
arr = items; arr = items;
this.dimensions = lengths.Length; this.dimensions = lengths.Length;
sizes = lengths; sizes = lengths;
if (arr.Length != Mathf.Product(lengths)) throw new InvalidSizeException("Too many or too few items were provided."); long allSizes = 1;
foreach (int i in sizes) allSizes *= i;
arr = new T[allSizes];
if (arr.Length != allSizes)
throw new InvalidSizeException("Too many or too few items were provided.");
} }
public T this[params int[] indexes] public T this[params int[] indexes]
@ -81,7 +105,8 @@ public class NDArray<T> : IEnumerable<T>, IEquatable<NDArray<T>>
private int FlattenIndex(params int[] indexes) private int FlattenIndex(params int[] indexes)
{ {
if (indexes.Length != sizes.Length) throw new InvalidSizeException("Too many or too few indexes were provided."); if (indexes.Length != sizes.Length)
throw new InvalidSizeException("Too many or too few indexes were provided.");
int ind = indexes[^1]; int ind = indexes[^1];
Console.WriteLine($"Start at {ind}"); Console.WriteLine($"Start at {ind}");

View File

@ -17,5 +17,5 @@ public static class Nerd_STF
{ "nuget", "https://www.nuget.org/packages/Nerd_STF/" } { "nuget", "https://www.nuget.org/packages/Nerd_STF/" }
}; };
public const string MainDeveloper = "That_One_Nerd"; public const string MainDeveloper = "That_One_Nerd";
public const string Version = "2.4.0"; public const string Version = "2.5.0";
} }

View File

@ -31,7 +31,7 @@ The last thing I did was add the ability to turn a matrix into its equivalent ro
Anyway, that's everything in this update. Again, pretty small, but meaningful nonetheless. Unless I haven't screwed anything up, the next update I work on will be `2.5`, so I'll see you then!</PackageReleaseNotes> Anyway, that's everything in this update. Again, pretty small, but meaningful nonetheless. Unless I haven't screwed anything up, the next update I work on will be `2.5`, so I'll see you then!</PackageReleaseNotes>
<PackageProjectUrl>https://github.com/That-One-Nerd/Nerd_STF</PackageProjectUrl> <PackageProjectUrl>https://github.com/That-One-Nerd/Nerd_STF</PackageProjectUrl>
<GenerateDocumentationFile>False</GenerateDocumentationFile> <GenerateDocumentationFile>True</GenerateDocumentationFile>
<SignAssembly>False</SignAssembly> <SignAssembly>False</SignAssembly>
<PackAsTool>False</PackAsTool> <PackAsTool>False</PackAsTool>
<Title>Nerd_STF</Title> <Title>Nerd_STF</Title>
@ -66,4 +66,9 @@ Anyway, that's everything in this update. Again, pretty small, but meaningful no
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Emgu.CV" Version="4.7.0.5276" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.7.0.5276" />
</ItemGroup>
</Project> </Project>

View File

@ -11,7 +11,7 @@
Nerd_STF is a multi-purpose .NET 7.0 library that contains many objects I feel would help the default C# library package. Feel free to do with it what you'd like. Nerd_STF is a multi-purpose .NET 7.0 library that contains many objects I feel would help the default C# library package. Feel free to do with it what you'd like.
Nerd_STF includes some math as well as many other computer science topics. It contains types like groups of floats/ints, geometry types like `Vert`, `Line`, and `Triangle`, and color types like `RGBA`, `CMYKA`, `HSVA`, and their byte equivalents, all of which can convert seamlessly between each other. Nerd_STF includes some math as well as many other computer science topics. It contains types like groups of floats/ints, geometry types like `Line` and `Triangle`, and color types like `RGBA`, `CMYKA`, `HSVA`, and their byte equivalents, all of which can convert seamlessly between each other.
## What about Nerd_STF Versions `2021`? ## What about Nerd_STF Versions `2021`?
Nerd_STF `2021` used an different version scheme, based on the year, as you might have guessed (it is not the year `2` right now), and while I will be keeping the `2021` versions up, I wouldn't recommend using them, and the code is old code, written by a more naive me. Hell, I wrote an entire `List<T>` class there before I knew of the `System.Collections.Generic.List<T>` class that did literally everything for me already. Oh well. So, keep that in mind when you check out those versions of the library. Nerd_STF `2021` used an different version scheme, based on the year, as you might have guessed (it is not the year `2` right now), and while I will be keeping the `2021` versions up, I wouldn't recommend using them, and the code is old code, written by a more naive me. Hell, I wrote an entire `List<T>` class there before I knew of the `System.Collections.Generic.List<T>` class that did literally everything for me already. Oh well. So, keep that in mind when you check out those versions of the library.