From 3682aaacf81d9a87797cee498b67399267a510ce Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Thu, 16 Nov 2023 10:04:55 -0500 Subject: [PATCH] Ellipse stuff. Some of this is old and I forgot my train of thought here. --- Nerd_STF/Exceptions/AspectLockedException.cs | 21 ++ Nerd_STF/Exceptions/NotAlignedException.cs | 10 + Nerd_STF/Helpers/GeometryHelper.cs | 100 +++++- .../{ => Algebra}/Abstract/IMatrix.cs | 2 +- .../{ => Algebra}/Abstract/IMatrixPresets.cs | 2 +- .../Abstract/IProjectionMatrix.cs | 2 +- .../{ => Algebra}/Abstract/IStaticMatrix.cs | 4 +- Nerd_STF/Mathematics/Algebra/Matrix.cs | 4 +- Nerd_STF/Mathematics/Algebra/Matrix2x2.cs | 4 +- Nerd_STF/Mathematics/Algebra/Matrix3x3.cs | 4 +- Nerd_STF/Mathematics/Algebra/Matrix4x4.cs | 1 + .../Algebra/SimpleProjectionMatrix.cs | 4 +- .../{ => Geometry}/Abstract/IClosestTo.cs | 2 +- .../{ => Geometry}/Abstract/IContains.cs | 2 +- .../Geometry/Abstract/IContainsGeometry2d.cs | 17 + .../Abstract/IContainsGeometry3d.cs} | 11 +- .../{ => Geometry}/Abstract/IEncapsulate.cs | 2 +- .../Abstract/IGeometricModifiers2d.cs | 6 + .../Abstract/IGeometricModifiers3d.cs | 12 + .../Geometry/Abstract/IGeometricRotate2d.cs | 12 + .../Geometry/Abstract/IGeometricScale2d.cs | 10 + .../Abstract/IGeometricTranslate2d.cs | 7 + .../{ => Geometry}/Abstract/IIntersect.cs | 2 +- .../{ => Geometry}/Abstract/IPolygon.cs | 10 +- .../{ => Geometry}/Abstract/ISubdivide.cs | 2 +- .../{ => Geometry}/Abstract/ITriangulate.cs | 2 +- Nerd_STF/Mathematics/Geometry/Box2d.cs | 115 +++++-- Nerd_STF/Mathematics/Geometry/Ellipse.cs | 321 ++++++++++++++++++ Nerd_STF/Mathematics/Geometry/Line.cs | 4 +- .../Mathematics/Geometry/Quadrilateral.cs | 4 +- Nerd_STF/Mathematics/Geometry/Triangle.cs | 4 +- Nerd_STF/Mathematics/Samples/Constants.cs | 1 + 32 files changed, 649 insertions(+), 55 deletions(-) create mode 100644 Nerd_STF/Exceptions/AspectLockedException.cs create mode 100644 Nerd_STF/Exceptions/NotAlignedException.cs rename Nerd_STF/Mathematics/{ => Algebra}/Abstract/IMatrix.cs (96%) rename Nerd_STF/Mathematics/{ => Algebra}/Abstract/IMatrixPresets.cs (83%) rename Nerd_STF/Mathematics/{ => Algebra}/Abstract/IProjectionMatrix.cs (84%) rename Nerd_STF/Mathematics/{ => Algebra}/Abstract/IStaticMatrix.cs (72%) rename Nerd_STF/Mathematics/{ => Geometry}/Abstract/IClosestTo.cs (63%) rename Nerd_STF/Mathematics/{ => Geometry}/Abstract/IContains.cs (63%) create mode 100644 Nerd_STF/Mathematics/Geometry/Abstract/IContainsGeometry2d.cs rename Nerd_STF/Mathematics/{Abstract/IContainsGeometry.cs => Geometry/Abstract/IContainsGeometry3d.cs} (53%) rename Nerd_STF/Mathematics/{ => Geometry}/Abstract/IEncapsulate.cs (72%) create mode 100644 Nerd_STF/Mathematics/Geometry/Abstract/IGeometricModifiers2d.cs create mode 100644 Nerd_STF/Mathematics/Geometry/Abstract/IGeometricModifiers3d.cs create mode 100644 Nerd_STF/Mathematics/Geometry/Abstract/IGeometricRotate2d.cs create mode 100644 Nerd_STF/Mathematics/Geometry/Abstract/IGeometricScale2d.cs create mode 100644 Nerd_STF/Mathematics/Geometry/Abstract/IGeometricTranslate2d.cs rename Nerd_STF/Mathematics/{ => Geometry}/Abstract/IIntersect.cs (64%) rename Nerd_STF/Mathematics/{ => Geometry}/Abstract/IPolygon.cs (75%) rename Nerd_STF/Mathematics/{ => Geometry}/Abstract/ISubdivide.cs (66%) rename Nerd_STF/Mathematics/{ => Geometry}/Abstract/ITriangulate.cs (87%) create mode 100644 Nerd_STF/Mathematics/Geometry/Ellipse.cs diff --git a/Nerd_STF/Exceptions/AspectLockedException.cs b/Nerd_STF/Exceptions/AspectLockedException.cs new file mode 100644 index 0000000..af66095 --- /dev/null +++ b/Nerd_STF/Exceptions/AspectLockedException.cs @@ -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) { } +} diff --git a/Nerd_STF/Exceptions/NotAlignedException.cs b/Nerd_STF/Exceptions/NotAlignedException.cs new file mode 100644 index 0000000..0cfce3a --- /dev/null +++ b/Nerd_STF/Exceptions/NotAlignedException.cs @@ -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) { } +} diff --git a/Nerd_STF/Helpers/GeometryHelper.cs b/Nerd_STF/Helpers/GeometryHelper.cs index 53d383f..421fdd6 100644 --- a/Nerd_STF/Helpers/GeometryHelper.cs +++ b/Nerd_STF/Helpers/GeometryHelper.cs @@ -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; + } } diff --git a/Nerd_STF/Mathematics/Abstract/IMatrix.cs b/Nerd_STF/Mathematics/Algebra/Abstract/IMatrix.cs similarity index 96% rename from Nerd_STF/Mathematics/Abstract/IMatrix.cs rename to Nerd_STF/Mathematics/Algebra/Abstract/IMatrix.cs index 1bf2d33..e9e8083 100644 --- a/Nerd_STF/Mathematics/Abstract/IMatrix.cs +++ b/Nerd_STF/Mathematics/Algebra/Abstract/IMatrix.cs @@ -1,4 +1,4 @@ -namespace Nerd_STF.Mathematics.Abstract; +namespace Nerd_STF.Mathematics.Algebra.Abstract; public interface IMatrix : IAbsolute, ICeiling, IClamp, IEquatable, IFloor, IGroup2d, ILerp, IRound diff --git a/Nerd_STF/Mathematics/Abstract/IMatrixPresets.cs b/Nerd_STF/Mathematics/Algebra/Abstract/IMatrixPresets.cs similarity index 83% rename from Nerd_STF/Mathematics/Abstract/IMatrixPresets.cs rename to Nerd_STF/Mathematics/Algebra/Abstract/IMatrixPresets.cs index 756a9bf..ede0878 100644 --- a/Nerd_STF/Mathematics/Abstract/IMatrixPresets.cs +++ b/Nerd_STF/Mathematics/Algebra/Abstract/IMatrixPresets.cs @@ -1,4 +1,4 @@ -namespace Nerd_STF.Mathematics.Abstract; +namespace Nerd_STF.Mathematics.Algebra.Abstract; public interface IMatrixPresets where T : IMatrix, IMatrixPresets { diff --git a/Nerd_STF/Mathematics/Abstract/IProjectionMatrix.cs b/Nerd_STF/Mathematics/Algebra/Abstract/IProjectionMatrix.cs similarity index 84% rename from Nerd_STF/Mathematics/Abstract/IProjectionMatrix.cs rename to Nerd_STF/Mathematics/Algebra/Abstract/IProjectionMatrix.cs index 0223894..ca4a785 100644 --- a/Nerd_STF/Mathematics/Abstract/IProjectionMatrix.cs +++ b/Nerd_STF/Mathematics/Algebra/Abstract/IProjectionMatrix.cs @@ -1,4 +1,4 @@ -namespace Nerd_STF.Mathematics.Abstract; +namespace Nerd_STF.Mathematics.Algebra.Abstract; public interface IProjectionMatrix : IMatrix where TThis : IProjectionMatrix diff --git a/Nerd_STF/Mathematics/Abstract/IStaticMatrix.cs b/Nerd_STF/Mathematics/Algebra/Abstract/IStaticMatrix.cs similarity index 72% rename from Nerd_STF/Mathematics/Abstract/IStaticMatrix.cs rename to Nerd_STF/Mathematics/Algebra/Abstract/IStaticMatrix.cs index bbb8e4f..90e8eac 100644 --- a/Nerd_STF/Mathematics/Abstract/IStaticMatrix.cs +++ b/Nerd_STF/Mathematics/Algebra/Abstract/IStaticMatrix.cs @@ -1,7 +1,7 @@ -namespace Nerd_STF.Mathematics.Abstract; +namespace Nerd_STF.Mathematics.Algebra.Abstract; public interface IStaticMatrix : IAverage, IEquatable, IMatrix, IMedian, IMatrixPresets where T : IStaticMatrix { - + } diff --git a/Nerd_STF/Mathematics/Algebra/Matrix.cs b/Nerd_STF/Mathematics/Algebra/Matrix.cs index 02d41b7..9c9a1b5 100644 --- a/Nerd_STF/Mathematics/Algebra/Matrix.cs +++ b/Nerd_STF/Mathematics/Algebra/Matrix.cs @@ -1,4 +1,6 @@ -namespace Nerd_STF.Mathematics.Algebra; +using Nerd_STF.Mathematics.Algebra.Abstract; + +namespace Nerd_STF.Mathematics.Algebra; public class Matrix : IMatrix { diff --git a/Nerd_STF/Mathematics/Algebra/Matrix2x2.cs b/Nerd_STF/Mathematics/Algebra/Matrix2x2.cs index d26f8dc..14b2a88 100644 --- a/Nerd_STF/Mathematics/Algebra/Matrix2x2.cs +++ b/Nerd_STF/Mathematics/Algebra/Matrix2x2.cs @@ -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 { diff --git a/Nerd_STF/Mathematics/Algebra/Matrix3x3.cs b/Nerd_STF/Mathematics/Algebra/Matrix3x3.cs index dfcdda3..6db0ae9 100644 --- a/Nerd_STF/Mathematics/Algebra/Matrix3x3.cs +++ b/Nerd_STF/Mathematics/Algebra/Matrix3x3.cs @@ -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 { diff --git a/Nerd_STF/Mathematics/Algebra/Matrix4x4.cs b/Nerd_STF/Mathematics/Algebra/Matrix4x4.cs index db00dbf..6e8b2d4 100644 --- a/Nerd_STF/Mathematics/Algebra/Matrix4x4.cs +++ b/Nerd_STF/Mathematics/Algebra/Matrix4x4.cs @@ -1,4 +1,5 @@ using System.Data.Common; +using Nerd_STF.Mathematics.Algebra.Abstract; namespace Nerd_STF.Mathematics.Algebra; diff --git a/Nerd_STF/Mathematics/Algebra/SimpleProjectionMatrix.cs b/Nerd_STF/Mathematics/Algebra/SimpleProjectionMatrix.cs index 7131308..4cdaebf 100644 --- a/Nerd_STF/Mathematics/Algebra/SimpleProjectionMatrix.cs +++ b/Nerd_STF/Mathematics/Algebra/SimpleProjectionMatrix.cs @@ -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 diff --git a/Nerd_STF/Mathematics/Abstract/IClosestTo.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IClosestTo.cs similarity index 63% rename from Nerd_STF/Mathematics/Abstract/IClosestTo.cs rename to Nerd_STF/Mathematics/Geometry/Abstract/IClosestTo.cs index 7946d69..39fbb33 100644 --- a/Nerd_STF/Mathematics/Abstract/IClosestTo.cs +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IClosestTo.cs @@ -1,4 +1,4 @@ -namespace Nerd_STF.Mathematics.Abstract; +namespace Nerd_STF.Mathematics.Geometry.Abstract; public interface IClosestTo where T : IEquatable { diff --git a/Nerd_STF/Mathematics/Abstract/IContains.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IContains.cs similarity index 63% rename from Nerd_STF/Mathematics/Abstract/IContains.cs rename to Nerd_STF/Mathematics/Geometry/Abstract/IContains.cs index c12be14..d348445 100644 --- a/Nerd_STF/Mathematics/Abstract/IContains.cs +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IContains.cs @@ -1,4 +1,4 @@ -namespace Nerd_STF.Mathematics.Abstract; +namespace Nerd_STF.Mathematics.Geometry.Abstract; public interface IContains where T : IEquatable { diff --git a/Nerd_STF/Mathematics/Geometry/Abstract/IContainsGeometry2d.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IContainsGeometry2d.cs new file mode 100644 index 0000000..9fbcab0 --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IContainsGeometry2d.cs @@ -0,0 +1,17 @@ +namespace Nerd_STF.Mathematics.Geometry.Abstract; + +public interface IContainsGeometry2d : IContains, IContains, IContains, + IContains, IIntersect, IIntersect, IIntersect + where T : IContainsGeometry2d, IEquatable +{ + public bool Contains(T obj); + public bool Intersects(T obj); + + public bool Contains(IEnumerable points); + public bool Contains(Fill points, int count); + public bool Intersects(IEnumerable lines); + public bool Intersects(Fill lines, int count); + + public bool Contains(TOther obj) where TOther : IPolygon; + public bool Intersects(TOther obj) where TOther : IPolygon; +} diff --git a/Nerd_STF/Mathematics/Abstract/IContainsGeometry.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IContainsGeometry3d.cs similarity index 53% rename from Nerd_STF/Mathematics/Abstract/IContainsGeometry.cs rename to Nerd_STF/Mathematics/Geometry/Abstract/IContainsGeometry3d.cs index 64ea406..582d5b5 100644 --- a/Nerd_STF/Mathematics/Abstract/IContainsGeometry.cs +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IContainsGeometry3d.cs @@ -1,8 +1,8 @@ -namespace Nerd_STF.Mathematics.Abstract; +namespace Nerd_STF.Mathematics.Geometry.Abstract; -public interface IContainsGeometry : IContains, IContains, IContains, +public interface IContainsGeometry3d : IContains, IContains, IContains, IContains, IIntersect, IIntersect, IIntersect - where T : IContainsGeometry, IEquatable + where T : IContainsGeometry3d, IEquatable { public bool Contains(T obj); public bool Intersects(T obj); @@ -12,7 +12,6 @@ public interface IContainsGeometry : IContains, IContains, ICo public bool Intersects(IEnumerable lines); public bool Intersects(Fill lines, int count); - // later: - //public bool Contains(TOther obj) where TOther : IPolygon; - //public bool Intersects(TOther obj) where TOther : IPolygon; + public bool Contains(TOther obj) where TOther : IPolygon; + public bool Intersects(TOther obj) where TOther : IPolygon; } diff --git a/Nerd_STF/Mathematics/Abstract/IEncapsulate.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IEncapsulate.cs similarity index 72% rename from Nerd_STF/Mathematics/Abstract/IEncapsulate.cs rename to Nerd_STF/Mathematics/Geometry/Abstract/IEncapsulate.cs index 9cc537b..3ae33da 100644 --- a/Nerd_STF/Mathematics/Abstract/IEncapsulate.cs +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IEncapsulate.cs @@ -1,4 +1,4 @@ -namespace Nerd_STF.Mathematics.Abstract; +namespace Nerd_STF.Mathematics.Geometry.Abstract; public interface IEncapsulate : IContains where T : IEquatable where TE : IEquatable { diff --git a/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricModifiers2d.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricModifiers2d.cs new file mode 100644 index 0000000..c09d410 --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricModifiers2d.cs @@ -0,0 +1,6 @@ +namespace Nerd_STF.Mathematics.Geometry.Abstract; + +public interface IGeometricModifiers2d : IGeometricRotate2d, IGeometricScale2d, + IGeometricTranslate2d + where T : IGeometricModifiers2d +{ } diff --git a/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricModifiers3d.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricModifiers3d.cs new file mode 100644 index 0000000..bd32450 --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricModifiers3d.cs @@ -0,0 +1,12 @@ +namespace Nerd_STF.Mathematics.Geometry.Abstract; + +public interface IGeometricModifiers3d where T : IGeometricModifiers3d +{ + 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); +} diff --git a/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricRotate2d.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricRotate2d.cs new file mode 100644 index 0000000..c495780 --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricRotate2d.cs @@ -0,0 +1,12 @@ +namespace Nerd_STF.Mathematics.Geometry.Abstract; + +public interface IGeometricRotate2d where T : IGeometricRotate2d +{ + 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); +} diff --git a/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricScale2d.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricScale2d.cs new file mode 100644 index 0000000..033a0af --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricScale2d.cs @@ -0,0 +1,10 @@ +namespace Nerd_STF.Mathematics.Geometry.Abstract; + +public interface IGeometricScale2d where T : IGeometricScale2d +{ + public void Scale(float factor); + public void Scale(Float2 factor); + + public T ScaleImmutable(float factor); + public T ScaleImmutable(Float2 factor); +} diff --git a/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricTranslate2d.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricTranslate2d.cs new file mode 100644 index 0000000..536fe62 --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IGeometricTranslate2d.cs @@ -0,0 +1,7 @@ +namespace Nerd_STF.Mathematics.Geometry.Abstract; + +public interface IGeometricTranslate2d where T : IGeometricTranslate2d +{ + public void Translate(Float2 offset); + public T TranslateImmutable(Float2 offset); +} diff --git a/Nerd_STF/Mathematics/Abstract/IIntersect.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IIntersect.cs similarity index 64% rename from Nerd_STF/Mathematics/Abstract/IIntersect.cs rename to Nerd_STF/Mathematics/Geometry/Abstract/IIntersect.cs index 01fc19d..d020dbd 100644 --- a/Nerd_STF/Mathematics/Abstract/IIntersect.cs +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IIntersect.cs @@ -1,4 +1,4 @@ -namespace Nerd_STF.Mathematics.Abstract; +namespace Nerd_STF.Mathematics.Geometry.Abstract; public interface IIntersect where T : IEquatable { diff --git a/Nerd_STF/Mathematics/Abstract/IPolygon.cs b/Nerd_STF/Mathematics/Geometry/Abstract/IPolygon.cs similarity index 75% rename from Nerd_STF/Mathematics/Abstract/IPolygon.cs rename to Nerd_STF/Mathematics/Geometry/Abstract/IPolygon.cs index c39b924..a1d1069 100644 --- a/Nerd_STF/Mathematics/Abstract/IPolygon.cs +++ b/Nerd_STF/Mathematics/Geometry/Abstract/IPolygon.cs @@ -1,8 +1,10 @@ -namespace Nerd_STF.Mathematics.Abstract; +using Nerd_STF.Mathematics.Geometry.Abstract; -public interface IPolygon : IAverage, IContainsGeometry, IEquatable, - IFloatArray, IGroup, IIndexAll, IIndexRangeAll, - ILerp, IMedian, ITriangulate +namespace Nerd_STF.Mathematics.Abstract; + +public interface IPolygon : IAverage, IContainsGeometry3d, IEquatable, + IFloatArray, IGeometricModifiers3d, IGroup, IIndexAll, + IIndexRangeAll, ILerp, IMedian, ITriangulate where T : IPolygon { public float Area { get; } diff --git a/Nerd_STF/Mathematics/Abstract/ISubdivide.cs b/Nerd_STF/Mathematics/Geometry/Abstract/ISubdivide.cs similarity index 66% rename from Nerd_STF/Mathematics/Abstract/ISubdivide.cs rename to Nerd_STF/Mathematics/Geometry/Abstract/ISubdivide.cs index 226f41d..ec398e6 100644 --- a/Nerd_STF/Mathematics/Abstract/ISubdivide.cs +++ b/Nerd_STF/Mathematics/Geometry/Abstract/ISubdivide.cs @@ -1,4 +1,4 @@ -namespace Nerd_STF.Mathematics.Abstract; +namespace Nerd_STF.Mathematics.Geometry.Abstract; public interface ISubdivide { diff --git a/Nerd_STF/Mathematics/Abstract/ITriangulate.cs b/Nerd_STF/Mathematics/Geometry/Abstract/ITriangulate.cs similarity index 87% rename from Nerd_STF/Mathematics/Abstract/ITriangulate.cs rename to Nerd_STF/Mathematics/Geometry/Abstract/ITriangulate.cs index faac70b..1636397 100644 --- a/Nerd_STF/Mathematics/Abstract/ITriangulate.cs +++ b/Nerd_STF/Mathematics/Geometry/Abstract/ITriangulate.cs @@ -1,4 +1,4 @@ -namespace Nerd_STF.Mathematics.Abstract; +namespace Nerd_STF.Mathematics.Geometry.Abstract; public interface ITriangulate { diff --git a/Nerd_STF/Mathematics/Geometry/Box2d.cs b/Nerd_STF/Mathematics/Geometry/Box2d.cs index 546a31a..c70f8af 100644 --- a/Nerd_STF/Mathematics/Geometry/Box2d.cs +++ b/Nerd_STF/Mathematics/Geometry/Box2d.cs @@ -1,12 +1,15 @@ -namespace Nerd_STF.Mathematics.Geometry; +using Nerd_STF.Mathematics.Geometry.Abstract; -public class Box2d : IAverage, IClosestTo, IContains, IContains, - IContains, IContains, IIntersect, IIntersect, - IIntersect, IEquatable, ILerp, IMedian, - ISubdivide, ITriangulate, IWithinRange +namespace Nerd_STF.Mathematics.Geometry; + +public class Box2d : IAverage, IContainsGeometry2d, IEquatable, + ILerp, IMedian, + ISplittable, ISubdivide, ITriangulate, + IWithinRange { 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, IClosestTo, IContains, 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 points) + public bool Contains(T poly) where T : IPolygon { - 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 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 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 points, int count) @@ -116,12 +121,81 @@ public class Box2d : IAverage, IClosestTo, IContains, ICon for (int i = 0; i < count; i++) if (!Contains(points(i))) return false; return true; } - public bool Contains(Fill 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 poly) where T : IPolygon + { + 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 points) + { + foreach (Float2 p in points) Encapsulate(p); + } + public void Encapsulate(Fill points, int count) + { + for (int i = 0; i < count; i++) Encapsulate(points(i)); + } + public void Encapsulate(IEnumerable lines) + { + foreach (Line l in lines) + { + Encapsulate((Float2)l.a); + Encapsulate((Float2)l.b); + } + } + public void Encapsulate(Fill 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 poly) where T : IPolygon + { + // For some odd reason, I have to cast backwards to IContains + // in order to use the Contains(Box2d) method, despite it being inherited by + // IContainsGeometry3d> which is inherited by IPolygon. + // Weird. + if (Contains(poly) || ((IContains)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, IClosestTo, IContains, 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, IClosestTo, IContains, 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, diff --git a/Nerd_STF/Mathematics/Geometry/Ellipse.cs b/Nerd_STF/Mathematics/Geometry/Ellipse.cs new file mode 100644 index 0000000..47ca8d8 --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Ellipse.cs @@ -0,0 +1,321 @@ +using Nerd_STF.Mathematics.Geometry.Abstract; + +namespace Nerd_STF.Mathematics.Geometry; + +public class Ellipse : IAverage, IEquatable, + IGeometricScale2d, IGeometricTranslate2d, ILerp, + IMedian, IPresets0d, + ISplittable, 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 fill, bool lockAspect = false) + : this(fill(0), fill(1), lockAspect) { } + public Ellipse(Fill fill, bool lockAspect = false) + : this(fill(0), fill(1), lockAspect) { } + public Ellipse(Fill fill, bool lockAspect = false) + : this(fill(0), fill(1), fill(2), fill(3), lockAspect) { } + public Ellipse(Fill 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 poly) where T : IPolygon + { + 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 points) + { + foreach (Float3 p in points) if (!Contains((Float2)p)) return false; + return true; + } + public bool Contains(Fill 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 + } +} diff --git a/Nerd_STF/Mathematics/Geometry/Line.cs b/Nerd_STF/Mathematics/Geometry/Line.cs index 9836bda..5d5ccd2 100644 --- a/Nerd_STF/Mathematics/Geometry/Line.cs +++ b/Nerd_STF/Mathematics/Geometry/Line.cs @@ -1,4 +1,6 @@ -namespace Nerd_STF.Mathematics.Geometry; +using Nerd_STF.Mathematics.Geometry.Abstract; + +namespace Nerd_STF.Mathematics.Geometry; public class Line : IAverage, IClosestTo, IContains, IEquatable, IFloatArray, IFromTuple, IGroup, diff --git a/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs b/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs index 10fea42..575e4ad 100644 --- a/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs +++ b/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs @@ -1,4 +1,6 @@ -namespace Nerd_STF.Mathematics.Geometry; +using Nerd_STF.Mathematics.Geometry.Abstract; + +namespace Nerd_STF.Mathematics.Geometry; public class Quadrilateral : IClosestTo, IFromTuple, IPolygon, diff --git a/Nerd_STF/Mathematics/Geometry/Triangle.cs b/Nerd_STF/Mathematics/Geometry/Triangle.cs index 2c44db6..a0a0e90 100644 --- a/Nerd_STF/Mathematics/Geometry/Triangle.cs +++ b/Nerd_STF/Mathematics/Geometry/Triangle.cs @@ -1,4 +1,6 @@ -namespace Nerd_STF.Mathematics.Geometry; +using Nerd_STF.Mathematics.Geometry.Abstract; + +namespace Nerd_STF.Mathematics.Geometry; public class Triangle : IClosestTo, IFromTuple, IPolygon, diff --git a/Nerd_STF/Mathematics/Samples/Constants.cs b/Nerd_STF/Mathematics/Samples/Constants.cs index 9c9f57e..dbb05b6 100644 --- a/Nerd_STF/Mathematics/Samples/Constants.cs +++ b/Nerd_STF/Mathematics/Samples/Constants.cs @@ -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;