diff --git a/.all-contributorsrc b/.all-contributorsrc
index dd7daa3444..855284566c 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -5946,6 +5946,25 @@
"doc",
"bug"
]
+ },
+ {
+ "login": "JulioGitLab",
+ "name": "Julio Lab",
+ "avatar_url": "https://avatars.githubusercontent.com/u/156870555?v=4",
+ "profile": "https://github.com/JulioGitLab",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "JordanSucher",
+ "name": "Jordan Sucher",
+ "avatar_url": "https://avatars.githubusercontent.com/u/9809109?v=4",
+ "profile": "https://github.com/JordanSucher",
+ "contributions": [
+ "bug",
+ "code"
+ ]
}
],
"repoType": "github",
diff --git a/README.md b/README.md
index a510181e40..b5cd9d68e1 100644
--- a/README.md
+++ b/README.md
@@ -1000,6 +1000,8 @@ We recognize all types of contributions. This project follows the [all-contribut
Nahuel Palumbo 🐛 📖 |
lottihill 📖 🐛 |
+ Julio Lab 📖 |
+ Jordan Sucher 🐛 💻 |
diff --git a/package-lock.json b/package-lock.json
index def3ab1e93..6efc99bc8e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "p5",
- "version": "1.9.3",
+ "version": "1.9.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "p5",
- "version": "1.9.3",
+ "version": "1.9.4",
"license": "LGPL-2.1",
"devDependencies": {
"@babel/core": "^7.7.7",
diff --git a/package.json b/package.json
index 49bc63822e..c0aea11b65 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,7 @@
"node --require @babel/register ./utils/sample-linter.js"
]
},
- "version": "1.9.3",
+ "version": "1.9.4",
"devDependencies": {
"@babel/core": "^7.7.7",
"@babel/preset-env": "^7.10.2",
diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js
index ac733b0f39..dfcd07a744 100644
--- a/src/color/p5.Color.js
+++ b/src/color/p5.Color.js
@@ -571,7 +571,7 @@ p5.Color = class Color {
/**
* Sets the red component of a color.
*
- * The range depends on the colorMode(). In the
+ * The range depends on the colorMode(). In the
* default RGB mode it's between 0 and 255.
*
* @method setRed
@@ -613,7 +613,7 @@ p5.Color = class Color {
/**
* Sets the green component of a color.
*
- * The range depends on the colorMode(). In the
+ * The range depends on the colorMode(). In the
* default RGB mode it's between 0 and 255.
*
* @method setGreen
@@ -655,7 +655,7 @@ p5.Color = class Color {
/**
* Sets the blue component of a color.
*
- * The range depends on the colorMode(). In the
+ * The range depends on the colorMode(). In the
* default RGB mode it's between 0 and 255.
*
* @method setBlue
@@ -698,7 +698,7 @@ p5.Color = class Color {
* Sets the alpha (transparency) value of a color.
*
* The range depends on the
- * colorMode(). In the default RGB mode it's
+ * colorMode(). In the default RGB mode it's
* between 0 and 255.
*
* @method setAlpha
diff --git a/src/core/p5.Graphics.js b/src/core/p5.Graphics.js
index 90974e8bf1..07ddb54301 100644
--- a/src/core/p5.Graphics.js
+++ b/src/core/p5.Graphics.js
@@ -320,8 +320,22 @@ p5.Graphics = class extends p5.Element {
/**
* Removes the graphics buffer from the web page.
*
- * Calling `myGraphics.remove()` removes the graphics buffer's canvas along
- * with any HTML elements it created.
+ * Calling `myGraphics.remove()` removes the graphics buffer's
+ * `<canvas>` element from the web page. The graphics buffer also uses
+ * a bit of memory on the CPU that can be freed like so:
+ *
+ * ```js
+ * // Remove the graphics buffer from the web page.
+ * myGraphics.remove();
+ *
+ * // Delete the graphics buffer from CPU memory.
+ * myGraphics = undefined;
+ * ```
+ *
+ * Note: All variables that reference the graphics buffer must be assigned
+ * the value `undefined` to delete the graphics buffer from CPU memory. If any
+ * variable still refers to the graphics buffer, then it won't be garbage
+ * collected.
*
* @method remove
*
@@ -358,6 +372,7 @@ p5.Graphics = class extends p5.Element {
* // Remove the p5.Graphics object when the
* // the user double-clicks.
* function doubleClicked() {
+ * // Remove the p5.Graphics object from the web page.
* pg.remove();
* pg = undefined;
* }
diff --git a/src/core/shape/attributes.js b/src/core/shape/attributes.js
index cad13c6a87..61e9b9e8a0 100644
--- a/src/core/shape/attributes.js
+++ b/src/core/shape/attributes.js
@@ -186,7 +186,7 @@ p5.prototype.noSmooth = function() {
*
* By default, the first two parameters of
* rect() and square(),
- * are the x- and y-coordinates of the shape's center. The next parameters set
+ * are the x- and y-coordinates of the shape's upper left corner. The next parameters set
* the shape's width and height. This is the same as calling
* `rectMode(CORNER)`.
*
diff --git a/src/core/transform.js b/src/core/transform.js
index e01ec1fc50..911d074d1a 100644
--- a/src/core/transform.js
+++ b/src/core/transform.js
@@ -266,7 +266,7 @@ p5.prototype.applyMatrix = function(...args) {
* // Translate the origin to the center.
* translate(50, 50);
*
- * // Draw a red circle at the coordinates (25, 25).
+ * // Draw a blue circle at the coordinates (25, 25).
* fill('blue');
* circle(25, 25, 20);
*
@@ -274,7 +274,7 @@ p5.prototype.applyMatrix = function(...args) {
* // The origin is now at the top-left corner.
* resetMatrix();
*
- * // Draw a blue circle at the coordinates (25, 25).
+ * // Draw a red circle at the coordinates (25, 25).
* fill('red');
* circle(25, 25, 20);
* }
@@ -317,9 +317,8 @@ p5.prototype.resetMatrix = function() {
* shapes to spin.
*
* @method rotate
- * @param {Number} angle the angle of rotation, specified in radians
- * or degrees, depending on current angleMode
- * @param {p5.Vector|Number[]} [axis] (in 3d) the axis to rotate around
+ * @param {Number} angle angle of rotation in the current angleMode().
+ * @param {p5.Vector|Number[]} [axis] axis to rotate about in 3D.
* @chainable
*
* @example
diff --git a/src/webgl/p5.Framebuffer.js b/src/webgl/p5.Framebuffer.js
index e4f85fcbda..4c816d9464 100644
--- a/src/webgl/p5.Framebuffer.js
+++ b/src/webgl/p5.Framebuffer.js
@@ -74,15 +74,33 @@ p5.FramebufferTexture = FramebufferTexture;
class Framebuffer {
/**
- * An object that one can draw to and then read as a texture. While similar
- * to a p5.Graphics, using a p5.Framebuffer as a texture will generally run
- * much faster, as it lives within the same WebGL context as the canvas it
- * is created on. It only works in WebGL mode.
+ * A class to describe a high-performance drawing surface for textures.
+ *
+ * Each `p5.Framebuffer` object provides a dedicated drawing surface called
+ * a *framebuffer*. They're similar to
+ * p5.Graphics objects but can run much faster.
+ * Performance is improved because the framebuffer shares the same WebGL
+ * context as the canvas used to create it.
+ *
+ * `p5.Framebuffer` objects have all the drawing features of the main
+ * canvas. Drawing instructions meant for the framebuffer must be placed
+ * between calls to
+ * myBuffer.begin() and
+ * myBuffer.end(). The resulting image
+ * can be applied as a texture by passing the `p5.Framebuffer` object to the
+ * texture() function, as in `texture(myBuffer)`.
+ * It can also be displayed on the main canvas by passing it to the
+ * image() function, as in `image(myBuffer, 0, 0)`.
+ *
+ * Note: createFramebuffer() is the
+ * recommended way to create an instance of this class.
*
* @class p5.Framebuffer
* @constructor
- * @param {p5.Graphics|p5} target A p5 global instance or p5.Graphics
- * @param {Object} [settings] A settings object
+ * @param {p5.Graphics|p5} target sketch instance or
+ * p5.Graphics
+ * object.
+ * @param {Object} [settings] configuration options.
*/
constructor(target, settings = {}) {
this.target = target;
@@ -91,20 +109,56 @@ class Framebuffer {
this._isClipApplied = false;
/**
- * A Uint8ClampedArray
- * containing the values for all the pixels in the Framebuffer.
+ * An array containing the color of each pixel in the framebuffer.
*
- * Like the main canvas pixels property, call
- * loadPixels() before reading
- * it, and call updatePixels()
- * afterwards to update its data.
+ * myBuffer.loadPixels() must be
+ * called before accessing the `myBuffer.pixels` array.
+ * myBuffer.updatePixels()
+ * must be called after any changes are made.
*
- * Note that updating pixels via this property will be slower than
- * drawing to the framebuffer directly.
- * Consider using a shader instead of looping over pixels.
+ * Note: Updating pixels via this property is slower than drawing to the
+ * framebuffer directly. Consider using a
+ * p5.Shader object instead of looping over
+ * `myBuffer.pixels`.
*
* @property {Number[]} pixels
+ *
+ * @example
+ *
+ *
+ * function setup() {
+ * createCanvas(100, 100, WEBGL);
+ *
+ * background(200);
+ *
+ * // Create a p5.Framebuffer object.
+ * let myBuffer = createFramebuffer();
+ *
+ * // Load the pixels array.
+ * myBuffer.loadPixels();
+ *
+ * // Get the number of pixels in the
+ * // top half of the framebuffer.
+ * let numPixels = myBuffer.pixels.length / 2;
+ *
+ * // Set the framebuffer's top half to pink.
+ * for (let i = 0; i < numPixels; i += 4) {
+ * myBuffer.pixels[i] = 255;
+ * myBuffer.pixels[i + 1] = 102;
+ * myBuffer.pixels[i + 2] = 204;
+ * myBuffer.pixels[i + 3] = 255;
+ * }
+ *
+ * // Update the pixels array.
+ * myBuffer.updatePixels();
+ *
+ * // Draw the p5.Framebuffer object to the canvas.
+ * image(myBuffer, -50, -50);
+ *
+ * describe('A pink rectangle above a gray rectangle.');
+ * }
+ *
+ *
*/
this.pixels = [];
@@ -182,48 +236,52 @@ class Framebuffer {
}
/**
- * Resizes the framebuffer to the given width and height.
+ * Resizes the framebuffer to a given width and height.
+ *
+ * The parameters, `width` and `height`, set the dimensions of the
+ * framebuffer. For example, calling `myBuffer.resize(300, 500)` resizes
+ * the framebuffer to 300×500 pixels, then sets `myBuffer.width` to 300
+ * and `myBuffer.height` 500.
*
* @method resize
- * @param {Number} width
- * @param {Number} height
+ * @param {Number} width width of the framebuffer.
+ * @param {Number} height height of the framebuffer.
*
* @example
*
*
- * let framebuffer;
+ * let myBuffer;
+ *
* function setup() {
* createCanvas(100, 100, WEBGL);
- * framebuffer = createFramebuffer();
- * noStroke();
- * }
*
- * function mouseMoved() {
- * framebuffer.resize(
- * max(20, mouseX),
- * max(20, mouseY)
- * );
+ * // Create a p5.Framebuffer object.
+ * myBuffer = createFramebuffer();
+ *
+ * describe('A multicolor sphere on a white surface. The image grows larger or smaller when the user moves the mouse, revealing a gray background.');
* }
*
* function draw() {
- * // Draw to the framebuffer
- * framebuffer.begin();
+ * background(200);
+ *
+ * // Draw to the p5.Framebuffer object.
+ * myBuffer.begin();
* background(255);
* normalMaterial();
* sphere(20);
- * framebuffer.end();
+ * myBuffer.end();
+ *
+ * // Display the p5.Framebuffer object.
+ * image(myBuffer, -50, -50);
+ * }
*
- * background(100);
- * // Draw the framebuffer to the main canvas
- * image(framebuffer, -width/2, -height/2);
+ * // Resize the p5.Framebuffer object when the
+ * // user moves the mouse.
+ * function mouseMoved() {
+ * myBuffer.resize(mouseX, mouseY);
* }
*
*
- *
- * @alt
- * A red, green, and blue sphere is drawn in the middle of a white rectangle
- * which starts in the top left of the canvas and whose bottom right is at
- * the user's mouse
*/
resize(width, height) {
this._autoSized = false;
@@ -237,16 +295,99 @@ class Framebuffer {
}
/**
- * Gets or sets the pixel scaling for high pixel density displays. By
- * default, the density will match that of the canvas the framebuffer was
- * created on, which will match the display density.
+ * Sets the framebuffer's pixel density or returns its current density.
+ *
+ * Computer displays are grids of little lights called pixels. A display's
+ * pixel density describes how many pixels it packs into an area. Displays
+ * with smaller pixels have a higher pixel density and create sharper
+ * images.
+ *
+ * The parameter, `density`, is optional. If a number is passed, as in
+ * `myBuffer.pixelDensity(1)`, it sets the framebuffer's pixel density. By
+ * default, the framebuffer's pixel density will match that of the canvas
+ * where it was created. All canvases default to match the display's pixel
+ * density.
*
- * Call this method with no arguments to get the current density, or pass
- * in a number to set the density.
+ * Calling `myBuffer.pixelDensity()` without an argument returns its current
+ * pixel density.
*
* @method pixelDensity
- * @param {Number} [density] A scaling factor for the number of pixels per
- * side of the framebuffer
+ * @param {Number} [density] pixel density to set.
+ * @returns {Number} current pixel density.
+ *
+ * @example
+ *
+ *
+ * let myBuffer;
+ *
+ * function setup() {
+ * createCanvas(100, 100, WEBGL);
+ *
+ * // Create a p5.Framebuffer object.
+ * myBuffer = createFramebuffer();
+ *
+ * describe("A white circle on a gray canvas. The circle's edge become fuzzy while the user presses and holds the mouse.");
+ * }
+ *
+ * function draw() {
+ * // Draw to the p5.Framebuffer object.
+ * myBuffer.begin();
+ * background(200);
+ * circle(0, 0, 40);
+ * myBuffer.end();
+ *
+ * // Display the p5.Framebuffer object.
+ * image(myBuffer, -50, -50);
+ * }
+ *
+ * // Decrease the pixel density when the user
+ * // presses the mouse.
+ * function mousePressed() {
+ * myBuffer.pixelDensity(1);
+ * }
+ *
+ * // Increase the pixel density when the user
+ * // releases the mouse.
+ * function mouseReleased() {
+ * myBuffer.pixelDensity(2);
+ * }
+ *
+ *
+ *
+ *
+ *
+ * let myBuffer;
+ * let myFont;
+ *
+ * // Load a font and create a p5.Font object.
+ * function preload() {
+ * myFont = loadFont('assets/inconsolata.otf');
+ * }
+ *
+ * function setup() {
+ * createCanvas(100, 100, WEBGL);
+ *
+ * background(200);
+ *
+ * // Create a p5.Framebuffer object.
+ * myBuffer = createFramebuffer();
+ *
+ * // Get the p5.Framebuffer object's pixel density.
+ * let d = myBuffer.pixelDensity();
+ *
+ * // Style the text.
+ * textAlign(CENTER, CENTER);
+ * textFont(myFont);
+ * textSize(16);
+ * fill(0);
+ *
+ * // Display the pixel density.
+ * text(`Density: ${d}`, 0, 0);
+ *
+ * describe(`The text "Density: ${d}" written in black on a gray background.`);
+ * }
+ *
+ *
*/
pixelDensity(density) {
if (density) {
@@ -259,15 +400,64 @@ class Framebuffer {
}
/**
- * Gets or sets whether or not this framebuffer will automatically resize
- * along with the canvas it's attached to in order to match its size.
+ * Toggles the framebuffer's autosizing mode or returns the current mode.
+ *
+ * By default, the framebuffer automatically resizes to match the canvas
+ * that created it. Calling `myBuffer.autoSized(false)` disables this
+ * behavior and calling `myBuffer.autoSized(true)` re-enables it.
*
- * Call this method with no arguments to see if it is currently auto-sized,
- * or pass in a boolean to set this property.
+ * Calling `myBuffer.autoSized()` without an argument returns `true` if
+ * the framebuffer automatically resizes and `false` if not.
*
* @method autoSized
- * @param {Boolean} [autoSized] Whether or not the framebuffer should resize
- * along with the canvas it's attached to
+ * @param {Boolean} [autoSized] whether to automatically resize the framebuffer to match the canvas.
+ * @returns {Boolean} current autosize setting.
+ *
+ * @example
+ *
+ *
+ * // Double-click to toggle the autosizing mode.
+ *
+ * let myBuffer;
+ *
+ * function setup() {
+ * createCanvas(100, 100, WEBGL);
+ *
+ * // Create a p5.Framebuffer object.
+ * myBuffer = createFramebuffer();
+ *
+ * describe('A multicolor sphere on a gray background. The image resizes when the user moves the mouse.');
+ * }
+ *
+ * function draw() {
+ * background(50);
+ *
+ * // Draw to the p5.Framebuffer object.
+ * myBuffer.begin();
+ * background(200);
+ * normalMaterial();
+ * sphere(width / 4);
+ * myBuffer.end();
+ *
+ * // Display the p5.Framebuffer object.
+ * image(myBuffer, -width / 2, -height / 2);
+ * }
+ *
+ * // Resize the canvas when the user moves the mouse.
+ * function mouseMoved() {
+ * let w = constrain(mouseX, 0, 100);
+ * let h = constrain(mouseY, 0, 100);
+ * resizeCanvas(w, h);
+ * }
+ *
+ * // Toggle autoSizing when the user double-clicks.
+ * // Note: opened an issue to fix(?) this.
+ * function doubleClicked() {
+ * let isAuto = myBuffer.autoSized();
+ * myBuffer.autoSized(!isAuto);
+ * }
+ *
+ *
*/
autoSized(autoSized) {
if (autoSized === undefined) {
@@ -700,13 +890,121 @@ class Framebuffer {
}
/**
- * Creates and returns a new
- * p5.FramebufferCamera to be used
- * while drawing to this framebuffer. The camera will be set as the
- * currently active camera.
+ * Creates a new
+ * p5.Camera object to use with the framebuffer.
+ *
+ * The new camera is initialized with a default position `(0, 0, 800)` and a
+ * default perspective projection. Its properties can be controlled with
+ * p5.Camera methods such as `myCamera.lookAt(0, 0, 0)`.
+ *
+ * Framebuffer cameras should be created between calls to
+ * myBuffer.begin() and
+ * myBuffer.end() like so:
+ *
+ * ```js
+ * let myCamera;
+ *
+ * myBuffer.begin();
+ *
+ * // Create the camera for the framebuffer.
+ * myCamera = myBuffer.createCamera();
+ *
+ * myBuffer.end();
+ * ```
+ *
+ * Calling setCamera() updates the
+ * framebuffer's projection using the camera.
+ * resetMatrix() must also be called for the
+ * view to change properly:
+ *
+ * ```js
+ * myBuffer.begin();
+ *
+ * // Set the camera for the framebuffer.
+ * setCamera(myCamera);
+ *
+ * // Reset all transformations.
+ * resetMatrix();
+ *
+ * // Draw stuff...
+ *
+ * myBuffer.end();
+ * ```
*
* @method createCamera
- * @returns {p5.Camera} A new camera
+ * @returns {p5.Camera} new camera.
+ *
+ * @example
+ *
+ *
+ * // Double-click to toggle between cameras.
+ *
+ * let myBuffer;
+ * let cam1;
+ * let cam2;
+ * let usingCam1 = true;
+ *
+ * function setup() {
+ * createCanvas(100, 100, WEBGL);
+ *
+ * // Create a p5.Framebuffer object.
+ * myBuffer = createFramebuffer();
+ *
+ * // Create the cameras between begin() and end().
+ * myBuffer.begin();
+ *
+ * // Create the first camera.
+ * // Keep its default settings.
+ * cam1 = myBuffer.createCamera();
+ *
+ * // Create the second camera.
+ * // Place it at the top-left.
+ * // Point it at the origin.
+ * cam2 = myBuffer.createCamera();
+ * cam2.setPosition(400, -400, 800);
+ * cam2.lookAt(0, 0, 0);
+ *
+ * myBuffer.end();
+ *
+ * describe(
+ * 'A white cube on a gray background. The camera toggles between frontal and aerial views when the user double-clicks.'
+ * );
+ * }
+ *
+ * function draw() {
+ * // Draw to the p5.Framebuffer object.
+ * myBuffer.begin();
+ * background(200);
+ *
+ * // Set the camera.
+ * if (usingCam1 === true) {
+ * setCamera(cam1);
+ * } else {
+ * setCamera(cam2);
+ * }
+ *
+ * // Reset all transformations.
+ * resetMatrix();
+ *
+ * // Draw the box.
+ * box();
+ *
+ * myBuffer.end();
+ *
+ * // Display the p5.Framebuffer object.
+ * image(myBuffer, -50, -50);
+ * }
+ *
+ * // Toggle the current camera when the user double-clicks.
+ * function doubleClicked() {
+ * if (usingCam1 === true) {
+ * usingCam1 = false;
+ * } else {
+ * usingCam1 = true;
+ * }
+ * }
+ *
+ *
*/
createCamera() {
const cam = new FramebufferCamera(this);
@@ -731,48 +1029,74 @@ class Framebuffer {
}
/**
- * Removes the framebuffer and frees its resources.
+ * Deletes the framebuffer from GPU memory.
+ *
+ * Calling `myBuffer.remove()` frees the GPU memory used by the framebuffer.
+ * The framebuffer also uses a bit of memory on the CPU which can be freed
+ * like so:
+ *
+ * ```js
+ * // Delete the framebuffer from GPU memory.
+ * myBuffer.remove();
+ *
+ * // Delete the framebuffer from CPU memory.
+ * myBuffer = undefined;
+ * ```
+ *
+ * Note: All variables that reference the framebuffer must be assigned
+ * the value `undefined` to delete the framebuffer from CPU memory. If any
+ * variable still refers to the framebuffer, then it won't be garbage
+ * collected.
*
* @method remove
*
* @example
*
*
- * let framebuffer;
+ * // Double-click to remove the p5.Framebuffer object.
+ *
+ * let myBuffer;
+ *
* function setup() {
* createCanvas(100, 100, WEBGL);
+ *
+ * // Create an options object.
+ * let options = { width: 60, height: 60 };
+ *
+ * // Create a p5.Framebuffer object and
+ * // configure it using options.
+ * myBuffer = createFramebuffer(options);
+ *
+ * describe('A white circle at the center of a dark gray square disappears when the user double-clicks.');
* }
*
* function draw() {
- * const useFramebuffer = (frameCount % 120) > 60;
- * if (useFramebuffer && !framebuffer) {
- * // Create a new framebuffer for us to use
- * framebuffer = createFramebuffer();
- * } else if (!useFramebuffer && framebuffer) {
- * // Free the old framebuffer's resources
- * framebuffer.remove();
- * framebuffer = undefined;
- * }
+ * background(200);
*
- * background(255);
- * if (useFramebuffer) {
- * // Draw to the framebuffer
- * framebuffer.begin();
- * background(255);
- * rotateX(frameCount * 0.01);
- * rotateY(frameCount * 0.01);
- * fill(255, 0, 0);
- * box(30);
- * framebuffer.end();
- *
- * image(framebuffer, -width/2, -height/2);
+ * // Display the p5.Framebuffer object if
+ * // it's available.
+ * if (myBuffer) {
+ * // Draw to the p5.Framebuffer object.
+ * myBuffer.begin();
+ * background(100);
+ * circle(0, 0, 20);
+ * myBuffer.end();
+ *
+ * image(myBuffer, -30, -30);
* }
* }
+ *
+ * // Remove the p5.Framebuffer object when the
+ * // the user double-clicks.
+ * function doubleClicked() {
+ * // Delete the framebuffer from GPU memory.
+ * myBuffer.remove();
+ *
+ * // Delete the framebuffer from CPU memory.
+ * myBuffer = undefined;
+ * }
*
*
- *
- * @alt
- * A rotating red cube blinks on and off every second.
*/
remove() {
const gl = this.gl;
@@ -792,47 +1116,53 @@ class Framebuffer {
}
/**
- * Begin drawing to this framebuffer. Subsequent drawing functions to the
- * canvas the framebuffer is attached to will not be immediately visible, and
- * will instead be drawn to the framebuffer's texture. Call
- * end() when finished to make draw
- * functions go right to the canvas again and to be able to read the
- * contents of the framebuffer's texture.
+ * Begins drawing shapes to the framebuffer.
+ *
+ * `myBuffer.begin()` and myBuffer.end()
+ * allow shapes to be drawn to the framebuffer. `myBuffer.begin()` begins
+ * drawing to the framebuffer and
+ * myBuffer.end() stops drawing to the
+ * framebuffer. Changes won't be visible until the framebuffer is displayed
+ * as an image or texture.
*
* @method begin
*
* @example
*
*
- * let framebuffer;
+ * let myBuffer;
+ *
* function setup() {
* createCanvas(100, 100, WEBGL);
- * framebuffer = createFramebuffer();
- * noStroke();
+ *
+ * // Create a p5.Framebuffer object.
+ * myBuffer = createFramebuffer();
+ *
+ * describe('An empty gray canvas. The canvas gets darker and a rotating, multicolor torus appears while the user presses and holds the mouse.');
* }
*
* function draw() {
- * // Draw to the framebuffer
- * framebuffer.begin();
- * background(255);
- * translate(0, 10*sin(frameCount * 0.01), 0);
- * rotateX(frameCount * 0.01);
+ * background(200);
+ *
+ * // Start drawing to the p5.Framebuffer object.
+ * myBuffer.begin();
+ *
+ * background(50);
* rotateY(frameCount * 0.01);
- * fill(255, 0, 0);
- * box(50);
- * framebuffer.end();
- *
- * background(100);
- * // Draw the framebuffer to the main canvas
- * image(framebuffer, -50, -50, 25, 25);
- * image(framebuffer, 0, 0, 35, 35);
+ * normalMaterial();
+ * torus(30);
+ *
+ * // Stop drawing to the p5.Framebuffer object.
+ * myBuffer.end();
+ *
+ * // Display the p5.Framebuffer object while
+ * // the user presses the mouse.
+ * if (mouseIsPressed === true) {
+ * image(myBuffer, -50, -50);
+ * }
* }
*
*
- *
- * @alt
- * A video of a floating and rotating red cube is pasted twice on the
- * canvas: once in the top left, and again, larger, in the bottom right.
*/
begin() {
this.prevFramebuffer = this.target._renderer.activeFramebuffer();
@@ -921,13 +1251,53 @@ class Framebuffer {
}
/**
- * After having previously called
- * begin(), this method stops drawing
- * functions from going to the framebuffer's texture, allowing them to go
- * right to the canvas again. After this, one can read from the framebuffer's
- * texture.
+ * Stops drawing shapes to the framebuffer.
+ *
+ * myBuffer.begin() and `myBuffer.end()`
+ * allow shapes to be drawn to the framebuffer.
+ * myBuffer.begin() begins drawing to
+ * the framebuffer and `myBuffer.end()` stops drawing to the framebuffer.
+ * Changes won't be visible until the framebuffer is displayed as an image
+ * or texture.
*
* @method end
+ *
+ * @example
+ *
+ *
+ * let myBuffer;
+ *
+ * function setup() {
+ * createCanvas(100, 100, WEBGL);
+ *
+ * // Create a p5.Framebuffer object.
+ * myBuffer = createFramebuffer();
+ *
+ * describe('An empty gray canvas. The canvas gets darker and a rotating, multicolor torus appears while the user presses and holds the mouse.');
+ * }
+ *
+ * function draw() {
+ * background(200);
+ *
+ * // Start drawing to the p5.Framebuffer object.
+ * myBuffer.begin();
+ *
+ * background(50);
+ * rotateY(frameCount * 0.01);
+ * normalMaterial();
+ * torus(30);
+ *
+ * // Stop drawing to the p5.Framebuffer object.
+ * myBuffer.end();
+ *
+ * // Display the p5.Framebuffer object while
+ * // the user presses the mouse.
+ * if (mouseIsPressed === true) {
+ * image(myBuffer, -50, -50);
+ * }
+ * }
+ *
+ *
*/
end() {
const gl = this.gl;
@@ -950,48 +1320,61 @@ class Framebuffer {
}
/**
- * Run a function while drawing to the framebuffer rather than to its canvas.
- * This is equivalent to calling `framebuffer.begin()`, running the function,
- * and then calling `framebuffer.end()`, but ensures that one never
- * accidentally forgets `begin` or `end`.
+ * Draws to the framebuffer by calling a function that contains drawing
+ * instructions.
+ *
+ * The parameter, `callback`, is a function with the drawing instructions
+ * for the framebuffer. For example, calling `myBuffer.draw(myFunction)`
+ * will call a function named `myFunction()` to draw to the framebuffer.
+ * Doing so has the same effect as the following:
+ *
+ * ```js
+ * myBuffer.begin();
+ * myFunction();
+ * myBuffer.end();
+ * ```
*
* @method draw
- * @param {Function} callback A function to run that draws to the canvas. The
- * function will immediately be run, but it will draw to the framebuffer
- * instead of the canvas.
+ * @param {Function} callback function that draws to the framebuffer.
*
* @example
*
*
- * let framebuffer;
+ * // Click the canvas to display the framebuffer.
+ *
+ * let myBuffer;
+ *
* function setup() {
* createCanvas(100, 100, WEBGL);
- * framebuffer = createFramebuffer();
- * noStroke();
+ *
+ * // Create a p5.Framebuffer object.
+ * myBuffer = createFramebuffer();
+ *
+ * describe('An empty gray canvas. The canvas gets darker and a rotating, multicolor torus appears while the user presses and holds the mouse.');
* }
*
* function draw() {
- * // Draw to the framebuffer
- * framebuffer.draw(function() {
- * background(255);
- * translate(0, 10*sin(frameCount * 0.01), 0);
- * rotateX(frameCount * 0.01);
- * rotateY(frameCount * 0.01);
- * fill(255, 0, 0);
- * box(50);
- * });
- *
- * background(100);
- * // Draw the framebuffer to the main canvas
- * image(framebuffer, -50, -50, 25, 25);
- * image(framebuffer, 0, 0, 35, 35);
+ * background(200);
+ *
+ * // Draw to the p5.Framebuffer object.
+ * myBuffer.draw(bagel);
+ *
+ * // Display the p5.Framebuffer object while
+ * // the user presses the mouse.
+ * if (mouseIsPressed === true) {
+ * image(myBuffer, -50, -50);
+ * }
+ * }
+ *
+ * // Draw a rotating, multicolor torus.
+ * function bagel() {
+ * background(50);
+ * rotateY(frameCount * 0.01);
+ * normalMaterial();
+ * torus(30);
* }
*
*
- *
- * @alt
- * A video of a floating and rotating red cube is pasted twice on the
- * canvas: once in the top left, and again, larger, in the bottom right.
*/
draw(callback) {
this.begin();
@@ -1000,10 +1383,50 @@ class Framebuffer {
}
/**
- * Call this befpre updating pixels
- * and calling updatePixels
- * to replace the content of the framebuffer with the data in the pixels
- * array.
+ * Loads the current value of each pixel in the framebuffer into its
+ * pixels array.
+ *
+ * `myBuffer.loadPixels()` must be called before reading from or writing to
+ * myBuffer.pixels.
+ *
+ * @method loadPixels
+ *
+ * @example
+ *
+ *
+ * function setup() {
+ * createCanvas(100, 100, WEBGL);
+ *
+ * background(200);
+ *
+ * // Create a p5.Framebuffer object.
+ * let myBuffer = createFramebuffer();
+ *
+ * // Load the pixels array.
+ * myBuffer.loadPixels();
+ *
+ * // Get the number of pixels in the
+ * // top half of the framebuffer.
+ * let numPixels = myBuffer.pixels.length / 2;
+ *
+ * // Set the framebuffer's top half to pink.
+ * for (let i = 0; i < numPixels; i += 4) {
+ * myBuffer.pixels[i] = 255;
+ * myBuffer.pixels[i + 1] = 102;
+ * myBuffer.pixels[i + 2] = 204;
+ * myBuffer.pixels[i + 3] = 255;
+ * }
+ *
+ * // Update the pixels array.
+ * myBuffer.updatePixels();
+ *
+ * // Draw the p5.Framebuffer object to the canvas.
+ * image(myBuffer, -50, -50);
+ *
+ * describe('A pink rectangle above a gray rectangle.');
+ * }
+ *
+ *
*/
loadPixels() {
const gl = this.gl;
@@ -1029,35 +1452,42 @@ class Framebuffer {
}
/**
- * Get a region of pixels from the canvas in the form of a
- * p5.Image, or a single pixel as an array of
- * numbers.
- *
- * Returns an array of [R,G,B,A] values for any pixel or grabs a section of
- * an image. If the Framebuffer has been set up to not store alpha values, then
- * only [R,G,B] will be returned. If no parameters are specified, the entire
- * image is returned.
- * Use the x and y parameters to get the value of one pixel. Get a section of
- * the display window by specifying additional w and h parameters. When
- * getting an image, the x and y parameters define the coordinates for the
- * upper-left corner of the image, regardless of the current imageMode().
+ * Gets a pixel or a region of pixels from the framebuffer.
+ *
+ * `myBuffer.get()` is easy to use but it's not as fast as
+ * myBuffer.pixels. Use
+ * myBuffer.pixels to read many pixel
+ * values.
+ *
+ * The version of `myBuffer.get()` with no parameters returns the entire
+ * framebuffer as a a p5.Image object.
+ *
+ * The version of `myBuffer.get()` with two parameters interprets them as
+ * coordinates. It returns an array with the `[R, G, B, A]` values of the
+ * pixel at the given point.
+ *
+ * The version of `myBuffer.get()` with four parameters interprets them as
+ * coordinates and dimensions. It returns a subsection of the framebuffer as
+ * a p5.Image object. The first two parameters are
+ * the coordinates for the upper-left corner of the subsection. The last two
+ * parameters are the width and height of the subsection.
*
* @method get
- * @param {Number} x x-coordinate of the pixel
- * @param {Number} y y-coordinate of the pixel
- * @param {Number} w width of the section to be returned
- * @param {Number} h height of the section to be returned
- * @return {p5.Image} the rectangle p5.Image
+ * @param {Number} x x-coordinate of the pixel. Defaults to 0.
+ * @param {Number} y y-coordinate of the pixel. Defaults to 0.
+ * @param {Number} w width of the subsection to be returned.
+ * @param {Number} h height of the subsection to be returned.
+ * @return {p5.Image} subsection as a p5.Image object.
*/
/**
* @method get
- * @return {p5.Image} the whole p5.Image
+ * @return {p5.Image} entire framebuffer as a p5.Image object.
*/
/**
* @method get
- * @param {Number} x
- * @param {Number} y
- * @return {Number[]} color of pixel at x,y in array format [R, G, B, A]
+ * @param {Number} x
+ * @param {Number} y
+ * @return {Number[]} color of the pixel at `(x, y)` as an array of color values `[R, G, B, A]`.
*/
get(x, y, w, h) {
p5._validateParameters('p5.Framebuffer.get', arguments);
@@ -1148,66 +1578,52 @@ class Framebuffer {
}
/**
- * Call this after initially calling
- * loadPixels() and updating pixels
- * to replace the content of the framebuffer with the data in the pixels
- * array.
+ * Updates the framebuffer with the RGBA values in the
+ * pixels array.
+ *
+ * `myBuffer.updatePixels()` only needs to be called after changing values
+ * in the myBuffer.pixels array. Such
+ * changes can be made directly after calling
+ * myBuffer.loadPixels().
*
- * This will also clear the depth buffer so that any future drawing done
- * afterwards will go on top.
+ * @method updatePixels
*
* @example
*
*
- * let framebuffer;
* function setup() {
* createCanvas(100, 100, WEBGL);
- * framebuffer = createFramebuffer();
- * }
-
- * function draw() {
- * noStroke();
- * lights();
- *
- * // Draw a sphere to the framebuffer
- * framebuffer.begin();
- * background(0);
- * sphere(25);
- * framebuffer.end();
- *
- * // Load its pixels and draw a gradient over the lower half of the canvas
- * framebuffer.loadPixels();
- * for (let y = height/2; y < height; y++) {
- * for (let x = 0; x < width; x++) {
- * const idx = (y * width + x) * 4;
- * framebuffer.pixels[idx] = (x / width) * 255;
- * framebuffer.pixels[idx + 1] = (y / height) * 255;
- * framebuffer.pixels[idx + 2] = 255;
- * framebuffer.pixels[idx + 3] = 255;
- * }
+ *
+ * background(200);
+ *
+ * // Create a p5.Framebuffer object.
+ * let myBuffer = createFramebuffer();
+ *
+ * // Load the pixels array.
+ * myBuffer.loadPixels();
+ *
+ * // Get the number of pixels in the
+ * // top half of the framebuffer.
+ * let numPixels = myBuffer.pixels.length / 2;
+ *
+ * // Set the framebuffer's top half to pink.
+ * for (let i = 0; i < numPixels; i += 4) {
+ * myBuffer.pixels[i] = 255;
+ * myBuffer.pixels[i + 1] = 102;
+ * myBuffer.pixels[i + 2] = 204;
+ * myBuffer.pixels[i + 3] = 255;
* }
- * framebuffer.updatePixels();
- *
- * // Draw a cube on top of the pixels we just wrote
- * framebuffer.begin();
- * push();
- * translate(20, 20);
- * rotateX(0.5);
- * rotateY(0.5);
- * box(20);
- * pop();
- * framebuffer.end();
- *
- * image(framebuffer, -width/2, -height/2);
- * noLoop();
+ *
+ * // Update the pixels array.
+ * myBuffer.updatePixels();
+ *
+ * // Draw the p5.Framebuffer object to the canvas.
+ * image(myBuffer, -50, -50);
+ *
+ * describe('A pink rectangle above a gray rectangle.');
* }
*
*
- *
- * @alt
- * A sphere partly occluded by a gradient from cyan to white to magenta on
- * the lower half of the canvas, with a 3D cube drawn on top of that in the
- * lower right corner.
*/
updatePixels() {
const gl = this.gl;
@@ -1282,15 +1698,20 @@ class Framebuffer {
}
/**
- * A texture with the color information of the framebuffer. Pass this (or the
- * framebuffer itself) to texture() to draw it to
- * the canvas, or pass it to a shader with
- * setUniform() to read its data.
+ * An object that stores the framebuffer's color data.
*
- * Since Framebuffers are controlled by WebGL, their y coordinates are stored
- * flipped compared to images and videos. When texturing with a framebuffer
- * texture, you may want to flip vertically, e.g. with
- * `plane(framebuffer.width, -framebuffer.height)`.
+ * Each framebuffer uses a
+ * WebGLTexture
+ * object internally to store its color data. The `myBuffer.color` property
+ * makes it possible to pass this data directly to other functions. For
+ * example, calling `texture(myBuffer.color)` or
+ * `myShader.setUniform('colorTexture', myBuffer.color)` may be helpful for
+ * advanced use cases.
+ *
+ * Note: By default, a framebuffer's y-coordinates are flipped compared to
+ * images and videos. It's easy to flip a framebuffer's y-coordinates as
+ * needed when applying it as a texture. For example, calling
+ * `plane(myBuffer.width, -myBuffer.height)` will flip the framebuffer.
*
* @property {p5.FramebufferTexture} color
* @for p5.Framebuffer
@@ -1298,59 +1719,71 @@ class Framebuffer {
* @example
*
*
- * let framebuffer;
* function setup() {
* createCanvas(100, 100, WEBGL);
- * framebuffer = createFramebuffer();
+ *
+ * background(200);
+ *
+ * // Create a p5.Framebuffer object.
+ * let myBuffer = createFramebuffer();
+ *
+ * // Start drawing to the p5.Framebuffer object.
+ * myBuffer.begin();
+ *
+ * triangle(-25, 25, 0, -25, 25, 25);
+ *
+ * // Stop drawing to the p5.Framebuffer object.
+ * myBuffer.end();
+ *
+ * // Use the p5.Framebuffer object's WebGLTexture.
+ * texture(myBuffer.color);
+ *
+ * // Style the plane.
* noStroke();
- * }
*
- * function draw() {
- * // Draw to the framebuffer
- * framebuffer.begin();
- * background(255);
- * normalMaterial();
- * sphere(20);
- * framebuffer.end();
+ * // Draw the plane.
+ * plane(myBuffer.width, myBuffer.height);
*
- * // Draw the framebuffer to the main canvas
- * image(framebuffer.color, -width/2, -height/2);
+ * describe('A white triangle on a gray background.');
* }
*
*
- *
- * @alt
- * A red, green, and blue sphere in the middle of the canvas
*/
/**
- * A texture with the depth information of the framebuffer. If the framebuffer
- * was created with `{ depth: false }` in its settings, then this property will
- * be undefined. Pass this to texture() to draw it to
- * the canvas, or pass it to a shader with
- * setUniform() to read its data.
+ * An object that stores the framebuffer's dpeth data.
*
- * Since Framebuffers are controlled by WebGL, their y coordinates are stored
- * flipped compared to images and videos. When texturing with a framebuffer
- * texture, you may want to flip vertically, e.g. with
- * `plane(framebuffer.width, -framebuffer.height)`.
+ * Each framebuffer uses a
+ * WebGLTexture
+ * object internally to store its depth data. The `myBuffer.depth` property
+ * makes it possible to pass this data directly to other functions. For
+ * example, calling `texture(myBuffer.depth)` or
+ * `myShader.setUniform('depthTexture', myBuffer.depth)` may be helpful for
+ * advanced use cases.
*
- * @property {p5.FramebufferTexture|undefined} depth
+ * Note: By default, a framebuffer's y-coordinates are flipped compared to
+ * images and videos. It's easy to flip a framebuffer's y-coordinates as
+ * needed when applying it as a texture. For example, calling
+ * `plane(myBuffer.width, -myBuffer.height)` will flip the framebuffer.
+ *
+ * @property {p5.FramebufferTexture} depth
* @for p5.Framebuffer
*
* @example
*
*
- * let framebuffer;
- * let depthShader;
+ * // Note: A "uniform" is a global variable within a shader program.
*
- * const vert = `
+ * // Create a string with the vertex shader program.
+ * // The vertex shader is called for each vertex.
+ * let vertSrc = `
* precision highp float;
* attribute vec3 aPosition;
* attribute vec2 aTexCoord;
* uniform mat4 uModelViewMatrix;
* uniform mat4 uProjectionMatrix;
* varying vec2 vTexCoord;
+ *
* void main() {
* vec4 viewModelPosition = uModelViewMatrix * vec4(aPosition, 1.0);
* gl_Position = uProjectionMatrix * viewModelPosition;
@@ -1358,48 +1791,63 @@ class Framebuffer {
* }
* `;
*
- * const frag = `
+ * // Create a string with the fragment shader program.
+ * // The fragment shader is called for each pixel.
+ * let fragSrc = `
* precision highp float;
* varying vec2 vTexCoord;
* uniform sampler2D depth;
+ *
* void main() {
+ * // Get the pixel's depth value.
* float depthVal = texture2D(depth, vTexCoord).r;
+ *
+ * // Set the pixel's color based on its depth.
* gl_FragColor = mix(
- * vec4(1., 1., 0., 1.), // yellow
- * vec4(0., 0., 1., 1.), // blue
- * pow(depthVal, 6.)
- * );
+ * vec4(0., 0., 0., 1.),
+ * vec4(1., 0., 1., 1.),
+ * depthVal);
* }
* `;
*
+ * let myBuffer;
+ * let myShader;
+ *
* function setup() {
* createCanvas(100, 100, WEBGL);
- * framebuffer = createFramebuffer();
- * depthShader = createShader(vert, frag);
- * noStroke();
+ *
+ * // Create a p5.Framebuffer object.
+ * myBuffer = createFramebuffer();
+ *
+ * // Create a p5.Shader object.
+ * myShader = createShader(vertSrc, fragSrc);
+ *
+ * // Compile and apply the shader.
+ * shader(myShader);
+ *
+ * describe('The shadow of a box rotates slowly against a magenta background.');
* }
*
* function draw() {
- * // Draw to the framebuffer
- * framebuffer.begin();
+ * // Draw to the p5.Framebuffer object.
+ * myBuffer.begin();
* background(255);
* rotateX(frameCount * 0.01);
- * box(20, 20, 100);
- * framebuffer.end();
+ * box(20, 20, 80);
+ * myBuffer.end();
*
- * push();
- * shader(depthShader);
- * depthShader.setUniform('depth', framebuffer.depth);
- * plane(framebuffer.width, framebuffer.height);
- * pop();
+ * // Set the shader's depth uniform using
+ * // the framebuffer's depth texture.
+ * myShader.setUniform('depth', myBuffer.depth);
+ *
+ * // Style the plane.
+ * noStroke();
+ *
+ * // Draw the plane.
+ * plane(myBuffer.width, myBuffer.height);
* }
*
*
- *
- * @alt
- * A video of a rectangular prism rotating, with parts closest to the camera
- * appearing yellow and colors getting progressively more blue the farther
- * from the camera they go
*/
p5.Framebuffer = Framebuffer;
diff --git a/src/webgl/shaders/line.vert b/src/webgl/shaders/line.vert
index bd608181a9..e9fc624bfc 100644
--- a/src/webgl/shaders/line.vert
+++ b/src/webgl/shaders/line.vert
@@ -88,19 +88,16 @@ void main() {
0.25
);
- // using a scale <1 moves the lines towards the camera
- // in order to prevent popping effects due to half of
- // the line disappearing behind the geometry faces.
- float scale = mix(1., 0.995, facingCamera);
-
// Moving vertices slightly toward the camera
// to avoid depth-fighting with the fill triangles.
- // Discussed here:
- // http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=252848
- posp.xyz = posp.xyz * scale;
- posqIn.xyz = posqIn.xyz * scale;
- posqOut.xyz = posqOut.xyz * scale;
-
+ // This prevents popping effects due to half of
+ // the line disappearing behind the geometry faces.
+
+ float zOffset = mix(-0.00045, -1., facingCamera);
+ posp.z -= zOffset;
+ posqIn.z -= zOffset;
+ posqOut.z -= zOffset;
+
vec4 p = uProjectionMatrix * posp;
vec4 qIn = uProjectionMatrix * posqIn;
vec4 qOut = uProjectionMatrix * posqOut;