diff --git a/Nerd_STF/Graphics/ColorChannel.cs b/Nerd_STF/Graphics/ColorChannel.cs new file mode 100644 index 0000000..86f3892 --- /dev/null +++ b/Nerd_STF/Graphics/ColorChannel.cs @@ -0,0 +1,10 @@ +namespace Nerd_STF.Graphics +{ + public enum ColorChannel + { + Red, + Green, + Blue, + Alpha + } +} diff --git a/Nerd_STF/Graphics/ColorRGB.cs b/Nerd_STF/Graphics/ColorRGB.cs new file mode 100644 index 0000000..a348619 --- /dev/null +++ b/Nerd_STF/Graphics/ColorRGB.cs @@ -0,0 +1,359 @@ +using Nerd_STF.Exceptions; +using Nerd_STF.Mathematics; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Nerd_STF.Graphics +{ + public struct ColorRGB : IColor +#if CS11_OR_GREATER + ,IFromTuple, + IFromTuple, + ISplittable +#endif + { + public static int ChannelCount => 4; + + public static ColorRGB Black => new ColorRGB(0, 0, 0, 1); + public static ColorRGB Blue => new ColorRGB(0, 0, 1, 1); + public static ColorRGB Clear => new ColorRGB(0, 0, 0, 0); + public static ColorRGB Cyan => new ColorRGB(0, 1, 1, 1); + public static ColorRGB Gray => new ColorRGB(0.5, 0.5, 0.5, 1); + public static ColorRGB Green => new ColorRGB(0, 1, 0, 1); + public static ColorRGB Magenta => new ColorRGB(1, 0, 1, 1); + public static ColorRGB Orange => new ColorRGB(1, 0.5, 0, 1); + public static ColorRGB Purple => new ColorRGB(0.5, 0, 1, 1); + public static ColorRGB Red => new ColorRGB(1, 0, 0, 1); + public static ColorRGB White => new ColorRGB(1, 1, 1, 1); + public static ColorRGB Yellow => new ColorRGB(1, 1, 0, 1); + + public double Magnitude => MathE.Sqrt(r * r + g * g + b * b); + + public double r, g, b, a; + + public ColorRGB(double r, double g, double b) + { + this.r = r; + this.g = g; + this.b = b; + a = 1; + } + public ColorRGB(double r, double g, double b, double a) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + public ColorRGB(IEnumerable nums) + { + r = 0; + g = 0; + b = 0; + a = 1; + + int index = 0; + foreach (double item in nums) + { + this[index] = item; + index++; + if (index == 4) break; + } + } + public ColorRGB(Fill fill) + { + r = fill(0); + g = fill(1); + b = fill(2); + a = fill(3); + } + + public double this[int index] + { + get + { + switch (index) + { + case 0: return r; + case 1: return g; + case 2: return b; + case 3: return a; + default: throw new ArgumentOutOfRangeException(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 ArgumentOutOfRangeException(nameof(index)); + } + } + } + public ListTuple this[string key] + { + get + { + double[] items = new double[key.Length]; + for (int i = 0; i < key.Length; i++) + { + char c = key[i]; + switch (c) + { + case 'r': items[i] = r; break; + case 'g': items[i] = g; break; + case 'b': items[i] = b; break; + case 'a': items[i] = a; break; + default: throw new ArgumentException("Invalid key.", nameof(key)); + } + } + return new ListTuple(items); + } + set + { + IEnumerator stepper = value.GetEnumerator(); + for (int i = 0; i < key.Length; i++) + { + char c = key[i]; + stepper.MoveNext(); + switch (c) + { + case 'r': r = stepper.Current; break; + case 'g': g = stepper.Current; break; + case 'b': b = stepper.Current; break; + case 'a': a = stepper.Current; break; + default: throw new ArgumentException("Invalid key.", nameof(key)); + } + } + } + } + + public static ColorRGB Average(double gamma, IEnumerable colors) + { + double avgR = 0, avgG = 0, avgB = 0, avgA = 0; + int count = 0; + foreach (ColorRGB color in colors) + { + double correctR = MathE.Pow(color.r, gamma), + correctG = MathE.Pow(color.g, gamma), + correctB = MathE.Pow(color.b, gamma); + // Gamma doesn't apply to the alpha channel. + + avgR += correctR; + avgG += correctG; + avgB += correctB; + avgA += color.a; + count++; + } + avgR /= count; + avgG /= count; + avgB /= count; + avgA /= count; + double invGamma = 1 / gamma; + return new ColorRGB(MathE.Pow(avgR, invGamma), + MathE.Pow(avgG, invGamma), + MathE.Pow(avgB, invGamma), + avgA); + } + public static ColorRGB Clamp(ColorRGB color, ColorRGB min, ColorRGB max) => + new ColorRGB(MathE.Clamp(color.r, min.r, max.r), + MathE.Clamp(color.g, min.g, max.g), + MathE.Clamp(color.b, min.b, max.b), + MathE.Clamp(color.a, min.a, max.a)); + public static ColorRGB ClampMagnitude(ColorRGB color, double minMag, double maxMag) + { + ColorRGB copy = color; + ClampMagnitude(ref copy, minMag, maxMag); + return copy; + } + public static void ClampMagnitude(ref ColorRGB color, double minMag, double maxMag) + { + if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag)); + double mag = color.Magnitude; + + if (mag < minMag) + { + double factor = minMag / mag; + color.r *= factor; + color.g *= factor; + color.b *= factor; + } + else if (mag > maxMag) + { + double factor = maxMag / mag; + color.r *= factor; + color.g *= factor; + color.b *= factor; + } + } + public static double Dot(ColorRGB a, ColorRGB b) => a.r * b.r + a.g * b.g + a.b * b.b; + public static double Dot(IEnumerable colors) + { + bool any = false; + double r = 1, g = 1, b = 1; + foreach (ColorRGB c in colors) + { + r *= c.r; + g *= c.g; + b *= c.b; + } + return any ? (r + g + b) : 0; + } + public static ColorRGB Lerp(double gamma, ColorRGB a, ColorRGB b, double t, bool clamp = true) + { + double aCorrectedR = MathE.Pow(a.r, gamma), + aCorrectedG = MathE.Pow(a.g, gamma), + aCorrectedB = MathE.Pow(a.b, gamma), + bCorrectedR = MathE.Pow(b.r, gamma), + bCorrectedG = MathE.Pow(b.g, gamma), + bCorrectedB = MathE.Pow(b.b, gamma); + // Gamma doesn't apply to the alpha channel. + + double newR = MathE.Lerp(aCorrectedR, bCorrectedR, t, clamp), + newG = MathE.Lerp(aCorrectedG, bCorrectedG, t, clamp), + newB = MathE.Lerp(aCorrectedB, bCorrectedB, t, clamp), + newA = MathE.Lerp(a.a, b.a, t, clamp); + + double invGamma = 1 / gamma; + return new ColorRGB(MathE.Pow(newR, invGamma), + MathE.Pow(newG, invGamma), + MathE.Pow(newB, invGamma), + newA); + } +#if CS11_OR_GREATER + static ColorRGB IInterpolable.Lerp(ColorRGB a, ColorRGB b, double t, bool clamp) => Lerp(1, a, b, t, clamp); +#endif + public static ColorRGB Product(IEnumerable colors) + { + bool any = false; + ColorRGB result = new ColorRGB(1, 1, 1, 1); + foreach (ColorRGB color in colors) + { + any = true; + result *= color; + } + return any ? result : Black; + } + public static ColorRGB Sum(IEnumerable colors) + { + bool any = false; + ColorRGB result = new ColorRGB(0, 0, 0, 1); + foreach (ColorRGB color in colors) + { + any = true; + result += color; + } + return any ? result : Black; + } + public static (double[] Rs, double[] Gs, double[] Bs, double[] As) SplitArray(IEnumerable colors) + { + int count = colors.Count(); + double[] Rs = new double[count], Gs = new double[count], Bs = new double[count], As = new double[count]; + int index = 0; + foreach (ColorRGB c in colors) + { + Rs[index] = c.r; + Gs[index] = c.g; + Bs[index] = c.b; + As[index] = c.a; + index++; + } + return (Rs, Gs, Bs, As); + } + + public Dictionary GetChannels() => new Dictionary() + { + { ColorChannel.Red, r }, + { ColorChannel.Green, g }, + { ColorChannel.Blue, b }, + { ColorChannel.Alpha, a }, + }; + + public TColor AsColor() where TColor : struct, IColor + { + Type type = typeof(TColor); + if (type == typeof(ColorRGB)) return (TColor)(object)this; + else throw new InvalidCastException(); + } + public ColorRGB AsRgb() => this; + + public IEnumerator GetEnumerator() + { + yield return r; + yield return g; + yield return b; + yield return a; + } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public void Deconstruct(out double r, out double g, out double b) + { + r = this.r; + g = this.g; + b = this.b; + } + public void Deconstruct(out double r, out double g, out double b, out double a) + { + r = this.r; + g = this.g; + b = this.b; + a = this.a; + } + + public bool Equals(ColorRGB other) + { + if (a <= 0 && b <= 0) return true; + else return r == other.r && g == other.g && b == other.b; + } + public bool Equals(IColor other) => Equals(other.AsRgb()); +#if CS8_OR_GREATER + public override bool Equals(object? other) +#else + public override bool Equals(object other) +#endif + { + if (other is IColor color) return Equals(color.AsRgb()); + else return false; + } + public override int GetHashCode() => base.GetHashCode(); + public override string ToString() => $"{{ r={r:0.00}, g={g:0.00}, b={b:0.00}, a={a:0.00} }}"; + + public double[] ToArray() => new double[] { r, g, b, a }; + public Fill ToFill() + { + ColorRGB copy = this; + return i => copy[i]; + } + public List ToList() => new List() { r, g, b, a }; + + public static ColorRGB operator +(ColorRGB a) => a; + public static ColorRGB operator +(ColorRGB a, ColorRGB b) => new ColorRGB(a.r + b.r, a.g + b.g, a.b + b.b, 1 - (1 - a.a) * (1 - b.b)); + public static ColorRGB operator -(ColorRGB a) => new ColorRGB(1 - a.r, 1 - a.g, 1 - a.b); + public static ColorRGB operator *(ColorRGB a, ColorRGB b) => new ColorRGB(a.r * b.r, a.g * b.g, a.b * b.b, a.a * b.a); + public static ColorRGB operator *(ColorRGB a, double b) => new ColorRGB(a.r * b, a.g * b, a.b * b); + public static bool operator ==(ColorRGB a, IColor b) => a.Equals(b.AsRgb()); + public static bool operator !=(ColorRGB a, IColor b) => !a.Equals(b.AsRgb()); + public static bool operator ==(ColorRGB a, ColorRGB b) => a.Equals(b); + public static bool operator !=(ColorRGB a, ColorRGB b) => !a.Equals(b); + + public static implicit operator ColorRGB(ListTuple tuple) + { + if (tuple.Length == 3) return new ColorRGB(tuple[0], tuple[1], tuple[2]); + else if (tuple.Length == 4) return new ColorRGB(tuple[0], tuple[1], tuple[2], tuple[3]); + else throw new InvalidCastException(); + } + public static implicit operator ColorRGB((double, double, double) tuple) => new ColorRGB(tuple.Item1, tuple.Item2, tuple.Item3); + public static implicit operator ColorRGB((double, double, double, double) tuple) => new ColorRGB(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4); + public static explicit operator ColorRGB(Float3 group) => new ColorRGB(group.x, group.y, group.z); + public static explicit operator ColorRGB(Float4 group) => new ColorRGB(group.x, group.y, group.z, group.w); + + public static implicit operator ListTuple(ColorRGB color) => new ListTuple(color.r, color.g, color.b, color.a); + public static implicit operator ValueTuple(ColorRGB color) => (color.r, color.g, color.b); + public static implicit operator ValueTuple(ColorRGB color) => (color.r, color.g, color.b, color.a); + } +} diff --git a/Nerd_STF/Graphics/IColor.cs b/Nerd_STF/Graphics/IColor.cs new file mode 100644 index 0000000..15529ac --- /dev/null +++ b/Nerd_STF/Graphics/IColor.cs @@ -0,0 +1,33 @@ +using Nerd_STF.Mathematics; +using System; +using System.Collections.Generic; + +namespace Nerd_STF.Graphics +{ + public interface IColor : INumberGroupBase + { + Dictionary GetChannels(); + + TColor AsColor() where TColor : struct, IColor; + ColorRGB AsRgb(); + + bool Equals(IColor other); + } + + public interface IColor : IColor, + IEquatable, + INumberGroup +#if CS11_OR_GREATER + ,IColorPresets +#endif + where TSelf : struct, IColor + { +#if CS11_OR_GREATER + static abstract int ChannelCount { get; } + + // TODO: Do all color formats have a gamma value? + static abstract TSelf Average(double gamma, IEnumerable colors); + static abstract TSelf Lerp(double gamma, TSelf a, TSelf b, double t, bool clamp = true); +#endif + } +} diff --git a/Nerd_STF/Graphics/IColorPresets.cs b/Nerd_STF/Graphics/IColorPresets.cs new file mode 100644 index 0000000..afaba3d --- /dev/null +++ b/Nerd_STF/Graphics/IColorPresets.cs @@ -0,0 +1,24 @@ +#if CS11_OR_GREATER +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nerd_STF.Graphics +{ + public interface IColorPresets where TSelf : struct, IColor, IColorPresets + { + static abstract TSelf Black { get; } + static abstract TSelf Blue { get; } + static abstract TSelf Clear { get; } + static abstract TSelf Cyan { get; } + static abstract TSelf Gray { get; } + static abstract TSelf Green { get; } + static abstract TSelf Magenta { get; } + static abstract TSelf Orange { get; } + static abstract TSelf Purple { get; } + static abstract TSelf Red { get; } + static abstract TSelf White { get; } + static abstract TSelf Yellow { get; } + } +} +#endif diff --git a/Nerd_STF/Mathematics/Float2.cs b/Nerd_STF/Mathematics/Float2.cs index db4aa53..2528de4 100644 --- a/Nerd_STF/Mathematics/Float2.cs +++ b/Nerd_STF/Mathematics/Float2.cs @@ -46,7 +46,7 @@ namespace Nerd_STF.Mathematics { this[index] = item; index++; - if (index >= 2) break; + if (index == 2) break; } } public Float2(Fill fill) diff --git a/Nerd_STF/Mathematics/Float3.cs b/Nerd_STF/Mathematics/Float3.cs index e39a2d7..058122e 100644 --- a/Nerd_STF/Mathematics/Float3.cs +++ b/Nerd_STF/Mathematics/Float3.cs @@ -49,7 +49,7 @@ namespace Nerd_STF.Mathematics { this[index] = item; index++; - if (index >= 2) break; + if (index == 3) break; } } public Float3(Fill fill) diff --git a/Nerd_STF/Mathematics/Float4.cs b/Nerd_STF/Mathematics/Float4.cs index 497b6f0..a46513c 100644 --- a/Nerd_STF/Mathematics/Float4.cs +++ b/Nerd_STF/Mathematics/Float4.cs @@ -53,7 +53,7 @@ namespace Nerd_STF.Mathematics { this[index] = item; index++; - if (index >= 2) break; + if (index == 4) break; } } public Float4(Fill fill) diff --git a/Nerd_STF/Mathematics/INumberGroup.cs b/Nerd_STF/Mathematics/INumberGroup.cs index ac909cc..1db4d3b 100644 --- a/Nerd_STF/Mathematics/INumberGroup.cs +++ b/Nerd_STF/Mathematics/INumberGroup.cs @@ -7,7 +7,8 @@ namespace Nerd_STF.Mathematics { public interface INumberGroup : ICombinationIndexer, IEnumerable, - IEquatable + IEquatable, + INumberGroupBase #if CS11_OR_GREATER , IInterpolable, ISimpleMathOperations, @@ -17,11 +18,5 @@ namespace Nerd_STF.Mathematics #if CS11_OR_GREATER where TItem : INumber #endif - { - TItem this[int index] { get; set; } - - TItem[] ToArray(); - Fill ToFill(); - List ToList(); - } + { } } diff --git a/Nerd_STF/Mathematics/INumberGroupBase.cs b/Nerd_STF/Mathematics/INumberGroupBase.cs new file mode 100644 index 0000000..4567b36 --- /dev/null +++ b/Nerd_STF/Mathematics/INumberGroupBase.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Numerics; + +namespace Nerd_STF.Mathematics +{ + public interface INumberGroupBase +#if CS11_OR_GREATER + where TItem : INumber +#endif + { + TItem this[int index] { get; set; } + + TItem[] ToArray(); + Fill ToFill(); + List ToList(); + } +} diff --git a/Nerd_STF/Mathematics/ISimpleMathOperations.cs b/Nerd_STF/Mathematics/ISimpleMathOperations.cs index 4734333..a122286 100644 --- a/Nerd_STF/Mathematics/ISimpleMathOperations.cs +++ b/Nerd_STF/Mathematics/ISimpleMathOperations.cs @@ -5,7 +5,6 @@ using System.Numerics; namespace Nerd_STF.Mathematics { public interface ISimpleMathOperations : IAdditionOperators, - ISubtractionOperators, IMultiplyOperators where TSelf : ISimpleMathOperations { diff --git a/Nerd_STF/Mathematics/Int2.cs b/Nerd_STF/Mathematics/Int2.cs index efb0fe0..1ad10d0 100644 --- a/Nerd_STF/Mathematics/Int2.cs +++ b/Nerd_STF/Mathematics/Int2.cs @@ -44,7 +44,7 @@ namespace Nerd_STF.Mathematics { this[index] = item; index++; - if (index >= 2) break; + if (index == 2) break; } } public Int2(Fill fill) diff --git a/Nerd_STF/Mathematics/Int3.cs b/Nerd_STF/Mathematics/Int3.cs index a0592e8..36c8b07 100644 --- a/Nerd_STF/Mathematics/Int3.cs +++ b/Nerd_STF/Mathematics/Int3.cs @@ -47,7 +47,7 @@ namespace Nerd_STF.Mathematics { this[index] = item; index++; - if (index >= 2) break; + if (index == 3) break; } } public Int3(Fill fill) diff --git a/Nerd_STF/Mathematics/Int4.cs b/Nerd_STF/Mathematics/Int4.cs index e2d4f3e..329bb1a 100644 --- a/Nerd_STF/Mathematics/Int4.cs +++ b/Nerd_STF/Mathematics/Int4.cs @@ -51,7 +51,7 @@ namespace Nerd_STF.Mathematics { this[index] = item; index++; - if (index >= 2) break; + if (index == 4) break; } } public Int4(Fill fill) diff --git a/Nerd_STF/Mathematics/MathE.cs b/Nerd_STF/Mathematics/MathE.cs index 7249a21..46cc7e9 100644 --- a/Nerd_STF/Mathematics/MathE.cs +++ b/Nerd_STF/Mathematics/MathE.cs @@ -703,7 +703,11 @@ namespace Nerd_STF.Mathematics public static IEquation Cot(IEquation inputRad, int terms = 8) => new Equation((double x) => Cot(inputRad[x], terms)); - public static double Sqrt(double num) => 1 / InverseSqrtFast((float)num); // !!TODO!!: Bring back Newton's + // YOU CANNOT USE POW HERE!!! + // The CordicHelper uses the Sqrt function for the Pow method. + // It'll cause a stack overflow. + // !!TODO!! - Bring back Newton's + public static double Sqrt(double num) => 1 / InverseSqrtFast((float)num); public static IEquation Sqrt(IEquation equ) => new Equation((double x) => Sqrt(equ.Get(x)));