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