From 85c112e0d866dd67e7180461e35465905e954f96 Mon Sep 17 00:00:00 2001 From: That-One-Nerd Date: Fri, 10 Jun 2022 17:22:45 -0400 Subject: [PATCH] Version 2.2.0 is out --- Changelog.md | 126 ++++++- Nerd_STF/FileType.cs | 13 + Nerd_STF/Fill2D.cs | 3 + Nerd_STF/Graphics/CMYKA.cs | 250 ++++++++++++++ Nerd_STF/Graphics/CMYKAByte.cs | 236 +++++++++++++ Nerd_STF/Graphics/ColorChannel.cs | 19 + Nerd_STF/Graphics/HSVA.cs | 256 ++++++++++++++ Nerd_STF/Graphics/HSVAByte.cs | 214 ++++++++++++ Nerd_STF/Graphics/IColor.cs | 12 + Nerd_STF/Graphics/IColorByte.cs | 12 + Nerd_STF/Graphics/IlluminationFlags.cs | 14 + Nerd_STF/Graphics/IlluminationModel.cs | 16 + Nerd_STF/Graphics/Image.cs | 162 +++++++++ Nerd_STF/Graphics/Material.cs | 190 ++++++++++ Nerd_STF/Graphics/RGBA.cs | 249 ++++++++++++++ Nerd_STF/Graphics/RGBAByte.cs | 213 ++++++++++++ Nerd_STF/Graphics/TextureConfig.cs | 25 ++ Nerd_STF/Mathematics/Angle.cs | 71 ++-- Nerd_STF/Mathematics/Constants.cs | 156 +++++++++ Nerd_STF/Mathematics/Float2.cs | 13 +- Nerd_STF/Mathematics/Float3.cs | 18 +- Nerd_STF/Mathematics/Float4.cs | 22 +- Nerd_STF/Mathematics/Geometry/Line.cs | 5 +- Nerd_STF/Mathematics/Geometry/Polygon.cs | 37 +- .../Mathematics/Geometry/Quadrilateral.cs | 324 +++++++++--------- Nerd_STF/Mathematics/Geometry/Sphere.cs | 4 +- Nerd_STF/Mathematics/Geometry/Triangle.cs | 105 +++--- Nerd_STF/Mathematics/Geometry/Vert.cs | 21 +- Nerd_STF/Mathematics/Int2.cs | 15 +- Nerd_STF/Mathematics/Int3.cs | 20 +- Nerd_STF/Mathematics/Int4.cs | 24 +- Nerd_STF/Mathematics/Mathf.cs | 124 +++++-- Nerd_STF/Miscellaneous/GlobalUsings.cs | 1 + Nerd_STF/bin/Release/net6.0/ref/Nerd_STF.dll | Bin 47104 -> 77312 bytes README.md | 2 +- 35 files changed, 2658 insertions(+), 314 deletions(-) create mode 100644 Nerd_STF/FileType.cs create mode 100644 Nerd_STF/Fill2D.cs create mode 100644 Nerd_STF/Graphics/CMYKA.cs create mode 100644 Nerd_STF/Graphics/CMYKAByte.cs create mode 100644 Nerd_STF/Graphics/ColorChannel.cs create mode 100644 Nerd_STF/Graphics/HSVA.cs create mode 100644 Nerd_STF/Graphics/HSVAByte.cs create mode 100644 Nerd_STF/Graphics/IColor.cs create mode 100644 Nerd_STF/Graphics/IColorByte.cs create mode 100644 Nerd_STF/Graphics/IlluminationFlags.cs create mode 100644 Nerd_STF/Graphics/IlluminationModel.cs create mode 100644 Nerd_STF/Graphics/Image.cs create mode 100644 Nerd_STF/Graphics/Material.cs create mode 100644 Nerd_STF/Graphics/RGBA.cs create mode 100644 Nerd_STF/Graphics/RGBAByte.cs create mode 100644 Nerd_STF/Graphics/TextureConfig.cs create mode 100644 Nerd_STF/Mathematics/Constants.cs diff --git a/Changelog.md b/Changelog.md index d2554b7..c76776a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,14 +1,124 @@ -# Nerd_STF v2.1.2 +# Nerd_STF v2.2.0 -This update just replaces instances of `double` with `float` instead. - -I know, this isn't the update you wanted. More stuff coming soon. +This update adds many types of graphics-based objects, as well as some math functions and constants. ``` * Nerd_STF - = Replace all instances of `double` with `float` + + delegate Fill2D(int, int) + * Exceptions + + FileParsingException + + FileType + + Graphics + + ColorChannel + + CMYKA + + CMYKAByte + + HSVA + + HSVAByte + + IColor + + IColorByte + + IlluminationFlags + + IlluminationModel + + Image + + Material + + RGBA + + RGBAByte * Mathematics - = Renamed `Double2` to `Float2` - = Renamed `Double3` to `Float3` - = Renamed `Double4` to `Float4` + * Angle + + Normalized + = Made fancier `ToString()` formatting + * Type + + Normalized + + Constants + * Float2 + + static SplitArray(params Float2[]) + = Renamed `static Multiply(params Float2[])` to `Product` + * Float3 + + static SplitArray(params Float3[]) + + explicit operator Float3(RGBA) + + explicit operator Float3(HSVA) + + explicit operator Float3(RGBAByte) + + explicit operator Float3(HSVAByte) + = Renamed `static Multiply(params Float3[])` to `Product` + * Float4 + + static SplitArray(params Float4[]) + + implicit operator Float4(RGBA) + + explicit operator Float4(CMYKA) + + implicit operator Float4(HSVA) + + implicit operator Float4(RGBAByte) + + explicit operator Float4(CMYKAByte) + + implicit operator Float4(HSVAByte) + = Renamed `static Multiply(params Float4[])` to `Product` + * Geometry + * Line + + Midpoint + = Renamed `ToDoubleArray()` to `ToFloatArray` + = Renamed `ToDoubleList()` to `ToFloatList` + * Polygon + + Midpoint + = Renamed `ToDoubleArray()` to `ToFloatArray` + = Renamed `ToDoubleList()` to `ToFloatList` + = Renamed `static ToDoubleArrayAll(params Triangle[])` to `ToFloatArrayAll` + = Renamed `static ToDoubleListAll(params Triangle[])` to `ToFloatListAll` + * Quadrilateral + + Midpoint + = Renamed `ToDoubleArray()` to `ToFloatArray` + = Renamed `ToDoubleList()` to `ToFloatList` + = Renamed `static ToDoubleArrayAll(params Triangle[])` to `ToFloatArrayAll` + = Renamed `static ToDoubleListAll(params Triangle[])` to `ToFloatListAll` + * Triangle + + Midpoint + = Renamed `ToDoubleArray()` to `ToFloatArray` + = Renamed `ToDoubleList()` to `ToFloatList` + = Renamed `static ToDoubleArrayAll(params Triangle[])` to `ToFloatArrayAll` + = Renamed `static ToDoubleListAll(params Triangle[])` to `ToFloatListAll` + * Vert + = Renamed `static ToDouble3Array(params Vert[])` to `ToFloat3Array` + = Renamed `static ToDouble3List(params Vert[])` to `ToFloat3List` + * Int2 + + static SplitArray(params Int[]) + = Renamed `static Multiply(params Int2[])` to `Product` + * Int3 + + static SplitArray(params Int3[]) + + explicit operator Int3(RGBA) + + explicit operator Int3(HSVA) + + explicit operator Int3(RGBAByte) + + explicit operator Int3(HSVAByte) + = Renamed `static Multiply(params Int3[])` to `Product` + * Int4 + + static SplitArray(params Int4[]) + + explicit operator Int4(RGBA) + + explicit operator Int4(CMYKA) + + explicit operator Int4(HSVA) + + implicit operator Int4(RGBAByte) + + explicit operator Int4(CMYKAByte) + + implicit operator Int4(HSVAByte) + = Renamed `static Multiply(params Int4[])` to `Product` + * Mathf + + static Combinations(int, int) + + static GreatestCommonFactor(params int[]) + + static InverseSqrt(float) + + static LeastCommonMultiple(params int[]) + + static Mode(params T[]) where T : IEquatable + + static Permutations(int, int) + + static Pow(float, int) + + static Product(Equation, float, float, float) + + static Sum(Equation, float, float, float) + + static UniqueItems(params T[]) where T : IEquatable + + static ZScore(float, params float[]) + + static ZScore(float, float, float) + - const RadToDeg + - const E + - const GoldenRatio + - const HalfPi + - const Pi + - const DegToRad + - const Tau + = GreatestCommonFactor actually works now + = Pow has been fixed + = Mode actually works + * static Average(params int[]) + = Replaced its `int` return type with `float` + * Miscellaneous + * GlobalUsings.cs + + global using Nerd_STF.Graphics; ``` diff --git a/Nerd_STF/FileType.cs b/Nerd_STF/FileType.cs new file mode 100644 index 0000000..01dd791 --- /dev/null +++ b/Nerd_STF/FileType.cs @@ -0,0 +1,13 @@ +namespace Nerd_STF; + +public enum FileType +{ + None = 0, + BMP, + HEIC, + JPEG, + MTL, + PNG, + TIFF, + WEBP, +} diff --git a/Nerd_STF/Fill2D.cs b/Nerd_STF/Fill2D.cs new file mode 100644 index 0000000..56e92ec --- /dev/null +++ b/Nerd_STF/Fill2D.cs @@ -0,0 +1,3 @@ +namespace Nerd_STF; + +public delegate T Fill2D(int indexX, int indexY); diff --git a/Nerd_STF/Graphics/CMYKA.cs b/Nerd_STF/Graphics/CMYKA.cs new file mode 100644 index 0000000..806c02d --- /dev/null +++ b/Nerd_STF/Graphics/CMYKA.cs @@ -0,0 +1,250 @@ +namespace Nerd_STF.Graphics; + +public struct CMYKA : IColor, IEquatable +{ + public static CMYKA Black => new(0, 0, 0, 1); + public static CMYKA Blue => new(1, 1, 0, 0); + public static CMYKA Clear => new(0, 0, 0, 0, 0); + public static CMYKA Cyan => new(1, 0, 0, 0); + public static CMYKA Gray => new(0, 0, 0, 0.5f); + public static CMYKA Green => new(1, 0, 1, 0); + public static CMYKA Magenta => new(0, 1, 0, 0); + public static CMYKA Orange => new(0, 0.5f, 1, 0); + public static CMYKA Purple => new(0.5f, 1, 0, 0); + public static CMYKA Red => new(0, 1, 1, 0); + public static CMYKA White => new(0, 0, 0, 0); + public static CMYKA Yellow => new(0, 0, 1, 0); + + public float C + { + get => p_c; + set => p_c = Mathf.Clamp(value, 0, 1); + } + public float M + { + get => p_m; + set => p_m = Mathf.Clamp(value, 0, 1); + } + public float Y + { + get => p_y; + set => p_y = Mathf.Clamp(value, 0, 1); + } + public float K + { + get => p_k; + set => p_k = Mathf.Clamp(value, 0, 1); + } + public float A + { + get => p_a; + set => p_a = Mathf.Clamp(value, 0, 1); + } + + public bool HasCyan => p_c > 0; + public bool HasMagenta => p_m > 0; + public bool HasYellow => p_y > 0; + public bool HasBlack => p_k > 0; + public bool IsOpaque => p_a == 1; + public bool IsVisible => p_a != 0; + + private float p_c, p_m, p_y, p_k, p_a; + + public CMYKA() : this(0, 0, 0, 0, 1) { } + public CMYKA(float all) : this(all, all, all, all, all) { } + public CMYKA(float all, float a) : this(all, all, all, all, a) { } + public CMYKA(float c, float m, float y, float k) : this(c, m, y, k, 1) { } + public CMYKA(float c, float m, float y, float k, float a) + { + p_c = Mathf.Clamp(c, 0, 1); + p_m = Mathf.Clamp(m, 0, 1); + p_y = Mathf.Clamp(y, 0, 1); + p_k = Mathf.Clamp(k, 0, 1); + p_a = Mathf.Clamp(a, 0, 1); + } + public CMYKA(Fill fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4)) { } + + public float this[int index] + { + get => index switch + { + 0 => C, + 1 => M, + 2 => Y, + 3 => K, + 4 => A, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + set + { + switch (index) + { + case 0: + C = value; + break; + + case 1: + M = value; + break; + + case 2: + Y = value; + break; + + case 3: + K = value; + break; + + case 4: + A = value; + break; + + default: throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public static CMYKA Average(params CMYKA[] vals) + { + CMYKA val = new(0, 0, 0, 0, 0); + for (int i = 0; i < vals.Length; i++) val += vals[i]; + + return val / vals.Length; + } + public static CMYKA Ceiling(CMYKA val) => new(Mathf.Ceiling(val.C), Mathf.Ceiling(val.M), + Mathf.Ceiling(val.Y), Mathf.Ceiling(val.K), Mathf.Ceiling(val.A)); + public static CMYKA Clamp(CMYKA val, CMYKA min, CMYKA max) => + new(Mathf.Clamp(val.C, min.C, max.C), + Mathf.Clamp(val.M, min.M, max.M), + Mathf.Clamp(val.Y, min.Y, max.Y), + Mathf.Clamp(val.K, min.K, max.K), + Mathf.Clamp(val.A, min.A, max.A)); + public static CMYKA Floor(CMYKA val) => new(Mathf.Floor(val.C), Mathf.Floor(val.M), + Mathf.Floor(val.Y), Mathf.Floor(val.K), Mathf.Floor(val.A)); + public static CMYKA Lerp(CMYKA a, CMYKA b, float t, bool clamp = true) => + new(Mathf.Lerp(a.C, b.C, t, clamp), Mathf.Lerp(a.M, b.M, t, clamp), Mathf.Lerp(a.Y, b.Y, t, clamp), + Mathf.Lerp(a.K, b.K, t, clamp), Mathf.Lerp(a.A, b.A, t, clamp)); + public static CMYKA LerpSquared(CMYKA a, CMYKA b, float t, bool clamp = true) + { + CMYKA val = Lerp(a * a, b * b, t, clamp); + float C = Mathf.Sqrt(val.C), M = Mathf.Sqrt(val.M), Y = Mathf.Sqrt(val.Y), K = Mathf.Sqrt(val.K), A = Mathf.Sqrt(val.A); + return new(C, M, Y, K, A); + } + public static CMYKA Median(params CMYKA[] vals) + { + float index = Mathf.Average(0, vals.Length - 1); + CMYKA valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; + return Average(valA, valB); + } + public static CMYKA Max(params CMYKA[] vals) + { + (float[] Cs, float[] Ms, float[] Ys, float[] Ks, float[] As) = SplitArray(vals); + return new(Mathf.Max(Cs), Mathf.Max(Ms), Mathf.Max(Ys), Mathf.Max(Ks), Mathf.Max(As)); + } + public static CMYKA Min(params CMYKA[] vals) + { + (float[] Cs, float[] Ms, float[] Ys, float[] Ks, float[] As) = SplitArray(vals); + return new(Mathf.Min(Cs), Mathf.Min(Ms), Mathf.Min(Ys), Mathf.Min(Ks), Mathf.Min(As)); + } + + public static (float[] Cs, float[] Ms, float[] Ys, float[] Ks, float[] As) SplitArray(params CMYKA[] vals) + { + float[] Cs = new float[vals.Length], Ms = new float[vals.Length], + Ys = new float[vals.Length], Ks = new float[vals.Length], + As = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Cs[i] = vals[i].C; + Ms[i] = vals[i].M; + Ys[i] = vals[i].Y; + Ks[i] = vals[i].K; + As[i] = vals[i].A; + } + return (Cs, Ms, Ys, Ks, As); + } + + public bool Equals(IColor? col) => col != null && Equals(col.ToCMYKA()); + public bool Equals(IColorByte? col) => col != null && Equals(col.ToCMYKA()); + public bool Equals(CMYKA col) => A == 0 && col.A == 0 || K == 1 && col.K == 1 || C == col.C && M == col.M + && Y == col.Y && K == col.K && A == col.A; + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null) return false; + Type t = obj.GetType(); + if (t == typeof(CMYKA)) return Equals((CMYKA)obj); + else if (t == typeof(RGBA)) return Equals((IColor)obj); + else if (t == typeof(HSVA)) return Equals((IColor)obj); + else if (t == typeof(IColor)) return Equals((IColor)obj); + else if (t == typeof(RGBAByte)) return Equals((IColorByte)obj); + else if (t == typeof(CMYKAByte)) return Equals((IColorByte)obj); + else if (t == typeof(HSVAByte)) return Equals((IColorByte)obj); + else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); + + return false; + } + public override int GetHashCode() => C.GetHashCode() ^ M.GetHashCode() ^ Y.GetHashCode() ^ K.GetHashCode() ^ A.GetHashCode(); + public string ToString(IFormatProvider provider) => "C: " + C.ToString(provider) + " M: " + M.ToString(provider) + + " Y: " + Y.ToString(provider) + " K: " + K.ToString(provider) + + " A: " + A.ToString(provider); + public string ToString(string? provider) => "C: " + C.ToString(provider) + " M: " + M.ToString(provider) + + " Y: " + Y.ToString(provider) + " K: " + K.ToString(provider) + + " A: " + A.ToString(provider); + public override string ToString() => ToString((string?)null); + + public RGBA ToRGBA() + { + float kInv = 1 - K, r = 1 - C, g = 1 - M, b = 1 - Y; + return new(r * kInv, g * kInv, b * kInv); + } + public CMYKA ToCMYKA() => this; + public HSVA ToHSVA() => ToRGBA().ToHSVA(); + + public RGBAByte ToRGBAByte() => ToRGBA().ToRGBAByte(); + public CMYKAByte ToCMYKAByte() => new(Mathf.RoundInt(C * 255), Mathf.RoundInt(M * 255), Mathf.RoundInt(Y * 255), + Mathf.RoundInt(K * 255), Mathf.RoundInt(A * 255)); + public HSVAByte ToHSVAByte() => ToRGBA().ToHSVAByte(); + + public float[] ToArray() => new[] { C, M, Y, K, A }; + public List ToList() => new() { C, M, Y, K, A }; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return C; + yield return M; + yield return Y; + yield return K; + yield return A; + } + + public object Clone() => new CMYKA(C, M, Y, K, A); + + public static CMYKA operator +(CMYKA a, CMYKA b) => new(a.C + b.C, a.M + b.M, a.Y + b.Y, a.K + b.K, a.A + b.A); + public static CMYKA operator -(CMYKA c) => new(1 - c.C, 1 - c.M, 1 - c.Y, 1 - c.K, c.A != 1 ? 1 - c.A : 1); + public static CMYKA operator -(CMYKA a, CMYKA b) => new(a.C - b.C, a.M - b.M, a.Y - b.Y, a.K - b.K, a.A - b.A); + public static CMYKA operator *(CMYKA a, CMYKA b) => new(a.C * b.C, a.M * b.M, a.Y * b.Y, a.K * b.K, a.A * b.A); + public static CMYKA operator *(CMYKA a, float b) => new(a.C * b, a.M * b, a.Y * b, a.K * b, a.A * b); + public static CMYKA operator /(CMYKA a, CMYKA b) => new(a.C / b.C, a.M / b.M, a.Y / b.Y, a.K / b.K, a.A / b.A); + public static CMYKA operator /(CMYKA a, float b) => new(a.C / b, a.M / b, a.Y / b, a.K / b, a.A / b); + public static bool operator ==(CMYKA a, RGBA b) => a.Equals(b); + public static bool operator !=(CMYKA a, RGBA b) => !a.Equals(b); + public static bool operator ==(CMYKA a, CMYKA b) => a.Equals(b); + public static bool operator !=(CMYKA a, CMYKA b) => !a.Equals(b); + public static bool operator ==(CMYKA a, HSVA b) => a.Equals(b); + public static bool operator !=(CMYKA a, HSVA b) => !a.Equals(b); + public static bool operator ==(CMYKA a, RGBAByte b) => a.Equals((IColorByte?)b); + public static bool operator !=(CMYKA a, RGBAByte b) => !a.Equals((IColorByte?)b); + public static bool operator ==(CMYKA a, CMYKAByte b) => a.Equals((IColorByte?)b); + public static bool operator !=(CMYKA a, CMYKAByte b) => !a.Equals((IColorByte?)b); + public static bool operator ==(CMYKA a, HSVAByte b) => a.Equals((IColorByte?)b); + public static bool operator !=(CMYKA a, HSVAByte b) => !a.Equals((IColorByte?)b); + + public static explicit operator CMYKA(Float3 val) => new(val.x, val.y, val.z, 0); + public static implicit operator CMYKA(Float4 val) => new(val.x, val.y, val.z, val.w); + public static implicit operator CMYKA(RGBA val) => val.ToCMYKA(); + public static implicit operator CMYKA(HSVA val) => val.ToCMYKA(); + public static implicit operator CMYKA(RGBAByte val) => val.ToCMYKA(); + public static implicit operator CMYKA(CMYKAByte val) => val.ToCMYKA(); + public static implicit operator CMYKA(HSVAByte val) => val.ToCMYKA(); + public static implicit operator CMYKA(Fill val) => new(val); +} diff --git a/Nerd_STF/Graphics/CMYKAByte.cs b/Nerd_STF/Graphics/CMYKAByte.cs new file mode 100644 index 0000000..54c8119 --- /dev/null +++ b/Nerd_STF/Graphics/CMYKAByte.cs @@ -0,0 +1,236 @@ +namespace Nerd_STF.Graphics; + +public struct CMYKAByte : IColorByte, IEquatable +{ + public static CMYKA Black => new(0, 0, 0, 255); + public static CMYKA Blue => new(255, 255, 0, 0); + public static CMYKA Clear => new(0, 0, 0, 0, 0); + public static CMYKA Cyan => new(255, 0, 0, 0); + public static CMYKA Gray => new(0, 0, 0, 127); + public static CMYKA Green => new(255, 0, 255, 0); + public static CMYKA Magenta => new(0, 255, 0, 0); + public static CMYKA Orange => new(0, 127, 255, 0); + public static CMYKA Purple => new(127, 255, 0, 0); + public static CMYKA Red => new(0, 255, 255, 0); + public static CMYKA White => new(0, 0, 0, 0); + public static CMYKA Yellow => new(0, 0, 255, 0); + + public byte C, M, Y, K, A; + + public bool HasCyan => C > 0; + public bool HasMagenta => M > 0; + public bool HasYellow => Y > 0; + public bool HasBlack => K > 0; + public bool IsOpaque => A == 255; + public bool IsVisible => A != 0; + + public CMYKAByte() : this(0, 0, 0, 0, 255) { } + public CMYKAByte(int all) : this(all, all, all, all, all) { } + public CMYKAByte(int all, int a) : this(all, all, all, all, a) { } + public CMYKAByte(int c, int m, int y, int k) : this(c, m, y, k, 255) { } + public CMYKAByte(int c, int m, int y, int k, int a) + { + C = (byte)Mathf.Clamp(c, 0, 255); + M = (byte)Mathf.Clamp(m, 0, 255); + Y = (byte)Mathf.Clamp(y, 0, 255); + K = (byte)Mathf.Clamp(k, 0, 255); + A = (byte)Mathf.Clamp(a, 0, 255); + } + public CMYKAByte(Fill fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4)) { } + public CMYKAByte(Fill fill) : this(fill(0), fill(1), fill(2), fill(3), fill(4)) { } + + public byte this[int index] + { + get => index switch + { + 0 => C, + 1 => M, + 2 => Y, + 3 => K, + 4 => A, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + set + { + switch (index) + { + case 0: + C = value; + break; + + case 1: + M = value; + break; + + case 2: + Y = value; + break; + + case 3: + K = value; + break; + + case 4: + A = value; + break; + + default: throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public static CMYKAByte Average(params CMYKAByte[] vals) + { + CMYKAByte val = new(0, 0, 0, 0, 0); + for (int i = 0; i < vals.Length; i++) val += vals[i]; + return val / vals.Length; + } + public static CMYKAByte Clamp(CMYKAByte val, CMYKAByte min, CMYKAByte max) => + new(Mathf.Clamp(val.C, min.C, max.C), + Mathf.Clamp(val.M, min.M, max.M), + Mathf.Clamp(val.Y, min.Y, max.Y), + Mathf.Clamp(val.K, min.K, max.K), + Mathf.Clamp(val.A, min.A, max.A)); + public static CMYKAByte Lerp(CMYKAByte a, CMYKAByte b, float t, bool clamp = true) => + new(Mathf.Lerp(a.C, b.C, t, clamp), Mathf.Lerp(a.M, b.M, t, clamp), Mathf.Lerp(a.Y, b.Y, t, clamp), + Mathf.Lerp(a.K, b.K, t, clamp), Mathf.Lerp(a.A, b.A, t, clamp)); + public static CMYKAByte LerpSquared(CMYKAByte a, CMYKAByte b, float t, bool clamp = true) => CMYKA.LerpSquared(a.ToCMYKA(), b.ToCMYKA(), t, clamp).ToCMYKAByte(); + public static CMYKAByte Median(params CMYKAByte[] vals) + { + float index = Mathf.Average(0, vals.Length - 1); + CMYKAByte valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; + return Average(valA, valB); + } + public static CMYKAByte Max(params CMYKAByte[] vals) + { + (int[] Cs, int[] Ms, int[] Ys, int[] Ks, int[] As) = SplitArrayInt(vals); + return new(Mathf.Max(Cs), Mathf.Max(Ms), Mathf.Max(Ys), Mathf.Max(Ks), Mathf.Max(As)); + } + public static CMYKAByte Min(params CMYKAByte[] vals) + { + (int[] Cs, int[] Ms, int[] Ys, int[] Ks, int[] As) = SplitArrayInt(vals); + return new(Mathf.Min(Cs), Mathf.Min(Ms), Mathf.Min(Ys), Mathf.Min(Ks), Mathf.Min(As)); + } + + public static (byte[] Cs, byte[] Ms, byte[] Ys, byte[] Ks, byte[] As) SplitArray(params CMYKAByte[] vals) + { + byte[] Cs = new byte[vals.Length], Ms = new byte[vals.Length], + Ys = new byte[vals.Length], Ks = new byte[vals.Length], + As = new byte[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Cs[i] = vals[i].C; + Ms[i] = vals[i].M; + Ys[i] = vals[i].Y; + Ks[i] = vals[i].K; + As[i] = vals[i].A; + } + return (Cs, Ms, Ys, Ks, As); + } + public static (int[] Cs, int[] Ms, int[] Ys, int[] Ks, int[] As) SplitArrayInt(params CMYKAByte[] vals) + { + int[] Cs = new int[vals.Length], Ms = new int[vals.Length], + Ys = new int[vals.Length], Ks = new int[vals.Length], + As = new int[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Cs[i] = vals[i].C; + Ms[i] = vals[i].M; + Ys[i] = vals[i].Y; + Ks[i] = vals[i].K; + As[i] = vals[i].A; + } + return (Cs, Ms, Ys, Ks, As); + } + + public bool Equals(IColor? col) => col != null && Equals(col.ToCMYKAByte()); + public bool Equals(IColorByte? col) => col != null && Equals(col.ToCMYKAByte()); + public bool Equals(CMYKAByte col) => A == 0 && col.A == 0 || K == 1 && col.K == 255 || C == col.C && M == col.M + && Y == col.Y && K == col.K && A == col.A; + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null) return false; + Type t = obj.GetType(); + if (t == typeof(CMYKAByte)) return Equals((CMYKAByte)obj); + else if (t == typeof(RGBA)) return Equals((IColor)obj); + else if (t == typeof(HSVA)) return Equals((IColor)obj); + else if (t == typeof(IColor)) return Equals((IColor)obj); + else if (t == typeof(RGBAByte)) return Equals((IColorByte)obj); + else if (t == typeof(CMYKA)) return Equals((IColor)obj); + else if (t == typeof(HSVAByte)) return Equals((IColorByte)obj); + else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); + + return false; + } + public override int GetHashCode() => C.GetHashCode() ^ M.GetHashCode() ^ Y.GetHashCode() + ^ K.GetHashCode() ^ A.GetHashCode(); + public string ToString(IFormatProvider provider) => "C: " + C.ToString(provider) + + " M: " + M.ToString(provider) + " Y: " + Y.ToString(provider) + + " K: " + K.ToString(provider) + " A: " + A.ToString(provider); + public string ToString(string? provider) => "C: " + C.ToString(provider) + + " M: " + M.ToString(provider) + " Y: " + Y.ToString(provider) + + " K: " + K.ToString(provider) + " A: " + A.ToString(provider); + public override string ToString() => ToString((string?)null); + + public RGBA ToRGBA() => ToCMYKA().ToRGBA(); + public CMYKA ToCMYKA() => new(C / 255f, M / 255f, Y / 255f, K / 255f, A / 255f); + public HSVA ToHSVA() => ToRGBA().ToHSVA(); + + public RGBAByte ToRGBAByte() => ToCMYKA().ToRGBAByte(); + public CMYKAByte ToCMYKAByte() => this; + public HSVAByte ToHSVAByte() => ToRGBA().ToHSVAByte(); + + public byte[] ToArray() => new[] { C, M, Y, K, A }; + public List ToList() => new() { C, M, Y, K, A }; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return C; + yield return M; + yield return Y; + yield return K; + yield return A; + } + + public object Clone() => new CMYKAByte(C, M, Y, K, A); + + public static CMYKAByte operator +(CMYKAByte a, CMYKAByte b) => + new(a.C + b.C, a.M + b.M, a.Y + b.Y, a.K + b.K, a.A + b.A); + public static CMYKAByte operator -(CMYKAByte c) => + new(255 - c.C, 255 - c.M, 255 - c.Y, 255 - c.K, c.A != 255 ? 255 - c.A : 255); + public static CMYKAByte operator -(CMYKAByte a, CMYKAByte b) => + new(a.C - b.C, a.M - b.M, a.Y - b.Y, a.K - b.K, a.A - b.A); + public static CMYKAByte operator *(CMYKAByte a, CMYKAByte b) => + new(a.C * b.C, a.M * b.M, a.Y * b.Y, a.K * b.K, a.A * b.A); + public static CMYKAByte operator *(CMYKAByte a, int b) => + new(a.C * b, a.M * b, a.Y * b, a.K * b, a.A * b); + public static CMYKAByte operator *(CMYKAByte a, float b) => (a.ToCMYKA() * b).ToCMYKAByte(); + public static CMYKAByte operator /(CMYKAByte a, CMYKAByte b) => + new(a.C / b.C, a.M / b.M, a.Y / b.Y, a.K / b.K, a.A / b.A); + public static CMYKAByte operator /(CMYKAByte a, int b) => + new(a.C / b, a.M / b, a.Y / b, a.K / b, a.A / b); + public static CMYKAByte operator /(CMYKAByte a, float b) => (a.ToCMYKA() / b).ToCMYKAByte(); + public static bool operator ==(CMYKAByte a, RGBA b) => a.Equals((IColor?)b); + public static bool operator !=(CMYKAByte a, RGBA b) => !a.Equals((IColor?)b); + public static bool operator ==(CMYKAByte a, CMYKA b) => a.Equals((IColor?)b); + public static bool operator !=(CMYKAByte a, CMYKA b) => !a.Equals((IColor?)b); + public static bool operator ==(CMYKAByte a, HSVA b) => a.Equals((IColor?)b); + public static bool operator !=(CMYKAByte a, HSVA b) => !a.Equals((IColor?)b); + public static bool operator ==(CMYKAByte a, RGBAByte b) => a.Equals(b); + public static bool operator !=(CMYKAByte a, RGBAByte b) => !a.Equals(b); + public static bool operator ==(CMYKAByte a, CMYKAByte b) => a.Equals(b); + public static bool operator !=(CMYKAByte a, CMYKAByte b) => !a.Equals(b); + public static bool operator ==(CMYKAByte a, HSVAByte b) => a.Equals(b); + public static bool operator !=(CMYKAByte a, HSVAByte b) => !a.Equals(b); + + public static explicit operator CMYKAByte(Int3 val) => new(val.x, val.y, val.z, 0); + public static implicit operator CMYKAByte(Int4 val) => new(val.x, val.y, val.z, val.w); + public static implicit operator CMYKAByte(RGBA val) => val.ToCMYKAByte(); + public static implicit operator CMYKAByte(HSVA val) => val.ToCMYKAByte(); + public static implicit operator CMYKAByte(RGBAByte val) => val.ToCMYKAByte(); + public static implicit operator CMYKAByte(CMYKA val) => val.ToCMYKAByte(); + public static implicit operator CMYKAByte(HSVAByte val) => val.ToCMYKAByte(); + public static implicit operator CMYKAByte(Fill val) => new(val); + public static implicit operator CMYKAByte(Fill val) => new(val); +} diff --git a/Nerd_STF/Graphics/ColorChannel.cs b/Nerd_STF/Graphics/ColorChannel.cs new file mode 100644 index 0000000..22c29a8 --- /dev/null +++ b/Nerd_STF/Graphics/ColorChannel.cs @@ -0,0 +1,19 @@ +namespace Nerd_STF.Graphics; + +public enum ColorChannel : byte +{ + Alpha, + Black, + Blue, + Cyan, + Green, + Hue, + Luminance, + Magenta, + Matte, + Red, + Saturation, + Yellow, + Value, + ZDepth +} diff --git a/Nerd_STF/Graphics/HSVA.cs b/Nerd_STF/Graphics/HSVA.cs new file mode 100644 index 0000000..706b992 --- /dev/null +++ b/Nerd_STF/Graphics/HSVA.cs @@ -0,0 +1,256 @@ +namespace Nerd_STF.Graphics; + +public struct HSVA : IColor, IEquatable +{ + public static HSVA Black => new(Angle.Zero, 0, 0); + public static HSVA Blue => new(new Angle(240), 1, 1); + public static HSVA Clear => new(Angle.Zero, 0, 0, 0); + public static HSVA Cyan => new(new Angle(180), 1, 1); + public static HSVA Gray => new(Angle.Zero, 0, 0.5f); + public static HSVA Green => new(new Angle(120), 1, 1); + public static HSVA Magenta => new(new Angle(300), 1, 1); + public static HSVA Orange => new(new Angle(30), 1, 1); + public static HSVA Purple => new(new Angle(270), 1, 1); + public static HSVA Red => new(Angle.Zero, 1, 1); + public static HSVA White => new(Angle.Zero, 0, 1); + public static HSVA Yellow => new(new Angle(60), 1, 1); + + public Angle H + { + get => p_h; + set => p_h = value.Bounded; + } + public float S + { + get => p_s; + set => p_s = Mathf.Clamp(value, 0, 1); + } + public float V + { + get => p_v; + set => p_v = Mathf.Clamp(value, 0, 1); + } + public float A + { + get => p_a; + set => p_a = Mathf.Clamp(value, 0, 1); + } + + private Angle p_h; + private float p_s, p_v, p_a; + + public bool HasColor => p_s != 0 && p_v != 0; + public bool IsOpaque => p_a == 1; + public bool IsVisible => p_a != 0; + + public HSVA() : this(Angle.Zero, 0, 0, 1) { } + public HSVA(Angle h, float s, float v) : this(h, s, v, 1) { } + public HSVA(Angle h, float s, float v, float a) + { + p_h = h.Bounded; + p_s = Mathf.Clamp(s, 0, 1); + p_v = Mathf.Clamp(v, 0, 1); + p_a = Mathf.Clamp(a, 0, 1); + } + public HSVA(Angle h, Fill fill) : this(h, fill(0), fill(1), fill(2)) { } + public HSVA(Angle h, Fill fill) : this(h, fill(0), fill(1), fill(2)) { } + public HSVA(float h, float s, float v) : this(new Angle(h, Angle.Type.Normalized), s, v) { } + public HSVA(float h, float s, float v, float a) : this(new Angle(h, Angle.Type.Normalized), s, v, a) { } + public HSVA(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + public HSVA(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + + public float this[int index] + { + get => index switch + { + 0 => H.Normalized, + 1 => S, + 2 => V, + 3 => A, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + set + { + switch (index) + { + case 0: + H = new(Mathf.Clamp(value, 0, 1), Angle.Type.Normalized); + break; + + case 1: + S = Mathf.Clamp(value, 0, 1); + break; + + case 2: + V = Mathf.Clamp(value, 0, 1); + break; + + case 3: + A = Mathf.Clamp(value, 0, 1); + break; + + default: throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public static HSVA Average(params HSVA[] vals) + { + HSVA val = new(Angle.Zero, 0, 0, 0); + for (int i = 0; i < vals.Length; i++) val += vals[i]; + return val / vals.Length; + } + public static HSVA Ceiling(HSVA val, Angle.Type type) => new(Angle.Ceiling(val.H, type), Mathf.Ceiling(val.S), + Mathf.Ceiling(val.V), Mathf.Ceiling(val.A)); + public static HSVA Clamp(HSVA val, HSVA min, HSVA max) => + new(Angle.Clamp(val.H, min.H, max.H), + Mathf.Clamp(val.S, min.S, max.S), + Mathf.Clamp(val.V, min.V, max.V), + Mathf.Clamp(val.A, min.A, max.A)); + public static HSVA Floor(HSVA val, Angle.Type type) => new(Angle.Floor(val.H, type), Mathf.Floor(val.S), + Mathf.Floor(val.V), Mathf.Floor(val.A)); + public static HSVA Lerp(HSVA a, HSVA b, float t, bool clamp = true) => + new(Angle.Lerp(a.H, b.H, t, clamp), Mathf.Lerp(a.S, b.S, t, clamp), Mathf.Lerp(a.V, b.V, t, clamp), + Mathf.Lerp(a.A, b.A, t, clamp)); + public static HSVA LerpSquared(HSVA a, HSVA b, float t, Angle.Type type = Angle.Type.Normalized, + bool clamp = true) + { + HSVA val = Lerp(a * a, b * b, t, clamp); + float H = Mathf.Sqrt(val.H.ValueFromType(type)), S = Mathf.Sqrt(val.S), V = Mathf.Sqrt(val.V), + A = Mathf.Sqrt(val.A); + return new(new Angle(H, Angle.Type.Normalized), S, V, A); + } + public static HSVA Median(params HSVA[] vals) + { + float index = Mathf.Average(0, vals.Length - 1); + HSVA valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; + return Average(valA, valB); + } + public static HSVA Max(params HSVA[] vals) + { + (Angle[] Hs, float[] Ss, float[] Vs, float[] As) = SplitArray(vals); + return new(Angle.Max(Hs), Mathf.Max(Ss), Mathf.Max(Vs), Mathf.Max(As)); + } + public static HSVA Min(params HSVA[] vals) + { + (Angle[] Hs, float[] Ss, float[] Vs, float[] As) = SplitArray(vals); + return new(Angle.Min(Hs), Mathf.Min(Ss), Mathf.Min(Vs), Mathf.Min(As)); + } + + public static (Angle[] Hs, float[] Ss, float[] Vs, float[] As) SplitArray(params HSVA[] vals) + { + Angle[] Hs = new Angle[vals.Length]; + float[] Ss = new float[vals.Length], Vs = new float[vals.Length], + As = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Hs[i] = vals[i].H; + Ss[i] = vals[i].S; + Vs[i] = vals[i].V; + As[i] = vals[i].A; + } + return (Hs, Ss, Vs, As); + } + public static (float[] Hs, float[] Ss, float[] Vs, float[] As) SplitArrayNormalized(params HSVA[] vals) + { + float[] Hs = new float[vals.Length], Ss = new float[vals.Length], + Vs = new float[vals.Length], As = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Hs[i] = vals[i].H.Normalized; + Ss[i] = vals[i].S; + Vs[i] = vals[i].V; + As[i] = vals[i].A; + } + return (Hs, Ss, Vs, As); + } + + public bool Equals(IColor? col) => col != null && Equals(col.ToHSVA()); + public bool Equals(IColorByte? col) => col != null && Equals(col.ToHSVA()); + public bool Equals(HSVA col) => S == 0 && col.S == 0 || V == 0 && col.V == 0 || A == 0 && col.A == 0 + || H == col.H && S == col.S && V == col.V && A == col.A; + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null) return false; + Type t = obj.GetType(); + if (t == typeof(HSVA)) return Equals((HSVA)obj); + else if (t == typeof(CMYKA)) return Equals((IColor)obj); + else if (t == typeof(RGBA)) return Equals((IColor)obj); + else if (t == typeof(IColor)) return Equals((IColor)obj); + else if (t == typeof(RGBAByte)) return Equals((IColorByte)obj); + else if (t == typeof(CMYKAByte)) return Equals((IColorByte)obj); + else if (t == typeof(HSVAByte)) return Equals((IColorByte)obj); + else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); + + return false; + } + public override int GetHashCode() => H.GetHashCode() ^ S.GetHashCode() ^ V.GetHashCode() ^ A.GetHashCode(); + public string ToString(IFormatProvider provider) => "H: " + H.ToString(provider) + " S: " + S.ToString(provider) + + " V: " + V.ToString(provider) + " A: " + A.ToString(provider); + public string ToString(string? provider) => "H: " + H.ToString(provider) + " S: " + S.ToString(provider) + + " V: " + V.ToString(provider) + " A: " + A.ToString(provider); + public override string ToString() => ToString((string?)null); + + public RGBA ToRGBA() + { + float d = H.Degrees, c = V * S, x = c * (1 - Mathf.Absolute(d / 60 % 2 - 1)), m = V - c; + (float r, float g, float b) vals = (0, 0, 0); + if (d < 60) vals = (c, x, 0); + else if (d < 120) vals = (x, c, 0); + else if (d < 180) vals = (0, c, x); + else if (d < 240) vals = (0, x, c); + else if (d < 300) vals = (x, 0, c); + else if (d < 360) vals = (c, 0, x); + return new(vals.r + m, vals.g + m, vals.b + m); + } + public CMYKA ToCMYKA() => ToRGBA().ToCMYKA(); + public HSVA ToHSVA() => this; + + public RGBAByte ToRGBAByte() => ToRGBA().ToRGBAByte(); + public CMYKAByte ToCMYKAByte() => ToRGBA().ToCMYKAByte(); + public HSVAByte ToHSVAByte() => new(Mathf.RoundInt(H.Normalized * 255), Mathf.RoundInt(S * 255), + Mathf.RoundInt(V * 255), Mathf.RoundInt(A * 255)); + + public float[] ToArray() => new[] { H.Normalized, S, V, A }; + public List ToList() => new() { H.Normalized, S, V, A }; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return H.Normalized; + yield return S; + yield return V; + yield return A; + } + + public object Clone() => new HSVA(H, S, V, A); + + public static HSVA operator +(HSVA a, HSVA b) => new(a.H + b.H, a.S + b.S, a.V + b.V, a.A + b.A); + public static HSVA operator -(HSVA c) => new(1 - c.H.Normalized, 1 - c.S, 1 - c.V, c.A != 1 ? 1 - c.A : 1); + public static HSVA operator -(HSVA a, HSVA b) => new(a.H - b.H, a.S - b.S, a.V - b.V, a.A - b.A); + public static HSVA operator *(HSVA a, HSVA b) => new(a.H * b.H, a.S * b.S, a.V * b.V, a.A * b.A); + public static HSVA operator *(HSVA a, float b) => new(a.H * b, a.S * b, a.V * b, a.A * b); + public static HSVA operator /(HSVA a, HSVA b) => new(a.H / b.H, a.S / b.S, a.V / b.V, a.A / b.A); + public static HSVA operator /(HSVA a, float b) => new(a.H / b, a.S / b, a.V / b, a.A / b); + public static bool operator ==(HSVA a, RGBA b) => a.Equals(b); + public static bool operator !=(HSVA a, RGBA b) => !a.Equals(b); + public static bool operator ==(HSVA a, CMYKA b) => a.Equals(b); + public static bool operator !=(HSVA a, CMYKA b) => !a.Equals(b); + public static bool operator ==(HSVA a, HSVA b) => a.Equals(b); + public static bool operator !=(HSVA a, HSVA b) => !a.Equals(b); + public static bool operator ==(HSVA a, RGBAByte b) => a.Equals((IColorByte?)b); + public static bool operator !=(HSVA a, RGBAByte b) => !a.Equals((IColorByte?)b); + public static bool operator ==(HSVA a, CMYKAByte b) => a.Equals((IColorByte?)b); + public static bool operator !=(HSVA a, CMYKAByte b) => !a.Equals((IColorByte?)b); + public static bool operator ==(HSVA a, HSVAByte b) => a.Equals((IColorByte?)b); + public static bool operator !=(HSVA a, HSVAByte b) => !a.Equals((IColorByte?)b); + + public static explicit operator HSVA(Float3 val) => new(val.x, val.y, val.z); + public static explicit operator HSVA(Float4 val) => new(val.x, val.y, val.z, val.w); + public static implicit operator HSVA(CMYKA val) => val.ToHSVA(); + public static implicit operator HSVA(RGBA val) => val.ToHSVA(); + public static implicit operator HSVA(RGBAByte val) => val.ToHSVA(); + public static implicit operator HSVA(CMYKAByte val) => val.ToHSVA(); + public static implicit operator HSVA(HSVAByte val) => val.ToHSVA(); + public static implicit operator HSVA(Fill val) => new(val); +} diff --git a/Nerd_STF/Graphics/HSVAByte.cs b/Nerd_STF/Graphics/HSVAByte.cs new file mode 100644 index 0000000..e8a87c1 --- /dev/null +++ b/Nerd_STF/Graphics/HSVAByte.cs @@ -0,0 +1,214 @@ +namespace Nerd_STF.Graphics; + +public struct HSVAByte : IColorByte, IEquatable +{ + public static HSVA Black => new(Angle.Zero, 0, 0); + public static HSVA Blue => new(new Angle(240), 255, 255); + public static HSVA Clear => new(Angle.Zero, 0, 0, 0); + public static HSVA Cyan => new(new Angle(180), 255, 255); + public static HSVA Gray => new(Angle.Zero, 0, 127); + public static HSVA Green => new(new Angle(120), 255, 255); + public static HSVA Magenta => new(new Angle(300), 255, 255); + public static HSVA Orange => new(new Angle(30), 255, 255); + public static HSVA Purple => new(new Angle(270), 255, 255); + public static HSVA Red => new(Angle.Zero, 255, 255); + public static HSVA White => new(Angle.Zero, 0, 255); + public static HSVA Yellow => new(new Angle(60), 255, 255); + + public byte H, S, V, A; + + public bool HasColor => S != 0 && V != 0; + public bool IsOpaque => A == 255; + public bool IsVisible => A == 0; + + public HSVAByte() : this(0, 0, 0, 255) { } + public HSVAByte(int all) : this(all, all, all, all) { } + public HSVAByte(int all, int a) : this(all, all, all, a) { } + public HSVAByte(int h, int s, int v) : this(h, s, v, 255) { } + public HSVAByte(int h, int s, int v, int a) + { + H = (byte)Mathf.Clamp(h, 0, 255); + S = (byte)Mathf.Clamp(s, 0, 255); + V = (byte)Mathf.Clamp(v, 0, 255); + A = (byte)Mathf.Clamp(a, 0, 255); + } + public HSVAByte(Angle h, int s, int v) : this(h, s, v, 255) { } + public HSVAByte(Angle h, int s, int v, int a) : this(Mathf.RoundInt(h.Normalized * 255), s, v, a) { } + public HSVAByte(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + public HSVAByte(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + + public byte this[int index] + { + get => index switch + { + 0 => H, + 1 => S, + 2 => V, + 3 => A, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + set + { + switch (index) + { + case 0: + H = value; + break; + + case 1: + S = value; + break; + + case 2: + V = value; + break; + + case 3: + A = value; + break; + + default: throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public static HSVAByte Average(params HSVAByte[] vals) + { + HSVAByte val = new(0, 0, 0, 0); + for (int i = 0; i < vals.Length; i++) val += vals[i]; + return val / vals.Length; + } + public static HSVAByte Clamp(HSVAByte val, HSVAByte min, HSVAByte max) => + new(Mathf.Clamp(val.H, min.H, max.H), + Mathf.Clamp(val.S, min.S, max.S), + Mathf.Clamp(val.V, min.V, max.V), + Mathf.Clamp(val.A, min.A, max.A)); + public static HSVAByte Lerp(HSVAByte a, HSVAByte b, byte t, bool clamp = true) => + new(Mathf.Lerp(a.H, b.H, t, clamp), Mathf.Lerp(a.S, b.S, t, clamp), Mathf.Lerp(a.V, b.V, t, clamp), + Mathf.Lerp(a.A, b.A, t, clamp)); + public static HSVAByte LerpSquared(HSVAByte a, HSVAByte b, byte t, Angle.Type type, bool clamp = true) => + HSVA.LerpSquared(a.ToHSVA(), b.ToHSVA(), t, type, clamp).ToHSVAByte(); + public static HSVAByte Median(params HSVAByte[] vals) + { + float index = Mathf.Average(0, vals.Length - 1); + HSVAByte valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; + return Average(valA, valB); + } + public static HSVAByte Max(params HSVAByte[] vals) + { + (int[] Hs, int[] Ss, int[] Vs, int[] As) = SplitArrayInt(vals); + return new(Mathf.Max(Hs), Mathf.Max(Ss), Mathf.Max(Vs), Mathf.Max(As)); + } + public static HSVAByte Min(params HSVAByte[] vals) + { + (int[] Hs, int[] Ss, int[] Vs, int[] As) = SplitArrayInt(vals); + return new(Mathf.Min(Hs), Mathf.Min(Ss), Mathf.Min(Vs), Mathf.Min(As)); + } + + public static (byte[] Hs, byte[] Ss, byte[] Vs, byte[] As) SplitArray(params HSVAByte[] vals) + { + byte[] Hs = new byte[vals.Length], Ss = new byte[vals.Length], + Vs = new byte[vals.Length], As = new byte[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Hs[i] = vals[i].H; + Ss[i] = vals[i].S; + Vs[i] = vals[i].V; + As[i] = vals[i].A; + } + return (Hs, Ss, Vs, As); + } + public static (int[] Hs, int[] Ss, int[] Vs, int[] As) SplitArrayInt(params HSVAByte[] vals) + { + int[] Hs = new int[vals.Length], Ss = new int[vals.Length], + Vs = new int[vals.Length], As = new int[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Hs[i] = vals[i].H; + Ss[i] = vals[i].S; + Vs[i] = vals[i].V; + As[i] = vals[i].A; + } + return (Hs, Ss, Vs, As); + } + + public bool Equals(IColor? col) => col != null && Equals(col.ToHSVAByte()); + public bool Equals(IColorByte? col) => col != null && Equals(col.ToHSVAByte()); + public bool Equals(HSVAByte col) => S == 0 && col.S == 0 || V == 0 && col.V == 0 || A == 0 && col.A == 0 + || H == col.H && S == col.S && V == col.V && A == col.A; + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null) return false; + Type t = obj.GetType(); + if (t == typeof(HSVAByte)) return Equals((HSVAByte)obj); + else if (t == typeof(CMYKA)) return Equals((IColor)obj); + else if (t == typeof(RGBA)) return Equals((IColor)obj); + else if (t == typeof(IColor)) return Equals((IColor)obj); + else if (t == typeof(RGBAByte)) return Equals((IColorByte)obj); + else if (t == typeof(CMYKAByte)) return Equals((IColorByte)obj); + else if (t == typeof(HSVA)) return Equals((IColor)obj); + else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); + + return false; + } + public override int GetHashCode() => H.GetHashCode() ^ S.GetHashCode() ^ V.GetHashCode() ^ A.GetHashCode(); + public string ToString(IFormatProvider provider) => "H: " + H.ToString(provider) + " S: " + S.ToString(provider) + + " V: " + V.ToString(provider) + " A: " + A.ToString(provider); + public string ToString(string? provider) => "H: " + H.ToString(provider) + " S: " + S.ToString(provider) + + " V: " + V.ToString(provider) + " A: " + A.ToString(provider); + public override string ToString() => ToString((string?)null); + + public RGBA ToRGBA() => ToHSVA().ToRGBA(); + public CMYKA ToCMYKA() => ToHSVA().ToCMYKA(); + public HSVA ToHSVA() => new(H / 255f, S / 255f, V / 255f, A / 255f); + + public RGBAByte ToRGBAByte() => ToHSVA().ToRGBAByte(); + public CMYKAByte ToCMYKAByte() => ToHSVA().ToCMYKAByte(); + public HSVAByte ToHSVAByte() => this; + + public byte[] ToArray() => new[] { H, S, V, A }; + public List ToList() => new() { H, S, V, A }; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return H; + yield return S; + yield return V; + yield return A; + } + + public object Clone() => new HSVAByte(H, S, V, A); + + public static HSVAByte operator +(HSVAByte a, HSVAByte b) => new(a.H + b.H, a.S + b.S, a.V + b.V, a.A + b.A); + public static HSVAByte operator -(HSVAByte c) => new(255 - c.H, 255 - c.S, 255 - c.V, c.A != 255 ? 255 - c.A : 255); + public static HSVAByte operator -(HSVAByte a, HSVAByte b) => new(a.H - b.H, a.S - b.S, a.V - b.V, a.A - b.A); + public static HSVAByte operator *(HSVAByte a, HSVAByte b) => new(a.H * b.H, a.S * b.S, a.V * b.V, a.A * b.A); + public static HSVAByte operator *(HSVAByte a, int b) => new(a.H * b, a.S * b, a.V * b, a.A * b); + public static HSVAByte operator *(HSVAByte a, float b) => (a.ToHSVA() * b).ToHSVAByte(); + public static HSVAByte operator /(HSVAByte a, HSVAByte b) => new(a.H / b.H, a.S / b.S, a.V / b.V, a.A / b.A); + public static HSVAByte operator /(HSVAByte a, int b) => new(a.H / b, a.S / b, a.V / b, a.A / b); + public static HSVAByte operator /(HSVAByte a, float b) => (a.ToHSVA() * b).ToHSVAByte(); + public static bool operator ==(HSVAByte a, RGBA b) => a.Equals((IColor?)b); + public static bool operator !=(HSVAByte a, RGBA b) => !a.Equals((IColor?)b); + public static bool operator ==(HSVAByte a, CMYKA b) => a.Equals((IColor?)b); + public static bool operator !=(HSVAByte a, CMYKA b) => !a.Equals((IColor?)b); + public static bool operator ==(HSVAByte a, HSVA b) => a.Equals((IColor?)b); + public static bool operator !=(HSVAByte a, HSVA b) => !a.Equals((IColor?)b); + public static bool operator ==(HSVAByte a, RGBAByte b) => a.Equals(b); + public static bool operator !=(HSVAByte a, RGBAByte b) => !a.Equals(b); + public static bool operator ==(HSVAByte a, CMYKAByte b) => a.Equals(b); + public static bool operator !=(HSVAByte a, CMYKAByte b) => !a.Equals(b); + public static bool operator ==(HSVAByte a, HSVAByte b) => a.Equals(b); + public static bool operator !=(HSVAByte a, HSVAByte b) => !a.Equals(b); + + public static implicit operator HSVAByte(Int3 val) => new(val.x, val.y, val.z); + public static implicit operator HSVAByte(Int4 val) => new(val.x, val.y, val.z, val.w); + public static implicit operator HSVAByte(CMYKA val) => val.ToHSVAByte(); + public static implicit operator HSVAByte(HSVA val) => val.ToHSVAByte(); + public static implicit operator HSVAByte(RGBA val) => val.ToHSVAByte(); + public static implicit operator HSVAByte(CMYKAByte val) => val.ToHSVAByte(); + public static implicit operator HSVAByte(RGBAByte val) => val.ToHSVAByte(); + public static implicit operator HSVAByte(Fill val) => new(val); + public static implicit operator HSVAByte(Fill val) => new(val); +} diff --git a/Nerd_STF/Graphics/IColor.cs b/Nerd_STF/Graphics/IColor.cs new file mode 100644 index 0000000..1bdeeb4 --- /dev/null +++ b/Nerd_STF/Graphics/IColor.cs @@ -0,0 +1,12 @@ +namespace Nerd_STF.Graphics; + +public interface IColor : ICloneable, IEquatable, IEquatable, IGroup +{ + public RGBA ToRGBA(); + public CMYKA ToCMYKA(); + public HSVA ToHSVA(); + + public RGBAByte ToRGBAByte(); + public CMYKAByte ToCMYKAByte(); + public HSVAByte ToHSVAByte(); +} diff --git a/Nerd_STF/Graphics/IColorByte.cs b/Nerd_STF/Graphics/IColorByte.cs new file mode 100644 index 0000000..1f3ab76 --- /dev/null +++ b/Nerd_STF/Graphics/IColorByte.cs @@ -0,0 +1,12 @@ +namespace Nerd_STF.Graphics; + +public interface IColorByte : ICloneable, IEquatable, IEquatable, IGroup +{ + public RGBA ToRGBA(); + public CMYKA ToCMYKA(); + public HSVA ToHSVA(); + + public RGBAByte ToRGBAByte(); + public CMYKAByte ToCMYKAByte(); + public HSVAByte ToHSVAByte(); +} diff --git a/Nerd_STF/Graphics/IlluminationFlags.cs b/Nerd_STF/Graphics/IlluminationFlags.cs new file mode 100644 index 0000000..faab27f --- /dev/null +++ b/Nerd_STF/Graphics/IlluminationFlags.cs @@ -0,0 +1,14 @@ +namespace Nerd_STF.Graphics; + +[Flags] +public enum IlluminationFlags : byte +{ + Color = 1, + Ambient = 2, + Highlight = 4, + Reflection = 8, + Raytrace = 16, + Fresnel = 32, + Refraction = 64, + Glass = 128, +} diff --git a/Nerd_STF/Graphics/IlluminationModel.cs b/Nerd_STF/Graphics/IlluminationModel.cs new file mode 100644 index 0000000..842611b --- /dev/null +++ b/Nerd_STF/Graphics/IlluminationModel.cs @@ -0,0 +1,16 @@ +namespace Nerd_STF.Graphics; + +public enum IlluminationModel : ushort +{ + Mode0 = IlluminationFlags.Color, + Mode1 = IlluminationFlags.Color | IlluminationFlags.Ambient, + Mode2 = IlluminationFlags.Highlight, + Mode3 = IlluminationFlags.Reflection | IlluminationFlags.Raytrace, + Mode4 = IlluminationFlags.Glass | IlluminationFlags.Raytrace, + Mode5 = IlluminationFlags.Fresnel | IlluminationFlags.Raytrace, + Mode6 = IlluminationFlags.Refraction | IlluminationFlags.Raytrace, + Mode7 = IlluminationFlags.Refraction | IlluminationFlags.Fresnel | IlluminationFlags.Raytrace, + Mode8 = IlluminationFlags.Reflection, + Mode9 = IlluminationFlags.Glass, + Mode10 = 256, +} diff --git a/Nerd_STF/Graphics/Image.cs b/Nerd_STF/Graphics/Image.cs new file mode 100644 index 0000000..2b7f670 --- /dev/null +++ b/Nerd_STF/Graphics/Image.cs @@ -0,0 +1,162 @@ +namespace Nerd_STF.Graphics; + +public struct Image : ICloneable, IEnumerable, IEquatable +{ + public IColor[,] Pixels { get; init; } + public Int2 Size { get; init; } + + public Image(int width, int height) + { + Pixels = new IColor[width, height]; + Size = new(width, height); + for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) Pixels[x, y] = RGBA.Clear; + } + public Image(int width, int height, IColor[] cols) + { + Pixels = new IColor[width, height]; + Size = new(width, height); + for (int y = 0; y < width; y++) for (int x = 0; x < height; x++) Pixels[x, y] = cols[y * width + x]; + } + public Image(int width, int height, IColor[,] cols) + { + Pixels = cols; + Size = new(width, height); + } + public Image(int width, int height, Fill fill) + { + Pixels = new IColor[width, height]; + Size = new(width, height); + for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) Pixels[x, y] = fill(y * width + x); + } + public Image(int width, int height, Fill2D fill) + { + Pixels = new IColor[width, height]; + Size = new(width, height); + for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) Pixels[x, y] = fill(x, y); + } + public Image(int width, int height, Fill fill) + { + Pixels = new IColor[width, height]; + Size = new(width, height); + for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) + Pixels[x, y] = (IColor)fill(y * width + x); + } + public Image(int width, int height, Fill2D fill) + { + Pixels = new IColor[width, height]; + Size = new(width, height); + for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) Pixels[x, y] = (IColor)fill(x, y); + } + public Image(Int2 size) : this(size.x, size.y) { } + public Image(Int2 size, IColor[] cols) : this(size.x, size.y, cols) { } + public Image(Int2 size, IColor[,] cols) : this(size.x, size.y, cols) { } + public Image(Int2 size, Fill fill) : this(size.x, size.y, fill) { } + public Image(Int2 size, Fill2D fill) : this(size.x, size.y, fill) { } + public Image(Int2 size, Fill fill) : this(size.x, size.y, fill) { } + public Image(Int2 size, Fill2D fill) : this(size.x, size.y, fill) { } + + public IColor this[int indexX, int indexY] + { + get => Pixels[indexX, indexY]; + set => Pixels[indexX, indexY] = value; + } + public IColor this[Int2 index] + { + get => Pixels[index.x, index.y]; + set => Pixels[index.x, index.y] = value; + } + + public static Image FromSingleColor(int width, int height, IColor col) => new(width, height, (x, y) => col); + public static Image FromSingleColor(Int2 size, IColor col) => new(size, (x, y) => col); + public static Image FromSingleColor(IColor col) => new(1, 1, (x, y) => col); + + public object Clone() => new Image(Size, Pixels); + + public bool Equals(Image other) => Pixels == other.Pixels; + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null) return false; + if (obj.GetType() == typeof(Image)) return Equals((Image)obj); + return false; + } + public override int GetHashCode() => Pixels.GetHashCode(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++) yield return Pixels[x, y]; + } + + public void ModifyBrightness(float value, bool set = false) + { + for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++) + { + RGBA col = Pixels[x, y].ToRGBA(); + col.R = (set ? 0 : col.R) + value; + col.G = (set ? 0 : col.G) + value; + col.B = (set ? 0 : col.B) + value; + Pixels[x, y] = col; + } + } + public void ModifyContrast(float value) + { + float factor = 259 * (255 + value) / (255 * (259 - value)); + for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++) + { + RGBA col = Pixels[x, y].ToRGBA(); + col.R = factor * (col.R - 0.5f) + 0.5f; + col.G = factor * (col.G - 0.5f) + 0.5f; + col.B = factor * (col.B - 0.5f) + 0.5f; + Pixels[x, y] = col; + } + } + public void ModifyHue(Angle value, bool set = false) + { + for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++) + { + HSVA col = Pixels[x, y].ToHSVA(); + col.H = (set ? Angle.Zero : col.H) + value; + Pixels[x, y] = col; + } + } + public void ModifySaturation(float value, bool set = false) + { + for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++) + { + HSVA col = Pixels[x, y].ToHSVA(); + col.S = (set ? 0 : col.S) + value; + Pixels[x, y] = col; + } + } + + public void Paint(Int2 min, Int2 max, IColor col) + { + for (int y = min.y; y <= max.y; y++) for (int x = min.x; x <= max.x; x++) Pixels[x, y] = col; + } + + public void Resize(Int2 resolution) => Scale(resolution / (Float2)Size); + + public void Scale(float factor) => Scale(Float2.One * factor); + public void Scale(Float2 factor) + { + Int2 newSize = (Int2)(Size * factor); + Image img = new(newSize); + for (int y = 0; y < newSize.y; y++) for (int x = 0; x < newSize.x; x++) + { + Float2 f = new((float)x / newSize.x, (float)y / newSize.y); + RGBA col = Pixels[Mathf.RoundInt(f.x * Size.x), Mathf.RoundInt(f.y * Size.y)].ToRGBA(); + img[x, y] = col; + } + this = img; + } + + public IColor[] ToArray() + { + IColor[] vals = new IColor[Pixels.Length]; + for (int y = 0; y < Size.y; y++) for (int x = 0; x < Size.x; x++) vals[y * Size.x + x] = Pixels[x, y]; + return vals; + } + + public static bool operator ==(Image a, Image b) => a.Equals(b); + public static bool operator !=(Image a, Image b) => !a.Equals(b); +} diff --git a/Nerd_STF/Graphics/Material.cs b/Nerd_STF/Graphics/Material.cs new file mode 100644 index 0000000..47e2389 --- /dev/null +++ b/Nerd_STF/Graphics/Material.cs @@ -0,0 +1,190 @@ +namespace Nerd_STF.Graphics; + +public struct Material : ICloneable, IEquatable +{ + public float Alpha; + public float Anisotropy; + public float AnisotropyRoughness; + public IColor AmbientColor; + public float ClearcoatRoughness; + public float ClearcoatThickness; + public IColor DiffuseColor; + public IColor Emissive; + public IlluminationModel IllumModel; + public float Metallic; + public float OpticalDensity; + public float Roughness; + public float Sheen; + public IColor SpecularColor; + public float SpecularExponent; + + public (Image Image, TextureConfig Config) AlphaTexture; + public (Image Image, TextureConfig Config) AmbientTexture; + public (Image Image, TextureConfig Config) DiffuseTexture; + public (Image Image, TextureConfig Config) DisplacementTexture; + public (Image Image, TextureConfig Config) EmissiveTexture; + public (Image Image, TextureConfig Config) MetallicTexture; + public (Image Image, TextureConfig Config) NormalTexture; + public (Image Image, TextureConfig Config) RoughnessTexture; + public (Image Image, TextureConfig Config) SheenTexture; + public (Image Image, TextureConfig Config) SpecularTexture; + public (Image Image, TextureConfig Config) SpecularHighlightTexture; + public (Image Image, TextureConfig Config) StencilTexture; + + public Material() + { + Alpha = 1; + Anisotropy = 0; + AnisotropyRoughness = 0; + AmbientColor = RGBA.White; + ClearcoatRoughness = 0; + ClearcoatThickness = 0; + DiffuseColor = RGBA.White; + Emissive = RGBA.Clear; + IllumModel = IlluminationModel.Mode0; + Metallic = 0; + OpticalDensity = 1.45f; + Roughness = 1; + Sheen = 0; + SpecularColor = RGBA.White; + SpecularExponent = 10; + + AlphaTexture = (Image.FromSingleColor(RGBA.White), new()); + AmbientTexture = (Image.FromSingleColor(RGBA.White), new()); + DiffuseTexture = (Image.FromSingleColor(RGBA.White), new()); + DisplacementTexture = (Image.FromSingleColor(RGBA.White), new()); + EmissiveTexture = (Image.FromSingleColor(RGBA.Clear), new()); + MetallicTexture = (Image.FromSingleColor(RGBA.Black), new()); + NormalTexture = (Image.FromSingleColor(new RGBA(0.5f, 0.5f, 1)), new()); + RoughnessTexture = (Image.FromSingleColor(RGBA.White), new()); + SheenTexture = (Image.FromSingleColor(RGBA.Black), new()); + SpecularTexture = (Image.FromSingleColor(RGBA.White), new()); + SpecularHighlightTexture = (Image.FromSingleColor(RGBA.White), new()); + StencilTexture = (Image.FromSingleColor(RGBA.White), new()); + } + public Material(Fill fill) + { + Alpha = (float)fill(0); + Anisotropy = (float)fill(1); + AnisotropyRoughness = (float)fill(2); + AmbientColor = (IColor)fill(3); + ClearcoatRoughness = (float)fill(4); + ClearcoatThickness = (float)fill(5); + DiffuseColor = (IColor)fill(6); + Emissive = (IColor)fill(7); + IllumModel = (IlluminationModel)fill(8); + Metallic = (float)fill(9); + OpticalDensity = (float)fill(10); + Roughness = (float)fill(11); + Sheen = (float)fill(12); + SpecularColor = (IColor)fill(13); + SpecularExponent = (float)fill(14); + + AlphaTexture = ((Image, TextureConfig))fill(15); + AmbientTexture = ((Image, TextureConfig))fill(16); + DiffuseTexture = ((Image, TextureConfig))fill(17); + DisplacementTexture = ((Image, TextureConfig))fill(18); + EmissiveTexture = ((Image, TextureConfig))fill(19); + MetallicTexture = ((Image, TextureConfig))fill(20); + NormalTexture = ((Image, TextureConfig))fill(21); + RoughnessTexture = ((Image, TextureConfig))fill(22); + SheenTexture = ((Image, TextureConfig))fill(23); + SpecularTexture = ((Image, TextureConfig))fill(24); + SpecularHighlightTexture = ((Image, TextureConfig))fill(25); + StencilTexture = ((Image, TextureConfig))fill(26); + } + public Material(IlluminationModel illum, Fill floats, Fill colors, Fill<(Image, TextureConfig)> images) + { + Alpha = floats(0); + Anisotropy = floats(1); + AnisotropyRoughness = floats(2); + AmbientColor = colors(0); + ClearcoatRoughness = floats(3); + ClearcoatThickness = floats(4); + DiffuseColor = colors(1); + Emissive = colors(2); + IllumModel = illum; + Metallic = floats(5); + OpticalDensity = floats(6); + Roughness = floats(7); + Sheen = floats(8); + SpecularColor = colors(3); + SpecularExponent = floats(9); + + AlphaTexture = images(0); + AmbientTexture = images(1); + DiffuseTexture = images(2); + DisplacementTexture = images(3); + EmissiveTexture = images(4); + MetallicTexture = images(5); + NormalTexture = images(6); + RoughnessTexture = images(7); + SheenTexture = images(8); + SpecularTexture = images(9); + SpecularHighlightTexture = images(10); + StencilTexture = images(11); + } + + public object Clone() => new Material() + { + Alpha = Alpha, + AmbientColor = AmbientColor, + Anisotropy = Anisotropy, + AnisotropyRoughness = AnisotropyRoughness, + ClearcoatRoughness = ClearcoatRoughness, + ClearcoatThickness = ClearcoatThickness, + DiffuseColor = DiffuseColor, + Emissive = Emissive, + IllumModel = IllumModel, + Metallic = Metallic, + OpticalDensity = OpticalDensity, + Roughness = Roughness, + Sheen = Sheen, + SpecularColor = SpecularColor, + SpecularExponent = SpecularExponent, + + AlphaTexture = AlphaTexture, + AmbientTexture = AmbientTexture, + DiffuseTexture = DiffuseTexture, + DisplacementTexture = DisplacementTexture, + EmissiveTexture = EmissiveTexture, + MetallicTexture = MetallicTexture, + NormalTexture = NormalTexture, + RoughnessTexture = RoughnessTexture, + SheenTexture = SheenTexture, + SpecularTexture = SpecularTexture, + SpecularHighlightTexture = SpecularHighlightTexture, + StencilTexture = StencilTexture + }; + public bool Equals(Material other) => Alpha.Equals(other.Alpha) && AmbientColor.Equals(other.AmbientColor) && + Anisotropy.Equals(other.Anisotropy) && AnisotropyRoughness.Equals(other.AnisotropyRoughness) && + ClearcoatRoughness.Equals(other.ClearcoatRoughness) && ClearcoatThickness.Equals(other.ClearcoatThickness) && + DiffuseColor.Equals(other.DiffuseColor) && Emissive.Equals(other.Emissive) && + IllumModel.Equals(other.IllumModel) && Metallic.Equals(other.Metallic) && + OpticalDensity.Equals(other.OpticalDensity) && Roughness.Equals(other.Roughness) && Sheen.Equals(other.Sheen) && + SpecularColor.Equals(other.SpecularColor) && SpecularExponent.Equals(other.SpecularExponent) && + AlphaTexture.Equals(other.AlphaTexture) && AmbientTexture.Equals(other.AmbientTexture) && + DiffuseTexture.Equals(other.DiffuseTexture) && DisplacementTexture.Equals(other.DisplacementTexture) && + EmissiveTexture.Equals(other.EmissiveTexture) && MetallicTexture.Equals(other.MetallicTexture) && + NormalTexture.Equals(other.NormalTexture) && RoughnessTexture.Equals(other.RoughnessTexture) && + SheenTexture.Equals(other.SheenTexture) && SpecularTexture.Equals(other.SheenTexture) && + SpecularHighlightTexture.Equals(other.SpecularHighlightTexture) && StencilTexture.Equals(other.StencilTexture); + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null) return false; + if (obj.GetType() == typeof(Material)) return Equals((Material)obj); + return false; + } + public override int GetHashCode() => Alpha.GetHashCode() ^ AmbientColor.GetHashCode() ^ Anisotropy.GetHashCode() ^ + AnisotropyRoughness.GetHashCode() ^ ClearcoatRoughness.GetHashCode() ^ ClearcoatThickness.GetHashCode() ^ + DiffuseColor.GetHashCode() ^ Emissive.GetHashCode() ^ IllumModel.GetHashCode() ^ Metallic.GetHashCode() ^ + OpticalDensity.GetHashCode() ^ Roughness.GetHashCode() ^ Sheen.GetHashCode() ^ SpecularColor.GetHashCode() ^ + SpecularExponent.GetHashCode() ^ AlphaTexture.GetHashCode() ^ AmbientTexture.GetHashCode() ^ + DiffuseTexture.GetHashCode() ^ DisplacementTexture.GetHashCode() ^ Emissive.GetHashCode() ^ + Metallic.GetHashCode() ^ NormalTexture.GetHashCode() ^ RoughnessTexture.GetHashCode() ^ + SheenTexture.GetHashCode() ^ SpecularTexture.GetHashCode() ^ SpecularHighlightTexture.GetHashCode() ^ + StencilTexture.GetHashCode(); + + public static bool operator ==(Material a, Material b) => a.Equals(b); + public static bool operator !=(Material a, Material b) => !a.Equals(b); +} diff --git a/Nerd_STF/Graphics/RGBA.cs b/Nerd_STF/Graphics/RGBA.cs new file mode 100644 index 0000000..333557b --- /dev/null +++ b/Nerd_STF/Graphics/RGBA.cs @@ -0,0 +1,249 @@ +namespace Nerd_STF.Graphics; + +public struct RGBA : IColor, IEquatable +{ + public static RGBA Black => new(0, 0, 0); + public static RGBA Blue => new(0, 0, 1); + public static RGBA Clear => new(0, 0, 0, 0); + public static RGBA Cyan => new(0, 1, 1); + public static RGBA Gray => new(0.5f, 0.5f, 0.5f); + public static RGBA Green => new(0, 1, 0); + public static RGBA Magenta => new(1, 0, 1); + public static RGBA Orange => new(1, 0.5f, 0); + public static RGBA Purple => new(0.5f, 0, 1); + public static RGBA Red => new(1, 0, 0); + public static RGBA White => new(1, 1, 1); + public static RGBA Yellow => new(1, 1, 0); + + public float R + { + get => p_r; + set => p_r = Mathf.Clamp(value, 0, 1); + } + public float G + { + get => p_g; + set => p_g = Mathf.Clamp(value, 0, 1); + } + public float B + { + get => p_b; + set => p_b = Mathf.Clamp(value, 0, 1); + } + public float A + { + get => p_a; + set => p_a = Mathf.Clamp(value, 0, 1); + } + + public bool HasBlue => p_b > 0; + public bool HasGreen => p_g > 0; + public bool HasRed => p_r > 0; + public bool IsOpaque => p_a == 1; + public bool IsVisible => p_a != 0; + + private float p_r, p_g, p_b, p_a; + + public RGBA() : this(0, 0, 0, 1) { } + public RGBA(float all) : this(all, all, all, all) { } + public RGBA(float all, float a) : this(all, all, all, a) { } + public RGBA(float r, float g, float b) : this(r, g, b, 1) { } + public RGBA(float r, float g, float b, float a) + { + p_r = Mathf.Clamp(r, 0, 1); + p_g = Mathf.Clamp(g, 0, 1); + p_b = Mathf.Clamp(b, 0, 1); + p_a = Mathf.Clamp(a, 0, 1); + } + public RGBA(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + + public float this[int index] + { + get => index switch + { + 0 => R, + 1 => G, + 2 => B, + 3 => A, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + set + { + switch (index) + { + case 0: + R = value; + break; + + case 1: + G = value; + break; + + case 2: + B = value; + break; + + case 3: + A = value; + break; + + default: throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public static RGBA Average(params RGBA[] vals) + { + RGBA val = new(0, 0, 0, 0); + for (int i = 0; i < vals.Length; i++) val += vals[i]; + return val / vals.Length; + } + public static RGBA Ceiling(RGBA val) => + new(Mathf.Ceiling(val.R), Mathf.Ceiling(val.G), Mathf.Ceiling(val.B), Mathf.Ceiling(val.A)); + public static RGBA Clamp(RGBA val, RGBA min, RGBA max) => + new(Mathf.Clamp(val.R, min.R, max.R), + Mathf.Clamp(val.G, min.G, max.G), + Mathf.Clamp(val.B, min.B, max.B), + Mathf.Clamp(val.A, min.A, max.A)); + public static RGBA Floor(RGBA val) => + new(Mathf.Floor(val.R), Mathf.Floor(val.G), Mathf.Floor(val.B), Mathf.Floor(val.A)); + public static RGBA Lerp(RGBA a, RGBA b, float t, bool clamp = true) => + new(Mathf.Lerp(a.R, b.R, t, clamp), Mathf.Lerp(a.G, b.G, t, clamp), Mathf.Lerp(a.B, b.B, t, clamp), + Mathf.Lerp(a.A, b.A, t, clamp)); + public static RGBA LerpSquared(RGBA a, RGBA b, float t, bool clamp = true) + { + RGBA val = Lerp(a * a, b * b, t, clamp); + float R = Mathf.Sqrt(val.R), G = Mathf.Sqrt(val.G), B = Mathf.Sqrt(val.B), A = Mathf.Sqrt(val.A); + return new(R, G, B, A); + } + public static RGBA Median(params RGBA[] vals) + { + float index = Mathf.Average(0, vals.Length - 1); + RGBA valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; + return Average(valA, valB); + } + public static RGBA Max(params RGBA[] vals) + { + (float[] Rs, float[] Gs, float[] Bs, float[] As) = SplitArray(vals); + return new(Mathf.Max(Rs), Mathf.Max(Gs), Mathf.Max(Bs), Mathf.Max(As)); + } + public static RGBA Min(params RGBA[] vals) + { + (float[] Rs, float[] Gs, float[] Bs, float[] As) = SplitArray(vals); + return new(Mathf.Min(Rs), Mathf.Min(Gs), Mathf.Min(Bs), Mathf.Min(As)); + } + + public static (float[] Rs, float[] Gs, float[] Bs, float[] As) SplitArray(params RGBA[] vals) + { + float[] Rs = new float[vals.Length], Gs = new float[vals.Length], + Bs = new float[vals.Length], As = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Rs[i] = vals[i].R; + Gs[i] = vals[i].G; + Bs[i] = vals[i].B; + As[i] = vals[i].A; + } + return (Rs, Gs, Bs, As); + } + + public bool Equals(IColor? col) => col != null && Equals(col.ToRGBA()); + public bool Equals(IColorByte? col) => col != null && Equals(col.ToRGBA()); + public bool Equals(RGBA col) => A == 0 && col.A == 0 || R == col.R && G == col.G && B == col.B && A == col.A; + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null) return false; + Type t = obj.GetType(); + if (t == typeof(RGBA)) return Equals((RGBA)obj); + else if (t == typeof(CMYKA)) return Equals((IColor)obj); + else if (t == typeof(HSVA)) return Equals((IColor)obj); + else if (t == typeof(IColor)) return Equals((IColor)obj); + else if (t == typeof(RGBAByte)) return Equals((IColorByte)obj); + else if (t == typeof(CMYKAByte)) return Equals((IColorByte)obj); + else if (t == typeof(HSVAByte)) return Equals((IColorByte)obj); + else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); + + return false; + } + public override int GetHashCode() => R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode() ^ A.GetHashCode(); + public string ToString(IFormatProvider provider) => "R: " + R.ToString(provider) + " G: " + G.ToString(provider) + + " B: " + B.ToString(provider) + " A: " + A.ToString(provider); + public string ToString(string? provider) => "R: " + R.ToString(provider) + " G: " + G.ToString(provider) + + " B: " + B.ToString(provider) + " A: " + A.ToString(provider); + public override string ToString() => ToString((string?)null); + + public RGBA ToRGBA() => this; + public CMYKA ToCMYKA() + { + float v = Mathf.Max(R, G, B), k = 1 - v; + if (v == 1) return new(0, 0, 0, 1, A); + + float kInv = 1 / v, c = 1 - R - k, m = 1 - G - k, y = 1 - B - k; + return new(c * kInv, m * kInv, y * kInv, k, A); + } + public HSVA ToHSVA() + { + float cMax = Mathf.Max(R, G, B), cMin = Mathf.Min(R, G, B), delta = cMax - cMin; + Angle hue = Angle.Zero; + if (delta != 0) + { + float val = 0; + if (cMax == R) val = (G - B) / delta % 6; + else if (cMax == G) val = (B - R) / delta + 2; + else if (cMax == B) val = (R - G) / delta + 4; + hue = new(val * 60); + } + + float sat = cMax == 0 ? 0 : delta / cMax; + + return new(hue, sat, cMax, A); + } + + public RGBAByte ToRGBAByte() => new(Mathf.RoundInt(R * 255), Mathf.RoundInt(G * 255), Mathf.RoundInt(B * 255), + Mathf.RoundInt(A * 255)); + public CMYKAByte ToCMYKAByte() => ToCMYKA().ToCMYKAByte(); + public HSVAByte ToHSVAByte() => ToHSVA().ToHSVAByte(); + + public float[] ToArray() => new[] { R, G, B, A }; + public List ToList() => new() { R, G, B, A }; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return R; + yield return G; + yield return B; + yield return A; + } + + public object Clone() => new RGBA(R, G, B, A); + + public static RGBA operator +(RGBA a, RGBA b) => new(a.R + b.R, a.G + b.G, a.B + b.B, a.A + b.A); + public static RGBA operator -(RGBA c) => new(1 - c.R, 1 - c.G, 1 - c.B, c.A != 1 ? 1 - c.A : 1); + public static RGBA operator -(RGBA a, RGBA b) => new(a.R - b.R, a.G - b.G, a.B - b.B, a.A - b.A); + public static RGBA operator *(RGBA a, RGBA b) => new(a.R * b.R, a.G * b.G, a.B * b.B, a.A * b.A); + public static RGBA operator *(RGBA a, float b) => new(a.R * b, a.G * b, a.B * b, a.A * b); + public static RGBA operator /(RGBA a, RGBA b) => new(a.R / b.R, a.G / b.G, a.B / b.B, a.A / b.A); + public static RGBA operator /(RGBA a, float b) => new(a.R / b, a.G / b, a.B / b, a.A / b); + public static bool operator ==(RGBA a, RGBA b) => a.Equals(b); + public static bool operator !=(RGBA a, RGBA b) => !a.Equals(b); + public static bool operator ==(RGBA a, CMYKA b) => a.Equals(b); + public static bool operator !=(RGBA a, CMYKA b) => !a.Equals(b); + public static bool operator ==(RGBA a, HSVA b) => a.Equals(b); + public static bool operator !=(RGBA a, HSVA b) => !a.Equals(b); + public static bool operator ==(RGBA a, RGBAByte b) => a.Equals((IColorByte?)b); + public static bool operator !=(RGBA a, RGBAByte b) => !a.Equals((IColorByte?)b); + public static bool operator ==(RGBA a, CMYKAByte b) => a.Equals((IColorByte?)b); + public static bool operator !=(RGBA a, CMYKAByte b) => !a.Equals((IColorByte?)b); + public static bool operator ==(RGBA a, HSVAByte b) => a.Equals((IColorByte?)b); + public static bool operator !=(RGBA a, HSVAByte b) => !a.Equals((IColorByte?)b); + + public static implicit operator RGBA(Float3 val) => new(val.x, val.y, val.z); + public static implicit operator RGBA(Float4 val) => new(val.x, val.y, val.z, val.w); + public static implicit operator RGBA(CMYKA val) => val.ToRGBA(); + public static implicit operator RGBA(HSVA val) => val.ToRGBA(); + public static implicit operator RGBA(RGBAByte val) => val.ToRGBA(); + public static implicit operator RGBA(CMYKAByte val) => val.ToRGBA(); + public static implicit operator RGBA(HSVAByte val) => val.ToRGBA(); + public static implicit operator RGBA(Fill val) => new(val); +} diff --git a/Nerd_STF/Graphics/RGBAByte.cs b/Nerd_STF/Graphics/RGBAByte.cs new file mode 100644 index 0000000..d363449 --- /dev/null +++ b/Nerd_STF/Graphics/RGBAByte.cs @@ -0,0 +1,213 @@ +namespace Nerd_STF.Graphics; + +public struct RGBAByte : IColorByte, IEquatable +{ + public static RGBAByte Black => new(0, 0, 0); + public static RGBAByte Blue => new(0, 0, 255); + public static RGBAByte Clear => new(0, 0, 0, 0); + public static RGBAByte Cyan => new(0, 255, 255); + public static RGBAByte Gray => new(127, 127, 127); + public static RGBAByte Green => new(0, 255, 0); + public static RGBAByte Magenta => new(255, 0, 255); + public static RGBAByte Orange => new(255, 127, 0); + public static RGBAByte Purple => new(127, 0, 255); + public static RGBAByte Red => new(255, 0, 0); + public static RGBAByte White => new(255, 255, 255); + public static RGBAByte Yellow => new(255, 255, 0); + + public byte R, G, B, A; + + public bool HasBlue => B > 0; + public bool HasGreen => G > 0; + public bool HasRed => R > 0; + public bool IsOpaque => A == 255; + public bool IsVisible => A != 0; + + public RGBAByte() : this(0, 0, 0, 255) { } + public RGBAByte(int all) : this(all, all, all, all) { } + public RGBAByte(int all, int a) : this(all, all, all, a) { } + public RGBAByte(int r, int g, int b) : this(r, g, b, 255) { } + public RGBAByte(int r, int g, int b, int a) + { + R = (byte)Mathf.Clamp(r, 0, 255); + G = (byte)Mathf.Clamp(g, 0, 255); + B = (byte)Mathf.Clamp(b, 0, 255); + A = (byte)Mathf.Clamp(a, 0, 255); + } + public RGBAByte(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + public RGBAByte(Fill fill) : this(fill(0), fill(1), fill(2), fill(3)) { } + + public byte this[int index] + { + get => index switch + { + 0 => R, + 1 => G, + 2 => B, + 3 => A, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + set + { + switch (index) + { + case 0: + R = value; + break; + + case 1: + G = value; + break; + + case 2: + B = value; + break; + + case 3: + A = value; + break; + + default: throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public static RGBAByte Average(params RGBAByte[] vals) + { + RGBAByte val = new(0, 0, 0, 0); + for (int i = 0; i < vals.Length; i++) val += vals[i]; + return val / vals.Length; + } + public static RGBAByte Clamp(RGBAByte val, RGBAByte min, RGBAByte max) => + new(Mathf.Clamp(val.R, min.R, max.R), + Mathf.Clamp(val.G, min.G, max.G), + Mathf.Clamp(val.B, min.B, max.B), + Mathf.Clamp(val.A, min.A, max.A)); + public static RGBAByte Lerp(RGBAByte a, RGBAByte b, byte t, bool clamp = true) => + new(Mathf.Lerp(a.R, b.R, t, clamp), Mathf.Lerp(a.G, b.G, t, clamp), Mathf.Lerp(a.B, b.B, t, clamp), + Mathf.Lerp(a.A, b.A, t, clamp)); + public static RGBAByte LerpSquared(RGBAByte a, RGBAByte b, byte t, bool clamp = true) => + RGBA.LerpSquared(a.ToRGBA(), b.ToRGBA(), t, clamp).ToRGBAByte(); + public static RGBAByte Median(params RGBAByte[] vals) + { + float index = Mathf.Average(0, vals.Length - 1); + RGBAByte valA = vals[Mathf.Floor(index)], valB = vals[Mathf.Ceiling(index)]; + return Average(valA, valB); + } + public static RGBAByte Max(params RGBAByte[] vals) + { + (int[] Rs, int[] Gs, int[] Bs, int[] As) = SplitArrayInt(vals); + return new(Mathf.Max(Rs), Mathf.Max(Gs), Mathf.Max(Bs), Mathf.Max(As)); + } + public static RGBAByte Min(params RGBAByte[] vals) + { + (int[] Rs, int[] Gs, int[] Bs, int[] As) = SplitArrayInt(vals); + return new(Mathf.Min(Rs), Mathf.Min(Gs), Mathf.Min(Bs), Mathf.Min(As)); + } + + public static (byte[] Rs, byte[] Gs, byte[] Bs, byte[] As) SplitArray(params RGBAByte[] vals) + { + byte[] Rs = new byte[vals.Length], Gs = new byte[vals.Length], + Bs = new byte[vals.Length], As = new byte[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Rs[i] = vals[i].R; + Gs[i] = vals[i].G; + Bs[i] = vals[i].B; + As[i] = vals[i].A; + } + return (Rs, Gs, Bs, As); + } + public static (int[] Rs, int[] Gs, int[] Bs, int[] As) SplitArrayInt(params RGBAByte[] vals) + { + int[] Rs = new int[vals.Length], Gs = new int[vals.Length], + Bs = new int[vals.Length], As = new int[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Rs[i] = vals[i].R; + Gs[i] = vals[i].G; + Bs[i] = vals[i].B; + As[i] = vals[i].A; + } + return (Rs, Gs, Bs, As); + } + + public bool Equals(IColor? col) => col != null && Equals(col.ToRGBAByte()); + public bool Equals(IColorByte? col) => col != null && Equals(col.ToRGBAByte()); + public bool Equals(RGBAByte col) => A == 0 && col.A == 0 || R == col.R && G == col.G && B == col.B && A == col.A; + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj == null) return false; + Type t = obj.GetType(); + if (t == typeof(RGBAByte)) return Equals((RGBAByte)obj); + else if (t == typeof(CMYKA)) return Equals((IColor)obj); + else if (t == typeof(HSVA)) return Equals((IColor)obj); + else if (t == typeof(IColor)) return Equals((IColor)obj); + else if (t == typeof(RGBA)) return Equals((IColorByte)obj); + else if (t == typeof(CMYKAByte)) return Equals((IColorByte)obj); + else if (t == typeof(HSVAByte)) return Equals((IColorByte)obj); + else if (t == typeof(IColorByte)) return Equals((IColorByte)obj); + + return false; + } + public override int GetHashCode() => R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode() ^ A.GetHashCode(); + public string ToString(IFormatProvider provider) => "R: " + R.ToString(provider) + " G: " + G.ToString(provider) + + " B: " + B.ToString(provider) + " A: " + A.ToString(provider); + public string ToString(string? provider) => "R: " + R.ToString(provider) + " G: " + G.ToString(provider) + + " B: " + B.ToString(provider) + " A: " + A.ToString(provider); + public override string ToString() => ToString((string?)null); + + public RGBA ToRGBA() => new(R / 255f, G / 255f, B / 255f, A / 255f); + public CMYKA ToCMYKA() => ToRGBA().ToCMYKA(); + public HSVA ToHSVA() => ToRGBA().ToHSVA(); + + public RGBAByte ToRGBAByte() => this; + public CMYKAByte ToCMYKAByte() => ToRGBA().ToCMYKAByte(); + public HSVAByte ToHSVAByte() => ToRGBA().ToHSVAByte(); + + public byte[] ToArray() => new[] { R, G, B, A }; + public List ToList() => new() { R, G, B, A }; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + yield return R; + yield return G; + yield return B; + yield return A; + } + + public object Clone() => new RGBAByte(R, G, B, A); + + public static RGBAByte operator +(RGBAByte a, RGBAByte b) => new(a.R + b.R, a.G + b.G, a.B + b.B, a.A + b.A); + public static RGBAByte operator -(RGBAByte c) => new(255 - c.R, 255 - c.G, 255 - c.B, c.A != 255 ? 255 - c.A : 255); + public static RGBAByte operator -(RGBAByte a, RGBAByte b) => new(a.R - b.R, a.G - b.G, a.B - b.B, a.A - b.A); + public static RGBAByte operator *(RGBAByte a, RGBAByte b) => new(a.R * b.R, a.G * b.G, a.B * b.B, a.A * b.A); + public static RGBAByte operator *(RGBAByte a, int b) => new(a.R * b, a.G * b, a.B * b, a.A * b); + public static RGBAByte operator *(RGBAByte a, float b) => (a.ToRGBA() * b).ToRGBAByte(); + public static RGBAByte operator /(RGBAByte a, RGBAByte b) => new(a.R / b.R, a.G / b.G, a.B / b.B, a.A / b.A); + public static RGBAByte operator /(RGBAByte a, int b) => new(a.R / b, a.G / b, a.B / b, a.A / b); + public static RGBAByte operator /(RGBAByte a, float b) => (a.ToRGBA() / b).ToRGBAByte(); + public static bool operator ==(RGBAByte a, RGBA b) => a.Equals((IColor?)b); + public static bool operator !=(RGBAByte a, RGBA b) => !a.Equals((IColor?)b); + public static bool operator ==(RGBAByte a, CMYKA b) => a.Equals((IColor?)b); + public static bool operator !=(RGBAByte a, CMYKA b) => !a.Equals((IColor?)b); + public static bool operator ==(RGBAByte a, HSVA b) => a.Equals((IColor?)b); + public static bool operator !=(RGBAByte a, HSVA b) => !a.Equals((IColor?)b); + public static bool operator ==(RGBAByte a, RGBAByte b) => a.Equals(b); + public static bool operator !=(RGBAByte a, RGBAByte b) => !a.Equals(b); + public static bool operator ==(RGBAByte a, CMYKAByte b) => a.Equals(b); + public static bool operator !=(RGBAByte a, CMYKAByte b) => !a.Equals(b); + public static bool operator ==(RGBAByte a, HSVAByte b) => a.Equals(b); + public static bool operator !=(RGBAByte a, HSVAByte b) => !a.Equals(b); + + public static implicit operator RGBAByte(Int3 val) => new(val.x, val.y, val.z); + public static implicit operator RGBAByte(Int4 val) => new(val.x, val.y, val.z, val.w); + public static implicit operator RGBAByte(CMYKA val) => val.ToRGBAByte(); + public static implicit operator RGBAByte(HSVA val) => val.ToRGBAByte(); + public static implicit operator RGBAByte(RGBA val) => val.ToRGBAByte(); + public static implicit operator RGBAByte(CMYKAByte val) => val.ToRGBAByte(); + public static implicit operator RGBAByte(HSVAByte val) => val.ToRGBAByte(); + public static implicit operator RGBAByte(Fill val) => new(val); + public static implicit operator RGBAByte(Fill val) => new(val); +} diff --git a/Nerd_STF/Graphics/TextureConfig.cs b/Nerd_STF/Graphics/TextureConfig.cs new file mode 100644 index 0000000..9a1ee73 --- /dev/null +++ b/Nerd_STF/Graphics/TextureConfig.cs @@ -0,0 +1,25 @@ +namespace Nerd_STF.Graphics; + +public struct TextureConfig +{ + public (bool U, bool V) BlendUV; + public float Boost; + public ColorChannel Channel; + public bool Clamp; + public float NormalStrength; + public Float3 Offset; + public Float3 Scale; + public Float3 Turbulance; + + public TextureConfig() + { + BlendUV = (true, true); + Boost = 0; + Channel = ColorChannel.Red; + Clamp = false; + NormalStrength = 1; + Offset = Float3.Zero; + Scale = Float3.One; + Turbulance = Float3.Zero; + } +} diff --git a/Nerd_STF/Mathematics/Angle.cs b/Nerd_STF/Mathematics/Angle.cs index 5fa167e..e0e83fa 100644 --- a/Nerd_STF/Mathematics/Angle.cs +++ b/Nerd_STF/Mathematics/Angle.cs @@ -9,20 +9,25 @@ public struct Angle : ICloneable, IComparable, IEquatable public static Angle Zero => new(0); public float Degrees - { - get => p_deg; - set => p_deg = value; - } + { + get => p_deg; + set => p_deg = value; + } public float Gradians - { - get => p_deg * 1.11111111111f; // Reciprocal of 9/10 as a constant (10/9) - set => p_deg = value * 0.9f; - } + { + get => p_deg * 1.11111111111f; // Reciprocal of 9/10 as a constant (10/9) + set => p_deg = value * 0.9f; + } + public float Normalized + { + get => p_deg / 360; + set => p_deg = value * 360; + } public float Radians - { - get => p_deg * Mathf.DegToRad; - set => p_deg = value * Mathf.RadToDeg; - } + { + get => p_deg * Constants.DegToRad; + set => p_deg = value * Constants.RadToDeg; + } public Angle Bounded => new(p_deg % 360); @@ -34,23 +39,24 @@ public struct Angle : ICloneable, IComparable, IEquatable { Type.Degrees => value, Type.Gradians => value * 0.9f, - Type.Radians => value * Mathf.RadToDeg, + Type.Normalized => value * 360, + Type.Radians => value * Constants.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 Average(params Angle[] vals) => new(Mathf.Average(SplitArray(Type.Degrees, vals))); + public static Angle Ceiling(Angle val, Type type = Type.Degrees) => new(Mathf.Ceiling(val.ValueFromType(type))); 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 Floor(Angle val, Type type = Type.Degrees) => new(Mathf.Floor(val.ValueFromType(type))); public static Angle Lerp(Angle a, Angle b, float 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 Angle Max(params Angle[] vals) => new(Mathf.Max(SplitArray(Type.Degrees, vals))); + public static Angle Median(params Angle[] vals) => new(Mathf.Median(SplitArray(Type.Degrees, vals))); + public static Angle Min(params Angle[] vals) => new(Mathf.Min(SplitArray(Type.Degrees, vals))); - public static float[] ToDoubles(Type outputType, params Angle[] vals) + public static float[] SplitArray(Type outputType, params Angle[] vals) { float[] res = new float[vals.Length]; for (int i = 0; i < vals.Length; i++) @@ -59,6 +65,7 @@ public struct Angle : ICloneable, IComparable, IEquatable { Type.Degrees => vals[i].Degrees, Type.Gradians => vals[i].Gradians, + Type.Normalized => vals[i].Normalized, Type.Radians => vals[i].Radians, _ => throw new ArgumentException("Unknown type.", nameof(outputType)), }; @@ -78,21 +85,32 @@ public struct Angle : ICloneable, IComparable, IEquatable 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), + Type.Degrees => p_deg.ToString(provider) + "°", + Type.Gradians => Gradians.ToString(provider) + "grad", + Type.Normalized => Normalized.ToString(provider) + "%", + Type.Radians => Radians.ToString(provider) + "rad", _ => 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), + Type.Degrees => p_deg.ToString(provider) + "°", + Type.Gradians => Gradians.ToString(provider) + "grad", + Type.Normalized => Normalized.ToString(provider) + "%", + Type.Radians => Radians.ToString(provider) + "rad", _ => throw new ArgumentException("Unknown type.", nameof(outputType)), }; public object Clone() => new Angle(p_deg); + public float ValueFromType(Type type) => type switch + { + Type.Degrees => Degrees, + Type.Gradians => Gradians, + Type.Normalized => Normalized, + Type.Radians => Radians, + _ => throw new ArgumentException("Unknown type.", nameof(type)), + }; + 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); @@ -111,6 +129,7 @@ public struct Angle : ICloneable, IComparable, IEquatable { Degrees, Gradians, + Normalized, Radians, } } diff --git a/Nerd_STF/Mathematics/Constants.cs b/Nerd_STF/Mathematics/Constants.cs new file mode 100644 index 0000000..84ddd58 --- /dev/null +++ b/Nerd_STF/Mathematics/Constants.cs @@ -0,0 +1,156 @@ +namespace Nerd_STF.Mathematics; + +public static class Constants +{ + public const float DegToRad = 180 / Pi; + public const float HalfPi = Pi / 2; + public const float Pi = 3.14159265359f; + public const float RadToDeg = Pi / 180; + public const float Tau = Pi * 2; + + public const float E = 2.71828182846f; + public const float EulerConstant = 0.5772156649f; + public const float Ln2 = 0.69314718056f; + public const float Ln3 = 1.09861228867f; + public const float Ln5 = 1.60943791243f; + public const float Ln10 = 2.30258509299f; + + public const float Log2 = 0.301029995664f; + public const float Log3 = 0.47712125472f; + public const float Log5 = 0.698970004336f; + public const float Log10 = 1; + + public const float GoldenAngle = 180 * (3 - Sqrt5); + public const float GoldenRatio = (1 + Sqrt5) / 2; + public const float SilverRatio = Sqrt2 + 1; + public const float SupergoldenRatio = 1.465571232f; + + public const float Cbrt2 = 1.25992104989f; + public const float Cbrt3 = 1.44224957031f; + public const float Cbrt5 = 1.70997594668f; + public const float Cbrt10 = 2.15443469003f; + public const float Sqrt2 = 1.4142135624f; + public const float Sqrt3 = 1.7320508076f; + public const float Sqrt5 = 2.2360679775f; + public const float Sqrt10 = 3.16227766017f; + public const float TwelthRoot2 = 1.05946309436f; + + public const float Cos0Deg = 1; + public const float Cos30Deg = Sqrt3 / 2; + public const float Cos45Deg = Sqrt2 / 2; + public const float Cos60Deg = 0.5f; + public const float Cos90Deg = 0; + public const float Cot0Deg = float.PositiveInfinity; + public const float Cot30Deg = Sqrt3; + public const float Cot45Deg = 1; + public const float Cot60Deg = Sqrt3 / 3; + public const float Cot90Deg = 0; + public const float Csc0Deg = float.PositiveInfinity; + public const float Csc30Deg = 2; + public const float Csc45Deg = Sqrt2; + public const float Csc60Deg = 2 * Sqrt3 / 3; + public const float Csc90Deg = 1; + public const float MagicAngle = 54.7356103172f; + public const float Sec0Deg = 1; + public const float Sec30Deg = 2 * Sqrt3 / 3; + public const float Sec45Deg = Sqrt2; + public const float Sec60Deg = 2; + public const float Sec90Deg = float.PositiveInfinity; + public const float Sin0Deg = 0; + public const float Sin30Deg = 0.5f; + public const float Sin45Deg = Sqrt2 / 2; + public const float Sin60Deg = Sqrt3 / 2; + public const float Sin90Deg = 1; + public const float Tan0Deg = 0; + public const float Tan30Deg = Sqrt3 / 3; + public const float Tan45Deg = 1; + public const float Tan60Deg = Sqrt3; + public const float Tan90Deg = float.PositiveInfinity; + + public const float AperyConstant = 1.2020569031f; + public const float ArtinConstant = 0.3739558136f; + public const float AsymptoticLebesgueConstant = 0.9894312738f; + public const float BackhouseConstant = 1.4560749485f; + public const float Base10ChampernowneConstant = 0.1234567891f; + public const float BernsteinConstant = 0.2801694990f; + public const float BlochConstant = (0.4332f + 0.4719f) / 2; + public const float BruijnNewmanConstant = (0 + 0.2f) / 2; + public const float BrunConstant = 1.9021605831f; + public const float CatalanConstant = 0.9159655941f; + public const float CahenConstant = 0.6434105462f; + public const float ChaitinConstant = 0.0078749969f; + public const float ChvatalSankoffConstant = (0.788071f + 0.826280f) / 2; + public const float ConwayConstant = 1.3035772690f; + public const float CopelandErdosConstant = 0.2357111317f; + public const float ConnectiveConstant = 1.847759065f; + public const float DeVicciTesseractConstant = 1.0074347568f; + public const float EmbreeTrefethenConstant = 0.70258f; + public const float EulerMascheroniConstant = 0.5772156649f; + public const float ErdosBorweinConstant = 1.6066951524f; + public const float ErdosTenenbaumFordConstant = 0.8607133205f; + public const float FeigenbaumConstant1 = 4.6692016091f; + public const float FeigenbaumConstant2 = 2.5029078750f; + public const float FellerTornierConstant = 0.6613170494f; + public const float FoiasConstant = 1.1874523511f; + public const float FransenRobinsonConstant = 2.8077702420f; + public const float GaussConstant = 0.8346268416f; + public const float GelfondConstant = 23.1406926327f; + public const float GelfondSchneiderConstant = 2.6651441426f; + public const float GiesekingConstant = 1.0149416064f; + public const float GlaisherKinkelinConstant = 1.2824271291f; + public const float GolombDickmanConstant = 0.6243299885f; + public const float GompertzConstant = 0.5963473623f; + public const float HafnerSarnakMcCurleyConstant = 0.3532363718f; + public const float HeathBrownMorozConstant = 0.0013176411f; + public const float KeplerBouwkampConstant = 0.1149420449f; + public const float KhinchinConstant = 2.6854520010f; + public const float KomornikLoreti = 1.7872316501f; + public const float LandauConstant = (0.5f + 0.54326f) / 2; + public const float LandauThirdConstant = (0.5f + 0.7853f) / 2; + public const float LandauRamanujanConstant = 0.7642236535f; + public const float LiebSquareIceConstant = 8 / (3 * Sqrt3); + public const float LemniscateConstant = 2.6220575542f; + public const float LevyConstant1 = Pi * Pi / (12 * Ln2); + public const float LevyConstant2 = 3.2758229187f; + public const float LiouvilleConstant = 0.110001f; + public const float LochsConstant = 6 * Ln2 * Ln10 / (Pi * Pi); + public const float MeisselMertensConstant = 0.2614972128f; + public const float MillsConstant = 1.3063778838f; + public const float MRBConstant = 0.1878596424f; + public const float NivenConstant = 1.7052111401f; + public const float OmegaConstant = 0.5671432904f; + public const float PorterConstant = 1.4670780794f; + public const float PrimeConstant = 0.4146825098f; + public const float ProuhetThueMorseConstant = 0.4124540336f; + public const float RamanujanConstant = 262_537_412_640_768_743.99999f; + public const float RamanujanSoldnerConstant = 1.4513692348f; + public const float ReciprocalFibonacciConstant = 3.3598856662f; + public const float RobbinsConstant = 0.6617071822f; + public const float SalemConstant = 1.1762808182f; + public const float SierpinskiConstant = 2.5849817595f; + public const float SomosQuadraticRecurranceConstant = 1.6616879496f; + public const float StephensConstant = 0.5759599688f; + public const float TaniguchiConstant = 0.6782344919f; + public const float TribonacciConstant = 1.8392867552f; + public const float TwinPrimesConstant = 0.6601618158f; + public const float VanDerPauwConstant = Pi / Ln2; + public const float ViswanathConstant = 1.1319882487f; + public const float WeierstrassConstant = 0.4749493799f; + + public const float FirstContinuedFractionConstant = 0.6977746579f; + public const float FirstNielsenRamanujanConstant = Pi * Pi / 12; + public const float SecondDuBoisRaymondConstant = (E * E - 7) / 2; + public const float SecondFavardConstant = 1.2337005501f; + public const float SecondHermiteConstant = 2 / Sqrt3; + public const float UniversalHyperbolicConstant = 2.2955871493f; + + public const float DottieNumber = 0.7390851332f; + public const float FractalDimensionOfTheApollonianPackingOfCircles = 1.305688f; + public const float FractalDimensionOfTheCanterSet = Log2 / Log3; + public const float HausdorffDimensionOfTheSerpinskiTriangle = Log3 / Log2; + public const float MagicNumber = 3; // 3 is a magic number. + public const float PlasticNumber = 1.3247179572f; + public const float LaplaceLimit = 0.6627434193f; + public const float LogarithmicCapacityOfTheUnitDisk = 0.5901702995f; + public const float RegularPaperfoldingSequence = 0.8507361882f; +} diff --git a/Nerd_STF/Mathematics/Float2.cs b/Nerd_STF/Mathematics/Float2.cs index 1171fbd..8c52472 100644 --- a/Nerd_STF/Mathematics/Float2.cs +++ b/Nerd_STF/Mathematics/Float2.cs @@ -111,7 +111,7 @@ public struct Float2 : ICloneable, IComparable, IEquatable, IGro foreach (Float2 d in vals) val = d < val ? d : val; return val; } - public static Float2 Multiply(params Float2[] vals) + public static Float2 Product(params Float2[] vals) { if (vals.Length < 1) return Zero; Float2 val = One; @@ -130,6 +130,17 @@ public struct Float2 : ICloneable, IComparable, IEquatable, IGro return val; } + public static (float[] Xs, float[] Ys) SplitArray(params Float2[] vals) + { + float[] Xs = new float[vals.Length], Ys = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Xs[i] = vals[i].x; + Ys[i] = vals[i].y; + } + return (Xs, Ys); + } + public int CompareTo(Float2 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { diff --git a/Nerd_STF/Mathematics/Float3.cs b/Nerd_STF/Mathematics/Float3.cs index 9e77b21..4067b79 100644 --- a/Nerd_STF/Mathematics/Float3.cs +++ b/Nerd_STF/Mathematics/Float3.cs @@ -131,7 +131,7 @@ public struct Float3 : ICloneable, IComparable, IEquatable, IGro foreach (Float3 d in vals) val = d < val ? d : val; return val; } - public static Float3 Multiply(params Float3[] vals) + public static Float3 Product(params Float3[] vals) { if (vals.Length < 1) return Zero; Float3 val = One; @@ -150,6 +150,18 @@ public struct Float3 : ICloneable, IComparable, IEquatable, IGro return val; } + public static (float[] Xs, float[] Ys, float[] Zs) SplitArray(params Float3[] vals) + { + float[] Xs = new float[vals.Length], Ys = new float[vals.Length], Zs = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Xs[i] = vals[i].x; + Ys[i] = vals[i].y; + Zs[i] = vals[i].z; + } + return (Xs, Ys, Zs); + } + public int CompareTo(Float3 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { @@ -197,6 +209,10 @@ public struct Float3 : ICloneable, IComparable, IEquatable, IGro public static implicit operator Float3(Int3 val) => new(val.x, val.y, val.z); public static explicit operator Float3(Int4 val) => new(val.x, val.y, val.z); public static implicit operator Float3(Vert val) => new(val.position.x, val.position.y, val.position.z); + public static explicit operator Float3(RGBA val) => new(val.R, val.G, val.B); + public static explicit operator Float3(HSVA val) => new(val.H.Normalized, val.S, val.V); + public static explicit operator Float3(RGBAByte val) => (Float3)val.ToRGBA(); + public static explicit operator Float3(HSVAByte val) => (Float3)val.ToHSVA(); public static implicit operator Float3(Fill fill) => new(fill); public static implicit operator Float3(Fill fill) => new(fill); } diff --git a/Nerd_STF/Mathematics/Float4.cs b/Nerd_STF/Mathematics/Float4.cs index 928ff25..22c471f 100644 --- a/Nerd_STF/Mathematics/Float4.cs +++ b/Nerd_STF/Mathematics/Float4.cs @@ -144,7 +144,7 @@ public struct Float4 : ICloneable, IComparable, IEquatable, IGro foreach (Float4 d in vals) val = d < val ? d : val; return val; } - public static Float4 Multiply(params Float4[] vals) + public static Float4 Product(params Float4[] vals) { if (vals.Length < 1) return Zero; Float4 val = One; @@ -163,6 +163,20 @@ public struct Float4 : ICloneable, IComparable, IEquatable, IGro return val; } + public static (float[] Xs, float[] Ys, float[] Zs, float[] Ws) SplitArray(params Float4[] vals) + { + float[] Xs = new float[vals.Length], Ys = new float[vals.Length], Zs = new float[vals.Length], + Ws = new float[vals.Length]; + for (int i = 0; i < vals.Length; i++) + { + Xs[i] = vals[i].x; + Ys[i] = vals[i].y; + Zs[i] = vals[i].z; + Ws[i] = vals[i].w; + } + return (Xs, Ys, Zs, Ws); + } + public int CompareTo(Float4 other) => Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { @@ -213,6 +227,12 @@ public struct Float4 : ICloneable, IComparable, IEquatable, IGro public static implicit operator Float4(Int3 val) => new(val.x, val.y, val.z, 0); public static implicit operator Float4(Int4 val) => new(val.x, val.y, val.z, val.w); public static implicit operator Float4(Vert val) => new(val.position.x, val.position.y, val.position.z, 0); + public static implicit operator Float4(RGBA val) => new(val.R, val.G, val.B, val.A); + public static explicit operator Float4(CMYKA val) => new(val.C, val.M, val.Y, val.K); + public static explicit operator Float4(HSVA val) => new(val.H.Normalized, val.S, val.V, val.A); + public static implicit operator Float4(RGBAByte val) => val.ToRGBA(); + public static explicit operator Float4(CMYKAByte val) => (Float4)val.ToCMYKA(); + public static implicit operator Float4(HSVAByte val) => (Float4)val.ToHSVA(); public static implicit operator Float4(Fill fill) => new(fill); public static implicit operator Float4(Fill fill) => new(fill); } diff --git a/Nerd_STF/Mathematics/Geometry/Line.cs b/Nerd_STF/Mathematics/Geometry/Line.cs index 5e07311..f117a7e 100644 --- a/Nerd_STF/Mathematics/Geometry/Line.cs +++ b/Nerd_STF/Mathematics/Geometry/Line.cs @@ -14,6 +14,7 @@ public struct Line : ICloneable, IClosest, IComparable, IContainer new(Vert.Zero, Vert.Zero); public float Length => (b - a).Magnitude; + public Vert Midpoint => Vert.Average(a, b); public Vert a, b; @@ -162,9 +163,9 @@ public struct Line : ICloneable, IClosest, IComparable, IContainer new Vert[] { a, b }; public List ToList() => new() { a, b }; - public float[] ToDoubleArray() => new float[] { a.position.x, a.position.y, a.position.z, + public float[] ToFloatArray() => new float[] { 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, + public List ToFloatList() => 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); diff --git a/Nerd_STF/Mathematics/Geometry/Polygon.cs b/Nerd_STF/Mathematics/Geometry/Polygon.cs index 14b45f7..290f8bc 100644 --- a/Nerd_STF/Mathematics/Geometry/Polygon.cs +++ b/Nerd_STF/Mathematics/Geometry/Polygon.cs @@ -3,23 +3,24 @@ public struct Polygon : ICloneable, IEquatable, IGroup, ISubdividable, ITriangulatable { public Line[] Lines + { + get => p_lines; + set { - get => p_lines; - set - { - p_lines = value; - p_verts = GenerateVerts(value); - } + p_lines = value; + p_verts = GenerateVerts(value); } + } + public Vert Midpoint => Vert.Average(Verts); public Vert[] Verts + { + get => p_verts; + set { - get => p_verts; - set - { - p_verts = value; - p_lines = GenerateLines(value); - } + p_verts = value; + p_lines = GenerateLines(value); } + } private Line[] p_lines; private Vert[] p_verts; @@ -132,7 +133,7 @@ public struct Polygon : ICloneable, IEquatable, IGroup, ISubdivid List parts = new(); for (int i = 0; i < vertCount; i++) { - float val = Mathf.Tau * i / vertCount; + float val = Constants.Tau * i / vertCount; parts.Add(new(Mathf.Cos(val), Mathf.Sin(val))); } return new(parts.ToArray()); @@ -245,11 +246,11 @@ public struct Polygon : ICloneable, IEquatable, IGroup, ISubdivid return new(res); } - public static float[] ToDoubleArrayAll(params Polygon[] polys) => ToDoubleListAll(polys).ToArray(); - public static List ToDoubleListAll(params Polygon[] polys) + public static float[] ToFloatArrayAll(params Polygon[] polys) => ToFloatListAll(polys).ToArray(); + public static List ToFloatListAll(params Polygon[] polys) { List vals = new(); - foreach (Polygon poly in polys) vals.AddRange(poly.ToDoubleArray()); + foreach (Polygon poly in polys) vals.AddRange(poly.ToFloatArray()); return vals; } @@ -286,7 +287,7 @@ public struct Polygon : ICloneable, IEquatable, IGroup, ISubdivid public Vert[] ToArray() => Verts; public List ToList() => new(Verts); - public float[] ToDoubleArray() + public float[] ToFloatArray() { float[] vals = new float[Verts.Length * 3]; for (int i = 0; i < Verts.Length; i++) @@ -298,7 +299,7 @@ public struct Polygon : ICloneable, IEquatable, IGroup, ISubdivid } return vals; } - public List ToDoubleList() => new(ToDoubleArray()); + public List ToFloatList() => new(ToFloatArray()); public Polygon Subdivide() { diff --git a/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs b/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs index f00c9e6..8ee6be2 100644 --- a/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs +++ b/Nerd_STF/Mathematics/Geometry/Quadrilateral.cs @@ -3,93 +3,93 @@ public struct Quadrilateral : ICloneable, IEquatable, IGroup, ITriangulatable { public Vert A + { + get => p_a; + set { - get => p_a; - set - { - p_a = value; - p_ab.a = value; - p_da.b = value; - } + p_a = value; + p_ab.a = value; + p_da.b = value; } + } public Vert B + { + get => p_b; + set { - get => p_b; - set - { - p_b = value; - p_ab.b = value; - p_bc.a = value; - } + p_b = value; + p_ab.b = value; + p_bc.a = value; } + } public Vert C + { + get => p_c; + set { - get => p_c; - set - { - p_c = value; - p_bc.b = value; - p_cd.a = value; - } + p_c = value; + p_bc.b = value; + p_cd.a = value; } + } public Vert D + { + get => p_d; + set { - get => p_d; - set - { - p_d = value; - p_cd.b = value; - p_da.a = value; - } + p_d = value; + p_cd.b = value; + p_da.a = value; } + } public Line AB + { + get => p_ab; + set { - 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; - } + 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 { - 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; - } + 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 { - 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; - } + 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 { - 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; - } + 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; @@ -103,33 +103,33 @@ public struct Quadrilateral : ICloneable, IEquatable, IGroup Vert.Average(A, B, C, D); public float 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); - } + { + 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; - } + { + 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(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) : this(new Vert(x1, y1), new(x2, y2), new(x3, y3), new(x4, y4)) { } public Quadrilateral(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, @@ -145,39 +145,39 @@ public struct Quadrilateral : ICloneable, IEquatable, IGroup index switch { - get => index switch + 0 => A, + 1 => B, + 2 => C, + 3 => D, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + set + { + switch (index) { - 0 => A, - 1 => B, - 2 => C, - 3 => D, - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - set - { - switch (index) - { - case 0: - A = value; - break; + case 0: + A = value; + break; - case 1: - B = value; - break; + case 1: + B = value; + break; - case 2: - C = value; - break; + case 2: + C = value; + break; - case 3: - D = value; - break; + case 3: + D = value; + break; - default: throw new IndexOutOfRangeException(nameof(index)); - } + 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)); @@ -213,56 +213,56 @@ public struct Quadrilateral : ICloneable, IEquatable, IGroup ToDoubleListAll(params Quadrilateral[] quads) => new(ToDoubleArrayAll(quads)); + return vals; + } + public static List ToFloatListAll(params Quadrilateral[] quads) => new(ToFloatArrayAll(quads)); public override bool Equals([NotNullWhen(true)] object? obj) { @@ -291,14 +291,14 @@ public struct Quadrilateral : ICloneable, IEquatable, IGroup new Vert[] { A, B, C, D }; public List ToList() => new() { A, B, C, D }; - public float[] ToDoubleArray() => new float[] { 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 float[] ToFloatArray() => new float[] { 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 ToFloatList() => 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) }; diff --git a/Nerd_STF/Mathematics/Geometry/Sphere.cs b/Nerd_STF/Mathematics/Geometry/Sphere.cs index 3dc5c25..39668ff 100644 --- a/Nerd_STF/Mathematics/Geometry/Sphere.cs +++ b/Nerd_STF/Mathematics/Geometry/Sphere.cs @@ -8,8 +8,8 @@ public struct Sphere : ICloneable, IClosest, IComparable, ICompara public Vert center; public float radius; - public float SurfaceArea => 4 * Mathf.Pi * radius * radius; - public float Volume => 4 / 3 * (Mathf.Pi * radius * radius * radius); + public float SurfaceArea => 4 * Constants.Pi * radius * radius; + public float Volume => 4 / 3 * (Constants.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); diff --git a/Nerd_STF/Mathematics/Geometry/Triangle.cs b/Nerd_STF/Mathematics/Geometry/Triangle.cs index 64033b3..39da3a1 100644 --- a/Nerd_STF/Mathematics/Geometry/Triangle.cs +++ b/Nerd_STF/Mathematics/Geometry/Triangle.cs @@ -3,71 +3,71 @@ public struct Triangle : ICloneable, IEquatable, IGroup { public Vert A + { + get => p_a; + set { - get => p_a; - set - { - p_a = value; - p_ab.a = value; - p_ca.b = value; - } + p_a = value; + p_ab.a = value; + p_ca.b = value; } + } public Vert B + { + get => p_b; + set { - get => p_b; - set - { - p_b = value; - p_ab.b = value; - p_bc.a = value; - } + p_b = value; + p_ab.b = value; + p_bc.a = value; } + } public Vert C + { + get => p_c; + set { - get => p_c; - set - { - p_c = value; - p_bc.b = value; - p_ca.a = value; - } + p_c = value; + p_bc.b = value; + p_ca.a = value; } + } public Line AB + { + get => p_ab; + set { - get => p_ab; - set - { - p_ab = value; - p_a = value.a; - p_b = value.b; - p_bc.a = value.b; - p_ca.b = value.a; - } + p_ab = value; + p_a = value.a; + p_b = value.b; + p_bc.a = value.b; + p_ca.b = value.a; } + } public Line BC + { + get => p_bc; + set { - get => p_bc; - set - { - p_bc = value; - p_b = value.a; - p_c = value.b; - p_ca.a = value.b; - p_ab.b = value.a; - } + p_bc = value; + p_b = value.a; + p_c = value.b; + p_ca.a = value.b; + p_ab.b = value.a; } + } public Line CA + { + get => p_ca; + set { - get => p_ca; - set - { - p_ca = value; - p_a = value.b; - p_c = value.a; - p_ab.a = value.b; - p_bc.b = value.a; - } + 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_ab, p_bc, p_ca; @@ -75,6 +75,7 @@ public struct Triangle : ICloneable, IEquatable, IGroup public float Area => (float)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.5f; + public Vert Midpoint => Vert.Average(A, B, C); public float Perimeter => AB.Length + BC.Length + CA.Length; public Triangle(Vert a, Vert b, Vert c) @@ -195,7 +196,7 @@ public struct Triangle : ICloneable, IEquatable, IGroup return (ab, bc, ca); } - public static float[] ToDoubleArrayAll(params Triangle[] tris) + public static float[] ToFloatArrayAll(params Triangle[] tris) { float[] vals = new float[tris.Length * 9]; for (int i = 0; i < tris.Length; i++) @@ -213,7 +214,7 @@ public struct Triangle : ICloneable, IEquatable, IGroup } return vals; } - public static List ToDoubleListAll(params Triangle[] tris) => new(ToDoubleArrayAll(tris)); + public static List ToFloatListAll(params Triangle[] tris) => new(ToFloatArrayAll(tris)); public override bool Equals([NotNullWhen(true)] object? obj) { @@ -241,10 +242,10 @@ public struct Triangle : ICloneable, IEquatable, IGroup public Vert[] ToArray() => new Vert[] { A, B, C }; public List ToList() => new() { A, B, C }; - public float[] ToDoubleArray() => new float[] { A.position.x, A.position.y, A.position.z, + public float[] ToFloatArray() => new float[] { 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 }; - public List ToDoubleList() => new() { A.position.x, A.position.y, A.position.z, + public List ToFloatList() => 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 }; public static Triangle operator +(Triangle a, Triangle b) => new(a.A + b.A, a.B + b.B, a.C + b.C); diff --git a/Nerd_STF/Mathematics/Geometry/Vert.cs b/Nerd_STF/Mathematics/Geometry/Vert.cs index 9b76ceb..1c40344 100644 --- a/Nerd_STF/Mathematics/Geometry/Vert.cs +++ b/Nerd_STF/Mathematics/Geometry/Vert.cs @@ -31,7 +31,7 @@ public struct Vert : ICloneable, IEquatable, IGroup } public static Vert Absolute(Vert val) => new(Float3.Absolute(val.position)); - public static Vert Average(params Vert[] vals) => Float3.Average(ToDouble3Array(vals)); + public static Vert Average(params Vert[] vals) => Float3.Average(ToFloat3Array(vals)); public static Vert Ceiling(Vert val) => new(Float3.Ceiling(val.position)); public static Vert Clamp(Vert val, Vert min, Vert max) => new(Float3.Clamp(val.position, min.position, max.position)); @@ -40,23 +40,24 @@ public struct Vert : ICloneable, IEquatable, IGroup public static Vert Cross(Vert a, Vert b, bool normalized = false) => new(Float3.Cross(a.position, b.position, normalized)); public static float Dot(Vert a, Vert b) => Float3.Dot(a.position, b.position); - public static float Dot(params Vert[] vals) => Float3.Dot(ToDouble3Array(vals)); + public static float Dot(params Vert[] vals) => Float3.Dot(ToFloat3Array(vals)); public static Vert Floor(Vert val) => new(Float3.Floor(val.position)); public static Vert Lerp(Vert a, Vert b, float t, bool clamp = true) => new(Float3.Lerp(a.position, b.position, t, clamp)); public static Vert Median(params Vert[] vals) => - Float3.Median(ToDouble3Array(vals)); + Float3.Median(ToFloat3Array(vals)); public static Vert Max(params Vert[] vals) => - Float3.Max(ToDouble3Array(vals)); + Float3.Max(ToFloat3Array(vals)); public static Vert Min(params Vert[] vals) => - Float3.Min(ToDouble3Array(vals)); - public static Float3[] ToDouble3Array(params Vert[] vals) + Float3.Min(ToFloat3Array(vals)); + + public static Float3[] ToFloat3Array(params Vert[] vals) { - Float3[] doubles = new Float3[vals.Length]; - for (int i = 0; i < vals.Length; i++) doubles[i] = vals[i].position; - return doubles; + Float3[] floats = new Float3[vals.Length]; + for (int i = 0; i < vals.Length; i++) floats[i] = vals[i].position; + return floats; } - public static List ToDouble3List(params Vert[] vals) => ToDouble3Array(vals).ToList(); + public static List ToFloat3List(params Vert[] vals) => ToFloat3Array(vals).ToList(); public override bool Equals([NotNullWhen(true)] object? obj) { diff --git a/Nerd_STF/Mathematics/Int2.cs b/Nerd_STF/Mathematics/Int2.cs index 3509f81..9841fa6 100644 --- a/Nerd_STF/Mathematics/Int2.cs +++ b/Nerd_STF/Mathematics/Int2.cs @@ -88,7 +88,7 @@ public struct Int2 : ICloneable, IComparable, IEquatable, IGroup, IEquatable, IGroup, IEquatable, IGroup Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { diff --git a/Nerd_STF/Mathematics/Int3.cs b/Nerd_STF/Mathematics/Int3.cs index f2811ce..0567940 100644 --- a/Nerd_STF/Mathematics/Int3.cs +++ b/Nerd_STF/Mathematics/Int3.cs @@ -108,7 +108,7 @@ public struct Int3 : ICloneable, IComparable, IEquatable, IGroup, IEquatable, IGroup, IEquatable, IGroup Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { @@ -193,5 +205,9 @@ public struct Int3 : ICloneable, IComparable, IEquatable, IGroup new(val.x, val.y, val.z); public static explicit operator Int3(Vert val) => new((int)val.position.x, (int)val.position.y, (int)val.position.z); + public static explicit operator Int3(RGBA val) => (Int3)val.ToRGBAByte(); + public static explicit operator Int3(HSVA val) => (Int3)val.ToHSVAByte(); + public static explicit operator Int3(RGBAByte val) => new(val.R, val.G, val.B); + public static explicit operator Int3(HSVAByte val) => new(val.H, val.S, val.V); public static implicit operator Int3(Fill fill) => new(fill); } diff --git a/Nerd_STF/Mathematics/Int4.cs b/Nerd_STF/Mathematics/Int4.cs index dda3fee..2157e4f 100644 --- a/Nerd_STF/Mathematics/Int4.cs +++ b/Nerd_STF/Mathematics/Int4.cs @@ -121,7 +121,7 @@ public struct Int4 : ICloneable, IComparable, IEquatable, IGroup, IEquatable, IGroup, IEquatable, IGroup Magnitude.CompareTo(other.Magnitude); public override bool Equals([NotNullWhen(true)] object? obj) { @@ -212,5 +226,11 @@ public struct Int4 : ICloneable, IComparable, IEquatable, IGroup new(val.x, val.y, val.z, 0); public static explicit operator Int4(Vert val) => new((int)val.position.x, (int)val.position.y, (int)val.position.z, 0); + public static explicit operator Int4(RGBA val) => val.ToRGBAByte(); + public static explicit operator Int4(CMYKA val) => (Int4)val.ToCMYKAByte(); + public static explicit operator Int4(HSVA val) => val.ToHSVAByte(); + public static implicit operator Int4(RGBAByte val) => new(val.R, val.G, val.B, val.A); + public static explicit operator Int4(CMYKAByte val) => new(val.C, val.M, val.Y, val.K); + public static implicit operator Int4(HSVAByte val) => new(val.H, val.S, val.V, val.A); public static implicit operator Int4(Fill fill) => new(fill); } diff --git a/Nerd_STF/Mathematics/Mathf.cs b/Nerd_STF/Mathematics/Mathf.cs index 816b81f..30337ca 100644 --- a/Nerd_STF/Mathematics/Mathf.cs +++ b/Nerd_STF/Mathematics/Mathf.cs @@ -2,18 +2,10 @@ public static class Mathf { - public const float RadToDeg = 0.0174532925199f; // Pi / 180 - public const float E = 2.71828182846f; - public const float GoldenRatio = 1.61803398875f; // (1 + Sqrt(5)) / 2 - public const float HalfPi = 1.57079632679f; // Pi / 2 - public const float Pi = 3.14159265359f; - public const float DegToRad = 57.2957795131f; // 180 / Pi - public const float Tau = 6.28318530718f; // 2 * Pi - public static float Absolute(float val) => val < 0 ? -val : val; public static int Absolute(int val) => val < 0 ? -val : val; - public static float ArcCos(float value) => -ArcSin(value) + HalfPi; + public static float ArcCos(float value) => -ArcSin(value) + Constants.HalfPi; public static float ArcCot(float value) => ArcCos(value / Sqrt(1 + value * value)); @@ -33,14 +25,14 @@ public static class Mathf return Average(vals.ToArray()); } public static float Average(params float[] vals) => Sum(vals) / vals.Length; - public static int Average(params int[] vals) => Sum(vals) / vals.Length; + public static float Average(params int[] vals) => Sum(vals) / (float)vals.Length; public static int Ceiling(float val) => (int)(val + (1 - (val % 1))); public static float Clamp(float val, float min, float max) { if (max < min) throw new ArgumentOutOfRangeException(nameof(max), - nameof(max) + " must be greater than or equal to " + nameof(min)); + nameof(max) + " must be greater than or equal to " + nameof(min) + "."); val = val < min ? min : val; val = val > max ? max : val; return val; @@ -54,7 +46,11 @@ public static class Mathf return val; } - public static float Cos(float radians) => Sin(radians + HalfPi); + // nCr (n = total, r = size) + public static int Combinations(int total, int size) => Factorial(total) / + (Factorial(size) * Factorial(total - size)); + + public static float Cos(float radians) => Sin(radians + Constants.HalfPi); public static float Cot(float radians) => Cos(radians) / Sin(radians); @@ -89,6 +85,22 @@ public static class Mathf return vals; } + public static int GreatestCommonFactor(params int[] vals) + { + int loops = Min(vals); + for (int i = loops; i > 0; i--) + { + bool fit = true; + foreach (int v in vals) fit &= v % i == 0; + if (fit) return i; + } + return -1; + } + + public static float InverseSqrt(float val) => 1 / Sqrt(val); + + public static int LeastCommonMultiple(params int[] vals) => Product(vals) / GreatestCommonFactor(vals); + public static float Lerp(float a, float b, float t, bool clamp = true) { float v = a + t * (b - a); @@ -169,27 +181,61 @@ public static class Mathf return val; } - public static float Multiply(params float[] vals) + public static (T value, int occurences) Mode(params T[] vals) where T : IEquatable + { + if (vals.Length < 1) throw new ArgumentException("List must contain at least 1 element.", nameof(vals)); + (T value, int occurences) = (vals[0], -1); + for (int i = 0; i < vals.Length; i++) + { + int count = vals.Count(x => x.Equals(vals[i])); + if (count > occurences) + { + value = vals[i]; + occurences = count; + } + } + return (value, occurences); + } + + // nPr (n = total, r = size) + public static int Permutations(int total, int size) => Factorial(total) / Factorial(total - size); + + public static float Product(params float[] vals) { if (vals.Length < 1) return 0; float val = 1; foreach (float d in vals) val *= d; return val; } - public static int Multiply(params int[] vals) + public static int Product(params int[] vals) { if (vals.Length < 1) return 0; int val = 1; foreach (int i in vals) val *= i; return val; } + public static float Product(Equation equ, float min, float max, float step = 1) + { + float total = 1; + for (float f = min; f <= max; f += step) total *= equ(f); + return total; + } public static float Power(float num, float pow) => (float)Math.Pow(num, pow); + public static float Power(float num, int pow) + { + if (pow < 0) return 0; + float val = 1; + for (int i = 0; i < Absolute(pow); i++) val *= num; + if (pow < 1) val = 1 / val; + return val; + } public static int Power(int num, int pow) { if (pow < 0) return 0; int val = 1; for (int i = 0; i < Absolute(pow); i++) val *= num; + if (pow < 1) val = 1 / val; return val; } @@ -205,16 +251,16 @@ public static class Mathf { // Really close polynomial to sin(x) (when modded by 2pi). RMSE of 0.000003833 const float a = 0.000013028f, - b = 0.999677f, - c = 0.00174164f, - d = -0.170587f, - e = 0.0046494f, - f = 0.00508955f, - g = 0.00140205f, - h = -0.000577413f, - i = 0.0000613134f, - j = -0.00000216852f; - float x = radians % Tau; + b = 0.999677f, + c = 0.00174164f, + d = -0.170587f, + e = 0.0046494f, + f = 0.00508955f, + g = 0.00140205f, + h = -0.000577413f, + i = 0.0000613134f, + j = -0.00000216852f; + float x = radians % Constants.Tau; return a + (b * x) + (c * x * x) + (d * x * x * x) + (e * x * x * x * x) + (f * x * x * x * x * x) @@ -247,6 +293,36 @@ public static class Mathf foreach (int i in vals) val += i; return val; } + public static float Sum(Equation equ, float min, float max, float step = 1) + { + float total = 0; + for (float f = min; f <= max; f += step) total += equ(f); + return total; + } + + // Known as stdev + public static float StandardDeviation(params float[] vals) => Sqrt(Variance(vals)); public static float Tan(float radians) => Sin(radians) / Cos(radians); + + public static T[] UniqueItems(params T[] vals) where T : IEquatable + { + List unique = new(); + foreach (T item in vals) if (!unique.Any(x => x.Equals(item))) unique.Add(item); + return unique.ToArray(); + } + + public static float Variance(params float[] vals) + { + float mean = Average(vals), sum = 0; + for (int i = 0; i < vals.Length; i++) + { + float val = vals[i] - mean; + sum += val * val; + } + return sum / vals.Length; + } + + public static float ZScore(float val, params float[] vals) => ZScore(val, Average(vals), StandardDeviation(vals)); + public static float ZScore(float val, float mean, float stdev) => (val - mean) / stdev; } diff --git a/Nerd_STF/Miscellaneous/GlobalUsings.cs b/Nerd_STF/Miscellaneous/GlobalUsings.cs index 520cd0b..45bcc57 100644 --- a/Nerd_STF/Miscellaneous/GlobalUsings.cs +++ b/Nerd_STF/Miscellaneous/GlobalUsings.cs @@ -8,6 +8,7 @@ global using System.Net.Http; global using System.Threading; global using System.Threading.Tasks; global using Nerd_STF; +global using Nerd_STF.Graphics; 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 0d80df7cc156eb829bb3dac304dbd441f0162a3d..89f2b0d0e1ecd793365ad91d508b7efe3e03dead 100644 GIT binary patch literal 77312 zcmeF4349dQwy$^fRGp#I-RVx!nIxpcC^9%8f@pvQQ4nPkK}Ci{1OW$7@gN8if)gsD zNa6rU2r4QlC>Rk@lQ`ml^9&MD#2Io#5qbZ$s(aOr9MpU7x#!;Z-r;9`u5YhhtE#JN z@2c)Jd9_YncpuH%PrPDW2^@Q8y(YYt_P$a+bsV2@qZ6x?LT?g`}UVjzHAJd_%-0QaG#d=zxJmPCEH(CI^kk; zq}N2RP0@DS9n!BSrT_is|NSHIW$5}I+wXq=lv`T`tIxea)&IZ$ivRrz^&@aA&8zrB zbCW~4)!;&z)Rjz zDmp`{kMaMHqlgDhxoq5|v6HFC`k$<+Fgj$A+JKnm$ml6UPA$U!Uxnwjg7=(o@o4Id zKbXRdq0QDNC8R@sF^*CAKka`vEd8(ygQS^C9l-Jm9Gg?WVmZ=j^`pM6u@obijb$ui zT9?{sdW>M9*I~`)DM4zd3AQ-7B+a8ze8eq@J<|NDEREXhx&7p%V!uZMv20iMP3}Q_pabl3zhU4QR6#vA0&rflpLHt#H z>h`GL%wsbW)ma9`M{xT|!Hq7DawSlowO-BV9@ag@mD{0CG8DitGkDFdDN zd#Abv;K|0#mSIeF4Z%6I>}6$Hf?A5&eUYQ{>tpK}vlO;%G4r#vidg{L45-BLRM#-9 zm#Px5^;PCn*L1W-czbK3wDpk6=G;JUyR_*l6E+Jyb0h82hN&#om+j*WvTQtq&|aUA z+AeK~dJsl6eGXdhK{beN9@|6ecDCJYtJG&~VYW5uID^)^o$YaTIopqH>(w&`ZT&|d zZEu4LJE-SruB)TdVT0JtWZS4_IOw>%rLM+6*`yw1OZQs#5ck{(n~vNgY}`*y zth51mt@}8)mwSF2v;N%kM>WVzdp#zcg)#VdwSsLZa%2y?sY+owRr$MG!}d7WJP4z$ zFEwd5l#yxCHU^~=_MzzO*eQB4V)BxCn9w(~YjFw@lN6a3<1wBmlG*)x&T((}u6Ku;c z9)_tRV*?u=Z(+s8b8OAodK)jY@yw@AFf*$p}ZyWen+LvIb;P>ErR*Sh8@w(_w;M~xDsXxX46Dmhi ziRW|tc$nf#13$S8)yA*fXU!ZRB4S1GA#PdC%wyK)Q~RYZVs0)m0XN{YW?s)c3RBlv zGgEN8LX z6PYQzhnI1DKl5{DF*Ad+(>Q*V`5d!3a}RSUa~`t??I|A#ZF+8BeF$;Z&-wFWUC<32xfd^Y#|mRsK9n&)y{$JOR> z{nMD2GvDKEU*zJ;z1d z9^?vLnajBHk=*X)cs<9La@?EwDX)7gw;#(b&6%UPr8V#89b1r*P6=x?@Ucb&pJ2tKPpHx@v05=GOiY?K(Dlq3-h7cz#QPF z_#uuHh?plo#e8*_yBUt>K|k?oA8~b%_$u>4ZaLAUmbKh-Vj{&YJ;b)D#IN#+v$*GS z)G^fG`~_e-&ZePW!!eKV&l2AH7t8~P?8yV+)~NHy5yaII;>=WHGn4psfOtN)Ow4HQ zru{z(E!U`y;CTk|H|7in#XFe!PKw7e?_}P?T+cMHZgW-VISyRqDZ~-Gi;vr-eEpPX z&@sF(Tm(+$xRUudZmIN9`-6dw;5)q5OT5;t+~;we5!^4h=Mxwa9@RJDRB$)*Lgp{b zE173A=Q5vVRxuxCZeiX58fq=Z0-lC3?-^rf!(PG7^xT@oK)n#d8aXj~?6DvH#j}Uv* zz;qfH1JkL_!1M{sLgppRDd6Jtsmxhmt)Ir!R8@|6Xz*5WU^S^2pw>2hZ!aeT^e$+Ye2Qa`eZ^>-{8FZ|pP= zxbZN;xq4%p>GW717CKeE@t&FN!COnt(e{3F=k)?dQQ>oY*>cmHk$)H zk+@+$huWrgd(QV{$8rl*M!3|IqxJNtT+J@>#I0V9_5aP27hlht7o4HlKA&!%dUYER zZek(Z%DoHHW=oZF@8Yz%nr%;??P(t07w`A!b2YovGux9dxtX51n!SScqLO)F=U zbCahap2KmOFSby%^Hq3S#&QeQ3Ui64l~{NG{hroh(?XAV+KR0T-|sm}Y<}ozPY1Ea z{3V`a#43VIJjaS{fgLB-JNlxhbIf)c2U6eibctELT9p2Q=LE?uO8?Z;RdSQf22VFJ zI!AFU#~J&wiGAleG1ilEKY31)nvLFts!(!~1jCHmLhp^KUb9Cmx6s=dtnl;_8;@L( zSZUO2_SP(D_7U3^^qQw>7Bu^bRYo(-{$kazGsR{EGtGfw^I(I;rUx?3v&0s_&W@W} z=4);a7yF?3k>&`o(!eq1D6xBwIMFQS5f+~d#>b4tbZ>Kl*fz7TIZ^CAbFg_yyoz^k z>TvT?v4s)c7B{isAwBo0SGNILuH0K@&i2G>mU}PtoG)9Txo57OgO!+<#kNkl@#YlG zCYzVX&08G2+Pp%ne{hz0Wz1*<%r&nPTVdX8{!OgAf37)AY+7icd9~PW=2G(-v5grI zn%9brHrJYS#B%dDnsYU)H?J346`pI}AT~d=*}PG#B6zcTi&$g+&E~CQTacSC)+=(e zd7D_f$XauO*ckM@L#%i7eRE;VXvBSCE{d5o;wmJ!DE%9=QgSrn?h>OB7dIMJdraN- znRiPS8e{jwY@6{y_-wJA#?J6TGoIUNbc%Ytw%wTT6Qj|XrdeZfuDLi?bD?)Ua!bTY zqiNpzG&{muCAKS==B?K32=D!3mC=^o2gRyk4~fkPw)8$CHV?L1YF_jY!lZ`!#*O>DGoJf@{5kLhP(uYtz& zmELDH8*kQXHrcF;8;|gMF&g17#EeGx4DU-~E6h3Gm&LmKXLw%`n--eqeO2r>bCLIT zv5gr^y<5aan=8HVh~?%#?tM?Qr@bGDtqRZZeke9SRO|gntRgta`#%xHvf^?n&MYlQET+@kcI-mfG_BYd|Qjqtcx zBV4zA=GRh%M))@|vqpH_tPyS-jd0s&gvX6W_}7{>24{G`i`AqN{$qA-dO(c}y)hsI^L99N~JRwo6Cek_~NvuB7F(FyAE(t!(3KLQ^>yzLYtBIVM z5YTLRLQw2Ae@Q~B*reYJ=&~nzS5#JY@HO9%F zS+jWYNsVk^w)3Hf5({Z}MJ#ioVkBov6_yz9ON6*!<9A2_3~Mg3}X@6>H3&o^YJl7UVjM^@>bSI9{w> zq#~h<*ckNeD%LytbV9e7(Y*9VLid5 zaYA3sb|joGRvG;!;S8~A*Z{E^!EX`M>EVlnqlq{v*w<7O`dz~97edIBnO+6I6k&@8t;?M z330hXA~-X#Osp|~X5t*NEy$IN^@_|)oGaEYQk{66*ckM@ zL9BQ5<-{9fM&tH_#Cb8Z#_i3LTa^A;;w_S+aeJ#6joY|c<5stQ=6tC_rZt zG;Vh!+SV!ht!6jo?-8SMyI-@$;8lsY$7<5J-Jf`eSZQ>B;+>kgk}AY@1@|Z3rI{<~ z9VTq?F4woI%tnw+#;tQxjLY(_9S=>f5Mu$5xd1IbAbi!Fey6047d zlOEM9JLxga@{`tR)+TAKW*w6r*X;PDC&X$ZCnv2Fd(D4p(t5GIsk((s-4;f4TinEk zhxFX1Ufl+0Icsdjw?|{MzigeJ3&thdxj{{Av~E0#r6-T#Cu6UHM)9Der!<>vZj2j` z>1V`fOmB)A-I<3cJtwxp9F_FE*tF2Nq*}4t%qdAPh;7W6p7gTVXmf7TX0hD-`AM&9 zR+03k*!<9vq%C3(l*J_nEqUh#&q1QF|FG^bGuZbG2IX|YfQ(@8q>DXn6{0^ zblhl6znElOV{mxV&R9(v(=R4{AyyiFF=>}(Zzk;)+ZB8<=^M@7O!`jj6VHoDKZq>~ zy_mE|Y&on^tTOsR(obU5u%E?d1V2dHFE$T$Kx}&8gQSCE3t)yL{!XJlvMtG}*_TN! z&Av-=i`7K_p5zg$j~qxcHFG6Ol4b?T$znB;cF8`m*ZiH6Q^ZDv zblaP%+xjNCkg4YuMs!=;#D+In?^CbdbAXn!=85>e&^*yi_RN|m>>SM#wjFfomeM3g zbBNuuMHBP5`zP7C;Y}>JiKXerGm)&vGm$@b1g)7Us99lhShLAyByKz}Ws1?flod0Y zm--~Nqs@zw3&e8sFHdf%+4SVrV)H{~$!)|cf@dTj zCDxdKMsjlHa8xuaOS$VJJW#KxfKF=D-=w8_h(v(M%LKnu#7swyiPPC%Jp9 zCe1{TB%dT!8hs?WP_uQ(J;infA4x9KY+Z71u}?gYB=-?p6nZ52G_mEdeqxo;XOjDi zRm08{n-P2_d7#)l*kG~gfoGD>5?cT}TdY3vLh^9UUQ0elvv-ou6|0GSoP3^U+mlC# z)knTcK3}sRlSgXyOY$hq9KO+FHIYQ$7_s_Ds;@+>CX(eV)hyq4LEJDN+WE$6*2#CF z*f|;PeB;E@^V|8xi~a0w=etPkHP{5PcZ2PG6U8orO%hvS9%EjtS!ds5%}(@95$o>n z>APHPTIdwt6`J+;T_xtv7~;EHYy|dthFB){`Wmrcv7a-=Mw{pOW@$FsH(RVY(8)JP zY*o0YuUxEk`Z(WQv0uVdeK(4ogYDfUHp@HJH&1MSXol}*&B}eZi5-`Evu~kTWBxJb zotky_-KE)7-`!#r!Op&WG@I(XS8Pl07;~{^oqfwRo9e3)yT+{aEf;Hrv~QEx7#y?bG@I&sUbAD&S}{**XWxrrmt%V`i9M74 zxbJ1L)@EnlD`G_n=lEXL>=fT?ar1r~dB*o<%*wqd<=1LzLwM1@<^gJ3l+pcxb z_Piyz6T_O}zWJt4a8tHKSwZDR97zxh5Fs|fD)?G$Uw-|hQCYzuNcAE5&fepKz;TPO+Lu-xRl4ePl?A zN9^0kxhZDMtSiely0UDeE6XcA>B_Q=uB-&f(Um2Jt7lY-Zg2V$<2{iZo06>Acrzt# ze4Y8l=sF9;jIOgwQc}fMm{+ER#Jc-0NePQh3tf|vCblYkNlHX)e&~jjOtFgKl_|Mm zjrmum93i#^xn^R$qj#j_#mu_S@?%EV*@~2?b5YV+u|lRJf!D7_3Ac2%i&#*f3_#S^>XjG&1-#PxLV(AVv`bj#`crm zY5bJZTQkGoPqSqI>6)ec`)fAdJVUd|=9zKB2*~pf5bGaorgT+>uo&7_^ zy8Ao$&k~y!I>|p&Y*n~}f4JEE(5e1&#PD3;KVPgdzq5a&*cRkQiS>>S_K%L4H3CXv zMk8RXzf^KG0xl4v5fC?P1n9QU94l4mn!ix&KfpajY&?2S)@;51anDBV!MK?{L?jC@4rT~=lrw8Dx)v^ zXNy(C=7`M*zU-eXHV<~a*z~~5{u{*>z~+h7M7H>E(d<+It(xuf&)00P|2EBj^Dl^5 zx!09G-+#MUO1d{-8;!p^B==p$y?)z%&hQ6pqdU+-=}C7WF^o-oPc@!{Wdw9<6}VH& zbq*}jtT<4i*?_=Znw=ZCN3*ek#hUp8OEmKamd1_8^nGG9rkBNRp*q$(DX?6uoo`B@ zT5N?mEwDnYyMIdHez9qx*?|YdR)wbo9uk`$x;gN$SY!UQz@uUn!D)fV#J0fJi2V|% z2&@&`9atK8JZ9E-eEd9l*y(}6n8>H{xm_D0|(v0cHZ1FvXSAK0wf8-drwDx>cQ-W02by(Lx? z*&299vmXNQYGwrA(<~T#U$d;>2b$#vKh&&k@FUHR4t^Z7a_`U1THhyP2c0i#mg%bX zeJZ(Tu9r1C3AxWCSA?8xCCF`++<4?{OLBD$Zj)T9>txN2MDBCR9gUoAXCb#;a_1pu z+sDqX!3N1~cb=>n%`ZD7NAr?xG~0+_ekl&>HYB(!z8-R;g5PL1G1#bCvfpsUzJHCL z>3)Z1hTo~#^q@<#@upj|$)+c6JlmLJG~0M%Mo$oPgNb4*%v*y=V)#C0Fj;I`s50mi z!#62|0kQd^2ZBMd#{64@X<`+@TZ8FhTVNSt1ETAK5wZN}Tfr=`Ujn;>Ibyp5dxN<# zv*w;7Vn%b1N^K@NntSrZXzq!dHTUSY&ulJLXzt06nKk#s&6;~`qq)a6ntS4A%{{i! z+!ME*2F)mOqZ!3a)h(1-5ZgLECCm-B6dRA8tuzazwh=3hnyE)>7D_!zvu3Fs#C8SE z)J~d(QjgKBS?Y0OmC-h-$BR|NP7te!9Fy8jv%=I9HS3q!U9+=OPtvR;^<>R1PAwFx zkNhpQhuF7~Yg2p1tlWDeMx1T0dTm>%I)ty2o(ok{`HGR%_b%E z)$H!nGc+4-4$!PRb)aUG&B2Ikv

I>q7i-LaHFb`t-ifq|h4u?4WZVrJc8?-8Rr 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

%Qu8=g||10kbNz*~! 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 class there before I knew of the `System.Collections.Generic.List` class that did literally everything for me already. Oh well. So, keep that in mind when you check out those versions of the library.