diff --git a/src/webgl/p5.Camera.js b/src/webgl/p5.Camera.js index 1fb6c5012d..44be60d048 100644 --- a/src/webgl/p5.Camera.js +++ b/src/webgl/p5.Camera.js @@ -2435,6 +2435,7 @@ p5.Camera = class Camera { const rotation = p5.Matrix.identity(this._renderer._pInst); rotation.rotate(this._renderer._pInst._toRadians(a), x, y, z); + // Apply the rotation matrix to the center vector /* eslint-disable max-len */ const rotatedCenter = [ centerX * rotation.mat4[0] + centerY * rotation.mat4[4] + centerZ * rotation.mat4[8], @@ -2443,11 +2444,18 @@ p5.Camera = class Camera { ]; /* eslint-enable max-len */ - // add eye position back into center + // Translate the rotated center back to world coordinates rotatedCenter[0] += this.eyeX; rotatedCenter[1] += this.eyeY; rotatedCenter[2] += this.eyeZ; + // Rotate the up vector to keep the correct camera orientation + /* eslint-disable max-len */ + const upX = this.upX * rotation.mat4[0] + this.upY * rotation.mat4[4] + this.upZ * rotation.mat4[8]; + const upY = this.upX * rotation.mat4[1] + this.upY * rotation.mat4[5] + this.upZ * rotation.mat4[9]; + const upZ = this.upX * rotation.mat4[2] + this.upY * rotation.mat4[6] + this.upZ * rotation.mat4[10]; + /* eslint-enable max-len */ + this.camera( this.eyeX, this.eyeY, @@ -2455,9 +2463,9 @@ p5.Camera = class Camera { rotatedCenter[0], rotatedCenter[1], rotatedCenter[2], - this.upX, - this.upY, - this.upZ + upX, + upY, + upZ ); } diff --git a/test/unit/webgl/p5.Camera.js b/test/unit/webgl/p5.Camera.js index b746a5053e..1047b6d4e1 100644 --- a/test/unit/webgl/p5.Camera.js +++ b/test/unit/webgl/p5.Camera.js @@ -277,6 +277,61 @@ suite('p5.Camera', function() { assert.strictEqual(myCam.upY, orig.uy, 'up Y pos changed'); assert.strictEqual(myCam.upZ, orig.uz, 'up Z pos changed'); }); + + suite('Camera Tilt and Up Vector', function() { + test('Tilt() correctly updates the up vector', function() { + var orig = getVals(myCam); // Store original camera values + + // Apply tilt to the camera + myCam.tilt(30); // Tilt by 30 degrees + + // Compute expected up vector (normalized) + let forward = myp5.createVector( + myCam.centerX - myCam.eyeX, + myCam.centerY - myCam.eyeY, + myCam.centerZ - myCam.eyeZ + ); + let up = myp5.createVector(orig.ux, orig.uy, orig.uz); + let right = p5.Vector.cross(forward, up); + let expectedUp = p5.Vector.cross(right, forward).normalize(); + + // Verify that the up vector has changed + assert.notStrictEqual(myCam.upX, orig.ux, 'upX should be updated'); + assert.notStrictEqual(myCam.upY, orig.uy, 'upY should be updated'); + assert.notStrictEqual(myCam.upZ, orig.uz, 'upZ should be updated'); + + // Verify up vector matches expected values within a small margin of error + assert.closeTo(myCam.upX, expectedUp.x, 0.001, 'upX mismatch'); + assert.closeTo(myCam.upY, expectedUp.y, 0.001, 'upY mismatch'); + assert.closeTo(myCam.upZ, expectedUp.z, 0.001, 'upZ mismatch'); + }); + + test('Tilt() with negative angle correctly updates the up vector', function() { + var orig = getVals(myCam); // Store original camera values + + myCam.tilt(-30); // Tilt by -30 degrees + + // Compute expected up vector (normalized) + let forward = myp5.createVector( + myCam.centerX - myCam.eyeX, + myCam.centerY - myCam.eyeY, + myCam.centerZ - myCam.eyeZ + ); + let up = myp5.createVector(orig.ux, orig.uy, orig.uz); + let right = p5.Vector.cross(forward, up); + let expectedUp = p5.Vector.cross(right, forward).normalize(); + + // Verify that the up vector has changed + assert.notStrictEqual(myCam.upX, orig.ux, 'upX should be updated'); + assert.notStrictEqual(myCam.upY, orig.uy, 'upY should be updated'); + assert.notStrictEqual(myCam.upZ, orig.uz, 'upZ should be updated'); + + // Verify up vector matches expected values within a small margin of error + assert.closeTo(myCam.upX, expectedUp.x, 0.001, 'upX mismatch'); + assert.closeTo(myCam.upY, expectedUp.y, 0.001, 'upY mismatch'); + assert.closeTo(myCam.upZ, expectedUp.z, 0.001, 'upZ mismatch'); + }); + }); }); suite('Rotation with angleMode(DEGREES)', function() {