From 89a5bb06a919fb2e5e784c3ed171db7fe566d29f Mon Sep 17 00:00:00 2001 From: yzrmn Date: Wed, 17 Apr 2024 10:11:19 +0200 Subject: [PATCH] Match complex/quaternion rotation methods with matrices --- .../redgeometry-app/src/parts/gpu-cube.ts | 39 ++--- .../redgeometry/src/primitives/complex.ts | 8 +- .../redgeometry/src/primitives/quaternion.ts | 96 +++++----- .../tests/primitives/complex.spec.ts | 4 +- .../tests/primitives/quaternion.spec.ts | 164 +++++++++++------- 5 files changed, 185 insertions(+), 126 deletions(-) diff --git a/packages/redgeometry-app/src/parts/gpu-cube.ts b/packages/redgeometry-app/src/parts/gpu-cube.ts index 5d15b73..6e5d190 100644 --- a/packages/redgeometry-app/src/parts/gpu-cube.ts +++ b/packages/redgeometry-app/src/parts/gpu-cube.ts @@ -300,8 +300,22 @@ function cameraMoveSystem(world: World): void { return; } - let camRot = transform.rotation; - let camPos = transform.translation; + const camRot = transform.rotation; + + if (mouse.isPressing(MouseButtons.Mouse3)) { + let dx = 0; + let dy = 0; + + for (const ev of world.readEvents("input-mouse-motion")) { + dx -= ev.movementX; + dy -= ev.movementY; + } + + const sens = 1 / 250; + + camRot.rotateXPre(sens * dy); + camRot.rotateY(sens * dx); + } let vel = 0.005; let x = 0; @@ -322,28 +336,9 @@ function cameraMoveSystem(world: World): void { v = v.unitOrZero().mul(delta * vel); v = camRot.mulVec(v); - camPos = camPos.add(v); + transform.translation = transform.translation.add(v); } - if (mouse.isPressing(MouseButtons.Mouse3)) { - let dx = 0; - let dy = 0; - - for (const ev of world.readEvents("input-mouse-motion")) { - dx -= ev.movementX; - dy -= ev.movementY; - } - - const sens = 1 / 250; - const yaw = sens * dx; - const pitch = sens * dy; - - camRot = camRot.rotateXPre(pitch).rotateY(yaw); - } - - transform.rotation = camRot; - transform.translation = camPos; - world.updateComponent(mainCamera, "transform"); const fovRad = fov * (Math.PI / 180); diff --git a/packages/redgeometry/src/primitives/complex.ts b/packages/redgeometry/src/primitives/complex.ts index 35c2fa3..485b347 100644 --- a/packages/redgeometry/src/primitives/complex.ts +++ b/packages/redgeometry/src/primitives/complex.ts @@ -94,10 +94,14 @@ export class Complex { return new Vector2(this.a * v.x - this.b * v.y, this.a * v.y + this.b * v.x); } - public rotate(angle: number): Complex { + public rotate(angle: number): void { const sin = Math.sin(angle); const cos = Math.cos(angle); - return new Complex(cos * this.a - sin * this.b, cos * this.b + sin * this.a); + const a = this.a; + const b = this.b; + + this.a = cos * a - sin * b; + this.b = cos * b + sin * a; } public sub(z: Complex): Complex { diff --git a/packages/redgeometry/src/primitives/quaternion.ts b/packages/redgeometry/src/primitives/quaternion.ts index 18381d3..41dc677 100644 --- a/packages/redgeometry/src/primitives/quaternion.ts +++ b/packages/redgeometry/src/primitives/quaternion.ts @@ -267,100 +267,112 @@ export class Quaternion { ); } - public rotateX(angleX: number): Quaternion { + public rotateX(angleX: number): void { const sin = Math.sin(0.5 * angleX); const cos = Math.cos(0.5 * angleX); + const a = this.a; + const b = this.b; + const c = this.c; + const d = this.d; // | cos | | a | // | sin | * | b | // | 0 | | c | // | 0 | | d | - return new Quaternion( - cos * this.a - sin * this.b, - cos * this.b + sin * this.a, - cos * this.c - sin * this.d, - cos * this.d + sin * this.c, - ); + this.a = cos * a - sin * b; + this.b = cos * b + sin * a; + this.c = cos * c - sin * d; + this.d = cos * d + sin * c; } - public rotateXPre(angleX: number): Quaternion { + public rotateXPre(angleX: number): void { const sin = Math.sin(0.5 * angleX); const cos = Math.cos(0.5 * angleX); + const a = this.a; + const b = this.b; + const c = this.c; + const d = this.d; // | a | | cos | // | b | * | sin | // | c | | 0 | // | d | | 0 | - return new Quaternion( - this.a * cos - this.b * sin, - this.b * cos + this.a * sin, - this.c * cos + this.d * sin, - this.d * cos - this.c * sin, - ); + this.a = a * cos - b * sin; + this.b = b * cos + a * sin; + this.c = c * cos + d * sin; + this.d = d * cos - c * sin; } - public rotateY(angleY: number): Quaternion { + public rotateY(angleY: number): void { const sin = Math.sin(0.5 * angleY); const cos = Math.cos(0.5 * angleY); + const a = this.a; + const b = this.b; + const c = this.c; + const d = this.d; // | cos | | a | // | 0 | * | b | // | sin | | c | // | 0 | | d | - return new Quaternion( - cos * this.a - sin * this.c, - cos * this.b + sin * this.d, - cos * this.c + sin * this.a, - cos * this.d - sin * this.b, - ); + this.a = cos * a - sin * c; + this.b = cos * b + sin * d; + this.c = cos * c + sin * a; + this.d = cos * d - sin * b; } - public rotateYPre(angleY: number): Quaternion { + public rotateYPre(angleY: number): void { const sin = Math.sin(0.5 * angleY); const cos = Math.cos(0.5 * angleY); + const a = this.a; + const b = this.b; + const c = this.c; + const d = this.d; // | a | | cos | // | b | * | 0 | // | c | | sin | // | d | | 0 | - return new Quaternion( - this.a * cos - this.c * sin, - this.b * cos - this.d * sin, - this.c * cos + this.a * sin, - this.d * cos + this.b * sin, - ); + this.a = a * cos - c * sin; + this.b = b * cos - d * sin; + this.c = c * cos + a * sin; + this.d = d * cos + b * sin; } - public rotateZ(angleZ: number): Quaternion { + public rotateZ(angleZ: number): void { const sin = Math.sin(0.5 * angleZ); const cos = Math.cos(0.5 * angleZ); + const a = this.a; + const b = this.b; + const c = this.c; + const d = this.d; // | cos | | a | // | 0 | * | b | // | 0 | | c | // | sin | | d | - return new Quaternion( - cos * this.a - sin * this.d, - cos * this.b - sin * this.c, - cos * this.c + sin * this.b, - cos * this.d + sin * this.a, - ); + this.a = cos * a - sin * d; + this.b = cos * b - sin * c; + this.c = cos * c + sin * b; + this.d = cos * d + sin * a; } - public rotateZPre(angleZ: number): Quaternion { + public rotateZPre(angleZ: number): void { const sin = Math.sin(0.5 * angleZ); const cos = Math.cos(0.5 * angleZ); + const a = this.a; + const b = this.b; + const c = this.c; + const d = this.d; // | a | | cos | // | b | * | 0 | // | c | | 0 | // | d | | sin | - return new Quaternion( - this.a * cos - this.d * sin, - this.b * cos + this.c * sin, - this.c * cos - this.b * sin, - this.d * cos + this.a * sin, - ); + this.a = a * cos - d * sin; + this.b = b * cos + c * sin; + this.c = c * cos - b * sin; + this.d = d * cos + a * sin; } public sub(q: Quaternion): Quaternion { diff --git a/packages/redgeometry/tests/primitives/complex.spec.ts b/packages/redgeometry/tests/primitives/complex.spec.ts index 33a52a1..f5d2431 100644 --- a/packages/redgeometry/tests/primitives/complex.spec.ts +++ b/packages/redgeometry/tests/primitives/complex.spec.ts @@ -20,9 +20,9 @@ test("Complex - rotate", () => { const a = 1; const z = Complex.fromRotationAngle(a); - const z1 = z.rotate(-a); + z.rotate(-a); - expectToBeCloseComplex(z1, Complex.createIdentity()); + expectToBeCloseComplex(z, Complex.createIdentity()); }); test("Quaternion - mulPt/mulVec", () => { diff --git a/packages/redgeometry/tests/primitives/quaternion.spec.ts b/packages/redgeometry/tests/primitives/quaternion.spec.ts index 93690a2..e44056f 100644 --- a/packages/redgeometry/tests/primitives/quaternion.spec.ts +++ b/packages/redgeometry/tests/primitives/quaternion.spec.ts @@ -6,14 +6,15 @@ import { Vector3 } from "../../src/primitives/vector.js"; import { expectToBeClosePoint3, expectToBeCloseQuaternion, expectToBeCloseVector3 } from "../expect.js"; test("Quaternion - fromRotationAngleX", () => { - const q = Quaternion.fromRotationAngleX(1); + const a = 1; + const q = Quaternion.fromRotationAngleX(a); - const q1 = Quaternion.fromRotationEuler(1, 0, 0, RotationOrder.XYZ); - const q2 = Quaternion.fromRotationEuler(1, 0, 0, RotationOrder.XZY); - const q3 = Quaternion.fromRotationEuler(1, 0, 0, RotationOrder.YXZ); - const q4 = Quaternion.fromRotationEuler(1, 0, 0, RotationOrder.YZX); - const q5 = Quaternion.fromRotationEuler(1, 0, 0, RotationOrder.ZXY); - const q6 = Quaternion.fromRotationEuler(1, 0, 0, RotationOrder.ZYX); + const q1 = Quaternion.fromRotationEuler(a, 0, 0, RotationOrder.XYZ); + const q2 = Quaternion.fromRotationEuler(a, 0, 0, RotationOrder.XZY); + const q3 = Quaternion.fromRotationEuler(a, 0, 0, RotationOrder.YXZ); + const q4 = Quaternion.fromRotationEuler(a, 0, 0, RotationOrder.YZX); + const q5 = Quaternion.fromRotationEuler(a, 0, 0, RotationOrder.ZXY); + const q6 = Quaternion.fromRotationEuler(a, 0, 0, RotationOrder.ZYX); expectToBeCloseQuaternion(q, q1); expectToBeCloseQuaternion(q, q2); @@ -24,14 +25,15 @@ test("Quaternion - fromRotationAngleX", () => { }); test("Quaternion - fromRotationAngleY", () => { - const q = Quaternion.fromRotationAngleY(1); + const a = 1; + const q = Quaternion.fromRotationAngleY(a); - const q1 = Quaternion.fromRotationEuler(0, 1, 0, RotationOrder.XYZ); - const q2 = Quaternion.fromRotationEuler(0, 1, 0, RotationOrder.XZY); - const q3 = Quaternion.fromRotationEuler(0, 1, 0, RotationOrder.YXZ); - const q4 = Quaternion.fromRotationEuler(0, 1, 0, RotationOrder.YZX); - const q5 = Quaternion.fromRotationEuler(0, 1, 0, RotationOrder.ZXY); - const q6 = Quaternion.fromRotationEuler(0, 1, 0, RotationOrder.ZYX); + const q1 = Quaternion.fromRotationEuler(0, a, 0, RotationOrder.XYZ); + const q2 = Quaternion.fromRotationEuler(0, a, 0, RotationOrder.XZY); + const q3 = Quaternion.fromRotationEuler(0, a, 0, RotationOrder.YXZ); + const q4 = Quaternion.fromRotationEuler(0, a, 0, RotationOrder.YZX); + const q5 = Quaternion.fromRotationEuler(0, a, 0, RotationOrder.ZXY); + const q6 = Quaternion.fromRotationEuler(0, a, 0, RotationOrder.ZYX); expectToBeCloseQuaternion(q, q1); expectToBeCloseQuaternion(q, q2); @@ -43,14 +45,15 @@ test("Quaternion - fromRotationAngleY", () => { }); test("Quaternion - fromRotationAngleZ", () => { - const q = Quaternion.fromRotationAngleZ(1); + const a = 1; + const q = Quaternion.fromRotationAngleZ(a); - const q1 = Quaternion.fromRotationEuler(0, 0, 1, RotationOrder.XYZ); - const q2 = Quaternion.fromRotationEuler(0, 0, 1, RotationOrder.XZY); - const q3 = Quaternion.fromRotationEuler(0, 0, 1, RotationOrder.YXZ); - const q4 = Quaternion.fromRotationEuler(0, 0, 1, RotationOrder.YZX); - const q5 = Quaternion.fromRotationEuler(0, 0, 1, RotationOrder.ZXY); - const q6 = Quaternion.fromRotationEuler(0, 0, 1, RotationOrder.ZYX); + const q1 = Quaternion.fromRotationEuler(0, 0, a, RotationOrder.XYZ); + const q2 = Quaternion.fromRotationEuler(0, 0, a, RotationOrder.XZY); + const q3 = Quaternion.fromRotationEuler(0, 0, a, RotationOrder.YXZ); + const q4 = Quaternion.fromRotationEuler(0, 0, a, RotationOrder.YZX); + const q5 = Quaternion.fromRotationEuler(0, 0, a, RotationOrder.ZXY); + const q6 = Quaternion.fromRotationEuler(0, 0, a, RotationOrder.ZYX); expectToBeCloseQuaternion(q, q1); expectToBeCloseQuaternion(q, q2); @@ -66,38 +69,76 @@ test("Quaternion - fromRotationEuler", () => { const az = 3; const qa1 = Quaternion.fromRotationEuler(ax, ay, az, RotationOrder.XYZ); + const qa2 = Quaternion.createIdentity(); + qa2.rotateX(ax); + qa2.rotateY(ay); + qa2.rotateZ(az); + const qa3 = Quaternion.createIdentity(); + qa3.rotateZPre(az); + qa3.rotateYPre(ay); + qa3.rotateXPre(ax); + const qb1 = Quaternion.fromRotationEuler(ax, ay, az, RotationOrder.XZY); + const qb2 = Quaternion.createIdentity(); + qb2.rotateX(ax); + qb2.rotateZ(az); + qb2.rotateY(ay); + const qb3 = Quaternion.createIdentity(); + qb3.rotateYPre(ay); + qb3.rotateZPre(az); + qb3.rotateXPre(ax); + const qc1 = Quaternion.fromRotationEuler(ax, ay, az, RotationOrder.YXZ); + const qc2 = Quaternion.createIdentity(); + qc2.rotateY(ay); + qc2.rotateX(ax); + qc2.rotateZ(az); + const qc3 = Quaternion.createIdentity(); + qc3.rotateZPre(az); + qc3.rotateXPre(ax); + qc3.rotateYPre(ay); + const qd1 = Quaternion.fromRotationEuler(ax, ay, az, RotationOrder.YZX); + const qd2 = Quaternion.createIdentity(); + qd2.rotateY(ay); + qd2.rotateZ(az); + qd2.rotateX(ax); + const qd3 = Quaternion.createIdentity(); + qd3.rotateXPre(ax); + qd3.rotateZPre(az); + qd3.rotateYPre(ay); + const qe1 = Quaternion.fromRotationEuler(ax, ay, az, RotationOrder.ZXY); - const qf1 = Quaternion.fromRotationEuler(ax, ay, az, RotationOrder.ZYX); + const qe2 = Quaternion.createIdentity(); + qe2.rotateZ(az); + qe2.rotateX(ax); + qe2.rotateY(ay); + const qe3 = Quaternion.createIdentity(); + qe3.rotateYPre(ay); + qe3.rotateXPre(ax); + qe3.rotateZPre(az); - const qa2 = Quaternion.fromRotationAngleX(ax).rotateY(ay).rotateZ(az); - const qb2 = Quaternion.fromRotationAngleX(ax).rotateZ(az).rotateY(ay); - const qc2 = Quaternion.fromRotationAngleY(ay).rotateX(ax).rotateZ(az); - const qd2 = Quaternion.fromRotationAngleY(ay).rotateZ(az).rotateX(ax); - const qe2 = Quaternion.fromRotationAngleZ(az).rotateX(ax).rotateY(ay); - const qf2 = Quaternion.fromRotationAngleZ(az).rotateY(ay).rotateX(ax); + const qf1 = Quaternion.fromRotationEuler(ax, ay, az, RotationOrder.ZYX); + const qf2 = Quaternion.createIdentity(); + qf2.rotateZ(az); + qf2.rotateY(ay); + qf2.rotateX(ax); + const qf3 = Quaternion.createIdentity(); + qf3.rotateXPre(ax); + qf3.rotateYPre(ay); + qf3.rotateZPre(az); expectToBeCloseQuaternion(qa1, qa2); - expectToBeCloseQuaternion(qb1, qb2); - expectToBeCloseQuaternion(qc1, qc2); - expectToBeCloseQuaternion(qd1, qd2); - expectToBeCloseQuaternion(qe1, qe2); - expectToBeCloseQuaternion(qf1, qf2); - - const qa3 = Quaternion.fromRotationAngleZ(az).rotateYPre(ay).rotateXPre(ax); - const qb3 = Quaternion.fromRotationAngleY(ay).rotateZPre(az).rotateXPre(ax); - const qc3 = Quaternion.fromRotationAngleZ(az).rotateXPre(ax).rotateYPre(ay); - const qd3 = Quaternion.fromRotationAngleX(ax).rotateZPre(az).rotateYPre(ay); - const qe3 = Quaternion.fromRotationAngleY(ay).rotateXPre(ax).rotateZPre(az); - const qf3 = Quaternion.fromRotationAngleX(ax).rotateYPre(ay).rotateZPre(az); - expectToBeCloseQuaternion(qa1, qa3); + expectToBeCloseQuaternion(qb1, qb2); expectToBeCloseQuaternion(qb1, qb3); + expectToBeCloseQuaternion(qc1, qc2); expectToBeCloseQuaternion(qc1, qc3); + expectToBeCloseQuaternion(qd1, qd2); expectToBeCloseQuaternion(qd1, qd3); + expectToBeCloseQuaternion(qe1, qe2); expectToBeCloseQuaternion(qe1, qe3); + expectToBeCloseQuaternion(qf1, qf2); expectToBeCloseQuaternion(qf1, qf3); }); @@ -123,8 +164,14 @@ test("Quaternion - mulPt/mulVec", () => { const mat2 = Matrix4A.fromRotation(q2.a, q2.b, q2.c, q2.d); const mat3 = Matrix4A.fromRotation(q3.a, q3.b, q3.c, q3.d); const qa = Quaternion.fromRotationEuler(ax, ay, az, RotationOrder.XYZ); - const qb = Quaternion.fromRotationAngleX(ax).rotateY(ay).rotateZ(az); - const qc = Quaternion.fromRotationAngleZ(az).rotateYPre(ay).rotateXPre(ax); + const qb = Quaternion.createIdentity(); + qb.rotateX(ax); + qb.rotateY(ay); + qb.rotateZ(az); + const qc = Quaternion.createIdentity(); + qc.rotateZPre(az); + qc.rotateYPre(ay); + qc.rotateXPre(ax); const p = new Point3(1, 2, 3); const v = new Vector3(1, 2, 3); @@ -132,12 +179,6 @@ test("Quaternion - mulPt/mulVec", () => { const v1 = mat3.mul(mat2).mul(mat1).mulVec(v); const p2 = q3.mul(q2).mul(q1).mulPt(p); const v2 = q3.mul(q2).mul(q1).mulVec(v); - - expectToBeClosePoint3(p1, p2); - expectToBeCloseVector3(v1, v2); - expect(p1).toEqual(v1); - expect(p2).toEqual(v2); - const p3 = qa.mulPt(p); const v3 = qa.mulVec(v); const p4 = qb.mulPt(p); @@ -145,22 +186,27 @@ test("Quaternion - mulPt/mulVec", () => { const p5 = qc.mulPt(p); const v5 = qc.mulVec(v); + expectToBeClosePoint3(p1, p2); + expectToBeCloseVector3(v1, v2); expectToBeClosePoint3(p1, p3); expectToBeCloseVector3(v1, v3); expectToBeClosePoint3(p1, p4); expectToBeCloseVector3(v1, v4); expectToBeClosePoint3(p1, p5); expectToBeCloseVector3(v1, v5); + expect(p1).toEqual(v1); + expect(p2).toEqual(v2); expect(p3).toEqual(v3); expect(p4).toEqual(v4); }); test("Quaternion - rotateX", () => { const a = 1; - const q = Quaternion.fromRotationAngleX(a); + const q1 = Quaternion.fromRotationAngleX(a); + const q2 = Quaternion.fromRotationAngleX(a); - const q1 = q.rotateX(-a); - const q2 = q.rotateXPre(-a); + q1.rotateX(-a); + q2.rotateXPre(-a); expectToBeCloseQuaternion(q1, Quaternion.createIdentity()); expectToBeCloseQuaternion(q2, Quaternion.createIdentity()); @@ -168,10 +214,11 @@ test("Quaternion - rotateX", () => { test("Quaternion - rotateY", () => { const a = 1; - const q = Quaternion.fromRotationAngleY(a); + const q1 = Quaternion.fromRotationAngleY(a); + const q2 = Quaternion.fromRotationAngleY(a); - const q1 = q.rotateY(-a); - const q2 = q.rotateYPre(-a); + q1.rotateY(-a); + q2.rotateYPre(-a); expectToBeCloseQuaternion(q1, Quaternion.createIdentity()); expectToBeCloseQuaternion(q2, Quaternion.createIdentity()); @@ -179,10 +226,11 @@ test("Quaternion - rotateY", () => { test("Quaternion - rotateZ", () => { const a = 1; - const q = Quaternion.fromRotationAngleZ(a); + const q1 = Quaternion.fromRotationAngleZ(a); + const q2 = Quaternion.fromRotationAngleZ(a); - const q1 = q.rotateZ(-a); - const q2 = q.rotateZPre(-a); + q1.rotateZ(-a); + q2.rotateZPre(-a); expectToBeCloseQuaternion(q1, Quaternion.createIdentity()); expectToBeCloseQuaternion(q2, Quaternion.createIdentity());