From 4be41701cd88c559df2a265592ee0cc0f5beb804 Mon Sep 17 00:00:00 2001 From: That-One-Nerd Date: Sun, 17 Apr 2022 13:43:38 -0400 Subject: [PATCH] Version 2.1.0 --- Changelog.md | 82 +++ Changelog.txt | 16 - .../Exceptions/DifferingVertCountException.cs | 30 + .../Exceptions/DisconnectedLinesException.cs | 35 ++ Nerd_STF/Exceptions/Nerd_STFException.cs | 18 + Nerd_STF/IClosest.cs | 13 + Nerd_STF/IContainer.cs | 13 + Nerd_STF/IEncapsulator.cs | 13 + Nerd_STF/Logger.cs | 2 +- Nerd_STF/Mathematics/Angle.cs | 124 +++++ Nerd_STF/Mathematics/Calculus.cs | 41 ++ Nerd_STF/Mathematics/Double2.cs | 6 +- Nerd_STF/Mathematics/Double3.cs | 6 +- Nerd_STF/Mathematics/Double4.cs | 6 +- Nerd_STF/Mathematics/Equation.cs | 10 + Nerd_STF/Mathematics/Geometry/Box2D.cs | 130 +++++ Nerd_STF/Mathematics/Geometry/Box3D.cs | 131 +++++ .../Mathematics/Geometry/ISubdividable.cs | 14 + .../Mathematics/Geometry/ITriangulatable.cs | 7 + Nerd_STF/Mathematics/Geometry/Line.cs | 128 +++-- Nerd_STF/Mathematics/Geometry/Polygon.cs | 519 ++++++++++++++++++ .../Mathematics/Geometry/Quadrilateral.cs | 342 ++++++++++++ Nerd_STF/Mathematics/Geometry/Sphere.cs | 126 +++++ Nerd_STF/Mathematics/Geometry/Triangle.cs | 130 +++-- Nerd_STF/Mathematics/Int2.cs | 9 +- Nerd_STF/Mathematics/Int3.cs | 9 +- Nerd_STF/Mathematics/Int4.cs | 9 +- Nerd_STF/Mathematics/Mathf.cs | 52 +- Nerd_STF/Miscellaneous/GlobalUsings.cs | 12 + Nerd_STF/bin/Release/net6.0/ref/Nerd_STF.dll | Bin 32256 -> 47616 bytes 30 files changed, 1894 insertions(+), 139 deletions(-) create mode 100644 Changelog.md delete mode 100644 Changelog.txt create mode 100644 Nerd_STF/Exceptions/DifferingVertCountException.cs create mode 100644 Nerd_STF/Exceptions/DisconnectedLinesException.cs create mode 100644 Nerd_STF/Exceptions/Nerd_STFException.cs create mode 100644 Nerd_STF/IClosest.cs create mode 100644 Nerd_STF/IContainer.cs create mode 100644 Nerd_STF/IEncapsulator.cs create mode 100644 Nerd_STF/Mathematics/Angle.cs create mode 100644 Nerd_STF/Mathematics/Calculus.cs create mode 100644 Nerd_STF/Mathematics/Equation.cs create mode 100644 Nerd_STF/Mathematics/Geometry/Box2D.cs create mode 100644 Nerd_STF/Mathematics/Geometry/Box3D.cs create mode 100644 Nerd_STF/Mathematics/Geometry/ISubdividable.cs create mode 100644 Nerd_STF/Mathematics/Geometry/Polygon.cs create mode 100644 Nerd_STF/Mathematics/Geometry/Quadrilateral.cs create mode 100644 Nerd_STF/Mathematics/Geometry/Sphere.cs create mode 100644 Nerd_STF/Miscellaneous/GlobalUsings.cs diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 0000000..c0c8185 --- /dev/null +++ b/Changelog.md @@ -0,0 +1,82 @@ +# Nerd_STF v2.1.0 + +``` +* Nerd_STF + + Exceptions + + Nerd_STFException + + DifferingVertCountException + + DisconnectedLinesException + + Miscellaneous + + `GlobalUsings.cs` + + IClosest + + IContainer + * Logger + * DefaultLogHandler(LogMessage) + = Replaced a `throw new Exception` with a `throw new ArgumentException` + * Mathematics + + Angle + + Calculus + + delegate double Equation(double) + * Double2 + = Made `CompareTo(Double2)` better + * Double3 + = Made `CompareTo(Double3)` better + * Double4 + = Made `CompareTo(Double4)` better + * Int2 + + operator &(Int2, Int2) + + operator |(Int2, Int2) + + operator ^(Int2, Int2) + = Made `CompareTo(Int2)` better + * Int3 + + operator &(Int3, Int3) + + operator |(Int3, Int3) + + operator ^(Int3, Int3) + = Made `CompareTo(Int3)` better + * Int4 + + operator &(Int4, Int4) + + operator |(Int4, Int4) + + operator ^(Int4, Int4) + = Made `CompareTo(Int4)` better + * Mathf + + Average(Equation, double, double, double) + + GetValues(Equation) + + MakeEquation(Dictionary) + + Max(Equation, double, double, double) + + Min(Equation, double, double, double) + = Swapped the names of "RadToDeg" and "DegToRad" + * Geometry + + Box2D + + Box3D + + Polygon + + Quadrilateral + + Sphere + + ISubdividable + * ITriangulatable + + Triangle[] TriangulateAll(params ITriangulatable[]) + * Line + + : IComparable + + : IContainer + + : IClosest + + : ISubdividable + + ClosestTo(Vert) + + ClosestTo(Vert, double) + + CompareTo(Line) + + Contains(Vert) + + Subdivide() + + operator -(Line) + + operator >(Line) + + operator <(Line) + + operator >=(Line) + + operator <=(Line) + = Renamed all instances of "start" to "a" + = Renamed all instances of "end" to "b" + * Triangle + + operator -(Triangle) + + ToDoubleArrayAll(params Triangle[]) + = Replaced the variable assignings in the Triangle to not re-assign the lines. + = Now uses custom exception in line constructor + = Renamed "L1" to "AB" + = Renamed "L2" to "BC" + = Renamed "L3" to "CA" +``` diff --git a/Changelog.txt b/Changelog.txt deleted file mode 100644 index 224a9e3..0000000 --- a/Changelog.txt +++ /dev/null @@ -1,16 +0,0 @@ -# Nerd_STF v2.0.1 - -* Nerd_STF - * Mathematics - * Geometry - * Line - + ToDoubleArray() - + ToDoubleList() - * Triangle - + ToDoubleArray() - + ToDoubleList() - * Vert - + : IGroup\ - + GetEnumerator() - + ToArray() - + ToList() diff --git a/Nerd_STF/Exceptions/DifferingVertCountException.cs b/Nerd_STF/Exceptions/DifferingVertCountException.cs new file mode 100644 index 0000000..0d5c81c --- /dev/null +++ b/Nerd_STF/Exceptions/DifferingVertCountException.cs @@ -0,0 +1,30 @@ +using System.Runtime.Serialization; + +namespace Nerd_STF.Exceptions +{ + + [Serializable] + public class DifferingVertCountException : Nerd_STFException + { + public string? ParamName; + public Polygon[]? Polygons; + + public DifferingVertCountException() : base("Not all polygons have the same vert count.") { } + public DifferingVertCountException(Exception inner) : base("Not all polygons have the same vert count.", inner) { } + public DifferingVertCountException(string paramName) : this() => ParamName = paramName; + public DifferingVertCountException(string paramName, Exception inner) : this(inner) => ParamName = paramName; + public DifferingVertCountException(params Polygon[] polys) : this() => Polygons = polys; + public DifferingVertCountException(Polygon[] polys, Exception inner) : this(inner) => Polygons = polys; + public DifferingVertCountException(string paramName, Polygon[] polys) : this() + { + ParamName = paramName; + Polygons = polys; + } + public DifferingVertCountException(string paramName, Polygon[] polys, Exception inner) : this(inner) + { + ParamName = paramName; + Polygons = polys; + } + protected DifferingVertCountException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/Nerd_STF/Exceptions/DisconnectedLinesException.cs b/Nerd_STF/Exceptions/DisconnectedLinesException.cs new file mode 100644 index 0000000..5f72248 --- /dev/null +++ b/Nerd_STF/Exceptions/DisconnectedLinesException.cs @@ -0,0 +1,35 @@ +using Nerd_STF.Mathematics.Geometry; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF.Exceptions +{ + [Serializable] + public class DisconnectedLinesException : Nerd_STFException + { + public string? ParamName; + public Line[]? Lines; + + public DisconnectedLinesException() : base("Lines are not connected.") { } + public DisconnectedLinesException(Exception inner) : base("Lines are not connected.", inner) { } + public DisconnectedLinesException(string paramName) : this() => ParamName = paramName; + public DisconnectedLinesException(string paramName, Exception inner) : this(inner) => ParamName = paramName; + public DisconnectedLinesException(params Line[] lines) : this() => Lines = lines; + public DisconnectedLinesException(Line[] lines, Exception inner) : this(inner) => Lines = lines; + public DisconnectedLinesException(string paramName, Line[] lines) : this() + { + ParamName = paramName; + Lines = lines; + } + public DisconnectedLinesException(string paramName, Line[] lines, Exception inner) : this(inner) + { + ParamName = paramName; + Lines = lines; + } + protected DisconnectedLinesException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/Nerd_STF/Exceptions/Nerd_STFException.cs b/Nerd_STF/Exceptions/Nerd_STFException.cs new file mode 100644 index 0000000..b36a9d9 --- /dev/null +++ b/Nerd_STF/Exceptions/Nerd_STFException.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF.Exceptions +{ + [Serializable] + public class Nerd_STFException : Exception + { + public Nerd_STFException() { } + public Nerd_STFException(string message) : base(message) { } + public Nerd_STFException(string message, Exception inner) : base(message, inner) { } + protected Nerd_STFException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/Nerd_STF/IClosest.cs b/Nerd_STF/IClosest.cs new file mode 100644 index 0000000..4c61357 --- /dev/null +++ b/Nerd_STF/IClosest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF +{ + public interface IClosest where T : IEquatable + { + public T ClosestTo(T item); + } +} diff --git a/Nerd_STF/IContainer.cs b/Nerd_STF/IContainer.cs new file mode 100644 index 0000000..5f47e28 --- /dev/null +++ b/Nerd_STF/IContainer.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF +{ + public interface IContainer where T : IEquatable + { + public bool Contains(T item); + } +} diff --git a/Nerd_STF/IEncapsulator.cs b/Nerd_STF/IEncapsulator.cs new file mode 100644 index 0000000..853eaf7 --- /dev/null +++ b/Nerd_STF/IEncapsulator.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF +{ + public interface IEncapsulator : IContainer where T : IEquatable where TE : IEquatable + { + public T Encapsulate(TE val); + } +} diff --git a/Nerd_STF/Logger.cs b/Nerd_STF/Logger.cs index 0ca41c7..b1e666c 100644 --- a/Nerd_STF/Logger.cs +++ b/Nerd_STF/Logger.cs @@ -58,7 +58,7 @@ namespace Nerd_STF LogSeverity.Warning => ConsoleColor.DarkYellow, LogSeverity.Error => ConsoleColor.Red, LogSeverity.Fatal => ConsoleColor.DarkRed, - _ => throw new Exception("Unknown log severity " + msg.Severity), + _ => throw new ArgumentException("Unknown log severity " + msg.Severity, nameof(msg)), }; ConsoleColor originalCol = Console.ForegroundColor; diff --git a/Nerd_STF/Mathematics/Angle.cs b/Nerd_STF/Mathematics/Angle.cs new file mode 100644 index 0000000..5b8990f --- /dev/null +++ b/Nerd_STF/Mathematics/Angle.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF.Mathematics +{ + public struct Angle : ICloneable, IComparable, IEquatable + { + public static Angle Full => new(360); + public static Angle Half => new(180); + public static Angle One => new(1); + public static Angle Quarter => new(90); + public static Angle Zero => new(0); + + public double Degrees + { + get => p_deg; + set => p_deg = value; + } + public double Gradians + { + get => p_deg * 1.11111111111; // Reciprocal of 9/10 as a constant (10/9) + set => p_deg = value * 0.9; + } + public double Radians + { + get => p_deg * Mathf.DegToRad; + set => p_deg = value * Mathf.RadToDeg; + } + + public Angle Bounded => new(p_deg % 360); + + private double p_deg; + + public Angle(double value, Type valueType = Type.Degrees) + { + p_deg = valueType switch + { + Type.Degrees => value, + Type.Gradians => value * 0.9, + Type.Radians => value * Mathf.RadToDeg, + _ => throw new ArgumentException("Unknown type.", nameof(valueType)), + }; + } + + public static Angle Absolute(Angle val) => new(Mathf.Absolute(val.p_deg)); + public static Angle Average(params Angle[] vals) => new(Mathf.Average(ToDoubles(Type.Degrees, vals))); + public static Angle Ceiling(Angle val) => new(Mathf.Ceiling(val.p_deg)); + public static Angle Clamp(Angle val, Angle min, Angle max) => new(Mathf.Clamp(val.p_deg, min.p_deg, max.p_deg)); + public static Angle Floor(Angle val) => new(Mathf.Ceiling(val.p_deg)); + public static Angle Lerp(Angle a, Angle b, double t, bool clamp = true) => + new(Mathf.Lerp(a.p_deg, b.p_deg, t, clamp)); + public static Angle Max(params Angle[] vals) => new(Mathf.Max(ToDoubles(Type.Degrees, vals))); + public static Angle Median(params Angle[] vals) => new(Mathf.Median(ToDoubles(Type.Degrees, vals))); + public static Angle Min(params Angle[] vals) => new(Mathf.Min(ToDoubles(Type.Degrees, vals))); + + public static double[] ToDoubles(Type outputType, params Angle[] vals) + { + double[] res = new double[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + res[i] = outputType switch + { + Type.Degrees => vals[i].Degrees, + Type.Gradians => vals[i].Gradians, + Type.Radians => vals[i].Radians, + _ => throw new ArgumentException("Unknown type.", nameof(outputType)), + }; + } + return res; + } + + public int CompareTo(Angle other) => p_deg.CompareTo(other.p_deg); + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Angle)) return false; + return Equals((Angle)obj); + } + public bool Equals(Angle other) => p_deg == other.p_deg; + public override int GetHashCode() => Degrees.GetHashCode() ^ Gradians.GetHashCode() ^ Radians.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(Type outputType) => ToString((string?)null, outputType); + public string ToString(string? provider, Type outputType = Type.Degrees) => outputType switch + { + Type.Degrees => p_deg.ToString(provider), + Type.Gradians => Gradians.ToString(provider), + Type.Radians => Radians.ToString(provider), + _ => throw new ArgumentException("Unknown type.", nameof(outputType)), + }; + public string ToString(IFormatProvider provider, Type outputType = Type.Degrees) => outputType switch + { + Type.Degrees => p_deg.ToString(provider), + Type.Gradians => Gradians.ToString(provider), + Type.Radians => Radians.ToString(provider), + _ => throw new ArgumentException("Unknown type.", nameof(outputType)), + }; + + public object Clone() => new Angle(p_deg); + + public static Angle operator +(Angle a, Angle b) => new(a.p_deg + b.p_deg); + public static Angle operator -(Angle a) => new(-a.p_deg); + public static Angle operator -(Angle a, Angle b) => new(a.p_deg - b.p_deg); + public static Angle operator *(Angle a, Angle b) => new(a.p_deg * b.p_deg); + public static Angle operator *(Angle a, double b) => new(a.p_deg * b); + public static Angle operator /(Angle a, Angle b) => new(a.p_deg / b.p_deg); + public static Angle operator /(Angle a, double b) => new(a.p_deg / b); + public static bool operator ==(Angle a, Angle b) => a.Equals(b); + public static bool operator !=(Angle a, Angle b) => !a.Equals(b); + public static bool operator >(Angle a, Angle b) => a.CompareTo(b) > 0; + public static bool operator <(Angle a, Angle b) => a.CompareTo(b) < 0; + public static bool operator >=(Angle a, Angle b) => a == b || a > b; + public static bool operator <=(Angle a, Angle b) => a == b || a < b; + + public enum Type + { + Degrees, + Gradians, + Radians, + } + } +} diff --git a/Nerd_STF/Mathematics/Calculus.cs b/Nerd_STF/Mathematics/Calculus.cs new file mode 100644 index 0000000..a2bdf63 --- /dev/null +++ b/Nerd_STF/Mathematics/Calculus.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF.Mathematics +{ + public static class Calculus + { + public const double DefaultStep = 0.001; + + public static Equation GetDerivative(Equation equ, double min, double max, double step = DefaultStep) + { + Dictionary vals = new(); + for (double x = min; x <= max; x += step) + { + double val1 = equ(x), val2 = equ(x + step), change = (val2 - val1) / step; + vals.Add(x, change); + } + return Mathf.MakeEquation(vals); + } + public static double GetDerivativeAtPoint(Equation equ, double x, double step = DefaultStep) => + (equ(x + DefaultStep) - equ(x)) / step; + + public static double GetIntegral(Equation equ, double lowerBound, double upperBound, double step = DefaultStep) + { + double val = 0; + for (double x = lowerBound; x <= upperBound; x += step) val += equ(x) * step; + return val; + } + + public static double GradientDescent(Equation equ, double initial, double rate, double stepCount = 1000, + double step = DefaultStep) + { + double val = initial; + for (int i = 0; i < stepCount; i++) val -= GetDerivativeAtPoint(equ, val, step) * rate; + return val; + } + } +} diff --git a/Nerd_STF/Mathematics/Double2.cs b/Nerd_STF/Mathematics/Double2.cs index ed56033..1504fbf 100644 --- a/Nerd_STF/Mathematics/Double2.cs +++ b/Nerd_STF/Mathematics/Double2.cs @@ -134,11 +134,7 @@ namespace Nerd_STF.Mathematics return val; } - public int CompareTo(Double2 other) - { - double magA = Magnitude, magB = other.Magnitude; - return magA == magB ? 0 : magA > magB ? 1 : -1; - } + public int CompareTo(Double2 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { if (obj == null || obj.GetType() != typeof(Double2)) return false; diff --git a/Nerd_STF/Mathematics/Double3.cs b/Nerd_STF/Mathematics/Double3.cs index 5109015..b675239 100644 --- a/Nerd_STF/Mathematics/Double3.cs +++ b/Nerd_STF/Mathematics/Double3.cs @@ -154,11 +154,7 @@ namespace Nerd_STF.Mathematics return val; } - public int CompareTo(Double3 other) - { - double magA = Magnitude, magB = other.Magnitude; - return magA == magB ? 0 : magA > magB ? 1 : -1; - } + public int CompareTo(Double3 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { if (obj == null || obj.GetType() != typeof(Double3)) return false; diff --git a/Nerd_STF/Mathematics/Double4.cs b/Nerd_STF/Mathematics/Double4.cs index e02c62a..7018175 100644 --- a/Nerd_STF/Mathematics/Double4.cs +++ b/Nerd_STF/Mathematics/Double4.cs @@ -167,11 +167,7 @@ namespace Nerd_STF.Mathematics return val; } - public int CompareTo(Double4 other) - { - double magA = Magnitude, magB = other.Magnitude; - return magA == magB ? 0 : magA > magB ? 1 : -1; - } + public int CompareTo(Double4 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { if (obj == null || obj.GetType() != typeof(Double4)) return false; diff --git a/Nerd_STF/Mathematics/Equation.cs b/Nerd_STF/Mathematics/Equation.cs new file mode 100644 index 0000000..4e53596 --- /dev/null +++ b/Nerd_STF/Mathematics/Equation.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF.Mathematics +{ + public delegate double Equation(double x); +} diff --git a/Nerd_STF/Mathematics/Geometry/Box2D.cs b/Nerd_STF/Mathematics/Geometry/Box2D.cs new file mode 100644 index 0000000..3191dc0 --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Box2D.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF.Mathematics.Geometry +{ + public struct Box2D : ICloneable, IContainer, IEquatable + { + public static Box2D Unit => new(Vert.Zero, Double2.One); + + public Vert MaxVert + { + get => center + (size / 2); + set + { + Vert diff = center - value; + size = (Double2)diff.position * 2; + } + } + public Vert MinVert + { + get => center - (size / 2); + set + { + Vert diff = center + value; + size = (Double2)diff.position * 2; + } + } + + public double Area => size.x * size.y; + public double Perimeter => size.x * 2 + size.y * 2; + + public Vert center; + public Double2 size; + + public Box2D(Vert min, Vert max) : this(Vert.Average(min, max), (Double2)(min - max)) { } + public Box2D(Vert center, Double2 size) + { + this.center = center; + this.size = size; + } + public Box2D(Fill fill) : this(fill, new Double2(fill(3), fill(4))) { } + + public double this[int index] + { + get => size[index]; + set => size[index] = value; + } + + public static Box2D Absolute(Box2D val) => new(Vert.Absolute(val.MinVert), Vert.Absolute(val.MaxVert)); + public static Box2D Average(params Box2D[] vals) + { + (Vert[] centers, Double2[] sizes) = SplitArray(vals); + return new(Vert.Average(centers), Double2.Average(sizes)); + } + public static Box2D Ceiling(Box2D val) => new(Vert.Ceiling(val.center), Double2.Ceiling(val.size)); + public static Box2D Clamp(Box2D val, Box2D min, Box2D max) => + new(Vert.Clamp(val.center, min.center, max.center), Double2.Clamp(val.size, min.size, max.size)); + public static Box2D Floor(Box2D val) => new(Vert.Floor(val.center), Double2.Floor(val.size)); + public static Box2D Lerp(Box2D a, Box2D b, float t, bool clamp = true) => + new(Vert.Lerp(a.center, b.center, t, clamp), Double2.Lerp(a.size, b.size, t, clamp)); + public static Box2D Median(params Box2D[] vals) + { + (Vert[] verts, Double2[] sizes) = SplitArray(vals); + return new(Vert.Median(verts), Double2.Median(sizes)); + } + public static Box2D Max(params Box2D[] vals) + { + (Vert[] verts, Double2[] sizes) = SplitArray(vals); + return new(Vert.Max(verts), Double2.Max(sizes)); + } + public static Box2D Min(params Box2D[] vals) + { + (Vert[] verts, Double2[] sizes) = SplitArray(vals); + return new(Vert.Min(verts), Double2.Min(sizes)); + } + public static (Vert[] centers, Double2[] sizes) SplitArray(params Box2D[] vals) + { + Vert[] centers = new Vert[vals.Length]; + Double2[] sizes = new Double2[vals.Length]; + + for (int i = 0; i < vals.Length; i++) + { + centers[i] = vals[i].center; + sizes[i] = vals[i].size; + } + + return (centers, sizes); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Box2D)) return false; + return Equals((Box2D)obj); + } + public bool Equals(Box2D other) => center == other.center && size == other.size; + public override int GetHashCode() => center.GetHashCode() ^ size.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(string? provider) => + "Min: " + MinVert.ToString(provider) + " Max: " + MaxVert.ToString(provider); + public string ToString(IFormatProvider provider) => + "Min: " + MinVert.ToString(provider) + " Max: " + MaxVert.ToString(provider); + + public bool Contains(Vert vert) + { + Double2 diff = Double2.Absolute((Double2)(center - vert)); + return diff.x <= size.x && diff.y <= size.y; + } + + public object Clone() => new Box2D(center, size); + + public static Box2D operator +(Box2D a, Vert b) => new(a.center + b, a.size); + public static Box2D operator +(Box2D a, Double2 b) => new(a.center, a.size + b); + public static Box2D operator -(Box2D b) => new(-b.MaxVert, -b.MinVert); + public static Box2D operator -(Box2D a, Vert b) => new(a.center - b, a.size); + public static Box2D operator -(Box2D a, Double2 b) => new(a.center, a.size - b); + public static Box2D operator *(Box2D a, double b) => new(a.center * b, a.size * b); + public static Box2D operator *(Box2D a, Double2 b) => new(a.center, a.size * b); + public static Box2D operator /(Box2D a, double b) => new(a.center / b, a.size / b); + public static Box2D operator /(Box2D a, Double2 b) => new(a.center, a.size / b); + public static bool operator ==(Box2D a, Box2D b) => a.Equals(b); + public static bool operator !=(Box2D a, Box2D b) => !a.Equals(b); + + public static implicit operator Box2D(Fill fill) => new(fill); + public static explicit operator Box2D(Box3D box) => new(box.center, (Double2)box.size); + } +} diff --git a/Nerd_STF/Mathematics/Geometry/Box3D.cs b/Nerd_STF/Mathematics/Geometry/Box3D.cs new file mode 100644 index 0000000..a3cc1df --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Box3D.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF.Mathematics.Geometry +{ + public struct Box3D : ICloneable, IContainer, IEquatable + { + public static Box3D Unit => new(Vert.Zero, Double3.One); + + public Vert MaxVert + { + get => center + (Vert)(size / 2); + set + { + Vert diff = center - value; + size = diff.position * 2; + } + } + public Vert MinVert + { + get => center - (Vert)(size / 2); + set + { + Vert diff = center + value; + size = diff.position * 2; + } + } + + public double Area => size.x * size.y * size.z; + public double Perimeter => size.x * 2 + size.y * 2 + size.z * 2; + + public Vert center; + public Double3 size; + + public Box3D(Box2D box) : this(box.center, (Double3)box.size) { } + public Box3D(Vert min, Vert max) : this(Vert.Average(min, max), (Double3)(min - max)) { } + public Box3D(Vert center, Double3 size) + { + this.center = center; + this.size = size; + } + public Box3D(Fill fill) : this(fill, new Double3(fill(3), fill(4), fill(5))) { } + + public double this[int index] + { + get => size[index]; + set => size[index] = value; + } + + public static Box3D Absolute(Box3D val) => new(Vert.Absolute(val.MinVert), Vert.Absolute(val.MaxVert)); + public static Box3D Average(params Box3D[] vals) + { + (Vert[] centers, Double3[] sizes) = SplitArray(vals); + return new(Vert.Average(centers), Double3.Average(sizes)); + } + public static Box3D Ceiling(Box3D val) => new(Vert.Ceiling(val.center), Double3.Ceiling(val.size)); + public static Box3D Clamp(Box3D val, Box3D min, Box3D max) => + new(Vert.Clamp(val.center, min.center, max.center), Double3.Clamp(val.size, min.size, max.size)); + public static Box3D Floor(Box3D val) => new(Vert.Floor(val.center), Double3.Floor(val.size)); + public static Box3D Lerp(Box3D a, Box3D b, float t, bool clamp = true) => + new(Vert.Lerp(a.center, b.center, t, clamp), Double3.Lerp(a.size, b.size, t, clamp)); + public static Box3D Median(params Box3D[] vals) + { + (Vert[] verts, Double3[] sizes) = SplitArray(vals); + return new(Vert.Median(verts), Double3.Median(sizes)); + } + public static Box3D Max(params Box3D[] vals) + { + (Vert[] verts, Double3[] sizes) = SplitArray(vals); + return new(Vert.Max(verts), Double3.Max(sizes)); + } + public static Box3D Min(params Box3D[] vals) + { + (Vert[] verts, Double3[] sizes) = SplitArray(vals); + return new(Vert.Min(verts), Double3.Min(sizes)); + } + public static (Vert[] centers, Double3[] sizes) SplitArray(params Box3D[] vals) + { + Vert[] centers = new Vert[vals.Length]; + Double3[] sizes = new Double3[vals.Length]; + + for (int i = 0; i < vals.Length; i++) + { + centers[i] = vals[i].center; + sizes[i] = vals[i].size; + } + + return (centers, sizes); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Box3D)) return false; + return Equals((Box3D)obj); + } + public bool Equals(Box3D other) => center == other.center && size == other.size; + public override int GetHashCode() => center.GetHashCode() ^ size.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(string? provider) => + "Min: " + MinVert.ToString(provider) + " Max: " + MaxVert.ToString(provider); + public string ToString(IFormatProvider provider) => + "Min: " + MinVert.ToString(provider) + " Max: " + MaxVert.ToString(provider); + + public bool Contains(Vert vert) + { + Double3 diff = Double3.Absolute(center - vert); + return diff.x <= size.x && diff.y <= size.y && diff.z <= size.z; + } + + public object Clone() => new Box3D(center, size); + + public static Box3D operator +(Box3D a, Vert b) => new(a.center + b, a.size); + public static Box3D operator +(Box3D a, Double3 b) => new(a.center, a.size + b); + public static Box3D operator -(Box3D b) => new(-b.MaxVert, -b.MinVert); + public static Box3D operator -(Box3D a, Vert b) => new(a.center - b, a.size); + public static Box3D operator -(Box3D a, Double3 b) => new(a.center, a.size - b); + public static Box3D operator *(Box3D a, double b) => new(a.center * b, a.size * b); + public static Box3D operator *(Box3D a, Double3 b) => new(a.center, a.size * b); + public static Box3D operator /(Box3D a, double b) => new(a.center / b, a.size / b); + public static Box3D operator /(Box3D a, Double3 b) => new(a.center, a.size / b); + public static bool operator ==(Box3D a, Box3D b) => a.Equals(b); + public static bool operator !=(Box3D a, Box3D b) => !a.Equals(b); + + public static implicit operator Box3D(Fill fill) => new(fill); + public static implicit operator Box3D(Box2D box) => new(box); + } +} diff --git a/Nerd_STF/Mathematics/Geometry/ISubdividable.cs b/Nerd_STF/Mathematics/Geometry/ISubdividable.cs new file mode 100644 index 0000000..ff93cba --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/ISubdividable.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF.Mathematics.Geometry +{ + public interface ISubdividable + { + public T Subdivide(); + public T Subdivide(int iterations); + } +} diff --git a/Nerd_STF/Mathematics/Geometry/ITriangulatable.cs b/Nerd_STF/Mathematics/Geometry/ITriangulatable.cs index eb8c89a..c142c4e 100644 --- a/Nerd_STF/Mathematics/Geometry/ITriangulatable.cs +++ b/Nerd_STF/Mathematics/Geometry/ITriangulatable.cs @@ -8,6 +8,13 @@ namespace Nerd_STF.Mathematics.Geometry { public interface ITriangulatable { + public static Triangle[] TriangulateAll(params ITriangulatable[] triangulatables) + { + List res = new(); + foreach (ITriangulatable triangulatable in triangulatables) res.AddRange(triangulatable.Triangulate()); + return res.ToArray(); + } + public Triangle[] Triangulate(); } } diff --git a/Nerd_STF/Mathematics/Geometry/Line.cs b/Nerd_STF/Mathematics/Geometry/Line.cs index 431f265..318455d 100644 --- a/Nerd_STF/Mathematics/Geometry/Line.cs +++ b/Nerd_STF/Mathematics/Geometry/Line.cs @@ -8,7 +8,8 @@ using System.Threading.Tasks; namespace Nerd_STF.Mathematics.Geometry { - public struct Line : ICloneable, IEquatable, IGroup + public struct Line : ICloneable, IClosest, IComparable, IContainer, IEquatable, + IGroup, ISubdividable { public static Line Back => new(Vert.Zero, Vert.Back); public static Line Down => new(Vert.Zero, Vert.Down); @@ -20,14 +21,14 @@ namespace Nerd_STF.Mathematics.Geometry public static Line One => new(Vert.Zero, Vert.One); public static Line Zero => new(Vert.Zero, Vert.Zero); - public double Length => (end - start).Magnitude; + public double Length => (b - a).Magnitude; - public Vert start, end; + public Vert a, b; - public Line(Vert start, Vert end) + public Line(Vert a, Vert b) { - this.start = start; - this.end = end; + this.a = a; + this.b = b; } public Line(double x1, double y1, double x2, double y2) : this(new(x1, y1), new(x2, y2)) { } public Line(double x1, double y1, double z1, double x2, double y2, double z2) @@ -42,8 +43,8 @@ namespace Nerd_STF.Mathematics.Geometry { get => index switch { - 0 => start, - 1 => end, + 0 => a, + 1 => b, _ => throw new IndexOutOfRangeException(nameof(index)), }; set @@ -51,11 +52,11 @@ namespace Nerd_STF.Mathematics.Geometry switch (index) { case 0: - start = value; + a = value; break; case 1: - end = value; + b = value; break; default: throw new IndexOutOfRangeException(nameof(index)); @@ -63,18 +64,18 @@ namespace Nerd_STF.Mathematics.Geometry } } - public static Line Absolute(Line val) => new(Vert.Absolute(val.start), Vert.Absolute(val.end)); + public static Line Absolute(Line val) => new(Vert.Absolute(val.a), Vert.Absolute(val.b)); public static Line Average(params Line[] vals) { (Vert[] starts, Vert[] ends) = SplitArray(vals); return new(Vert.Average(starts), Vert.Average(ends)); } - public static Line Ceiling(Line val) => new(Vert.Ceiling(val.start), Vert.Ceiling(val.end)); + public static Line Ceiling(Line val) => new(Vert.Ceiling(val.a), Vert.Ceiling(val.b)); public static Line Clamp(Line val, Line min, Line max) => - new(Vert.Clamp(val.start, min.start, max.start), Vert.Clamp(val.end, min.end, max.end)); - public static Line Floor(Line val) => new(Vert.Floor(val.start), Vert.Floor(val.end)); + new(Vert.Clamp(val.a, min.a, max.a), Vert.Clamp(val.b, min.b, max.b)); + public static Line Floor(Line val) => new(Vert.Floor(val.a), Vert.Floor(val.b)); public static Line Lerp(Line a, Line b, double t, bool clamp = true) => - new(Vert.Lerp(a.start, b.start, t, clamp), Vert.Lerp(a.end, b.end, t, clamp)); + new(Vert.Lerp(a.a, b.a, t, clamp), Vert.Lerp(a.b, b.b, t, clamp)); public static Line Median(params Line[] vals) { (Vert[] starts, Vert[] ends) = SplitArray(vals); @@ -96,8 +97,8 @@ namespace Nerd_STF.Mathematics.Geometry Vert[] starts = new Vert[lines.Length], ends = new Vert[lines.Length]; for (int i = 0; i < lines.Length; i++) { - starts[i] = lines[i].start; - ends[i] = lines[i].end; + starts[i] = lines[i].a; + ends[i] = lines[i].b; } return (starts, ends); } @@ -107,43 +108,90 @@ namespace Nerd_STF.Mathematics.Geometry if (obj == null || obj.GetType() != typeof(Line)) return false; return Equals((Line)obj); } - public bool Equals(Line other) => start == other.start && end == other.end; - public override int GetHashCode() => start.GetHashCode() ^ end.GetHashCode(); + public bool Equals(Line other) => a == other.a && b == other.b; + public override int GetHashCode() => a.GetHashCode() ^ b.GetHashCode(); public override string ToString() => ToString((string?)null); public string ToString(string? provider) => - "A: " + start.ToString(provider) + " B: " + end.ToString(provider); + "A: " + a.ToString(provider) + " B: " + b.ToString(provider); public string ToString(IFormatProvider provider) => - "A: " + start.ToString(provider) + " B: " + end.ToString(provider); + "A: " + a.ToString(provider) + " B: " + b.ToString(provider); - public object Clone() => new Line(start, end); + public object Clone() => new Line(a, b); + + public int CompareTo(Line line) => Length.CompareTo(line.Length); + + public bool Contains(Vert vert) + { + Double3 diffA = a - vert, diffB = a - b; + double lerpVal = diffA.Magnitude / diffB.Magnitude; + return Vert.Lerp(a, b, lerpVal) == vert; + } + + public Vert ClosestTo(Vert vert) => ClosestTo(vert, Calculus.DefaultStep); + public Vert ClosestTo(Vert vert, double step) + { + Vert closestA = a, closestB = b; + for (double t = 0; t <= 1; t += step) + { + Vert valA = Vert.Lerp(a, b, t); + Vert valB = Vert.Lerp(b, a, t); + closestA = (valA - vert).Magnitude < (closestA - vert).Magnitude ? valA : closestA; + closestB = (valB - vert).Magnitude < (closestB - vert).Magnitude ? valB : closestB; + } + + return (closestA - vert).Magnitude >= (closestB - vert).Magnitude ? closestA : closestB; + } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public IEnumerator GetEnumerator() { - yield return start; - yield return end; + yield return a; + yield return b; } - public Vert[] ToArray() => new Vert[] { start, end }; - public List ToList() => new() { start, end }; + public Line[] Subdivide() + { + Vert middle = Vert.Lerp(a, b, 0.5); + return new Line[] { new(a, middle), new(middle, b) }; + } + public Line[] Subdivide(int iterations) + { + if (iterations < 1) return Array.Empty(); + List lines = new(Subdivide()); + for (int i = 1; i < iterations; i++) + { + List add = new(); + for (int j = 0; j < lines.Count; j++) add.AddRange(lines[j].Subdivide()); + lines = add; + } + return lines.ToArray(); + } - public double[] ToDoubleArray() => new double[] { start.position.x, start.position.y, start.position.z, - end.position.x, end.position.y, end.position.z }; - public List ToDoubleList() => new() { start.position.x, start.position.y, start.position.z, - end.position.x, end.position.y, end.position.z }; + public Vert[] ToArray() => new Vert[] { a, b }; + public List ToList() => new() { a, b }; - public static Line operator +(Line a, Line b) => new(a.start + b.start, a.end + b.end); - public static Line operator +(Line a, Vert b) => new(a.start + b, a.end + b); - public static Line operator -(Line a, Line b) => new(a.start - b.start, a.end - b.end); - public static Line operator -(Line a, Vert b) => new(a.start - b, a.end - b); - public static Line operator *(Line a, Line b) => new(a.start * b.start, a.end * b.end); - public static Line operator *(Line a, Vert b) => new(a.start * b, a.end * b); - public static Line operator *(Line a, double b) => new(a.start * b, a.end * b); - public static Line operator /(Line a, Line b) => new(a.start / b.start, a.end / b.end); - public static Line operator /(Line a, Vert b) => new(a.start / b, a.end / b); - public static Line operator /(Line a, double b) => new(a.start / b, a.end / b); + public double[] ToDoubleArray() => new double[] { a.position.x, a.position.y, a.position.z, + b.position.x, b.position.y, b.position.z }; + public List ToDoubleList() => new() { a.position.x, a.position.y, a.position.z, + b.position.x, b.position.y, b.position.z }; + + public static Line operator +(Line a, Line b) => new(a.a + b.a, a.b + b.b); + public static Line operator +(Line a, Vert b) => new(a.a + b, a.b + b); + public static Line operator -(Line l) => new(-l.a, -l.b); + public static Line operator -(Line a, Line b) => new(a.a - b.a, a.b - b.b); + public static Line operator -(Line a, Vert b) => new(a.a - b, a.b - b); + public static Line operator *(Line a, Line b) => new(a.a * b.a, a.b * b.b); + public static Line operator *(Line a, Vert b) => new(a.a * b, a.b * b); + public static Line operator *(Line a, double b) => new(a.a * b, a.b * b); + public static Line operator /(Line a, Line b) => new(a.a / b.a, a.b / b.b); + public static Line operator /(Line a, Vert b) => new(a.a / b, a.b / b); + public static Line operator /(Line a, double b) => new(a.a / b, a.b / b); public static bool operator ==(Line a, Line b) => a.Equals(b); public static bool operator !=(Line a, Line b) => !a.Equals(b); + public static bool operator >(Line a, Line b) => a.CompareTo(b) > 0; + public static bool operator <(Line a, Line b) => a.CompareTo(b) < 0; + public static bool operator >=(Line a, Line b) => a > b || a == b; + public static bool operator <=(Line a, Line b) => a < b || a == b; public static implicit operator Line(Fill fill) => new(fill); public static implicit operator Line(Fill fill) => new(fill); diff --git a/Nerd_STF/Mathematics/Geometry/Polygon.cs b/Nerd_STF/Mathematics/Geometry/Polygon.cs new file mode 100644 index 0000000..8fe2a34 --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Polygon.cs @@ -0,0 +1,519 @@ +using Nerd_STF.Exceptions; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF.Mathematics.Geometry +{ + public struct Polygon : ICloneable, IEquatable, IGroup, ISubdividable, ITriangulatable + { + public Line[] Lines + { + get => p_lines; + set + { + p_lines = value; + p_verts = GenerateVerts(value); + } + } + public Vert[] Verts + { + get => p_verts; + set + { + p_verts = value; + p_lines = GenerateLines(value); + } + } + + private Line[] p_lines; + private Vert[] p_verts; + + [Obsolete("This method uses the Polygon.Triangulate() function, which has issues. It will be fixed in a " + + "future update.")] + public double Area + { + get + { + double val = 0; + foreach (Triangle t in Triangulate()) val += t.Area; + return val; + } + } + public double Perimeter + { + get + { + double val = 0; + foreach (Line l in Lines) val += l.Length; + return val; + } + } + + public Polygon() + { + p_lines = Array.Empty(); + p_verts = Array.Empty(); + } + public Polygon(Fill fill) + { + List verts = new(); + int i = 0; + while (true) + { + Vert? v = fill(i); + if (!v.HasValue) break; + verts.Add(v.Value); + } + this = new(verts.ToArray()); + } + public Polygon(Fill fill) + { + List verts = new(); + int i = 0; + while (true) + { + Vert? v = fill(i); + if (!v.HasValue) break; + verts.Add(v.Value); + } + this = new(verts.ToArray()); + } + public Polygon(Fill fill) + { + List lines = new(); + int i = 0; + while (true) + { + Line? v = fill(i); + if (!v.HasValue) break; + lines.Add(v.Value); + } + this = new(lines.ToArray()); + } + public Polygon(Fill fill, int length) + { + List verts = new(); + for (int i = 0; i < length; i++) verts.Add(fill(i)); + this = new(verts.ToArray()); + } + public Polygon(Fill fill, int length) + { + List verts = new(); + for (int i = 0; i < length; i++) verts.Add(fill(i)); + this = new(verts.ToArray()); + } + public Polygon(Fill fill, int length) + { + List lines = new(); + for (int i = 0; i < length; i++) lines.Add(fill(i)); + this = new(lines.ToArray()); + } + public Polygon(params Double3[] verts) + { + p_verts = new Vert[verts.Length]; + for (int i = 0; i < verts.Length; i++) p_verts[i] = verts[i]; + p_lines = GenerateLines(p_verts); + } + public Polygon(params Vert[] verts) + { + p_verts = verts; + p_lines = GenerateLines(verts); + } + public Polygon(params Line[] lines) + { + p_lines = lines; + p_verts = GenerateVerts(lines); + } + + public Vert this[int index] + { + get => Verts[index]; + set => Verts[index] = value; + } + + public static Polygon CreateCircle(int vertCount) + { + List parts = new(); + for (int i = 0; i < vertCount; i++) + { + double val = Mathf.Tau * i / vertCount; + parts.Add(new(Mathf.Cos(val), Mathf.Sin(val))); + } + return new(parts.ToArray()); + } + + public static Polygon Absolute(Polygon val) + { + Vert[] v = val.Verts; + for (int i = 0; i < v.Length; i++) v[i] = Vert.Absolute(v[i]); + return new(v); + } + public static Polygon Average(params Polygon[] vals) + { + if (!CheckVerts(vals)) throw new DifferingVertCountException(nameof(vals), vals); + if (vals.Length < 1) return default; + + Line[][] lines = new Line[vals.Length][]; + for (int i = 0; i < vals.Length; i++) lines[i] = vals[i].Lines; + + Line[] res = new Line[vals[0].Lines.Length]; + for (int i = 0; i < res.Length; i++) + { + Line[] row = new Line[vals.Length]; + for (int j = 0; j < vals[0].Lines.Length; j++) row[j] = vals[j].Lines[i]; + res[i] = Line.Average(row); + } + + return new(res); + } + public static Polygon Ceiling(Polygon val) + { + Vert[] v = val.Verts; + for (int i = 0; i < v.Length; i++) v[i] = Vert.Ceiling(v[i]); + return new(v); + } + public static Polygon Clamp(Polygon val, Polygon min, Polygon max) + { + if (!CheckVerts(val, min, max)) throw new DifferingVertCountException(val, min, max); + Line[][] lines = new Line[3][] { val.Lines, min.Lines, max.Lines }; + Line[] res = new Line[val.Lines.Length]; + for (int i = 0; i < res.Length; i++) res[i] = Line.Clamp(lines[0][i], lines[1][i], lines[2][i]); + return new(res); + } + public static Polygon Floor(Polygon val) + { + Vert[] v = val.Verts; + for (int i = 0; i < v.Length; i++) v[i] = Vert.Floor(v[i]); + return new(v); + } + public static Polygon Lerp(Polygon a, Polygon b, double t, bool clamp = true) + { + if (!CheckVerts(a, b)) throw new DifferingVertCountException(a, b); + Line[][] lines = new Line[2][] { a.Lines, b.Lines }; + Line[] res = new Line[a.Lines.Length]; + for (int i = 0; i < res.Length; i++) res[i] = Line.Lerp(lines[0][i], lines[1][i], t, clamp); + return new(res); + } + public static Polygon Max(params Polygon[] vals) + { + if (!CheckVerts(vals)) throw new DifferingVertCountException(nameof(vals), vals); + if (vals.Length < 1) return default; + + Line[][] lines = new Line[vals.Length][]; + for (int i = 0; i < vals.Length; i++) lines[i] = vals[i].Lines; + + Line[] res = new Line[vals[0].Lines.Length]; + for (int i = 0; i < res.Length; i++) + { + Line[] row = new Line[vals.Length]; + for (int j = 0; j < vals[0].Lines.Length; j++) row[j] = vals[j].Lines[i]; + res[i] = Line.Max(row); + } + + return new(res); + } + public static Polygon Median(params Polygon[] vals) + { + if (!CheckVerts(vals)) throw new DifferingVertCountException(nameof(vals), vals); + if (vals.Length < 1) return default; + + Line[][] lines = new Line[vals.Length][]; + for (int i = 0; i < vals.Length; i++) lines[i] = vals[i].Lines; + + Line[] res = new Line[vals[0].Lines.Length]; + for (int i = 0; i < res.Length; i++) + { + Line[] row = new Line[vals.Length]; + for (int j = 0; j < vals[0].Lines.Length; j++) row[j] = vals[j].Lines[i]; + res[i] = Line.Median(row); + } + + return new(res); + } + public static Polygon Min(params Polygon[] vals) + { + if (!CheckVerts(vals)) throw new DifferingVertCountException(nameof(vals), vals); + if (vals.Length < 1) return default; + + Line[][] lines = new Line[vals.Length][]; + for (int i = 0; i < vals.Length; i++) lines[i] = vals[i].Lines; + + Line[] res = new Line[vals[0].Lines.Length]; + for (int i = 0; i < res.Length; i++) + { + Line[] row = new Line[vals.Length]; + for (int j = 0; j < vals[0].Lines.Length; j++) row[j] = vals[j].Lines[i]; + res[i] = Line.Min(row); + } + + return new(res); + } + + public static double[] ToDoubleArrayAll(params Polygon[] polys) => ToDoubleListAll(polys).ToArray(); + public static List ToDoubleListAll(params Polygon[] polys) + { + List vals = new(); + foreach (Polygon poly in polys) vals.AddRange(poly.ToDoubleArray()); + return vals; + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Polygon)) return false; + return Equals((Polygon)obj); + } + public bool Equals(Polygon other) + { + if (!CheckVerts(this, other)) return false; + return Lines == other.Lines; + } + public override int GetHashCode() => Lines.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(string? provider) + { + string s = ""; + for (int i = 0; i < Lines.Length; i++) s += "L" + i + ": " + Lines[i].ToString(provider) + " "; + return s; + } + public string ToString(IFormatProvider provider) + { + string s = ""; + for (int i = 0; i < Lines.Length; i++) s += "L" + i + ": " + Lines[i].ToString(provider) + " "; + return s; + } + + public object Clone() => new Polygon(Lines); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() { foreach (Vert v in Verts) yield return v; } + + public Vert[] ToArray() => Verts; + public List ToList() => new(Verts); + + public double[] ToDoubleArray() + { + double[] vals = new double[Verts.Length * 3]; + for (int i = 0; i < Verts.Length; i++) + { + int pos = i * 3; + vals[pos + 0] = Verts[i].position.x; + vals[pos + 1] = Verts[i].position.y; + vals[pos + 2] = Verts[i].position.z; + } + return vals; + } + public List ToDoubleList() => new(ToDoubleArray()); + + public Polygon Subdivide() + { + Polygon poly = new(); + List lines = new(); + for (int i = 0; i < Lines.Length; i++) lines.AddRange(Lines[i].Subdivide()); + return poly; + } + public Polygon Subdivide(int iterations) + { + if (iterations < 1) return new(); + Polygon poly = this; + for (int i = 0; i < iterations; i++) poly = poly.Subdivide(); + return poly; + } + + public Polygon SubdivideCatmullClark(int segments) + { + // Thanks Saalty for making this accidentally. + List newVerts = new(); + for (int i = 0; i < Verts.Length; i++) + { + for (int factor = 0; factor < segments; factor++) + { + double unit = factor / (double)(segments * 2), unit2 = unit + 0.5, lastUnit = unit * 2; + Vert p1, p2; + if (i == Verts.Length - 1) + { + p1 = Verts[^1] + (Verts[0] - Verts[^1]) * unit2; + p2 = Verts[0] + (Verts[1] - Verts[0]) * unit; + } + else if (i == Verts.Length - 2) + { + p1 = Verts[^2] + (Verts[^1] - Verts[^2]) * unit2; + p2 = Verts[^1] + (Verts[0] - Verts[^1]) * unit; + } + else + { + p1 = Verts[i] + (Verts[i + 1] - Verts[i]) * unit2; + p2 = Verts[i + 1] + (Verts[i + 2] - Verts[i + 1]) * unit; + } + newVerts.Add(p1 + (p2 - p1) * lastUnit); + } + } + return new(newVerts.ToArray()); + } + + [Obsolete("This method doesn't work very well, and will give very weird results in certain cases. " + + "This will be fixed in a future update.")] + public Triangle[] Triangulate() + { + // This may cause issues. FIXME + // Tbh, not even sure if this works. This was a bit confusing. + + if (Verts.Length == 3) return new Triangle[] { new(Verts[0], Verts[1], Verts[2]) }; + + (int posA, int posB, Line line)? closest = null; + for (int i = 0; i < Verts.Length; i++) + { + for (int j = 0; j < Verts.Length; j++) + { + if (i == j) continue; + Line l = new(Verts[i], Verts[j]); + if (Lines.Contains(l)) continue; + + if (!closest.HasValue || closest.Value.line.Length > l.Length) closest = (i, j, l); + } + } + + if (closest == null) throw new("Unknown error triangulating the polygon."); + + if (closest.Value.posB > closest.Value.posA) + closest = (closest.Value.posB, closest.Value.posA, closest.Value.line); + + List partA = new(Lines[closest.Value.posA..(closest.Value.posB - 1)]); + partA.Add(closest.Value.line); + + Polygon pA = new(partA.ToArray()); + + List partB = new(Lines[0..(closest.Value.posA - 1)]); + partB.AddRange(Lines[closest.Value.posB..(Lines.Length - 1)]); + partB.Add(closest.Value.line); + + Polygon pB = new(partB.ToArray()); + + List tris = new(pA.Triangulate()); + tris.AddRange(pB.Triangulate()); + return tris.ToArray(); + } + + private static bool CheckVerts(params Polygon[] polys) + { + int len = -1; + foreach (Polygon poly in polys) + { + if (len == -1) + { + len = poly.Verts.Length; + continue; + } + if (poly.Verts.Length != len) return false; + } + return true; + } + private static Line[] GenerateLines(Vert[] verts) + { + Line[] lines = new Line[verts.Length]; + for (int i = 0; i < lines.Length; i++) + lines[i] = new(verts[i], verts[i == lines.Length - 1 ? 0 : i + 1]); + return lines; + } + private static Vert[] GenerateVerts(Line[] lines) + { + Vert[] verts = new Vert[lines.Length]; + for (int i = 0; i < verts.Length; i++) + { + verts[i] = lines[i].a; + if (lines[i].b != lines[i == verts.Length - 1 ? 0 : i + 1].a) + throw new DisconnectedLinesException(nameof(lines), lines); + } + return verts; + } + + public static Polygon operator +(Polygon a, Polygon b) + { + if (!CheckVerts(a, b)) throw new DifferingVertCountException(a, b); + Line[][] lines = new Line[2][] { a.Lines, b.Lines }; + Line[] res = new Line[a.Lines.Length]; + for (int i = 0; i < res.Length; i++) res[i] = lines[0][i] + lines[1][i]; + return new(res); + } + public static Polygon operator +(Polygon a, Vert b) + { + Line[] lines = a.Lines; + for (int i = 0; i < lines.Length; i++) lines[i] += b; + return new(lines); + } + public static Polygon operator -(Polygon p) + { + Line[] lines = p.Lines; + for (int i = 0; i < lines.Length; i++) lines[i] = -lines[i]; + return new(lines); + } + public static Polygon operator -(Polygon a, Polygon b) + { + if (!CheckVerts(a, b)) throw new DifferingVertCountException(a, b); + Line[][] lines = new Line[2][] { a.Lines, b.Lines }; + Line[] res = new Line[a.Lines.Length]; + for (int i = 0; i < res.Length; i++) res[i] = lines[0][i] - lines[1][i]; + return new(res); + } + public static Polygon operator -(Polygon a, Vert b) + { + Line[] lines = a.Lines; + for (int i = 0; i < lines.Length; i++) lines[i] -= b; + return new(lines); + } + public static Polygon operator *(Polygon a, Polygon b) + { + if (!CheckVerts(a, b)) throw new DifferingVertCountException(a, b); + Line[][] lines = new Line[2][] { a.Lines, b.Lines }; + Line[] res = new Line[a.Lines.Length]; + for (int i = 0; i < res.Length; i++) res[i] = lines[0][i] * lines[1][i]; + return new(res); + } + public static Polygon operator *(Polygon a, Vert b) + { + Line[] lines = a.Lines; + for (int i = 0; i < lines.Length; i++) lines[i] *= b; + return new(lines); + } + public static Polygon operator *(Polygon a, float b) + { + Line[] lines = a.Lines; + for (int i = 0; i < lines.Length; i++) lines[i] *= b; + return new(lines); + } + public static Polygon operator /(Polygon a, Polygon b) + { + if (!CheckVerts(a, b)) throw new DifferingVertCountException(a, b); + Line[][] lines = new Line[2][] { a.Lines, b.Lines }; + Line[] res = new Line[a.Lines.Length]; + for (int i = 0; i < res.Length; i++) res[i] = lines[0][i] / lines[1][i]; + return new(res); + } + public static Polygon operator /(Polygon a, Vert b) + { + Line[] lines = a.Lines; + for (int i = 0; i < lines.Length; i++) lines[i] /= b; + return new(lines); + } + public static Polygon operator /(Polygon a, float b) + { + Line[] lines = a.Lines; + for (int i = 0; i < lines.Length; i++) lines[i] /= b; + return new(lines); + } + public static bool operator ==(Polygon a, Polygon b) => a.Equals(b); + public static bool operator !=(Polygon a, Polygon b) => !a.Equals(b); + + public static implicit operator Polygon(Fill fill) => new(fill); + public static implicit operator Polygon(Fill fill) => new(fill); + public static implicit operator Polygon(Fill fill) => new(fill); + public static implicit operator Polygon(Vert[] verts) => new(verts); + public static implicit operator Polygon(Double3[] verts) => new(verts); + public static implicit operator Polygon(Line[] lines) => new(lines); + public static implicit operator Polygon(Triangle tri) => new(tri.AB, tri.BC, tri.CA); + public static implicit operator Polygon(Quadrilateral quad) => new(quad.AB, quad.BC, quad.CD, quad.DA); + } +} diff --git a/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs b/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs new file mode 100644 index 0000000..6b5e70d --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs @@ -0,0 +1,342 @@ +using Nerd_STF.Exceptions; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF.Mathematics.Geometry +{ + public struct Quadrilateral : ICloneable, IEquatable, IGroup, ITriangulatable + { + public Vert A + { + get => p_a; + set + { + p_a = value; + p_ab.a = value; + p_da.b = value; + } + } + public Vert B + { + get => p_b; + set + { + p_b = value; + p_ab.b = value; + p_bc.a = value; + } + } + public Vert C + { + get => p_c; + set + { + p_c = value; + p_bc.b = value; + p_cd.a = value; + } + } + public Vert D + { + get => p_d; + set + { + p_d = value; + p_cd.b = value; + p_da.a = value; + } + } + public Line AB + { + get => p_ab; + set + { + p_ab = value; + p_a = value.a; + p_b = value.b; + p_bc.a = value.b; + p_da.b = value.a; + } + } + public Line BC + { + get => p_bc; + set + { + p_bc = value; + p_b = value.a; + p_c = value.b; + p_cd.a = value.b; + p_ab.b = value.a; + } + } + public Line CD + { + get => p_cd; + set + { + p_cd = value; + p_c = value.a; + p_d = value.b; + p_da.a = value.b; + p_bc.b = value.a; + } + } + public Line DA + { + get => p_da; + set + { + p_da = value; + p_d = value.a; + p_a = value.b; + p_ab.a = value.b; + p_cd.b = value.a; + } + } + + private Vert p_a, p_b, p_c, p_d; + private Line p_ab, p_bc, p_cd, p_da; + + public double Area + { + get + { + double val = 0; + foreach (Triangle t in Triangulate()) val += t.Area; + return val; + } + } + public double Perimeter => AB.Length + BC.Length + CD.Length + DA.Length; + + public Quadrilateral(Vert a, Vert b, Vert c, Vert d) + { + p_a = a; + p_b = b; + p_c = c; + p_d = d; + p_ab = new(a, b); + p_bc = new(b, c); + p_cd = new(c, d); + p_da = new(d, a); + } + public Quadrilateral(Line ab, Line bc, Line cd, Line da) + { + if (ab.a != da.b || ab.b != bc.a || bc.b != cd.a || cd.b != da.a) + throw new DisconnectedLinesException(ab, bc, cd, da); + + p_a = ab.a; + p_b = bc.a; + p_c = cd.a; + p_d = da.a; + p_ab = ab; + p_bc = bc; + p_cd = cd; + p_da = da; + } + public Quadrilateral(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) + : this(new Vert(x1, y1), new(x2, y2), new(x3, y3), new(x4, y4)) { } + public Quadrilateral(double x1, double y1, double z1, double x2, double y2, double z2, double x3, double y3, + double z3, double x4, double y4, double z4) + : this(new Vert(x1, y1, z1), new(x2, y2, z2), new(x3, y3, z3), new(x4, y4, z4)) { } + public Quadrilateral(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + public Quadrilateral(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + public Quadrilateral(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + public Quadrilateral(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + public Quadrilateral(Fill fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4), fill(5), fill(6), + fill(7), fill(8), fill(9), fill(10), fill(11)) { } + public Quadrilateral(Fill fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4), fill(5), fill(6), + fill(7), fill(8), fill(9), fill(10), fill(11)) { } + + public Vert this[int index] + { + get => index switch + { + 0 => A, + 1 => B, + 2 => C, + 3 => D, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + set + { + switch (index) + { + case 0: + A = value; + break; + + case 1: + B = value; + break; + + case 2: + C = value; + break; + + case 3: + D = value; + break; + + default: throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public static Quadrilateral Absolute(Quadrilateral val) => + new(Vert.Absolute(val.A), Vert.Absolute(val.B), Vert.Absolute(val.C), Vert.Absolute(val.D)); + public static Quadrilateral Average(params Quadrilateral[] vals) + { + (Vert[] As, Vert[] Bs, Vert[] Cs, Vert[] Ds) = SplitVertArray(vals); + return new(Vert.Average(As), Vert.Average(Bs), Vert.Average(Cs), Vert.Average(Ds)); + } + public static Quadrilateral Ceiling(Quadrilateral val) => + new(Vert.Ceiling(val.A), Vert.Ceiling(val.B), Vert.Ceiling(val.C), Vert.Ceiling(val.D)); + public static Quadrilateral Clamp(Quadrilateral val, Quadrilateral min, Quadrilateral max) => + new(Vert.Clamp(val.A, min.A, max.A), Vert.Clamp(val.B, min.B, max.B), Vert.Clamp(val.C, min.C, max.C), + Vert.Clamp(val.D, min.D, max.D)); + public static Quadrilateral Floor(Quadrilateral val) => + new(Vert.Floor(val.A), Vert.Floor(val.B), Vert.Floor(val.C), Vert.Floor(val.D)); + public static Quadrilateral Lerp(Quadrilateral a, Quadrilateral b, double t, bool clamp = true) => + new(Vert.Lerp(a.A, b.A, t, clamp), Vert.Lerp(a.B, b.B, t, clamp), Vert.Lerp(a.C, b.C, t, clamp), + Vert.Lerp(a.D, b.D, t, clamp)); + public static Quadrilateral Max(params Quadrilateral[] vals) + { + (Vert[] As, Vert[] Bs, Vert[] Cs, Vert[] Ds) = SplitVertArray(vals); + return new(Vert.Max(As), Vert.Max(Bs), Vert.Max(Cs), Vert.Max(Ds)); + } + public static Quadrilateral Median(params Quadrilateral[] vals) + { + (Vert[] As, Vert[] Bs, Vert[] Cs, Vert[] Ds) = SplitVertArray(vals); + return new(Vert.Median(As), Vert.Median(Bs), Vert.Median(Cs), Vert.Median(Ds)); + } + public static Quadrilateral Min(params Quadrilateral[] vals) + { + (Vert[] As, Vert[] Bs, Vert[] Cs, Vert[] Ds) = SplitVertArray(vals); + return new(Vert.Min(As), Vert.Min(Bs), Vert.Min(Cs), Vert.Min(Ds)); + } + + public static (Vert[] As, Vert[] Bs, Vert[] Cs, Vert[] Ds) SplitVertArray(params Quadrilateral[] quads) + { + Vert[] a = new Vert[quads.Length], b = new Vert[quads.Length], + c = new Vert[quads.Length], d = new Vert[quads.Length]; + for (int i = 0; i < quads.Length; i++) + { + a[i] = quads[i].A; + b[i] = quads[i].B; + c[i] = quads[i].C; + d[i] = quads[i].D; + } + + return (a, b, c, d); + } + public static (Line[] ABs, Line[] BCs, Line[] CDs, Line[] DAs) SplitLineArray(params Quadrilateral[] quads) + { + Line[] ab = new Line[quads.Length], bc = new Line[quads.Length], + cd = new Line[quads.Length], da = new Line[quads.Length]; + for (int i = 0; i < quads.Length; i++) + { + ab[i] = quads[i].AB; + bc[i] = quads[i].BC; + cd[i] = quads[i].CD; + da[i] = quads[i].DA; + } + + return (ab, bc, cd, da); + } + + public static double[] ToDoubleArrayAll(params Quadrilateral[] quads) + { + double[] vals = new double[quads.Length * 12]; + for (int i = 0; i < quads.Length; i++) + { + int pos = i * 12; + vals[pos + 0] = quads[i].A.position.x; + vals[pos + 1] = quads[i].A.position.y; + vals[pos + 2] = quads[i].A.position.z; + vals[pos + 3] = quads[i].B.position.x; + vals[pos + 4] = quads[i].B.position.y; + vals[pos + 5] = quads[i].B.position.z; + vals[pos + 6] = quads[i].C.position.x; + vals[pos + 7] = quads[i].C.position.y; + vals[pos + 8] = quads[i].C.position.z; + vals[pos + 9] = quads[i].D.position.x; + vals[pos + 10] = quads[i].D.position.y; + vals[pos + 11] = quads[i].D.position.z; + } + return vals; + } + public static List ToDoubleListAll(params Quadrilateral[] quads) => new(ToDoubleArrayAll(quads)); + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null || obj.GetType() != typeof(Quadrilateral)) return false; + return Equals((Quadrilateral)obj); + } + public bool Equals(Quadrilateral other) => A == other.A && B == other.B && C == other.C && D == other.D; + public override int GetHashCode() => A.GetHashCode() ^ B.GetHashCode() ^ C.GetHashCode() ^ D.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(string? provider) => "A: " + A.ToString(provider) + " B: " + B.ToString(provider) + + " C: " + C.ToString(provider) + " D: " + D.ToString(provider); + public string ToString(IFormatProvider provider) => "A: " + A.ToString(provider) + " B: " + + B.ToString(provider) + " C: " + C.ToString(provider) + " D: " + D.ToString(provider); + + public object Clone() => new Quadrilateral(A, B, C, D); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return A; + yield return B; + yield return C; + yield return D; + } + + public Vert[] ToArray() => new Vert[] { A, B, C, D }; + public List ToList() => new() { A, B, C, D }; + + public double[] ToDoubleArray() => new double[] { A.position.x, A.position.y, A.position.z, + B.position.x, B.position.y, B.position.z, + C.position.x, C.position.y, C.position.z, + D.position.x, D.position.y, D.position.z }; + public List ToDoubleList() => new() { A.position.x, A.position.y, A.position.z, + B.position.x, B.position.y, B.position.z, + C.position.x, C.position.y, C.position.z, + D.position.x, D.position.y, D.position.z }; + + public Triangle[] Triangulate() => new Line(A, C).Length > new Line(B, D).Length ? + new Triangle[] { new(A, B, C), new(C, D, A) } : new Triangle[] { new(B, C, D), new(D, A, B) }; + + public static Quadrilateral operator +(Quadrilateral a, Quadrilateral b) => new(a.A + b.A, a.B + b.B, + a.C + b.C, a.D + b.D); + public static Quadrilateral operator +(Quadrilateral a, Vert b) => new(a.A + b, a.B + b, a.C + b, a.D + b); + public static Quadrilateral operator -(Quadrilateral q) => new(-q.A, -q.B, -q.C, -q.D); + public static Quadrilateral operator -(Quadrilateral a, Quadrilateral b) => new(a.A - b.A, a.B - b.B, + a.C - b.C, a.D - b.D); + public static Quadrilateral operator -(Quadrilateral a, Vert b) => new(a.A - b, a.B - b, a.C - b, a.D - b); + public static Quadrilateral operator *(Quadrilateral a, Quadrilateral b) => new(a.A * b.A, a.B * b.B, + a.C * b.C, a.D * b.D); + public static Quadrilateral operator *(Quadrilateral a, Vert b) => new(a.A * b, a.B * b, a.C * b, a.D * b); + public static Quadrilateral operator *(Quadrilateral a, double b) => new(a.A * b, a.B * b, a.C * b, a.D * b); + public static Quadrilateral operator /(Quadrilateral a, Quadrilateral b) => new(a.A / b.A, a.B / b.B, + a.C / b.C, a.D / b.D); + public static Quadrilateral operator /(Quadrilateral a, Vert b) => new(a.A / b, a.B / b, a.C / b, a.D / b); + public static Quadrilateral operator /(Quadrilateral a, double b) => new(a.A / b, a.B / b, a.C / b, a.D / b); + public static bool operator ==(Quadrilateral a, Quadrilateral b) => a.Equals(b); + public static bool operator !=(Quadrilateral a, Quadrilateral b) => !a.Equals(b); + + public static implicit operator Quadrilateral(Fill fill) => new(fill); + public static implicit operator Quadrilateral(Fill fill) => new(fill); + public static implicit operator Quadrilateral(Fill fill) => new(fill); + public static implicit operator Quadrilateral(Fill fill) => new(fill); + public static implicit operator Quadrilateral(Fill fill) => new(fill); + public static implicit operator Quadrilateral(Fill fill) => new(fill); + public static explicit operator Quadrilateral(Polygon poly) => new(poly.Lines[0], poly.Lines[1], + poly.Lines[2], poly.Lines[3]); + } +} diff --git a/Nerd_STF/Mathematics/Geometry/Sphere.cs b/Nerd_STF/Mathematics/Geometry/Sphere.cs new file mode 100644 index 0000000..ba85ee3 --- /dev/null +++ b/Nerd_STF/Mathematics/Geometry/Sphere.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nerd_STF.Mathematics.Geometry +{ + public struct Sphere : ICloneable, IClosest, IComparable, IComparable, IContainer, + IEquatable, IEquatable + { + public static Sphere Unit => new(Vert.Zero, 1); + + public Vert center; + public double radius; + + public double SurfaceArea => 4 * Mathf.Pi * radius * radius; + public double Volume => 4 / 3 * (Mathf.Pi * radius * radius * radius); + + public static Sphere FromDiameter(Vert a, Vert b) => new(Vert.Average(a, b), (a - b).Magnitude / 2); + public static Sphere FromRadius(Vert center, Vert radius) => new(center, (center - radius).Magnitude); + + public Sphere(Vert center, double radius) + { + this.center = center; + this.radius = radius; + } + public Sphere(double cX, double cY, double radius) : this(new Vert(cX, cY), radius) { } + public Sphere(double cX, double cY, double cZ, double radius) : this(new Vert(cX, cY, cZ), radius) { } + public Sphere(Fill fill, double radius) : this(new Vert(fill), radius) { } + public Sphere(Fill fill) : this(new Vert(fill), fill(3)) { } + public Sphere(Fill fill, double radius) : this(new Vert(fill), radius) { } + public Sphere(Fill fill) : this(new Vert(fill), fill(3)) { } + public Sphere(Fill fill, double radius) : this(fill(0), radius) { } + public Sphere(Fill fillA, Fill fillB) : this(fillA(0), fillB(0)) { } + + public static Sphere Average(params Sphere[] vals) + { + (Vert[] centers, double[] radii) = SplitArray(vals); + return new(Vert.Average(centers), Mathf.Average(radii)); + } + public static Sphere Ceiling(Sphere val) => new(Vert.Ceiling(val.center), Mathf.Ceiling(val.radius)); + public static Sphere Clamp(Sphere val, Sphere min, Sphere max) => + new(Vert.Clamp(val.center, min.center, max.center), Mathf.Clamp(val.radius, min.radius, max.radius)); + public static Sphere Floor(Sphere val) => new(Vert.Floor(val.center), Mathf.Floor(val.radius)); + public static Sphere Lerp(Sphere a, Sphere b, double t, bool clamp = true) => + new(Vert.Lerp(a.center, b.center, t, clamp), Mathf.Lerp(a.radius, b.radius, t, clamp)); + public static Sphere Max(params Sphere[] vals) + { + (Vert[] centers, double[] radii) = SplitArray(vals); + return new(Vert.Max(centers), Mathf.Max(radii)); + } + public static Sphere Median(params Sphere[] vals) + { + (Vert[] centers, double[] radii) = SplitArray(vals); + return new(Vert.Median(centers), Mathf.Median(radii)); + } + public static Sphere Min(params Sphere[] vals) + { + (Vert[] centers, double[] radii) = SplitArray(vals); + return new(Vert.Min(centers), Mathf.Min(radii)); + } + + public static (Vert[] centers, double[] radii) SplitArray(params Sphere[] spheres) + { + Vert[] centers = new Vert[spheres.Length]; + double[] radii = new double[spheres.Length]; + for (int i = 0; i < spheres.Length; i++) + { + centers[i] = spheres[i].center; + radii[i] = spheres[i].radius; + } + return (centers, radii); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null) return false; + Type type = obj.GetType(); + if (type == typeof(Sphere)) return Equals((Sphere)obj); + if (type == typeof(double)) return Equals((double)obj); + return false; + } + public bool Equals(double other) => Volume == other; + public bool Equals(Sphere other) => center == other.center && radius == other.radius; + public override int GetHashCode() => center.GetHashCode() ^ radius.GetHashCode(); + public override string ToString() => ToString((string?)null); + public string ToString(string? provider) => "Center: " + center.ToString(provider) + + " Radius: " + radius.ToString(provider); + public string ToString(IFormatProvider provider) => "Center: " + center.ToString(provider) + + " Radius: " + radius.ToString(provider); + + public object Clone() => new Sphere(center, radius); + + public int CompareTo(Sphere sphere) => Volume.CompareTo(sphere.Volume); + public int CompareTo(double volume) => Volume.CompareTo(volume); + + public bool Contains(Vert vert) => (center - vert).Magnitude <= radius; + + public Vert ClosestTo(Vert vert) => Contains(vert) ? vert : ((vert - center).Normalized * radius) + vert; + + public static Sphere operator +(Sphere a, Sphere b) => new(a.center + b.center, a.radius + b.radius); + public static Sphere operator +(Sphere a, Vert b) => new(a.center + b, a.radius); + public static Sphere operator +(Sphere a, double b) => new(a.center, a.radius + b); + public static Sphere operator -(Sphere a, Sphere b) => new(a.center + b.center, a.radius + b.radius); + public static Sphere operator -(Sphere a, Vert b) => new(a.center + b, a.radius); + public static Sphere operator -(Sphere a, double b) => new(a.center, a.radius + b); + public static Sphere operator *(Sphere a, Sphere b) => new(a.center * b.center, a.radius * b.radius); + public static Sphere operator *(Sphere a, double b) => new(a.center * b, a.radius * b); + public static Sphere operator /(Sphere a, Sphere b) => new(a.center * b.center, a.radius * b.radius); + public static Sphere operator /(Sphere a, double b) => new(a.center * b, a.radius * b); + public static bool operator ==(Sphere a, Sphere b) => a.Equals(b); + public static bool operator !=(Sphere a, Sphere b) => !a.Equals(b); + public static bool operator ==(Sphere a, double b) => a.Equals(b); + public static bool operator !=(Sphere a, double b) => !a.Equals(b); + public static bool operator >(Sphere a, Sphere b) => a.CompareTo(b) > 0; + public static bool operator <(Sphere a, Sphere b) => a.CompareTo(b) < 0; + public static bool operator >(Sphere a, double b) => a.CompareTo(b) > 0; + public static bool operator <(Sphere a, double b) => a.CompareTo(b) < 0; + public static bool operator >=(Sphere a, Sphere b) => a > b || a == b; + public static bool operator <=(Sphere a, Sphere b) => a < b || a == b; + public static bool operator >=(Sphere a, double b) => a > b || a == b; + public static bool operator <=(Sphere a, double b) => a < b || a == b; + } +} diff --git a/Nerd_STF/Mathematics/Geometry/Triangle.cs b/Nerd_STF/Mathematics/Geometry/Triangle.cs index 9b6e631..b1fcc62 100644 --- a/Nerd_STF/Mathematics/Geometry/Triangle.cs +++ b/Nerd_STF/Mathematics/Geometry/Triangle.cs @@ -1,4 +1,5 @@ -using System.Collections; +using Nerd_STF.Exceptions; +using System.Collections; using System.Diagnostics.CodeAnalysis; namespace Nerd_STF.Mathematics.Geometry @@ -11,8 +12,8 @@ namespace Nerd_STF.Mathematics.Geometry set { p_a = value; - p_l1 = new(value, p_b); - p_l3 = new(p_c, value); + p_ab.a = value; + p_ca.b = value; } } public Vert B @@ -21,8 +22,8 @@ namespace Nerd_STF.Mathematics.Geometry set { p_b = value; - p_l1 = new(p_a, value); - p_l2 = new(value, p_c); + p_ab.b = value; + p_bc.a = value; } } public Vert C @@ -31,67 +32,74 @@ namespace Nerd_STF.Mathematics.Geometry set { p_c = value; - p_l2 = new(p_b, value); - p_l3 = new(value, p_a); + p_bc.b = value; + p_ca.a = value; } } - public Line L1 + public Line AB { - get => p_l1; + get => p_ab; set { - p_a = value.start; - p_b = value.end; - p_l2 = new(value.end, p_c); - p_l3 = new(p_c, value.start); + p_ab = value; + p_a = value.a; + p_b = value.b; + p_bc.a = value.b; + p_ca.b = value.a; } } - public Line L2 + public Line BC { - get => p_l2; + get => p_bc; set { - p_b = value.start; - p_c = value.end; - p_l1 = new(p_a, value.start); - p_l3 = new(value.end, p_a); + p_bc = value; + p_b = value.a; + p_c = value.b; + p_ca.a = value.b; + p_ab.b = value.a; } } - public Line L3 + public Line CA { - get => p_l3; + get => p_ca; set { - p_a = value.end; - p_c = value.start; - p_l1 = new(value.end, p_b); - p_l2 = new(p_b, value.start); + p_ca = value; + p_a = value.b; + p_c = value.a; + p_ab.a = value.b; + p_bc.b = value.a; } } private Vert p_a, p_b, p_c; - private Line p_l1, p_l2, p_l3; + private Line p_ab, p_bc, p_ca; + + public double Area => Mathf.Absolute((A.position.x * B.position.y) + (B.position.x * C.position.y) + (C.position.x * A.position.y) - + ((B.position.x * A.position.y) + (C.position.x * B.position.y) + (A.position.x * C.position.y))) * 0.5; + public double Perimeter => AB.Length + BC.Length + CA.Length; public Triangle(Vert a, Vert b, Vert c) { p_a = a; p_b = b; p_c = c; - p_l1 = new(a, b); - p_l2 = new(b, c); - p_l3 = new(c, a); + p_ab = new(a, b); + p_bc = new(b, c); + p_ca = new(c, a); } - public Triangle(Line l1, Line l2, Line l3) + public Triangle(Line ab, Line bc, Line ca) { - if (l1.start != l3.end && l1.end != l2.start && l2.end != l3.start) - throw new ArgumentException("Lines are not connected."); + if (ab.a != ca.b || ab.b != bc.a || bc.b != ca.a) + throw new DisconnectedLinesException(ab, bc, ca); - p_a = l1.start; - p_b = l2.start; - p_c = l3.start; - p_l1 = l1; - p_l2 = l2; - p_l3 = l3; + p_a = ab.a; + p_b = bc.a; + p_c = ca.a; + p_ab = ab; + p_bc = bc; + p_ca = ca; } public Triangle(double x1, double y1, double x2, double y2, double x3, double y3) : this(new Vert(x1, y1), new Vert(x2, y2), new Vert(x3, y3)) { } @@ -151,18 +159,18 @@ namespace Nerd_STF.Mathematics.Geometry new(Vert.Clamp(val.A, min.A, max.A), Vert.Clamp(val.B, min.B, max.B), Vert.Clamp(val.C, min.C, max.C)); public static Triangle Floor(Triangle val) => new(Vert.Floor(val.A), Vert.Floor(val.B), Vert.Floor(val.C)); - public static Triangle Lerp(Triangle a, Triangle b, double t, bool clamp = false) => + public static Triangle Lerp(Triangle a, Triangle b, double t, bool clamp = true) => new(Vert.Lerp(a.A, b.A, t, clamp), Vert.Lerp(a.B, b.B, t, clamp), Vert.Lerp(a.C, b.C, t, clamp)); - public static Triangle Median(params Triangle[] vals) - { - (Vert[] As, Vert[] Bs, Vert[] Cs) = SplitVertArray(vals); - return new(Vert.Median(As), Vert.Median(Bs), Vert.Median(Cs)); - } public static Triangle Max(params Triangle[] vals) { (Vert[] As, Vert[] Bs, Vert[] Cs) = SplitVertArray(vals); return new(Vert.Max(As), Vert.Max(Bs), Vert.Max(Cs)); } + public static Triangle Median(params Triangle[] vals) + { + (Vert[] As, Vert[] Bs, Vert[] Cs) = SplitVertArray(vals); + return new(Vert.Median(As), Vert.Median(Bs), Vert.Median(Cs)); + } public static Triangle Min(params Triangle[] vals) { (Vert[] As, Vert[] Bs, Vert[] Cs) = SplitVertArray(vals); @@ -181,19 +189,39 @@ namespace Nerd_STF.Mathematics.Geometry return (a, b, c); } - public static (Line[] L1s, Line[] L2s, Line[] L3s) SplitLineArray(params Triangle[] tris) + public static (Line[] ABs, Line[] BCs, Line[] CAs) SplitLineArray(params Triangle[] tris) { - Line[] l1 = new Line[tris.Length], l2 = new Line[tris.Length], l3 = new Line[tris.Length]; + Line[] ab = new Line[tris.Length], bc = new Line[tris.Length], ca = new Line[tris.Length]; for (int i = 0; i < tris.Length; i++) { - l1[i] = tris[i].L1; - l2[i] = tris[i].L2; - l3[i] = tris[i].L3; + ab[i] = tris[i].AB; + bc[i] = tris[i].BC; + ca[i] = tris[i].CA; } - return (l1, l2, l3); + return (ab, bc, ca); } + public static double[] ToDoubleArrayAll(params Triangle[] tris) + { + double[] vals = new double[tris.Length * 9]; + for (int i = 0; i < tris.Length; i++) + { + int pos = i * 9; + vals[pos + 0] = tris[i].A.position.x; + vals[pos + 1] = tris[i].A.position.y; + vals[pos + 2] = tris[i].A.position.z; + vals[pos + 3] = tris[i].B.position.x; + vals[pos + 4] = tris[i].B.position.y; + vals[pos + 5] = tris[i].B.position.z; + vals[pos + 6] = tris[i].C.position.x; + vals[pos + 7] = tris[i].C.position.y; + vals[pos + 8] = tris[i].C.position.z; + } + return vals; + } + public static List ToDoubleListAll(params Triangle[] tris) => new(ToDoubleArrayAll(tris)); + public override bool Equals([NotNullWhen(true)] object? obj) { if (obj == null || obj.GetType() != typeof(Triangle)) return false; @@ -229,6 +257,7 @@ namespace Nerd_STF.Mathematics.Geometry public static Triangle operator +(Triangle a, Triangle b) => new(a.A + b.A, a.B + b.B, a.C + b.C); public static Triangle operator +(Triangle a, Vert b) => new(a.A + b, a.B + b, a.C + b); + public static Triangle operator -(Triangle t) => new(-t.A, -t.B, -t.C); public static Triangle operator -(Triangle a, Triangle b) => new(a.A - b.A, a.B - b.B, a.C - b.C); public static Triangle operator -(Triangle a, Vert b) => new(a.A - b, a.B - b, a.C - b); public static Triangle operator *(Triangle a, Triangle b) => new(a.A * b.A, a.B * b.B, a.C * b.C); @@ -246,5 +275,6 @@ namespace Nerd_STF.Mathematics.Geometry public static implicit operator Triangle(Fill fill) => new(fill); public static implicit operator Triangle(Fill fill) => new(fill); public static implicit operator Triangle(Fill fill) => new(fill); + public static explicit operator Triangle(Polygon poly) => new(poly.Lines[0], poly.Lines[1], poly.Lines[2]); } } diff --git a/Nerd_STF/Mathematics/Int2.cs b/Nerd_STF/Mathematics/Int2.cs index 7023457..7d8f476 100644 --- a/Nerd_STF/Mathematics/Int2.cs +++ b/Nerd_STF/Mathematics/Int2.cs @@ -129,11 +129,7 @@ namespace Nerd_STF.Mathematics return val; } - public int CompareTo(Int2 other) - { - double magA = Magnitude, magB = other.Magnitude; - return magA == magB ? 0 : magA > magB ? 1 : -1; - } + public int CompareTo(Int2 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { if (obj == null || obj.GetType() != typeof(Int2)) return false; @@ -166,6 +162,9 @@ namespace Nerd_STF.Mathematics public static Int2 operator *(Int2 a, int b) => new(a.x * b, a.y * b); public static Int2 operator /(Int2 a, Int2 b) => new(a.x / b.x, a.y / b.y); public static Int2 operator /(Int2 a, int b) => new(a.x / b, a.y / b); + public static Int2 operator &(Int2 a, Int2 b) => new(a.x & b.x, a.y & b.y); + public static Int2 operator |(Int2 a, Int2 b) => new(a.x | b.x, a.y | b.y); + public static Int2 operator ^(Int2 a, Int2 b) => new(a.x ^ b.x, a.y ^ b.y); public static bool operator ==(Int2 a, Int2 b) => a.Equals(b); public static bool operator !=(Int2 a, Int2 b) => !a.Equals(b); public static bool operator >(Int2 a, Int2 b) => a.CompareTo(b) > 0; diff --git a/Nerd_STF/Mathematics/Int3.cs b/Nerd_STF/Mathematics/Int3.cs index 20b827e..1cd49ba 100644 --- a/Nerd_STF/Mathematics/Int3.cs +++ b/Nerd_STF/Mathematics/Int3.cs @@ -149,11 +149,7 @@ namespace Nerd_STF.Mathematics return val; } - public int CompareTo(Int3 other) - { - double magA = Magnitude, magB = other.Magnitude; - return magA == magB ? 0 : magA > magB ? 1 : -1; - } + public int CompareTo(Int3 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { if (obj == null || obj.GetType() != typeof(Int3)) return false; @@ -187,6 +183,9 @@ namespace Nerd_STF.Mathematics public static Int3 operator *(Int3 a, int b) => new(a.x * b, a.y * b, a.z * b); public static Int3 operator /(Int3 a, Int3 b) => new(a.x / b.x, a.y / b.y, a.z / b.z); public static Int3 operator /(Int3 a, int b) => new(a.x / b, a.y / b, a.z / b); + public static Int3 operator &(Int3 a, Int3 b) => new(a.x & b.x, a.y & b.y, a.z & b.z); + public static Int3 operator |(Int3 a, Int3 b) => new(a.x | b.x, a.y | b.y, a.z | b.z); + public static Int3 operator ^(Int3 a, Int3 b) => new(a.x ^ b.x, a.y ^ b.y, a.z ^ b.z); public static bool operator ==(Int3 a, Int3 b) => a.Equals(b); public static bool operator !=(Int3 a, Int3 b) => !a.Equals(b); public static bool operator >(Int3 a, Int3 b) => a.CompareTo(b) > 0; diff --git a/Nerd_STF/Mathematics/Int4.cs b/Nerd_STF/Mathematics/Int4.cs index c58bc39..a2a4ad2 100644 --- a/Nerd_STF/Mathematics/Int4.cs +++ b/Nerd_STF/Mathematics/Int4.cs @@ -162,11 +162,7 @@ namespace Nerd_STF.Mathematics return val; } - public int CompareTo(Int4 other) - { - double magA = Magnitude, magB = other.Magnitude; - return magA == magB ? 0 : magA > magB ? 1 : -1; - } + public int CompareTo(Int4 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { if (obj == null || obj.GetType() != typeof(Int4)) return false; @@ -203,6 +199,9 @@ namespace Nerd_STF.Mathematics public static Int4 operator *(Int4 a, int b) => new(a.x * b, a.y * b, a.z * b, a.w * b); public static Int4 operator /(Int4 a, Int4 b) => new(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); public static Int4 operator /(Int4 a, int b) => new(a.x / b, a.y / b, a.z / b, a.w / b); + public static Int4 operator &(Int4 a, Int4 b) => new(a.x & b.x, a.y & b.y, a.z & b.z, a.w & b.w); + public static Int4 operator |(Int4 a, Int4 b) => new(a.x | b.x, a.y | b.y, a.z | b.z, a.w | b.w); + public static Int4 operator ^(Int4 a, Int4 b) => new(a.x ^ b.x, a.y ^ b.y, a.z ^ b.z, a.w ^ b.w); public static bool operator ==(Int4 a, Int4 b) => a.Equals(b); public static bool operator !=(Int4 a, Int4 b) => !a.Equals(b); public static bool operator >(Int4 a, Int4 b) => a.CompareTo(b) > 0; diff --git a/Nerd_STF/Mathematics/Mathf.cs b/Nerd_STF/Mathematics/Mathf.cs index 2fd958c..10118ad 100644 --- a/Nerd_STF/Mathematics/Mathf.cs +++ b/Nerd_STF/Mathematics/Mathf.cs @@ -2,12 +2,12 @@ { public static class Mathf { - public const double DegToRad = 0.0174532925199; // Pi / 180 + public const double RadToDeg = 0.0174532925199; // Pi / 180 public const double E = 2.71828182846; public const double GoldenRatio = 1.61803398875; // (1 + Sqrt(5)) / 2 public const double HalfPi = 1.57079632679; // Pi / 2 public const double Pi = 3.14159265359; - public const double RadToDeg = 57.2957795131; // 180 / Pi + public const double DegToRad = 57.2957795131; // 180 / Pi public const double Tau = 6.28318530718; // 2 * Pi public static double Absolute(double val) => val < 0 ? -val : val; @@ -26,6 +26,12 @@ public static double ArcTan(double value) => ArcSin(value / Sqrt(1 + value * value)); + public static double Average(Equation equ, double min, double max, double step = Calculus.DefaultStep) + { + List vals = new(); + for (double x = min; x <= max; x += step) vals.Add(equ(x)); + return Average(vals.ToArray()); + } public static double Average(params double[] vals) => Sum(vals) / vals.Length; public static int Average(params int[] vals) => Sum(vals) / vals.Length; @@ -75,6 +81,14 @@ public static int Floor(double val) => (int)(val - (val % 1)); + public static Dictionary GetValues(Equation equ, double min, double max, + double step = Calculus.DefaultStep) + { + Dictionary vals = new(); + for (double x = min; x <= max; x += step) vals.Add(x, equ(x)); + return vals; + } + public static double Lerp(double a, double b, double t, bool clamp = true) { double v = a + t * (b - a); @@ -83,6 +97,30 @@ } public static int Lerp(int a, int b, double value, bool clamp = true) => Floor(Lerp(a, b, value, clamp)); + public static Equation MakeEquation(Dictionary vals) => (x) => + { + double min = -1, max = -1; + foreach (KeyValuePair val in vals) + { + if (val.Key <= x) min = val.Key; + if (val.Key >= x) max = val.Key; + + if (min != -1 && max != -1) break; + } + double per = x % (max - min); + return Lerp(min, max, per); + }; + + public static double Max(Equation equ, double min, double max, double step = Calculus.DefaultStep) + { + double Y = equ(min); + for (double x = min; x <= max; x += step) + { + double val = equ(x); + Y = val > Y ? val : Y; + } + return Y; + } public static double Max(params double[] vals) { if (vals.Length < 1) return 0; @@ -106,6 +144,16 @@ } public static int Median(params int[] vals) => vals[Floor(Average(0, vals.Length - 1))]; + public static double Min(Equation equ, double min, double max, double step = Calculus.DefaultStep) + { + double Y = equ(min); + for (double x = min; x <= max; x += step) + { + double val = equ(x); + Y = val < Y ? val : Y; + } + return Y; + } public static double Min(params double[] vals) { if (vals.Length < 1) return 0; diff --git a/Nerd_STF/Miscellaneous/GlobalUsings.cs b/Nerd_STF/Miscellaneous/GlobalUsings.cs new file mode 100644 index 0000000..d25b3b7 --- /dev/null +++ b/Nerd_STF/Miscellaneous/GlobalUsings.cs @@ -0,0 +1,12 @@ +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Net.Http; +global using System.Threading; +global using System.Threading.Tasks; +global using Nerd_STF; +global using Nerd_STF.Exceptions; +global using Nerd_STF.Mathematics; +global using Nerd_STF.Mathematics.Geometry; diff --git a/Nerd_STF/bin/Release/net6.0/ref/Nerd_STF.dll b/Nerd_STF/bin/Release/net6.0/ref/Nerd_STF.dll index 135be99fbeed21df5863a283ba6f84567f81c3f6..be99c01b8876c69956513e05cb96d269671ccef3 100644 GIT binary patch literal 47616 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≈8&#{idz2AIz2Z(xj2Z#mm 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 BLJ$A| literal 32256 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/Zd*WhB0i#fKYrv~V4a)9292k3XO1NeNo z2+&^uu$LsKY#9H$YfiU2X7^Yb`AM+MzT{8eU%Tj)4^Ow>f402#FN0vbUQhueTxQ?> zA9elqZK^KwQu>zhk7@_X5C}g)ZxPg$wcue7Yh*O4U0aJ?=Hv#g7_3Va*D$Aywl21D%KZN}ijdFYQ^YW>^ z`2+Lvsl7n@NA1PRZ!b)4FV@J%?S+X=p}i;;a0_|u&F7{fgmjaUonvt$++twbTUc=; z^7FV6`FY%k{5)<%ejYa>Up0a)ZbTt9qR@&3s1e+XLLlN54$Ld0RsiWAwE`<@g(L%B zt~*S(?#LW)vwC$eF?uBmM3*EXGx^*v9b!Us~UQBf_ z=Ke-K0Y9mRdKY6(q1C{=VyZrn{!#U5WmBgsA(IaR*2sq%*_4FhHWv#JiZFj!c}LV{z5p zVqmU%iCgs&x9Zq&)T|P>>LqT~OH|cMsp_TFmr!*+7^OfIC>@wrN_7X)KdL*ea4;kp z@N$h|x-~}TfSc8;aVggr1vx)w=LlS5w-}gfT@i~2?l_8z4~c`tHsY>z8e6QNDJbi#Q&>#A5>Edxu9=wQ<35}l|y4|*`~TCre8l5W5zcp zQmZ)wx0)?<6Tj8NxWRlLL2IcMbU3IL+(ebu|5*9{d%J>FId$XaYDeTY~cZoS15@Zf&9Qy*)|q;d1E74Jn{Z!(Viv@Nl!*gVHc z#ulYoll8IrPO=#xomjnl6H(t%Gj2-ZMr;B8RG}e)Bk-rL@b{yl*p+_W1br1DI>q4| zGpTgjv~=?L`I$^}%c&=w)R4%`Z=G9EmuftT+IJ#uK%YqEPRbU>b)|cCiG7amLk#yl zo!+*3PhC#<^iw4~K>DI$MMo%xCo^AEtoR7U@N5Vl`W4vW?(>jNmBjnq5l1M4=Q5vL z=7`n9-}d^v?Hs9Yc&bB3;^ZUNO}+1@_lifVBnp=qV)W-1_8&4FApjmp;Q_x`b|F1! ztd-HR$z*Dg_f8X!3}m>f3HQu!>$RS;RrmPfZ1^;JfbU0&9`1k)@muVc0G`g^@2GXb z4=m6iHPCzTc`vf%*cLgiMV7;h5r?Ugyl|sDxy|a~EoSuyEJeuC`9<%*^w*p_v$TZl zvMlSC|M%tGwAq*`Dw4@b7=F0@TZd`G;hV4I{dg#1d~!2-s4wk*`W`U&0Q3=}z4NAc z3c}~`KAleaCSo~fhY-cwqbP;!V(8iU49#q4rI-S%3ZJf-46cHXhfS}C18@C}-jY*y z*Vm_$6E6x$9{p7TFc38^a4#93u1Vt4HdUetpAJfKwjo@q7T!2gamNe0ff zc=|hD#1Od!8pl$O&p5?UlLTT=z0&wxLJZ~RbKPR#R<3J|YZ=4$V<=h%c}Br5#P={L zP7|WX@ChSoNd`J}4=5V;i= zZDl^EHN&D>I#|;d=Aks@pqd@IP0h$rhfh1ju}>Y;9g{9T58>}G@;0IEld-0KN3U9M zFU&@tXhPa-wI>C>+k= z0%u76&Jn(+#`~4>HKBfZLZoW>$@m4>8%g}5*E}%@+o?wU1};TCqZw_aS~j3<^!Ix| zofgCARO;|Kx0o1#<2wTXVxPMw=#TP7v7Ix$xOD8Pj()!-a`))x)pZ2#&l