From 36f3c0a970fed545f954c7268fddfd61c5b906bb Mon Sep 17 00:00:00 2001 From: Om <144691499+ombalgude@users.noreply.github.com> Date: Sat, 11 Oct 2025 18:34:49 +0530 Subject: [PATCH 1/2] refactor: Move setViewport to standalone addon in lib/addons --- lib/addons/p5.viewport.js | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 lib/addons/p5.viewport.js diff --git a/lib/addons/p5.viewport.js b/lib/addons/p5.viewport.js new file mode 100644 index 0000000000..1b80caad65 --- /dev/null +++ b/lib/addons/p5.viewport.js @@ -0,0 +1,50 @@ +/** + * @module p5.viewport + * @submodule p5.viewport + * @for p5 + * @version 0.0.1 + * @author om balgude + * @description This addon provides a setViewport function to remap the canvas coordinate system. + * + */ + +(function() { + /** + * The viewportAddon function is automatically called by p5.js to add the addon + * functionality to the p5.js library. + * + * @private + * @param {Object} p5 - The p5.js instance. + * @param {Object} fn - The p5.js prototype object. + * @param {Object} lifecycles - The p5.js lifecycle hooks. + */ + function viewportAddon(p5, fn, lifecycles) { + /** + * Sets the coordinate system to a new viewport. + * + * Calling `setViewport(xmin, xmax, ymin, ymax)` remaps the canvas's + * coordinate system. The top-left corner of the canvas will be `(xmin, ymin)` + * and the bottom-right corner will be `(xmax, ymax)`. + * + * @method setViewport + * @param {Number} xmin The minimum x-value of the viewport. + * @param {Number} xmax The maximum x-value of the viewport. + * @param {Number} ymin The minimum y-value of the viewport. + * @param {Number} ymax The maximum y-value of the viewport. + * @chainable + */ + fn.setViewport = function(xmin, xmax, ymin, ymax) { + this.resetMatrix(); + const scaleX = this.width / (xmax - xmin); + const scaleY = this.height / (ymax - ymin); + this.scale(scaleX, scaleY); + this.translate(-xmin, -ymin); + return this; + }; + } + + // Register the addon with p5.js + if (typeof p5 !== 'undefined') { + p5.registerAddon(viewportAddon); + } +})(); From f4e381c0cf3f3a439492a2aa01356c23fbe16b39 Mon Sep 17 00:00:00 2001 From: Om <144691499+ombalgude@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:25:34 +0530 Subject: [PATCH 2/2] Add activeCamera() joint getter/setter for camera access --- src/webgl/p5.Camera.js | 119 +++++++++++++++++++++++++++++++++++ test/unit/webgl/p5.Camera.js | 46 ++++++++++++++ 2 files changed, 165 insertions(+) diff --git a/src/webgl/p5.Camera.js b/src/webgl/p5.Camera.js index b687f916c5..e1cb9332f8 100644 --- a/src/webgl/p5.Camera.js +++ b/src/webgl/p5.Camera.js @@ -3822,6 +3822,116 @@ function camera(p5, fn){ this._renderer.setCamera(cam); }; + /** + * Returns or sets the current (active) camera of a 3D sketch. + * + * `activeCamera()` provides a standard way to access and modify the active + * camera. When called with no arguments, it returns the current active camera. + * When called with a camera argument, it sets that camera as the active camera. + * + * This function works alongside setCamera() and + * provides a joint getter/setter pattern that's consistent with other p5.js + * functions. + * + * Note: `activeCamera()` can only be used in WebGL mode. + * + * @method activeCamera + * @param {p5.Camera} [cam] camera that should be made active. If omitted, returns the current active camera. + * @return {p5.Camera|p5} the current active camera (when called with no arguments) or the p5 instance (for chaining when setting). + * @for p5 + * + * @example + *
+ * + * // Get the current active camera. + * + * let cam1; + * let cam2; + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * // Create two cameras. + * cam1 = createCamera(); + * cam2 = createCamera(); + * cam2.setPosition(400, -400, 800); + * cam2.lookAt(0, 0, 0); + * + * // Set cam1 as active. + * activeCamera(cam1); + * + * describe('A white cube on a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Get the current active camera. + * let currentCam = activeCamera(); + * + * // Draw the box. + * box(); + * } + * + *
+ * + * @example + *
+ * + * // Double-click to toggle between cameras. + * + * let cam1; + * let cam2; + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * // Create the first camera. + * cam1 = createCamera(); + * + * // Create the second camera. + * cam2 = createCamera(); + * cam2.setPosition(400, -400, 800); + * cam2.lookAt(0, 0, 0); + * + * // Set the current camera to cam1. + * activeCamera(cam1); + * + * describe('A white cube on a gray background. The camera toggles between frontal and aerial views when the user double-clicks.'); + * } + * + * function draw() { + * background(200); + * + * // Draw the box. + * box(); + * } + * + * // Toggle the current camera when the user double-clicks. + * function doubleClicked() { + * // Get the current camera. + * let currentCam = activeCamera(); + * + * // Switch to the other camera. + * if (currentCam === cam1) { + * activeCamera(cam2); + * } else { + * activeCamera(cam1); + * } + * } + * + *
+ */ + fn.activeCamera = function (cam) { + this._assert3d('activeCamera'); + if (cam !== undefined) { + this._renderer.setCamera(cam); + return this; + } else { + return this._renderer.states.curCamera; + } + }; + /** * A class to describe a camera for viewing a 3D sketch. * @@ -3989,6 +4099,15 @@ function camera(p5, fn){ this.states.setValue('uViewMatrix', this.states.uViewMatrix.clone()); this.states.uViewMatrix.set(cam.cameraMatrix); }; + + RendererGL.prototype.activeCamera = function(cam) { + if (cam !== undefined) { + this.setCamera(cam); + return this; + } else { + return this.states.curCamera; + } + }; } export default camera; diff --git a/test/unit/webgl/p5.Camera.js b/test/unit/webgl/p5.Camera.js index 92cfe69962..3cff5e1276 100644 --- a/test/unit/webgl/p5.Camera.js +++ b/test/unit/webgl/p5.Camera.js @@ -1134,6 +1134,52 @@ suite('p5.Camera', function() { }); }); + suite('activeCamera() getter/setter', function() { + test('activeCamera() returns current camera when called with no arguments', function() { + var currentCam = myp5.activeCamera(); + assert.deepCloseTo(currentCam, myp5._renderer.states.curCamera); + assert.deepCloseTo(currentCam, myCam); + }); + + test('activeCamera() sets camera when called with camera argument', function() { + var myCam2 = myp5.createCamera(); + myp5.activeCamera(myCam2); + assert.deepCloseTo(myCam2, myp5._renderer.states.curCamera); + var retrievedCam = myp5.activeCamera(); + assert.deepCloseTo(retrievedCam, myCam2); + }); + + test('activeCamera() works correctly with multiple cameras', function() { + var myCam2 = myp5.createCamera(); + var myCam3 = myp5.createCamera(); + + // Set cam2 + myp5.activeCamera(myCam2); + assert.deepCloseTo(myCam2, myp5.activeCamera()); + + // Set cam3 + myp5.activeCamera(myCam3); + assert.deepCloseTo(myCam3, myp5.activeCamera()); + + // Set back to original + myp5.activeCamera(myCam); + assert.deepCloseTo(myCam, myp5.activeCamera()); + }); + + test('activeCamera() returns p5 instance for chaining when setting', function() { + var myCam2 = myp5.createCamera(); + var result = myp5.activeCamera(myCam2); + assert.strictEqual(result, myp5); + }); + + test('activeCamera() returns the same camera object that was set', function() { + var myCam2 = myp5.createCamera(); + myp5.activeCamera(myCam2); + var retrievedCam = myp5.activeCamera(); + assert.strictEqual(retrievedCam, myCam2); + }); + }); + suite('Camera attributes after resizing', function() { test('Camera position is the same', function() { myp5.createCanvas(1, 1, myp5.WEBGL);