Ellipse stuff. Some of this is old and I forgot my train of thought here.

This commit is contained in:
That_One_Nerd 2023-11-16 10:04:55 -05:00
parent 209a310363
commit 3682aaacf8
32 changed files with 649 additions and 55 deletions

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,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

@ -1,10 +1,11 @@
using Nerd_STF.Mathematics.Abstract; using System;
using System.Numerics;
namespace Nerd_STF.Helpers; namespace Nerd_STF.Helpers;
internal static class GeometryHelper internal static class GeometryHelper
{ {
public static Float2 Box2dAlongRay(Box2d box, Float2 p) public static Float2 Box2dAlongRay(in Box2d box, Float2 p)
{ {
// This is an approximate system. It's good enough for most purposes // 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 // but maybe not all of them. Basically it just makes a ray through the point
@ -52,12 +53,11 @@ internal static class GeometryHelper
else return option2; else return option2;
} }
public static bool LineIntersects(Line a, Line b) => public static bool LineIntersects(in Line a, in Line b) =>
LineIntersects2d(a, b, CrossSection2d.XY) && LineIntersects2d(a, b, CrossSection2d.XY) &&
LineIntersects2d(a, b, CrossSection2d.YZ) && LineIntersects2d(a, b, CrossSection2d.YZ) &&
LineIntersects2d(a, b, CrossSection2d.ZX); LineIntersects2d(a, b, CrossSection2d.ZX);
public static bool LineIntersects2d(in Line a, in Line b, CrossSection2d plane)
public static bool LineIntersects2d(Line a, Line b, CrossSection2d plane)
{ {
Float2 p1 = a.a.GetCrossSection(plane), q1 = a.b.GetCrossSection(plane), Float2 p1 = a.a.GetCrossSection(plane), q1 = a.b.GetCrossSection(plane),
p2 = b.a.GetCrossSection(plane), q2 = b.b.GetCrossSection(plane); p2 = b.a.GetCrossSection(plane), q2 = b.b.GetCrossSection(plane);
@ -74,6 +74,77 @@ internal static class GeometryHelper
(o4 == OrientationType.Colinear && PointOnSegmentCo(p2, q1, 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) private static bool PointOnSegmentCo(Float2 a, Float2 r, Float2 b)
{ {
// Just checks the box. Points must be colinear. // Just checks the box. Points must be colinear.
@ -97,4 +168,23 @@ internal static class GeometryHelper
Clockwise, Clockwise,
CounterClockwise 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

@ -1,4 +1,4 @@
namespace Nerd_STF.Mathematics.Abstract; namespace Nerd_STF.Mathematics.Algebra.Abstract;
public interface IMatrix<T> : IAbsolute<T>, ICeiling<T>, IClamp<T>, public interface IMatrix<T> : IAbsolute<T>, ICeiling<T>, IClamp<T>,
IEquatable<T>, IFloor<T>, IGroup2d<float>, ILerp<T, float>, IRound<T> IEquatable<T>, IFloor<T>, IGroup2d<float>, ILerp<T, float>, IRound<T>

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

@ -1,4 +1,4 @@
namespace Nerd_STF.Mathematics.Abstract; namespace Nerd_STF.Mathematics.Algebra.Abstract;
public interface IProjectionMatrix<TThis, TBaseMatrix, TDim> : IMatrix<TThis> public interface IProjectionMatrix<TThis, TBaseMatrix, TDim> : IMatrix<TThis>
where TThis : IProjectionMatrix<TThis, TBaseMatrix, TDim> where TThis : IProjectionMatrix<TThis, TBaseMatrix, TDim>

View File

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

View File

@ -1,4 +1,6 @@
namespace Nerd_STF.Mathematics.Algebra; using Nerd_STF.Mathematics.Algebra.Abstract;
namespace Nerd_STF.Mathematics.Algebra;
public class Matrix2x2 : ICloneable, IStaticMatrix<Matrix2x2> public class Matrix2x2 : ICloneable, IStaticMatrix<Matrix2x2>
{ {

View File

@ -1,4 +1,6 @@
namespace Nerd_STF.Mathematics.Algebra; using Nerd_STF.Mathematics.Algebra.Abstract;
namespace Nerd_STF.Mathematics.Algebra;
public class Matrix3x3 : ICloneable, IStaticMatrix<Matrix3x3> public class Matrix3x3 : ICloneable, IStaticMatrix<Matrix3x3>
{ {

View File

@ -1,4 +1,5 @@
using System.Data.Common; using System.Data.Common;
using Nerd_STF.Mathematics.Algebra.Abstract;
namespace Nerd_STF.Mathematics.Algebra; namespace Nerd_STF.Mathematics.Algebra;

View File

@ -1,4 +1,6 @@
namespace Nerd_STF.Mathematics.Algebra; using Nerd_STF.Mathematics.Algebra.Abstract;
namespace Nerd_STF.Mathematics.Algebra;
public class SimpleProjectionMatrix : Matrix3x3, public class SimpleProjectionMatrix : Matrix3x3,
IProjectionMatrix<SimpleProjectionMatrix, Matrix3x3, Float3> IProjectionMatrix<SimpleProjectionMatrix, Matrix3x3, Float3>

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

@ -1,8 +1,8 @@
namespace Nerd_STF.Mathematics.Abstract; namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface IContainsGeometry<T> : IContains<Float3>, IContains<Box2d>, IContains<Line>, public interface IContainsGeometry3d<T> : IContains<Float3>, IContains<Box2d>, IContains<Line>,
IContains<Triangle>, IIntersect<Box2d>, IIntersect<Line>, IIntersect<Triangle> IContains<Triangle>, IIntersect<Box2d>, IIntersect<Line>, IIntersect<Triangle>
where T : IContainsGeometry<T>, IEquatable<T> where T : IContainsGeometry3d<T>, IEquatable<T>
{ {
public bool Contains(T obj); public bool Contains(T obj);
public bool Intersects(T obj); public bool Intersects(T obj);
@ -12,7 +12,6 @@ public interface IContainsGeometry<T> : IContains<Float3>, IContains<Box2d>, ICo
public bool Intersects(IEnumerable<Line> lines); public bool Intersects(IEnumerable<Line> lines);
public bool Intersects(Fill<Line> lines, int count); public bool Intersects(Fill<Line> lines, int count);
// later: public bool Contains<TOther>(TOther obj) where TOther : IPolygon<TOther>;
//public bool Contains<TOther>(TOther obj) where TOther : IPolygon<TOther>; public bool Intersects<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

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

View File

@ -1,8 +1,10 @@
namespace Nerd_STF.Mathematics.Abstract; using Nerd_STF.Mathematics.Geometry.Abstract;
public interface IPolygon<T> : IAverage<T>, IContainsGeometry<T>, IEquatable<T>, namespace Nerd_STF.Mathematics.Abstract;
IFloatArray<T>, IGroup<Float3>, IIndexAll<Float3>, IIndexRangeAll<Float3>,
ILerp<T, float>, IMedian<T>, ITriangulate 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> where T : IPolygon<T>
{ {
public float Area { get; } public float Area { get; }

View File

@ -1,4 +1,4 @@
namespace Nerd_STF.Mathematics.Abstract; namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface ISubdivide<T> public interface ISubdivide<T>
{ {

View File

@ -1,4 +1,4 @@
namespace Nerd_STF.Mathematics.Abstract; namespace Nerd_STF.Mathematics.Geometry.Abstract;
public interface ITriangulate public interface ITriangulate
{ {

View File

@ -1,12 +1,15 @@
namespace Nerd_STF.Mathematics.Geometry; using Nerd_STF.Mathematics.Geometry.Abstract;
public class Box2d : IAverage<Box2d>, IClosestTo<Float2>, IContains<Box2d>, IContains<Float2>, namespace Nerd_STF.Mathematics.Geometry;
IContains<Line>, IContains<Triangle>, IIntersect<Box2d>, IIntersect<Line>,
IIntersect<Triangle>, IEquatable<Box2d>, ILerp<Box2d, float>, IMedian<Box2d>, public class Box2d : IAverage<Box2d>, IContainsGeometry2d<Box2d>, IEquatable<Box2d>,
ISubdivide<Box2d>, ITriangulate, IWithinRange<Float2, float> 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 Area => Size.x * Size.y;
public float Perimeter => 2 * Size.x + 2 * Size.y; public float Perimeter => 2 * Size.x + 2 * Size.y;
public Float2 Midpoint => center;
public float Height public float Height
{ {
@ -95,20 +98,22 @@ public class Box2d : IAverage<Box2d>, IClosestTo<Float2>, IContains<Box2d>, ICon
Float2 diff = Float2.Absolute(point - center); Float2 diff = Float2.Absolute(point - center);
return diff.x <= extents.x && diff.y <= extents.y; return diff.x <= extents.x && diff.y <= extents.y;
} }
public bool Contains(Float3 point) => Contains((Float2)point);
public bool Contains(Box2d box) => Contains(box.Min) && Contains(box.Max); public bool Contains<T>(T poly) where T : IPolygon<T>
public bool Contains(Line line) => Contains(line.a) && Contains(line.b);
public bool Contains(Triangle tri) =>
Contains(tri.a) && Contains(tri.b) & Contains(tri.c);
public bool Contains(IEnumerable<Float2> points)
{ {
foreach (Float3 p in points) if (!Contains(p)) return false; Float3[] verts = poly.GetAllVerts();
if (verts.Length < 1) return false;
foreach (Float3 v in verts) if (!Contains((Float2)v)) return false;
return true; return true;
} }
public bool Contains(IEnumerable<Float3> points) 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(p)) return false; foreach (Float3 p in points) if (!Contains((Float2)p)) return false;
return true; return true;
} }
public bool Contains(Fill<Float2> points, int count) public bool Contains(Fill<Float2> points, int count)
@ -116,12 +121,81 @@ public class Box2d : IAverage<Box2d>, IClosestTo<Float2>, IContains<Box2d>, ICon
for (int i = 0; i < count; i++) if (!Contains(points(i))) return false; for (int i = 0; i < count; i++) if (!Contains(points(i))) return false;
return true; return true;
} }
public bool Contains(Fill<Float3> points, int count)
public void Encapsulate(Float2 point)
{ {
for (int i = 0; i < count; i++) if (!Contains(points(i))) return false; if (Contains(point)) return;
return true;
// 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) public bool Intersects(Box2d box)
{ {
if (Contains(box) || box.Contains(this)) return true; if (Contains(box) || box.Contains(this)) return true;
@ -158,7 +232,8 @@ public class Box2d : IAverage<Box2d>, IClosestTo<Float2>, IContains<Box2d>, ICon
public Float2 LerpAcrossOutline(float t, bool clamp = true) public Float2 LerpAcrossOutline(float t, bool clamp = true)
{ {
if (!clamp) return LerpAcrossOutline(Mathf.AbsoluteMod(t, 1), true); if (clamp) t = Mathf.Clamp(t, 0, 1);
else t = Mathf.AbsoluteMod(t, 1);
(Line top, Line right, Line bottom, Line left) = GetOutlines(); (Line top, Line right, Line bottom, Line left) = GetOutlines();
float weightTB = top.Length / Perimeter, weightLR = left.Length / Perimeter; float weightTB = top.Length / Perimeter, weightLR = left.Length / Perimeter;
@ -181,10 +256,6 @@ public class Box2d : IAverage<Box2d>, IClosestTo<Float2>, IContains<Box2d>, ICon
public override string ToString() => $"{nameof(Box2d)} {{ " + public override string ToString() => $"{nameof(Box2d)} {{ " +
$"Min = {Min}, Max = {Max} }}"; $"Min = {Min}, Max = {Max} }}";
// todo: lerp across the rectangle bounds.
// also todo: add contains and contains partially for the polygon interface.
// also also todo: add the encapsulate method
public (Float2 topLeft, Float2 topRight, Float2 bottomRight, Float2 bottomLeft) GetCorners() public (Float2 topLeft, Float2 topRight, Float2 bottomRight, Float2 bottomLeft) GetCorners()
{ {
float top = center.y + extents.y, bottom = center.y - extents.y, float top = center.y + extents.y, bottom = center.y - extents.y,

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,4 +1,6 @@
namespace Nerd_STF.Mathematics.Geometry; using Nerd_STF.Mathematics.Geometry.Abstract;
namespace Nerd_STF.Mathematics.Geometry;
public class Line : IAverage<Line>, IClosestTo<Float3>, IContains<Float3>, IEquatable<Line>, public class Line : IAverage<Line>, IClosestTo<Float3>, IContains<Float3>, IEquatable<Line>,
IFloatArray<Line>, IFromTuple<Line, (Float3 a, Float3 b)>, IGroup<Float3>, IFloatArray<Line>, IFromTuple<Line, (Float3 a, Float3 b)>, IGroup<Float3>,

View File

@ -1,4 +1,6 @@
namespace Nerd_STF.Mathematics.Geometry; using Nerd_STF.Mathematics.Geometry.Abstract;
namespace Nerd_STF.Mathematics.Geometry;
public class Quadrilateral : IClosestTo<Float3>, public class Quadrilateral : IClosestTo<Float3>,
IFromTuple<Quadrilateral, (Float3 a, Float3 b, Float3 c, Float3 d)>, IPolygon<Quadrilateral>, IFromTuple<Quadrilateral, (Float3 a, Float3 b, Float3 c, Float3 d)>, IPolygon<Quadrilateral>,

View File

@ -1,4 +1,6 @@
namespace Nerd_STF.Mathematics.Geometry; using Nerd_STF.Mathematics.Geometry.Abstract;
namespace Nerd_STF.Mathematics.Geometry;
public class Triangle : IClosestTo<Float3>, public class Triangle : IClosestTo<Float3>,
IFromTuple<Triangle, (Float3 a, Float3 b, Float3 c)>, IPolygon<Triangle>, IFromTuple<Triangle, (Float3 a, Float3 b, Float3 c)>, IPolygon<Triangle>,

View File

@ -29,6 +29,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;