Ellipse stuff. Some of this is old and I forgot my train of thought here.
This commit is contained in:
parent
209a310363
commit
3682aaacf8
21
Nerd_STF/Exceptions/AspectLockedException.cs
Normal file
21
Nerd_STF/Exceptions/AspectLockedException.cs
Normal 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) { }
|
||||
}
|
||||
10
Nerd_STF/Exceptions/NotAlignedException.cs
Normal file
10
Nerd_STF/Exceptions/NotAlignedException.cs
Normal 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) { }
|
||||
}
|
||||
@ -1,10 +1,11 @@
|
||||
using Nerd_STF.Mathematics.Abstract;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nerd_STF.Helpers;
|
||||
|
||||
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
|
||||
// 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;
|
||||
}
|
||||
|
||||
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.YZ) &&
|
||||
LineIntersects2d(a, b, CrossSection2d.ZX);
|
||||
|
||||
public static bool LineIntersects2d(Line a, Line b, CrossSection2d plane)
|
||||
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);
|
||||
@ -74,6 +74,77 @@ internal static class GeometryHelper
|
||||
(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.
|
||||
@ -97,4 +168,23 @@ internal static class GeometryHelper
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>,
|
||||
IEquatable<T>, IFloor<T>, IGroup2d<float>, ILerp<T, float>, IRound<T>
|
||||
@ -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>
|
||||
{
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
namespace Nerd_STF.Mathematics.Algebra.Abstract;
|
||||
|
||||
public interface IProjectionMatrix<TThis, TBaseMatrix, TDim> : IMatrix<TThis>
|
||||
where TThis : IProjectionMatrix<TThis, TBaseMatrix, TDim>
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
namespace Nerd_STF.Mathematics.Algebra.Abstract;
|
||||
|
||||
public interface IStaticMatrix<T> : IAverage<T>, IEquatable<T>, IMatrix<T>, IMedian<T>,
|
||||
IMatrixPresets<T> where T : IStaticMatrix<T>
|
||||
@ -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>
|
||||
{
|
||||
|
||||
@ -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>
|
||||
{
|
||||
|
||||
@ -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>
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Data.Common;
|
||||
using Nerd_STF.Mathematics.Algebra.Abstract;
|
||||
|
||||
namespace Nerd_STF.Mathematics.Algebra;
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
namespace Nerd_STF.Mathematics.Algebra;
|
||||
using Nerd_STF.Mathematics.Algebra.Abstract;
|
||||
|
||||
namespace Nerd_STF.Mathematics.Algebra;
|
||||
|
||||
public class SimpleProjectionMatrix : Matrix3x3,
|
||||
IProjectionMatrix<SimpleProjectionMatrix, Matrix3x3, Float3>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
namespace Nerd_STF.Mathematics.Geometry.Abstract;
|
||||
|
||||
public interface IClosestTo<T> where T : IEquatable<T>
|
||||
{
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
namespace Nerd_STF.Mathematics.Geometry.Abstract;
|
||||
|
||||
public interface IContains<T> where T : IEquatable<T>
|
||||
{
|
||||
@ -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>;
|
||||
}
|
||||
@ -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>
|
||||
where T : IContainsGeometry<T>, IEquatable<T>
|
||||
where T : IContainsGeometry3d<T>, IEquatable<T>
|
||||
{
|
||||
public bool Contains(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(Fill<Line> lines, int count);
|
||||
|
||||
// later:
|
||||
//public bool Contains<TOther>(TOther obj) where TOther : IPolygon<TOther>;
|
||||
//public bool Intersects<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>;
|
||||
}
|
||||
@ -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>
|
||||
{
|
||||
@ -0,0 +1,6 @@
|
||||
namespace Nerd_STF.Mathematics.Geometry.Abstract;
|
||||
|
||||
public interface IGeometricModifiers2d<T> : IGeometricRotate2d<T>, IGeometricScale2d<T>,
|
||||
IGeometricTranslate2d<T>
|
||||
where T : IGeometricModifiers2d<T>
|
||||
{ }
|
||||
@ -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);
|
||||
}
|
||||
12
Nerd_STF/Mathematics/Geometry/Abstract/IGeometricRotate2d.cs
Normal file
12
Nerd_STF/Mathematics/Geometry/Abstract/IGeometricRotate2d.cs
Normal 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);
|
||||
}
|
||||
10
Nerd_STF/Mathematics/Geometry/Abstract/IGeometricScale2d.cs
Normal file
10
Nerd_STF/Mathematics/Geometry/Abstract/IGeometricScale2d.cs
Normal 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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
namespace Nerd_STF.Mathematics.Geometry.Abstract;
|
||||
|
||||
public interface IIntersect<T> where T : IEquatable<T>
|
||||
{
|
||||
@ -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>,
|
||||
IFloatArray<T>, IGroup<Float3>, IIndexAll<Float3>, IIndexRangeAll<Float3>,
|
||||
ILerp<T, float>, IMedian<T>, ITriangulate
|
||||
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; }
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
namespace Nerd_STF.Mathematics.Geometry.Abstract;
|
||||
|
||||
public interface ISubdivide<T>
|
||||
{
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Nerd_STF.Mathematics.Abstract;
|
||||
namespace Nerd_STF.Mathematics.Geometry.Abstract;
|
||||
|
||||
public interface ITriangulate
|
||||
{
|
||||
@ -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>,
|
||||
IContains<Line>, IContains<Triangle>, IIntersect<Box2d>, IIntersect<Line>,
|
||||
IIntersect<Triangle>, IEquatable<Box2d>, ILerp<Box2d, float>, IMedian<Box2d>,
|
||||
ISubdivide<Box2d>, ITriangulate, IWithinRange<Float2, float>
|
||||
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
|
||||
{
|
||||
@ -95,20 +98,22 @@ public class Box2d : IAverage<Box2d>, IClosestTo<Float2>, IContains<Box2d>, ICon
|
||||
Float2 diff = Float2.Absolute(point - center);
|
||||
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(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)
|
||||
public bool Contains<T>(T poly) where T : IPolygon<T>
|
||||
{
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
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;
|
||||
return true;
|
||||
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;
|
||||
@ -158,7 +232,8 @@ public class Box2d : IAverage<Box2d>, IClosestTo<Float2>, IContains<Box2d>, ICon
|
||||
|
||||
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();
|
||||
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)} {{ " +
|
||||
$"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()
|
||||
{
|
||||
float top = center.y + extents.y, bottom = center.y - extents.y,
|
||||
|
||||
321
Nerd_STF/Mathematics/Geometry/Ellipse.cs
Normal file
321
Nerd_STF/Mathematics/Geometry/Ellipse.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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>,
|
||||
IFloatArray<Line>, IFromTuple<Line, (Float3 a, Float3 b)>, IGroup<Float3>,
|
||||
|
||||
@ -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>,
|
||||
IFromTuple<Quadrilateral, (Float3 a, Float3 b, Float3 c, Float3 d)>, IPolygon<Quadrilateral>,
|
||||
|
||||
@ -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>,
|
||||
IFromTuple<Triangle, (Float3 a, Float3 b, Float3 c)>, IPolygon<Triangle>,
|
||||
|
||||
@ -29,6 +29,7 @@ public static class Constants
|
||||
public const float Cbrt3 = 1.44224957031f;
|
||||
public const float Cbrt5 = 1.70997594668f;
|
||||
public const float Cbrt10 = 2.15443469003f;
|
||||
public const float HalfSqrt2 = 0.707106781187f;
|
||||
public const float Sqrt2 = 1.4142135624f;
|
||||
public const float Sqrt3 = 1.7320508076f;
|
||||
public const float Sqrt5 = 2.2360679775f;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user