A Github pages blog about the code I write. You can contact me via my email: joe.subbiani@gmail.com
© 2024. All rights reserved.
/* * Based on a c++ template by Marc ten Bosch * https://marctenbosch.com/news/2011/05/4d-rotations-and-the-4d-equivalent-of-quaternions/ * * Rotor4 was founded on Marc ten Bosch's Rotor3 * https://marctenbosch.com/quaternions/code.htm * * Rotor equation derivations are explained in https://joesubbi.github.io/2022/04/04/4D-Rotation/ */ public class Bivector4 { public float bxy = 0; public float bxz = 0; public float byz = 0; public float bxw = 0; public float byw = 0; public float bzw = 0; public Bivector4(float bxy, float bxz, float byz, float bxw, float byw, float bzw) { this.bxy = bxy; this.bxz = bxz; this.byz = byz; this.bxw = bxw; this.byw = byw; this.bzw = bzw; } public static Bivector4 Wedge(Vector4 u, Vector4 v) { Bivector4 b = new Bivector4( u.x * v.y - u.y * v.x, //XY u.x * v.z - u.z * v.x, //XZ u.y * v.z - u.z * v.y, //YZ u.x * v.w - u.w * v.x, //XW u.y * v.w - u.w * v.y, //YW u.z * v.w - u.w * v.z //ZW ); return b; } } public class Rotor4 { // scalar part public float a = 1; // bivector part public float bxy = 0; public float bxz = 0; public float byz = 0; public float bxw = 0; public float byw = 0; public float bzw = 0; // pseudo-vector part public float pxyzw = 0; private const float epsilon = 1e-6f; private static Rotor4 identity = new Rotor4(1, 0, 0, 0, 0, 0, 0, 0); // ---- CONSTRUCTORS ---- public Rotor4(float a, float bxy, float bxz, float byz, float bxw, float byw, float bzw, float bxyzw) { this.a = a; this.bxy = bxy; this.bxz = bxz; this.byz = byz; this.bxw = bxw; this.byw = byw; this.bzw = bzw; this.pxyzw = bxyzw; } public Rotor4(Bivector4 bv, float angle) { float sina = Mathf.Sin(angle / 2); a = Mathf.Cos(angle / 2); bxy = -sina * bv.bxy; bxz = -sina * bv.bxz; byz = -sina * bv.byz; bxw = -sina * bv.bxw; byw = -sina * bv.byw; bzw = -sina * bv.bzw; pxyzw = 0; Normalise(); } public static Rotor4 Identity() { return identity; } // ---- OPERATORS ---- // Rotor4-Rotor4 Product public static Rotor4 operator *(Rotor4 a, Rotor4 b) { float e = -a.bxw * b.bxw - a.bxy * b.bxy - a.bxz * b.bxz - a.byw * b.byw - a.byz * b.byz - a.bzw * b.bzw + a.pxyzw * b.pxyzw + a.a * b.a; float exy = -a.bxw * b.byw + a.bxy * b.a - a.bxz * b.byz + a.byw * b.bxw + a.byz * b.bxz - a.bzw * b.pxyzw - a.pxyzw * b.bzw + a.a * b.bxy; float exz = -a.bxw * b.bzw + a.bxy * b.byz + a.bxz * b.a + a.byw * b.pxyzw - a.byz * b.bxy + a.bzw * b.bxw + a.pxyzw * b.byw + a.a * b.bxz; float exw = a.bxw * b.a + a.bxy * b.byw + a.bxz * b.bzw - a.byw * b.bxy - a.byz * b.pxyzw - a.bzw * b.bxz - a.pxyzw * b.byz + a.a * b.bxw; float eyz = -a.bxw * b.pxyzw - a.bxy * b.bxz + a.bxz * b.bxy - a.byw * b.bzw + a.byz * b.a + a.bzw * b.byw - a.pxyzw * b.bxw + a.a * b.byz; float eyw = a.bxw * b.bxy - a.bxy * b.bxw + a.bxz * b.pxyzw + a.byw * b.a + a.byz * b.bzw - a.bzw * b.byz + a.pxyzw * b.bxz + a.a * b.byw; float ezw = a.bxw * b.bxz - a.bxy * b.pxyzw - a.bxz * b.bxw + a.byw * b.byz - a.byz * b.byw + a.bzw * b.a - a.pxyzw * b.bxy + a.a * b.bzw; float exyzw = a.bxw * b.byz + a.bxy * b.bzw - a.bxz * b.byw - a.byw * b.bxz + a.byz * b.bxw + a.bzw * b.bxy + a.pxyzw * b.a + a.a * b.pxyzw; return new Rotor4(e, exy, exz, eyz, exw, eyw, ezw, exyzw); } // Rotor4-Vector4 Product (Rotation) public static Vector4 operator *(Rotor4 r, Vector4 a) { return r.Rotate(a); } public static Vector4 operator *(Vector4 a, Rotor4 r) { return r.Rotate(a); } // Rotate a Vector with a Rotor public Vector4 Rotate(Vector4 a) { float s = this.a; float s2 = s * s; float bxy2 = bxy * bxy; float bxz2 = bxz * bxz; float bxw2 = bxw * bxw; float byz2 = byz * byz; float byw2 = byw * byw; float bzw2 = bzw * bzw; float bxyzw2 = pxyzw * pxyzw; Vector4 r; r.x = ( 2 * a.w * bxw * s + 2 * a.w * bxy * byw + 2 * a.w * bxz * bzw + 2 * a.w * byz * pxyzw - a.x * bxw2 - a.x * bxy2 - a.x * bxz2 + a.x * byw2 + a.x * byz2 + a.x * bzw2 - a.x * bxyzw2 + a.x * s2 - 2 * a.y * bxw * byw + 2 * a.y * bxy * s - 2 * a.y * bxz * byz + 2 * a.y * bzw * pxyzw - 2 * a.z * bxw * bzw + 2 * a.z * bxy * byz + 2 * a.z * bxz * s - 2 * a.z * byw * pxyzw ); r.y = ( - 2 * a.w * bxw * bxy - 2 * a.w * bxz * pxyzw + 2 * a.w * byw * s + 2 * a.w * byz * bzw - 2 * a.x * bxw * byw - 2 * a.x * bxy * s - 2 * a.x * bxz * byz - 2 * a.x * bzw * pxyzw + a.y * bxw2 - a.y * bxy2 + a.y * bxz2 - a.y * byw2 - a.y * byz2 + a.y * bzw2 - a.y * bxyzw2 + a.y * s2 + 2 * a.z * bxw * pxyzw - 2 * a.z * bxy * bxz - 2 * a.z * byw * bzw + 2 * a.z * byz * s ); r.z = ( - 2 * a.w * bxw * bxz + 2 * a.w * bxy * pxyzw - 2 * a.w * byw * byz + 2 * a.w * bzw * s - 2 * a.x * bxw * bzw + 2 * a.x * bxy * byz - 2 * a.x * bxz * s + 2 * a.x * byw * pxyzw - 2 * a.y * bxw * pxyzw - 2 * a.y * bxy * bxz - 2 * a.y * byw * bzw - 2 * a.y * byz * s + a.z * bxw2 + a.z * bxy2 - a.z * bxz2 + a.z * byw2 - a.z * byz2 - a.z * bzw2 - a.z * bxyzw2 + a.z * s2 ); r.w = ( - a.w * bxw2 + a.w * bxy2 + a.w * bxz2 - a.w * byw2 + a.w * byz2 - a.w * bzw2 - a.w * bxyzw2 + a.w * s2 - 2 * a.x * bxw * s + 2 * a.x * bxy * byw + 2 * a.x * bxz * bzw - 2 * a.x * byz * pxyzw - 2 * a.y * bxw * bxy + 2 * a.y * bxz * pxyzw - 2 * a.y * byw * s + 2 * a.y * byz * bzw - 2 * a.z * bxw * bxz - 2 * a.z * bxy * pxyzw - 2 * a.z * byw * byz - 2 * a.z * bzw * s ); return r; } // Rotate one Rotor to another public Rotor4 Rotate(Rotor4 r) { return this * r * this.Reverse(); } // Spherical Linear Interpolation between two Rotors (MOSTLY WORKS - NOT PERFECT - FIX IN PROGRESS) public static Rotor4 SLerp(Rotor4 from, Rotor4 to, float ratio, bool normalise = true) { // The following SLerp is from: // https://referencesource.microsoft.com/#System.Numerics/System/Numerics/Quaternion.cs float t = ratio; float sq(float x) => x * x; Rotor4 difference = RotorFromTo(from, to); float cosOmega = Mathf.Sqrt(sq(difference.a) + sq(difference.pxyzw)); bool flip = false; if (cosOmega < 0.0f) { flip = true; cosOmega = -cosOmega; } float s1, s2; if (cosOmega > (1.0f - epsilon)) { // Too close, do straight linear interpolation. s1 = 1.0f - t; s2 = (flip) ? -t : t; } else { float omega = (float)Mathf.Acos(cosOmega); float invSinOmega = (float)(1 / Mathf.Sin(omega)); s1 = (float)Mathf.Sin((1.0f - t) * omega) * invSinOmega; s2 = (flip) ? (float)-Mathf.Sin(t * omega) * invSinOmega : (float)Mathf.Sin(t * omega) * invSinOmega; } Rotor4 toReturn = new Rotor4(s1 * from.a + s2 * to.a, s1 * from.bxy + s2 * to.bxy, s1 * from.bxz + s2 * to.bxz, s1 * from.byz + s2 * to.byz, s1 * from.bxw + s2 * to.bxw, s1 * from.byw + s2 * to.byw, s1 * from.bzw + s2 * to.bzw, s1 * from.pxyzw + s2 * to.pxyzw); if (normalise) toReturn.Normalise(); return toReturn; // Get the angle between rotors // create a rotor, but with the angle scaled by t //Rotor4 toReturn = RotorFromTo(from, to); } // ---- ROTOR OPERATIONS ---- // Conjugate public Rotor4 Reverse() { return new Rotor4(a, -bxy, -bxz, -byz, -bxw, -byw, -bzw, pxyzw); } // Length Squared private float LengthSquared() { float sq(float x) => x * x; return sq(a) + sq(bxy) + sq(bxz) + sq(byz) + sq(bxw) + sq(byw) + sq(bzw) + sq(pxyzw); } // Length private float Length() { return Mathf.Sqrt(LengthSquared()); } // Normalise this Rotor public void Normalise() { float l = Length(); a /= l; bxy /= l; bxz /= l; byz /= l; bxw /= l; byw /= l; bzw /= l; pxyzw /= l; } // Normalised copy of a Rotor public Rotor4 Normal() { Rotor4 r = this; r.Normalise(); return r; } // The Rotor to rotate one Rotor to another public static Rotor4 RotorFromTo(Rotor4 a, Rotor4 b) { Rotor4 difference = b * a.Reverse(); difference.Normalise(); return difference; } // The Angle between two Rotors (radians) public static float AngleFromTo(Rotor4 a, Rotor4 b) { float sq(float x) => x * x; Rotor4 difference = RotorFromTo(a, b); return Mathf.Acos( Mathf.Sqrt(sq(difference.a) + sq(difference.pxyzw)) ) * 2; } // The Rotor to get from a to b, and the angle between rotor public static (Rotor4, float) Difference(Rotor4 a, Rotor4 b) { Rotor4 difference = RotorFromTo(a, b); float angle = AngleFromTo(a, b); return (difference, angle); } // Dot Product of two Rotors public static Rotor4 Dot(Rotor4 a, Rotor4 b) { float e = -a.bxw * b.bxw - a.bxy * b.bxy - a.bxz * b.bxz - a.byw * b.byw - a.byz * b.byz - a.bzw * b.bzw + a.pxyzw - b.pxyzw; float exy = -a.bzw * b.pxyzw - a.pxyzw * b.bzw; float exz = a.byw * b.pxyzw + a.pxyzw * b.byw; float exw = -a.byz * b.pxyzw + a.pxyzw * b.byz; float eyz = -a.bxw * b.pxyzw + a.pxyzw * b.bxw; float eyw = a.bxz * b.pxyzw + a.pxyzw * b.bxz; float ezw = -a.bxy * b.pxyzw + a.pxyzw * b.bxy; float exyzw = 0; return new Rotor4(e, exy, exz, eyz, exw, eyw, ezw, exyzw); } // Geometric Product public static Rotor4 GeoProd(Vector4 a, Vector4 b) { Bivector4 bv = Bivector4.Wedge(a, b); return new Rotor4(Vector4.Dot(a, b), bv.bxy, bv.bxz, bv.byz, bv.bxw, bv.byw, bv.bzw, 0); } // ---- EQUALITY OPERATIONS ---- public static bool ExactlyEqual(Rotor4 a, Rotor4 b) { if (a.a == b.a && a.byz == b.byz && a.bxz == b.bxz && a.bxy == b.bxy && a.bxw == b.bxw && a.byw == b.byw && a.bzw == b.bzw) return true; return false; } public static bool ApproxEqual(Rotor4 a, Rotor4 b, float e = 0.005f) { float angle = AngleFromTo(a, b); return angle < e; } public static bool operator ==(Rotor4 lhs, Rotor4 rhs) { return ApproxEqual(lhs, rhs); } public static bool operator !=(Rotor4 lhs, Rotor4 rhs) { // Returns true in the presence of NaN values. return !(lhs == rhs); } public bool Equals(Rotor4 rotor) { return this == rotor; } public override bool Equals(object obj) => Equals(obj as Rotor4); public override int GetHashCode() { return HashCode.Combine(a, bxy, bxz, byz, bxw, byw, bzw, pxyzw); } // ---- Utility Functions ---- public override string ToString() { return a + " + " + bxy + " + " + bxz + " + " + byz + " + " + bxw + " + " + byw + " + " + bzw + " + " + pxyzw; } public float[] ToArray() { return new float[] { a, bxy, bxz, byz, bxw, byw, bzw, pxyzw }; } }