From 3a7a3c320e85782819f11e6367a54d08f4e65b3f Mon Sep 17 00:00:00 2001 From: That-One-Nerd Date: Tue, 13 May 2025 08:59:02 -0400 Subject: [PATCH] Partial work on quaternions and some other tweaks. --- Nerd_STF/Helpers/CordicHelper.cs | 2 +- Nerd_STF/Helpers/ToStringHelper.cs | 40 +++ Nerd_STF/Mathematics/IPresets4d.cs | 5 +- Nerd_STF/Mathematics/Int2.cs | 2 +- Nerd_STF/Mathematics/Numbers/Complex.cs | 143 ++++++--- Nerd_STF/Mathematics/Numbers/Fraction.cs | 2 +- Nerd_STF/Mathematics/Numbers/Quaternion.cs | 327 +++++++++++++++++++++ 7 files changed, 472 insertions(+), 49 deletions(-) create mode 100644 Nerd_STF/Mathematics/Numbers/Quaternion.cs diff --git a/Nerd_STF/Helpers/CordicHelper.cs b/Nerd_STF/Helpers/CordicHelper.cs index 409334c..693641d 100644 --- a/Nerd_STF/Helpers/CordicHelper.cs +++ b/Nerd_STF/Helpers/CordicHelper.cs @@ -2,7 +2,7 @@ namespace Nerd_STF.Helpers { - public static class CordicHelper + internal static class CordicHelper { // Starts at 4 radians. Each index downwards is half that. // Goes from 2^2 to 2^-19. diff --git a/Nerd_STF/Helpers/ToStringHelper.cs b/Nerd_STF/Helpers/ToStringHelper.cs index ecb21e6..470644f 100644 --- a/Nerd_STF/Helpers/ToStringHelper.cs +++ b/Nerd_STF/Helpers/ToStringHelper.cs @@ -1,5 +1,6 @@ using Nerd_STF.Mathematics; using Nerd_STF.Mathematics.Algebra; +using System; using System.Collections.Generic; using System.Text; @@ -110,5 +111,44 @@ namespace Nerd_STF.Helpers } return total.ToString(); } + + private static readonly string dimNumSymbols = " ijk?"; +#if CS8_OR_GREATER + public static string HighDimNumberToString(IEnumerable terms, string? format, IFormatProvider? provider) +#else + public static string HighDimNumberToString(IEnumerable terms, string format, IFormatProvider provider) +#endif + { + StringBuilder builder = new StringBuilder(); + int index = 0; + bool first = true; + foreach (double term in terms) + { + if (term == 0) + { + index++; + continue; + } + if (first) builder.Append(term.ToString(format, provider)); + else + { + if (term > 0) + { + builder.Append(" + "); + builder.Append(term.ToString(format, provider)); + } + else + { + builder.Append(" - "); + builder.Append((-term).ToString(format, provider)); + } + } + if (index > 0) builder.Append(dimNumSymbols[MathE.Min(index, dimNumSymbols.Length)]); + first = false; + index++; + } + if (first) builder.Append(0.0.ToString(format, provider)); + return builder.ToString(); + } } } diff --git a/Nerd_STF/Mathematics/IPresets4d.cs b/Nerd_STF/Mathematics/IPresets4d.cs index af82370..1d5d025 100644 --- a/Nerd_STF/Mathematics/IPresets4d.cs +++ b/Nerd_STF/Mathematics/IPresets4d.cs @@ -4,8 +4,9 @@ namespace Nerd_STF.Mathematics public interface IPresets4d : IPresets3d where TSelf : IPresets4d { - static abstract TSelf LowW { get; } - static abstract TSelf HighW { get; } + // TODO: The HighW and LowW vectors could also be called "ana" and "kata." + static abstract TSelf LowW { get; } // Kata + static abstract TSelf HighW { get; } // Ana } } #endif diff --git a/Nerd_STF/Mathematics/Int2.cs b/Nerd_STF/Mathematics/Int2.cs index 2e9446f..b1a878a 100644 --- a/Nerd_STF/Mathematics/Int2.cs +++ b/Nerd_STF/Mathematics/Int2.cs @@ -260,7 +260,7 @@ namespace Nerd_STF.Mathematics public static bool operator ==(Int2 a, Int2 b) => a.Equals(b); public static bool operator !=(Int2 a, Int2 b) => !a.Equals(b); - public static explicit operator Int2(Complex complex) => new Int2((int)complex.Real, (int)complex.Imaginary); + public static explicit operator Int2(Complex complex) => new Int2((int)complex.r, (int)complex.i); public static explicit operator Int2(Float2 floats) => new Int2((int)floats.x, (int)floats.y); public static explicit operator Int2(Float3 floats) => new Int2((int)floats.x, (int)floats.y); public static explicit operator Int2(Float4 floats) => new Int2((int)floats.x, (int)floats.y); diff --git a/Nerd_STF/Mathematics/Numbers/Complex.cs b/Nerd_STF/Mathematics/Numbers/Complex.cs index b4b97f5..85789d0 100644 --- a/Nerd_STF/Mathematics/Numbers/Complex.cs +++ b/Nerd_STF/Mathematics/Numbers/Complex.cs @@ -2,18 +2,21 @@ using Nerd_STF.Helpers; using Nerd_STF.Mathematics.Algebra; using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Numerics; -using System.Text; namespace Nerd_STF.Mathematics.Numbers { - public struct Complex : IEquatable, - IFormattable + public struct Complex : IComparable, + IEquatable, + IFormattable, + INumberGroup #if CS11_OR_GREATER ,INumber, + IFromTuple, IInterpolable, IPresets2d, IRoundable, @@ -55,6 +58,61 @@ namespace Nerd_STF.Mathematics.Numbers i = imaginary; } + public double this[int index] + { + get + { + switch (index) + { + case 0: return r; + case 1: return i; + default: throw new ArgumentOutOfRangeException(nameof(index)); + } + } + set + { + switch (index) + { + case 0: r = value; break; + case 1: i = 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 'i': items[i] = this.i; 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 'i': this.i = stepper.Current; break; + default: throw new ArgumentException("Invalid key.", nameof(key)); + } + } + } + } + #if CS8_OR_GREATER public static Complex Parse(string? str) => #else @@ -195,20 +253,19 @@ namespace Nerd_STF.Mathematics.Numbers public static Complex Floor(Complex num) => new Complex(MathE.Floor(num.r), MathE.Floor(num.i)); - public static Complex Lerp(Complex a, Complex b, double t, bool clamp = false) => + public static Complex Lerp(Complex a, Complex b, double t, bool clamp = true) => new Complex(MathE.Lerp(a.r, b.r, t, clamp), MathE.Lerp(a.i, b.i, t, clamp)); public static Complex Product(IEnumerable vals) { bool any = false; - double resultR = 1, resultI = 1; + Complex result = One; foreach (Complex val in vals) { any = true; - resultR *= val.r; - resultI *= val.i; + result *= val; } - return any ? new Complex(resultR, resultI) : Zero; + return any ? result : Zero; } public static Complex Round(Complex val) => new Complex(MathE.Round(val.r), @@ -338,12 +395,20 @@ namespace Nerd_STF.Mathematics.Numbers { reals[index] = val.r; imaginaries[index] = val.i; + index++; } return (reals, imaginaries); } - public int CompareTo(double other) => Magnitude.CompareTo(MathE.Abs(other)); - public int CompareTo(Complex other) => Magnitude.CompareTo(other.Magnitude); + public IEnumerator GetEnumerator() + { + yield return r; + yield return i; + } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int CompareTo(double other) => MagnitudeSqr.CompareTo(MathE.Abs(other * other)); + public int CompareTo(Complex other) => MagnitudeSqr.CompareTo(other.MagnitudeSqr); #if CS8_OR_GREATER public int CompareTo(object? other) #else @@ -352,8 +417,9 @@ namespace Nerd_STF.Mathematics.Numbers { if (other is null) return 1; else if (other is Complex otherComplex) return CompareTo(otherComplex); + else if (other is double otherDouble) return CompareTo(otherDouble); else if (TryConvertFrom(other, out Complex otherConvert)) return CompareTo(otherConvert); - else return 0; + else return 1; } public bool Equals(double other) => r == other && i == 0; public bool Equals(Complex other) => r == other.r && i == other.i; @@ -369,41 +435,24 @@ namespace Nerd_STF.Mathematics.Numbers else return false; } public override int GetHashCode() => base.GetHashCode(); - public override string ToString() => ToString(null, null); + public override string ToString() => ToStringHelper.HighDimNumberToString(this, null, null); #if CS8_OR_GREATER - public string ToString(string? format) => ToString(format, null); - public string ToString(IFormatProvider? provider) => ToString(null, provider); + public string ToString(string? format) => ToStringHelper.HighDimNumberToString(this, format, null); + public string ToString(IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, null, provider); + public string ToString(string? format, IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, format, provider); #else - public string ToString(string format) => ToString(format, null); - public string ToString(IFormatProvider provider) => ToString(null, provider); -#endif -#if CS8_OR_GREATER - public string ToString(string? format, IFormatProvider? provider) -#else - public string ToString(string format, IFormatProvider provider) + public string ToString(string format) => ToStringHelper.HighDimNumberToString(this, format, null); + public string ToString(IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, null, provider); + public string ToString(string format, IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, format, provider); #endif + + public double[] ToArray() => new double[] { r, i }; + public Fill ToFill() { - if (r == 0 && i == 0) return 0.0.ToString(format, provider); - else if (r == 0) return $"{i.ToString(format, provider)}i"; - else - { - StringBuilder builder = new StringBuilder(); - builder.Append(r.ToString(format, provider)); - if (i > 0) - { - builder.Append(" + "); - builder.Append(i.ToString(format, provider)); - builder.Append('i'); - } - else if (i < 0) - { - builder.Append(" - "); - builder.Append((-i).ToString(format, provider)); - builder.Append('i'); - } - return builder.ToString(); - } + Complex @this = this; + return i => @this[i]; } + public List ToList() => new List() { r, i }; #if CS11_OR_GREATER public bool TryFormat(Span dest, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) @@ -414,16 +463,17 @@ namespace Nerd_STF.Mathematics.Numbers charsWritten = result.Length; return true; } + + static Complex IIncrementOperators.operator ++(Complex a) => new Complex(a.r + 1, a.i); + static Complex IDecrementOperators.operator --(Complex a) => new Complex(a.r - 1, a.i); #endif public static Complex operator +(Complex a) => a; public static Complex operator +(Complex a, Complex b) => new Complex(a.r + b.r, a.i + b.i); public static Complex operator +(Complex a, double b) => new Complex(a.r + b, a.i); - public static Complex operator ++(Complex a) => new Complex(a.r + 1, a.i); public static Complex operator -(Complex a) => new Complex(-a.r, -a.i); public static Complex operator -(Complex a, Complex b) => new Complex(a.r - b.r, a.i - b.i); public static Complex operator -(Complex a, double b) => new Complex(a.r - b, a.i); - public static Complex operator --(Complex a) => new Complex(a.r - 1, a.i); public static Complex operator *(Complex a, Complex b) => new Complex(a.r * b.r - a.i * b.i, a.r * b.i + a.i * b.r); public static Complex operator *(Complex a, double b) => new Complex(a.r * b, a.i * b); public static Complex operator /(Complex a, Complex b) @@ -447,9 +497,14 @@ namespace Nerd_STF.Mathematics.Numbers public static bool operator <=(Complex a, Complex b) => a.CompareTo(b) <= 0; public static implicit operator Complex(System.Numerics.Complex num) => new Complex(num.Real, num.Imaginary); - public static implicit operator System.Numerics.Complex(Complex num) => new Complex(num.r, num.i); public static implicit operator Complex(Float2 group) => new Complex(group.x, group.y); public static explicit operator Complex(Int2 group) => new Complex(group.x, group.y); + public static implicit operator Complex(ListTuple tuple) => new Complex(tuple[0], tuple[1]); + public static implicit operator Complex(ValueTuple tuple) => new Complex(tuple.Item1, tuple.Item2); public static explicit operator Complex(Vector2 group) => new Complex(group.X, group.Y); + + public static implicit operator System.Numerics.Complex(Complex num) => new Complex(num.r, num.i); + public static implicit operator ListTuple(Complex num) => new ListTuple(num.r, num.i); + public static implicit operator ValueTuple(Complex num) => (num.r, num.i); } } diff --git a/Nerd_STF/Mathematics/Numbers/Fraction.cs b/Nerd_STF/Mathematics/Numbers/Fraction.cs index c7c077d..2115611 100644 --- a/Nerd_STF/Mathematics/Numbers/Fraction.cs +++ b/Nerd_STF/Mathematics/Numbers/Fraction.cs @@ -325,7 +325,7 @@ namespace Nerd_STF.Mathematics.Numbers return false; } else if (value is Fraction valueFrac) result = valueFrac; - else if (value is Complex valueComp) result = Approximate(valueComp.Real); + else if (value is Complex valueComp) result = Approximate(valueComp.r); else if (value is double valueDouble) result = Approximate(valueDouble); else if (value is float valueSingle) result = Approximate(valueSingle); #if NET5_0_OR_GREATER diff --git a/Nerd_STF/Mathematics/Numbers/Quaternion.cs b/Nerd_STF/Mathematics/Numbers/Quaternion.cs new file mode 100644 index 0000000..0a89cc1 --- /dev/null +++ b/Nerd_STF/Mathematics/Numbers/Quaternion.cs @@ -0,0 +1,327 @@ +using Nerd_STF.Exceptions; +using Nerd_STF.Helpers; +using Nerd_STF.Mathematics.Algebra; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace Nerd_STF.Mathematics.Numbers +{ + public struct Quaternion : IComparable, + IEnumerable, + IFormattable, + INumberGroup +#if CS11_OR_GREATER + ,//INumber, + //IFromTuple, + IInterpolable, + IPresets4d, + IRoundable, + ISimpleMathOperations, + ISplittable, + IVectorOperations +#endif + { + public static Quaternion Backward => new Quaternion(0, 0, 0, -1); + public static Quaternion Down => new Quaternion(0, 0, -1, 0); + public static Quaternion Forward => new Quaternion(0, 0, 0, 1); + public static Quaternion HighW => new Quaternion(1, 0, 0, 0); + public static Quaternion Left => new Quaternion(0, -1, 0, 0); + public static Quaternion LowW => new Quaternion(-1, 0, 0, 0); + public static Quaternion Right => new Quaternion(0, 1, 0, 0); + public static Quaternion Up => new Quaternion(0, 0, 1, 0); + + public static Quaternion One => new Quaternion(1, 1, 1, 1); + public static Quaternion Zero => new Quaternion(0, 0, 0, 0); + + public Quaternion Conjugate => new Quaternion(w, -x, -y, -z); + public double Magnitude => MathE.Sqrt(w * w + x * x + y * y + z * z); + public double MagnitudeSqr => w * w + x * x + y * y + z * z; + + public double w, x, y, z; + + public Quaternion(double w, double x, double y, double z) + { + this.w = w; + this.x = x; + this.y = y; + this.z = z; + } + public Quaternion(IEnumerable nums) + { + w = 0; + x = 0; + y = 0; + z = 0; + + int index = 0; + foreach (double item in nums) + { + this[index] = item; + index++; + if (index == 4) break; + } + } + public Quaternion(Fill fill) + { + w = fill(0); + x = fill(1); + y = fill(2); + z = fill(3); + } + + public double this[int index] + { + get + { + switch (index) + { + case 0: return w; + case 1: return x; + case 2: return y; + case 3: return z; + default: throw new ArgumentOutOfRangeException(nameof(index)); + } + } + set + { + switch (index) + { + case 0: w = value; break; + case 1: x = value; break; + case 2: y = value; break; + case 3: z = 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 'w': items[i] = w; break; + case 'x': items[i] = x; break; + case 'y': items[i] = y; break; + case 'z': items[i] = z; 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 'w': w = stepper.Current; break; + case 'x': x = stepper.Current; break; + case 'y': y = stepper.Current; break; + case 'z': z = stepper.Current; break; + default: throw new ArgumentException("Invalid key.", nameof(key)); + } + } + } + } + + public static Quaternion Abs(Quaternion num) => new Quaternion(num.Magnitude, 0, 0, 0); + public static Quaternion Ceiling(Quaternion num) => + new Quaternion(MathE.Ceiling(num.w), + MathE.Ceiling(num.x), + MathE.Ceiling(num.y), + MathE.Ceiling(num.z)); + public static Quaternion Ceiling(Quaternion num, Quaternion min, Quaternion max) => + new Quaternion(MathE.Clamp(num.w, min.w, max.w), + MathE.Clamp(num.x, min.x, max.x), + MathE.Clamp(num.y, min.y, max.y), + MathE.Clamp(num.z, min.z, max.z)); + public static Quaternion ClampMagnitude(Quaternion num, double minMag, double maxMag) + { + Quaternion copy = num; + ClampMagnitude(ref copy, minMag, maxMag); + return copy; + } + public static void ClampMagnitude(ref Quaternion num, double minMag, double maxMag) + { + if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag)); + double mag = num.Magnitude; + + double factor; + if (mag < minMag) factor = minMag / mag; + else if (mag > maxMag) factor = maxMag / mag; + else factor = 1; + + num.w *= factor; + num.x *= factor; + num.y *= factor; + num.z *= factor; + } + public static double Dot(Quaternion a, Quaternion b) => a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z; + public static double Dot(IEnumerable nums) + { + double w = 1, x = 1, y = 1, z = 1; + foreach (Quaternion q in nums) + { + w *= q.w; + x *= q.x; + y *= q.y; + z *= q.z; + } + return w + x + y + z; + } + public static Quaternion Floor(Quaternion num) => + new Quaternion(MathE.Floor(num.w), + MathE.Floor(num.x), + MathE.Floor(num.y), + MathE.Floor(num.z)); + public static Quaternion Lerp(Quaternion a, Quaternion b, double t, bool clamp = true) => + new Quaternion(MathE.Lerp(a.w, b.w, t, clamp), + MathE.Lerp(a.x, b.x, t, clamp), + MathE.Lerp(a.y, b.y, t, clamp), + MathE.Lerp(a.z, b.z, t, clamp)); + public static Quaternion Product(IEnumerable nums) + { + bool any = false; + Quaternion result = One; + foreach (Quaternion q in nums) + { + any = true; + result *= q; + } + return any ? result : Zero; + } + public static Quaternion Round(Quaternion num) => + new Quaternion(MathE.Round(num.w), + MathE.Round(num.x), + MathE.Round(num.y), + MathE.Round(num.z)); + public static Quaternion Sum(IEnumerable nums) + { + bool any = false; + Quaternion result = One; + foreach (Quaternion q in nums) + { + any = true; + result += q; + } + return any ? result : Zero; + } + + public static (double[] Ws, double[] Xs, double[] Ys, double[] Zs) SplitArray(IEnumerable nums) + { + int count = nums.Count(); + double[] Ws = new double[count], + Xs = new double[count], + Ys = new double[count], + Zs = new double[count]; + int index = 0; + foreach (Quaternion q in nums) + { + Ws[index] = q.w; + Xs[index] = q.x; + Ys[index] = q.y; + Zs[index] = q.z; + index++; + } + return (Ws, Xs, Ys, Zs); + } + + public IEnumerator GetEnumerator() + { + yield return w; + yield return x; + yield return y; + yield return z; + } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int CompareTo(double other) => MagnitudeSqr.CompareTo(other * other); + public int CompareTo(Quaternion other) => MagnitudeSqr.CompareTo(other.MagnitudeSqr); +#if CS8_OR_GREATER + public int CompareTo(object? other) +#else + public int CompareTo(object other) +#endif + { + if (other is null) return 1; + else if (other is Quaternion otherQuat) return CompareTo(otherQuat); + else if (other is double otherNum) return CompareTo(otherNum); + //else if (TryConvertFrom(other, out Quaternion otherConvert)) return CompareTo(otherConvert); + else return 1; + } + public bool Equals(double other) => w == other && x == 0 && y == 0 && z == 0; + public bool Equals(Complex other) => w == other.r && x == other.i && y == 0 && z == 0; + public bool Equals(Quaternion other) => w == other.w && x == other.x && y == other.y && z == other.z; +#if CS8_OR_GREATER + public override bool Equals(object? other) +#else + public override bool Equals(object other) +#endif + { + if (other is null) return false; + else if (other is Quaternion otherQuat) return Equals(otherQuat); + else if (other is Complex otherComp) return Equals(otherComp); + else if (other is double otherNum) return Equals(otherNum); + //else if (TryConvertFrom(other, out Quaternion otherConvert)) return Equals(otherConvert); + else return false; + } + public override int GetHashCode() => base.GetHashCode(); + public override string ToString() => ToStringHelper.HighDimNumberToString(this, null, null); +#if CS8_OR_GREATER + public string ToString(string? format) => ToStringHelper.HighDimNumberToString(this, format, null); + public string ToString(IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, null, provider); + public string ToString(string? format, IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, format, provider); +#else + public string ToString(string format) => ToStringHelper.HighDimNumberToString(this, format, null); + public string ToString(IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, null, provider); + public string ToString(string format, IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, format, provider); +#endif + + public double[] ToArray() => new double[] { w, x, y, z }; + public Fill ToFill() + { + Quaternion @this = this; + return i => @this[i]; + } + public List ToList() => new List() { w, x, y, z }; + + public static Quaternion operator +(Quaternion a) => a; + public static Quaternion operator +(Quaternion a, Quaternion b) => new Quaternion(a.w + b.w, a.x + b.x, a.y + b.y, a.z + b.z); + public static Quaternion operator +(Quaternion a, Complex b) => new Quaternion(a.w + b.r, a.x + b.i, a.y, a.z); + public static Quaternion operator +(Quaternion a, double b) => new Quaternion(a.w + b, a.x, a.y, a.z); + public static Quaternion operator -(Quaternion a) => new Quaternion(-a.w, -a.x, -a.y, -a.z); + public static Quaternion operator -(Quaternion a, Quaternion b) => new Quaternion(a.w - b.w, a.x - b.x, a.y - b.y, a.z - b.z); + public static Quaternion operator -(Quaternion a, Complex b) => new Quaternion(a.w - b.r, a.x - b.i, a.y, a.z); + public static Quaternion operator -(Quaternion a, double b) => new Quaternion(a.w - b, a.x, a.y, a.z); + public static Quaternion operator *(Quaternion a, Quaternion b) => + new Quaternion(a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z, + a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, + a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x, + a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w); + public static Quaternion operator *(Quaternion a, Complex b) => + new Quaternion(a.w * b.r - a.x * b.i, + a.x * b.r + a.w * b.i, + a.y * b.r + a.z * b.i, + a.z * b.r - a.y * b.i); + public static Quaternion operator *(Quaternion a, double b) => new Quaternion(a.w * b, a.x * b, a.y * b, a.z * b); + public static bool operator ==(Quaternion a, Quaternion b) => a.Equals(b); + public static bool operator !=(Quaternion a, Quaternion b) => !a.Equals(b); + public static bool operator >(Quaternion a, Quaternion b) => a.CompareTo(b) > 0; + public static bool operator <(Quaternion a, Quaternion b) => a.CompareTo(b) < 0; + public static bool operator >=(Quaternion a, Quaternion b) => a.CompareTo(b) >= 0; + public static bool operator <=(Quaternion a, Quaternion b) => a.CompareTo(b) <= 0; + + public static implicit operator Quaternion(Complex complex) => new Quaternion(complex.r, complex.i, 0, 0); + } +}