From 38118452c5671bed17294a4552f34674dd025a41 Mon Sep 17 00:00:00 2001 From: Muhammad Haroon Date: Sun, 18 Feb 2024 22:58:54 +0500 Subject: [PATCH 1/3] Implemented roll function --- lib/empty-example/sketch.js | 45 ++++++++++++++++++++++++++-- src/webgl/p5.Camera.js | 58 +++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index c614f47b93..624cfb7619 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,7 +1,46 @@ +// function setup() { +// // put setup code here +// } + +// function draw() { +// // put drawing code here +// } + +let cam; +let delta = 0.01; + function setup() { - // put setup code here + createCanvas(500, 500, WEBGL); + normalMaterial(); + cam = createCamera(); + // set initial pan angle + cam.roll(-0.8); } function draw() { - // put drawing code here -} + background(1000); + + // pan camera according to angle 'delta' + cam.roll(delta); + + // every 160 frames, switch direction + if (frameCount % 160 === 0) { + delta *= -1; + } + + rotateX(frameCount * 0.01); + translate(0, 0, -100); + box(20); + translate(0, 0, 35); + box(20); + translate(0, 0, 35); + box(20); + translate(0, 0, 35); + box(20); + translate(0, 0, 35); + box(20); + translate(0, 0, 35); + box(20); + translate(0, 0, 35); + box(20); +} \ No newline at end of file diff --git a/src/webgl/p5.Camera.js b/src/webgl/p5.Camera.js index abdc4b4c2b..7778cba827 100644 --- a/src/webgl/p5.Camera.js +++ b/src/webgl/p5.Camera.js @@ -2460,6 +2460,64 @@ p5.Camera = class Camera { ); } + /** + * Rolling rotates the camera view in forward direction. + * @method roll + * @param {Number} angle amount to rotate camera in current + * angleMode units. + * Greater than 0 values rotate counterclockwise (to the left). + * @example + *
+ * + * let cam; + * let delta = 0.01; + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * normalMaterial(); + * cam = createCamera(); + * // set initial pan angle + * cam.roll(-0.8); + * } + * + * function draw() { + * background(200); + * + * // pan camera according to angle 'delta' + * cam.roll(delta); + * + * // every 160 frames, switch direction + * if (frameCount % 160 === 0) { + * delta *= -1; + * } + * + * rotateX(frameCount * 0.01); + * translate(0, 0, -100); + * box(20); + * translate(0, 0, 35); + * box(20); + * translate(0, 0, 35); + * box(20); + * translate(0, 0, 35); + * box(20); + * translate(0, 0, 35); + * box(20); + * translate(0, 0, 35); + * box(20); + * translate(0, 0, 35); + * box(20); + * } + * + *
+ * + * @alt + * camera view pans in forward direction across a series boxes. + */ + roll(amount) { + const local = this._getLocalAxes(); + this._rotateView(amount, local.z[0], local.z[1], local.z[2]); + } + /** * Rotates the camera left and right. * From 37202c608d5cfa640503afefdb29778828503812 Mon Sep 17 00:00:00 2001 From: Muhammad Haroon <104259212+haroon10725@users.noreply.github.com> Date: Sun, 18 Feb 2024 23:03:55 +0500 Subject: [PATCH 2/3] Update sketch.js --- lib/empty-example/sketch.js | 45 +++---------------------------------- 1 file changed, 3 insertions(+), 42 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 624cfb7619..c614f47b93 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,46 +1,7 @@ -// function setup() { -// // put setup code here -// } - -// function draw() { -// // put drawing code here -// } - -let cam; -let delta = 0.01; - function setup() { - createCanvas(500, 500, WEBGL); - normalMaterial(); - cam = createCamera(); - // set initial pan angle - cam.roll(-0.8); + // put setup code here } function draw() { - background(1000); - - // pan camera according to angle 'delta' - cam.roll(delta); - - // every 160 frames, switch direction - if (frameCount % 160 === 0) { - delta *= -1; - } - - rotateX(frameCount * 0.01); - translate(0, 0, -100); - box(20); - translate(0, 0, 35); - box(20); - translate(0, 0, 35); - box(20); - translate(0, 0, 35); - box(20); - translate(0, 0, 35); - box(20); - translate(0, 0, 35); - box(20); - translate(0, 0, 35); - box(20); -} \ No newline at end of file + // put drawing code here +} From 0636f813cb88408a26de0fd0a1e8120a7ac450aa Mon Sep 17 00:00:00 2001 From: Rohan Julka Date: Thu, 13 Jun 2024 19:50:23 +0100 Subject: [PATCH 3/3] Implement roll function using quaternions --- src/app.js | 1 + src/math/p5.Vector.js | 32 +++++++++++- src/webgl/p5.Camera.js | 60 ++++++++++++++-------- src/webgl/p5.Quat.js | 96 ++++++++++++++++++++++++++++++++++++ test/unit/webgl/p5.Camera.js | 85 +++++++++++++++++++++++++++++++ 5 files changed, 252 insertions(+), 22 deletions(-) create mode 100644 src/webgl/p5.Quat.js diff --git a/src/app.js b/src/app.js index 1bbb7d1be2..672acc6142 100644 --- a/src/app.js +++ b/src/app.js @@ -91,6 +91,7 @@ import './webgl/p5.Camera'; import './webgl/p5.DataArray'; import './webgl/p5.Geometry'; import './webgl/p5.Matrix'; +import './webgl/p5.Quat'; import './webgl/p5.RendererGL.Immediate'; import './webgl/p5.RendererGL'; import './webgl/p5.RendererGL.Retained'; diff --git a/src/math/p5.Vector.js b/src/math/p5.Vector.js index 703cfa6cda..0b48490258 100644 --- a/src/math/p5.Vector.js +++ b/src/math/p5.Vector.js @@ -3887,5 +3887,33 @@ p5.Vector = class { } return v.equals(v2); } -}; -export default p5.Vector; + + + /** + * Replaces the components of a p5.Vector that are very close to zero with zero. + * + * In computers, handling numbers with decimals can give slightly imprecise answers due to the way those numbers are represented. + * This can make it hard to check if a number is zero, as it may be close but not exactly zero. + * This method rounds very close numbers to zero to make those checks easier + * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON + * + * @method clampToZero + * @return {p5.Vector} with components very close to zero replaced with zero. + * @chainable + */ + clampToZero() { + this.x = this._clampToZero(this.x); + this.y = this._clampToZero(this.y); + this.z = this._clampToZero(this.z); + return this; + } + + /** + * Helper function for clampToZero + * @private + */ + _clampToZero(val) { + return Math.abs((val||0) - 0) <= Number.EPSILON ? 0 : val; + } +};export default p5.Vector; diff --git a/src/webgl/p5.Camera.js b/src/webgl/p5.Camera.js index 7778cba827..9056f2e63c 100644 --- a/src/webgl/p5.Camera.js +++ b/src/webgl/p5.Camera.js @@ -2461,11 +2461,22 @@ p5.Camera = class Camera { } /** - * Rolling rotates the camera view in forward direction. + * Rotates the camera in a clockwise/counter-clockwise direction. + * + * Rolling rotates the camera without changing its orientation. The rotation + * happens in the camera’s "local" space. + * + * The parameter, `angle`, is the angle the camera should rotate. Passing a + * positive angle, as in `myCamera.roll(0.001)`, rotates the camera in counter-clockwise direction. + * Passing a negative angle, as in `myCamera.roll(-0.001)`, rotates the + * camera in clockwise direction. + * + * Note: Angles are interpreted based on the current + * angleMode(). + * * @method roll * @param {Number} angle amount to rotate camera in current * angleMode units. - * Greater than 0 values rotate counterclockwise (to the left). * @example *
* @@ -2475,47 +2486,56 @@ p5.Camera = class Camera { * function setup() { * createCanvas(100, 100, WEBGL); * normalMaterial(); + * // Create a p5.Camera object. * cam = createCamera(); - * // set initial pan angle - * cam.roll(-0.8); * } * * function draw() { * background(200); * - * // pan camera according to angle 'delta' + * // Roll camera according to angle 'delta' * cam.roll(delta); * - * // every 160 frames, switch direction - * if (frameCount % 160 === 0) { - * delta *= -1; - * } - * - * rotateX(frameCount * 0.01); - * translate(0, 0, -100); + * translate(0, 0, 0); * box(20); - * translate(0, 0, 35); + * translate(0, 25, 0); * box(20); - * translate(0, 0, 35); + * translate(0, 26, 0); * box(20); - * translate(0, 0, 35); + * translate(0, 27, 0); * box(20); - * translate(0, 0, 35); + * translate(0, 28, 0); * box(20); - * translate(0, 0, 35); + * translate(0,29, 0); * box(20); - * translate(0, 0, 35); + * translate(0, 30, 0); * box(20); * } * *
* * @alt - * camera view pans in forward direction across a series boxes. + * camera view rotates in counter clockwise direction with vertically stacked boxes in front of it. */ roll(amount) { const local = this._getLocalAxes(); - this._rotateView(amount, local.z[0], local.z[1], local.z[2]); + const axisQuaternion = p5.Quat.fromAxisAngle( + this._renderer._pInst._toRadians(amount), + local.z[0], local.z[1], local.z[2]); + // const upQuat = new p5.Quat(0, this.upX, this.upY, this.upZ); + const newUpVector = axisQuaternion.rotateVector( + new p5.Vector(this.upX, this.upY, this.upZ)); + this.camera( + this.eyeX, + this.eyeY, + this.eyeZ, + this.centerX, + this.centerY, + this.centerZ, + newUpVector.x, + newUpVector.y, + newUpVector.z + ); } /** diff --git a/src/webgl/p5.Quat.js b/src/webgl/p5.Quat.js new file mode 100644 index 0000000000..625595ccc0 --- /dev/null +++ b/src/webgl/p5.Quat.js @@ -0,0 +1,96 @@ +/** + * @module Math + * @submodule Quaternion + */ + +import p5 from '../core/main'; + +/** + * A class to describe a Quaternion + * for vector rotations in the p5js webgl renderer. + * Please refer the following link for details on the implementation + * https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html + * @class p5.Quat + * @constructor + * @param {Number} [w] Scalar part of the quaternion + * @param {Number} [x] x component of imaginary part of quaternion + * @param {Number} [y] y component of imaginary part of quaternion + * @param {Number} [z] z component of imaginary part of quaternion + * @private + */ +p5.Quat = class { + constructor(w, x, y, z) { + this.w = w; + this.vec = new p5.Vector(x, y, z); + } + + /** + * Returns a Quaternion for the + * axis angle representation of the rotation + * + * @method fromAxisAngle + * @param {Number} [angle] Angle with which the points needs to be rotated + * @param {Number} [x] x component of the axis vector + * @param {Number} [y] y component of the axis vector + * @param {Number} [z] z component of the axis vector + * @chainable + */ + static fromAxisAngle(angle, x, y, z) { + const w = Math.cos(angle/2); + const vec = new p5.Vector(x, y, z).normalize().mult(Math.sin(angle/2)); + return new p5.Quat(w, vec.x, vec.y, vec.z); + } + + conjugate() { + return new p5.Quat(this.w, -this.vec.x, -this.vec.y, -this.vec.z); + } + + /** + * Multiplies a quaternion with other quaternion. + * @method mult + * @param {p5.Quat} [quat] quaternion to multiply with the quaternion calling the method. + * @chainable + */ + multiply(quat) { + /* eslint-disable max-len */ + return new p5.Quat( + this.w * quat.w - this.vec.x * quat.vec.x - this.vec.y * quat.vec.y - this.vec.z - quat.vec.z, + this.w * quat.vec.x + this.vec.x * quat.w + this.vec.y * quat.vec.z - this.vec.z * quat.vec.y, + this.w * quat.vec.y - this.vec.x * quat.vec.z + this.vec.y * quat.w + this.vec.z * quat.vec.x, + this.w * quat.vec.z + this.vec.x * quat.vec.y - this.vec.y * quat.vec.x + this.vec.z * quat.w + ); + /* eslint-enable max-len */ + } + + /** + * This is similar to quaternion multiplication + * but when multipying vector with quaternion + * the multiplication can be simplified to the below formula. + * This was taken from the below stackexchange link + * https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion/50545#50545 + * @param {p5.Vector} [p] vector to rotate on the axis quaternion + * @returns + */ + rotateVector(p) { + return new p5.Vector.mult( p, this.w*this.w - this.vec.dot(this.vec) ) + .add( p5.Vector.mult( this.vec, 2 * p.dot(this.vec) ) ) + .add( p5.Vector.mult( this.vec, 2 * this.w ).cross( p ) ) + .clampToZero(); + } + + /** + * Rotates the Quaternion by the quaternion passed + * which contains the axis of roation and angle of rotation + * + * @method rotateBy + * @param {p5.Quat} [axesQuat] axis quaternion which contains + * the axis of rotation and angle of rotation + * @chainable + */ + rotateBy(axesQuat) { + return axesQuat.multiply(this).multiply(axesQuat.conjugate()). + vec.clampToZero(); + } +}; + +export default p5.Quat; diff --git a/test/unit/webgl/p5.Camera.js b/test/unit/webgl/p5.Camera.js index 96a5165d14..b746a5053e 100644 --- a/test/unit/webgl/p5.Camera.js +++ b/test/unit/webgl/p5.Camera.js @@ -1,3 +1,5 @@ +import { HALF_PI } from '../../../src/core/constants'; + suite('p5.Camera', function() { var myp5; var myCam; @@ -194,6 +196,69 @@ suite('p5.Camera', function() { assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed'); }); + test('Roll() with positive parameter sets correct Matrix w/o \ + changing eyeXYZ', function() { + var orig = getVals(myCam); + + var expectedMatrix = new Float32Array([ + 0, -1, 0, 0, + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, 0, -86.6025390625, 1 + ]); + + myCam.roll(HALF_PI); + + assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix); + + assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed'); + assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed'); + assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed'); + }); + + test('Roll() with negative parameter sets correct matrix w/o \ + changing eyeXYZ', function() { + var orig = getVals(myCam); + + var expectedMatrix = new Float32Array([ + 0, 1, 0, 0, + -1, 0, 0, 0, + 0, 0, 1, 0, + 0, 0, -86.6025390625, 1 + ]); + + myCam.tilt(HALF_PI); + + assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix); + + assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed'); + assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed'); + assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed'); + }); + + test('Roll(0) sets correct matrix w/o changing upXYZ and eyeXYZ', function() { + var orig = getVals(myCam); + + var expectedMatrix = new Float32Array([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, -86.6025390625, 1 + ]); + + myCam.roll(0); + + assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix); + + assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed'); + assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed'); + assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed'); + + assert.strictEqual(myCam.upX, orig.ux, 'up X pos changed'); + assert.strictEqual(myCam.upY, orig.uy, 'up Y pos changed'); + assert.strictEqual(myCam.upZ, orig.uz, 'up Z pos changed'); + }); + test('LookAt() should set centerXYZ without changing eyeXYZ or \ upXYZ', function() { var orig = getVals(myCam); @@ -266,6 +331,26 @@ suite('p5.Camera', function() { assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed'); assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed'); }); + + test('Roll() with positive parameter sets correct Matrix w/o \ + changing eyeXYZ', function() { + var orig = getVals(myCam); + + var expectedMatrix = new Float32Array([ + 0, -1, 0, 0, + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, 0, -86.6025390625, 1 + ]); + + myCam.roll(90); + + assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix); + + assert.strictEqual(myCam.eyeX, orig.eyeX, 'eye X pos changed'); + assert.strictEqual(myCam.eyeY, orig.eyeY, 'eye Y pos changed'); + assert.strictEqual(myCam.eyeZ, orig.eyeZ, 'eye Z pos changed'); + }); }); suite('Position / Orientation', function() {