diff --git a/src/color/color_conversion.js b/src/color/color_conversion.js index 53b089d139..850d24918b 100644 --- a/src/color/color_conversion.js +++ b/src/color/color_conversion.js @@ -14,130 +14,96 @@ */ import p5 from '../core/main'; -p5.ColorConversion = {}; +p5.ColorConversion = { + /** + * Convert an HSBA array to HSLA. + */ + _hsbaToHSLA(hsba) { + const hue = hsba[0]; + let sat = hsba[1]; + const val = hsba[2]; -/** - * Convert an HSBA array to HSLA. - */ -p5.ColorConversion._hsbaToHSLA = function(hsba) { - const hue = hsba[0]; - let sat = hsba[1]; - const val = hsba[2]; - - // Calculate lightness. - const li = (2 - sat) * val / 2; + // Calculate lightness. + const li = (2 - sat) * val / 2; - // Convert saturation. - if (li !== 0) { - if (li === 1) { - sat = 0; - } else if (li < 0.5) { - sat = sat / (2 - sat); - } else { - sat = sat * val / (2 - li * 2); + // Convert saturation. + if (li !== 0) { + if (li === 1) { + sat = 0; + } else if (li < 0.5) { + sat = sat / (2 - sat); + } else { + sat = sat * val / (2 - li * 2); + } } - } - // Hue and alpha stay the same. - return [hue, sat, li, hsba[3]]; -}; + // Hue and alpha stay the same. + return [hue, sat, li, hsba[3]]; + }, -/** - * Convert an HSBA array to RGBA. - */ -p5.ColorConversion._hsbaToRGBA = function(hsba) { - const hue = hsba[0] * 6; // We will split hue into 6 sectors. - const sat = hsba[1]; - const val = hsba[2]; + /** + * Convert an HSBA array to RGBA. + */ + _hsbaToRGBA(hsba) { + const hue = hsba[0] * 6; // We will split hue into 6 sectors. + const sat = hsba[1]; + const val = hsba[2]; - let RGBA = []; + let RGBA = []; - if (sat === 0) { - RGBA = [val, val, val, hsba[3]]; // Return early if grayscale. - } else { - const sector = Math.floor(hue); - const tint1 = val * (1 - sat); - const tint2 = val * (1 - sat * (hue - sector)); - const tint3 = val * (1 - sat * (1 + sector - hue)); - let red, green, blue; - if (sector === 1) { - // Yellow to green. - red = tint2; - green = val; - blue = tint1; - } else if (sector === 2) { - // Green to cyan. - red = tint1; - green = val; - blue = tint3; - } else if (sector === 3) { - // Cyan to blue. - red = tint1; - green = tint2; - blue = val; - } else if (sector === 4) { - // Blue to magenta. - red = tint3; - green = tint1; - blue = val; - } else if (sector === 5) { - // Magenta to red. - red = val; - green = tint1; - blue = tint2; + if (sat === 0) { + RGBA = [val, val, val, hsba[3]]; // Return early if grayscale. } else { - // Red to yellow (sector could be 0 or 6). - red = val; - green = tint3; - blue = tint1; + const sector = Math.floor(hue); + const tint1 = val * (1 - sat); + const tint2 = val * (1 - sat * (hue - sector)); + const tint3 = val * (1 - sat * (1 + sector - hue)); + let red, green, blue; + if (sector === 1) { + // Yellow to green. + red = tint2; + green = val; + blue = tint1; + } else if (sector === 2) { + // Green to cyan. + red = tint1; + green = val; + blue = tint3; + } else if (sector === 3) { + // Cyan to blue. + red = tint1; + green = tint2; + blue = val; + } else if (sector === 4) { + // Blue to magenta. + red = tint3; + green = tint1; + blue = val; + } else if (sector === 5) { + // Magenta to red. + red = val; + green = tint1; + blue = tint2; + } else { + // Red to yellow (sector could be 0 or 6). + red = val; + green = tint3; + blue = tint1; + } + RGBA = [red, green, blue, hsba[3]]; } - RGBA = [red, green, blue, hsba[3]]; - } - return RGBA; -}; + return RGBA; + }, -/** - * Convert an HSLA array to HSBA. - */ -p5.ColorConversion._hslaToHSBA = function(hsla) { - const hue = hsla[0]; - let sat = hsla[1]; - const li = hsla[2]; + /** + * Convert an HSLA array to HSBA. + */ + _hslaToHSBA(hsla) { + const hue = hsla[0]; + let sat = hsla[1]; + const li = hsla[2]; - // Calculate brightness. - let val; - if (li < 0.5) { - val = (1 + sat) * li; - } else { - val = li + sat - li * sat; - } - - // Convert saturation. - sat = 2 * (val - li) / val; - - // Hue and alpha stay the same. - return [hue, sat, val, hsla[3]]; -}; - -/** - * Convert an HSLA array to RGBA. - * - * We need to change basis from HSLA to something that can be more easily be - * projected onto RGBA. We will choose hue and brightness as our first two - * components, and pick a convenient third one ('zest') so that we don't need - * to calculate formal HSBA saturation. - */ -p5.ColorConversion._hslaToRGBA = function(hsla) { - const hue = hsla[0] * 6; // We will split hue into 6 sectors. - const sat = hsla[1]; - const li = hsla[2]; - - let RGBA = []; - - if (sat === 0) { - RGBA = [li, li, li, hsla[3]]; // Return early if grayscale. - } else { // Calculate brightness. let val; if (li < 0.5) { @@ -146,126 +112,159 @@ p5.ColorConversion._hslaToRGBA = function(hsla) { val = li + sat - li * sat; } - // Define zest. - const zest = 2 * li - val; + // Convert saturation. + sat = 2 * (val - li) / val; - // Implement projection (project onto green by default). - const hzvToRGB = (hue, zest, val) => { - if (hue < 0) { - // Hue must wrap to allow projection onto red and blue. - hue += 6; - } else if (hue >= 6) { - hue -= 6; - } - if (hue < 1) { - // Red to yellow (increasing green). - return zest + (val - zest) * hue; - } else if (hue < 3) { - // Yellow to cyan (greatest green). - return val; - } else if (hue < 4) { - // Cyan to blue (decreasing green). - return zest + (val - zest) * (4 - hue); + // Hue and alpha stay the same. + return [hue, sat, val, hsla[3]]; + }, + + /** + * Convert an HSLA array to RGBA. + * + * We need to change basis from HSLA to something that can be more easily be + * projected onto RGBA. We will choose hue and brightness as our first two + * components, and pick a convenient third one ('zest') so that we don't need + * to calculate formal HSBA saturation. + */ + _hslaToRGBA(hsla) { + const hue = hsla[0] * 6; // We will split hue into 6 sectors. + const sat = hsla[1]; + const li = hsla[2]; + + let RGBA = []; + + if (sat === 0) { + RGBA = [li, li, li, hsla[3]]; // Return early if grayscale. + } else { + // Calculate brightness. + let val; + if (li < 0.5) { + val = (1 + sat) * li; } else { - // Blue to red (least green). - return zest; + val = li + sat - li * sat; } - }; - // Perform projections, offsetting hue as necessary. - RGBA = [ - hzvToRGB(hue + 2, zest, val), - hzvToRGB(hue, zest, val), - hzvToRGB(hue - 2, zest, val), - hsla[3] - ]; - } + // Define zest. + const zest = 2 * li - val; - return RGBA; -}; + // Implement projection (project onto green by default). + const hzvToRGB = (hue, zest, val) => { + if (hue < 0) { + // Hue must wrap to allow projection onto red and blue. + hue += 6; + } else if (hue >= 6) { + hue -= 6; + } + if (hue < 1) { + // Red to yellow (increasing green). + return zest + (val - zest) * hue; + } else if (hue < 3) { + // Yellow to cyan (greatest green). + return val; + } else if (hue < 4) { + // Cyan to blue (decreasing green). + return zest + (val - zest) * (4 - hue); + } else { + // Blue to red (least green). + return zest; + } + }; -/** - * Convert an RGBA array to HSBA. - */ -p5.ColorConversion._rgbaToHSBA = function(rgba) { - const red = rgba[0]; - const green = rgba[1]; - const blue = rgba[2]; + // Perform projections, offsetting hue as necessary. + RGBA = [ + hzvToRGB(hue + 2, zest, val), + hzvToRGB(hue, zest, val), + hzvToRGB(hue - 2, zest, val), + hsla[3] + ]; + } - const val = Math.max(red, green, blue); - const chroma = val - Math.min(red, green, blue); + return RGBA; + }, - let hue, sat; - if (chroma === 0) { - // Return early if grayscale. - hue = 0; - sat = 0; - } else { - sat = chroma / val; - if (red === val) { - // Magenta to yellow. - hue = (green - blue) / chroma; - } else if (green === val) { - // Yellow to cyan. - hue = 2 + (blue - red) / chroma; - } else if (blue === val) { - // Cyan to magenta. - hue = 4 + (red - green) / chroma; - } - if (hue < 0) { - // Confine hue to the interval [0, 1). - hue += 6; - } else if (hue >= 6) { - hue -= 6; + /** + * Convert an RGBA array to HSBA. + */ + _rgbaToHSBA(rgba) { + const red = rgba[0]; + const green = rgba[1]; + const blue = rgba[2]; + + const val = Math.max(red, green, blue); + const chroma = val - Math.min(red, green, blue); + + let hue, sat; + if (chroma === 0) { + // Return early if grayscale. + hue = 0; + sat = 0; + } else { + sat = chroma / val; + if (red === val) { + // Magenta to yellow. + hue = (green - blue) / chroma; + } else if (green === val) { + // Yellow to cyan. + hue = 2 + (blue - red) / chroma; + } else if (blue === val) { + // Cyan to magenta. + hue = 4 + (red - green) / chroma; + } + if (hue < 0) { + // Confine hue to the interval [0, 1). + hue += 6; + } else if (hue >= 6) { + hue -= 6; + } } - } - return [hue / 6, sat, val, rgba[3]]; -}; + return [hue / 6, sat, val, rgba[3]]; + }, -/** - * Convert an RGBA array to HSLA. - */ -p5.ColorConversion._rgbaToHSLA = function(rgba) { - const red = rgba[0]; - const green = rgba[1]; - const blue = rgba[2]; + /** + * Convert an RGBA array to HSLA. + */ + _rgbaToHSLA(rgba) { + const red = rgba[0]; + const green = rgba[1]; + const blue = rgba[2]; - const val = Math.max(red, green, blue); - const min = Math.min(red, green, blue); - const li = val + min; // We will halve this later. - const chroma = val - min; + const val = Math.max(red, green, blue); + const min = Math.min(red, green, blue); + const li = val + min; // We will halve this later. + const chroma = val - min; - let hue, sat; - if (chroma === 0) { - // Return early if grayscale. - hue = 0; - sat = 0; - } else { - if (li < 1) { - sat = chroma / li; + let hue, sat; + if (chroma === 0) { + // Return early if grayscale. + hue = 0; + sat = 0; } else { - sat = chroma / (2 - li); - } - if (red === val) { - // Magenta to yellow. - hue = (green - blue) / chroma; - } else if (green === val) { - // Yellow to cyan. - hue = 2 + (blue - red) / chroma; - } else if (blue === val) { - // Cyan to magenta. - hue = 4 + (red - green) / chroma; - } - if (hue < 0) { - // Confine hue to the interval [0, 1). - hue += 6; - } else if (hue >= 6) { - hue -= 6; + if (li < 1) { + sat = chroma / li; + } else { + sat = chroma / (2 - li); + } + if (red === val) { + // Magenta to yellow. + hue = (green - blue) / chroma; + } else if (green === val) { + // Yellow to cyan. + hue = 2 + (blue - red) / chroma; + } else if (blue === val) { + // Cyan to magenta. + hue = 4 + (red - green) / chroma; + } + if (hue < 0) { + // Confine hue to the interval [0, 1). + hue += 6; + } else if (hue >= 6) { + hue -= 6; + } } - } - return [hue / 6, sat, li / 2, rgba[3]]; + return [hue / 6, sat, li / 2, rgba[3]]; + } }; - export default p5.ColorConversion; diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js index 6e5c305a42..717897707d 100644 --- a/src/color/p5.Color.js +++ b/src/color/p5.Color.js @@ -11,451 +11,6 @@ import p5 from '../core/main'; import * as constants from '../core/constants'; import color_conversion from './color_conversion'; -/** - * Each color stores the color mode and level maxes that were applied at the - * time of its construction. These are used to interpret the input arguments - * (at construction and later for that instance of color) and to format the - * output e.g. when saturation() is requested. - * - * Internally, we store an array representing the ideal RGBA values in floating - * point form, normalized from 0 to 1. From this we calculate the closest - * screen color (RGBA levels from 0 to 255) and expose this to the renderer. - * - * We also cache normalized, floating-point components of the color in various - * representations as they are calculated. This is done to prevent repeating a - * conversion that has already been performed. - * - * color() is the recommended way to create an instance - * of this class. However, one can also create a color instace from the constructor - * using the parameters below. - * - * @class p5.Color - * @constructor - * @param {p5} [pInst] pointer to p5 instance. - * - * @param {Number[]|String} vals an array containing the color values - * for red, green, blue and alpha channel - * or CSS color. - */ -p5.Color = function(pInst, vals) { - // Record color mode and maxes at time of construction. - this._storeModeAndMaxes(pInst._colorMode, pInst._colorMaxes); - - // Calculate normalized RGBA values. - if ( - this.mode !== constants.RGB && - this.mode !== constants.HSL && - this.mode !== constants.HSB - ) { - throw new Error(`${this.mode} is an invalid colorMode.`); - } else { - this._array = p5.Color._parseInputs.apply(this, vals); - } - - // Expose closest screen color. - this._calculateLevels(); - return this; -}; - -/** - * This method returns the color formatted as a string. This can be useful - * for debugging, or for using p5.js with other libraries. - * - * @method toString - * @param {String} [format] How the color string will be formatted. - * Leaving this empty formats the string as rgba(r, g, b, a). - * '#rgb' '#rgba' '#rrggbb' and '#rrggbbaa' format as hexadecimal color codes. - * 'rgb' 'hsb' and 'hsl' return the color formatted in the specified color mode. - * 'rgba' 'hsba' and 'hsla' are the same as above but with alpha channels. - * 'rgb%' 'hsb%' 'hsl%' 'rgba%' 'hsba%' and 'hsla%' format as percentages. - * @return {String} the formatted string - * - * @example - *
- * - * createCanvas(200, 100); - * let myColor; - * stroke(255); - * myColor = color(100, 100, 250); - * fill(myColor); - * rotate(HALF_PI); - * text(myColor.toString(), 0, -5); - * text(myColor.toString('#rrggbb'), 0, -30); - * text(myColor.toString('rgba%'), 0, -55); - * describe('A canvas with 3 text representation of their color.'); - * - *
- * - *
- * - * let myColor = color(100, 130, 250); - * text(myColor.toString('#rrggbb'), 25, 25); - * - *
- */ -p5.Color.prototype.toString = function(format) { - const a = this.levels; - const f = this._array; - const alpha = f[3]; // String representation uses normalized alpha - - switch (format) { - case '#rrggbb': - return '#'.concat( - a[0] < 16 ? '0'.concat(a[0].toString(16)) : a[0].toString(16), - a[1] < 16 ? '0'.concat(a[1].toString(16)) : a[1].toString(16), - a[2] < 16 ? '0'.concat(a[2].toString(16)) : a[2].toString(16) - ); - - case '#rrggbbaa': - return '#'.concat( - a[0] < 16 ? '0'.concat(a[0].toString(16)) : a[0].toString(16), - a[1] < 16 ? '0'.concat(a[1].toString(16)) : a[1].toString(16), - a[2] < 16 ? '0'.concat(a[2].toString(16)) : a[2].toString(16), - a[3] < 16 ? '0'.concat(a[3].toString(16)) : a[3].toString(16) - ); - - case '#rgb': - return '#'.concat( - Math.round(f[0] * 15).toString(16), - Math.round(f[1] * 15).toString(16), - Math.round(f[2] * 15).toString(16) - ); - - case '#rgba': - return '#'.concat( - Math.round(f[0] * 15).toString(16), - Math.round(f[1] * 15).toString(16), - Math.round(f[2] * 15).toString(16), - Math.round(f[3] * 15).toString(16) - ); - - case 'rgb': - return 'rgb('.concat(a[0], ', ', a[1], ', ', a[2], ')'); - - case 'rgb%': - return 'rgb('.concat( - (100 * f[0]).toPrecision(3), - '%, ', - (100 * f[1]).toPrecision(3), - '%, ', - (100 * f[2]).toPrecision(3), - '%)' - ); - - case 'rgba%': - return 'rgba('.concat( - (100 * f[0]).toPrecision(3), - '%, ', - (100 * f[1]).toPrecision(3), - '%, ', - (100 * f[2]).toPrecision(3), - '%, ', - (100 * f[3]).toPrecision(3), - '%)' - ); - - case 'hsb': - case 'hsv': - if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array); - return 'hsb('.concat( - this.hsba[0] * this.maxes[constants.HSB][0], - ', ', - this.hsba[1] * this.maxes[constants.HSB][1], - ', ', - this.hsba[2] * this.maxes[constants.HSB][2], - ')' - ); - - case 'hsb%': - case 'hsv%': - if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array); - return 'hsb('.concat( - (100 * this.hsba[0]).toPrecision(3), - '%, ', - (100 * this.hsba[1]).toPrecision(3), - '%, ', - (100 * this.hsba[2]).toPrecision(3), - '%)' - ); - - case 'hsba': - case 'hsva': - if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array); - return 'hsba('.concat( - this.hsba[0] * this.maxes[constants.HSB][0], - ', ', - this.hsba[1] * this.maxes[constants.HSB][1], - ', ', - this.hsba[2] * this.maxes[constants.HSB][2], - ', ', - alpha, - ')' - ); - - case 'hsba%': - case 'hsva%': - if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array); - return 'hsba('.concat( - (100 * this.hsba[0]).toPrecision(3), - '%, ', - (100 * this.hsba[1]).toPrecision(3), - '%, ', - (100 * this.hsba[2]).toPrecision(3), - '%, ', - (100 * alpha).toPrecision(3), - '%)' - ); - - case 'hsl': - if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array); - return 'hsl('.concat( - this.hsla[0] * this.maxes[constants.HSL][0], - ', ', - this.hsla[1] * this.maxes[constants.HSL][1], - ', ', - this.hsla[2] * this.maxes[constants.HSL][2], - ')' - ); - - case 'hsl%': - if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array); - return 'hsl('.concat( - (100 * this.hsla[0]).toPrecision(3), - '%, ', - (100 * this.hsla[1]).toPrecision(3), - '%, ', - (100 * this.hsla[2]).toPrecision(3), - '%)' - ); - - case 'hsla': - if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array); - return 'hsla('.concat( - this.hsla[0] * this.maxes[constants.HSL][0], - ', ', - this.hsla[1] * this.maxes[constants.HSL][1], - ', ', - this.hsla[2] * this.maxes[constants.HSL][2], - ', ', - alpha, - ')' - ); - - case 'hsla%': - if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array); - return 'hsl('.concat( - (100 * this.hsla[0]).toPrecision(3), - '%, ', - (100 * this.hsla[1]).toPrecision(3), - '%, ', - (100 * this.hsla[2]).toPrecision(3), - '%, ', - (100 * alpha).toPrecision(3), - '%)' - ); - - case 'rgba': - default: - return 'rgba('.concat(a[0], ',', a[1], ',', a[2], ',', alpha, ')'); - } -}; - -/** - * The setRed method sets the red component of a color. - * The range depends on your color mode, in the default RGB mode it's between 0 and 255. - * @method setRed - * @param {Number} red the new red value - * @example - *
- * - * let backgroundColor; - * - * function setup() { - * backgroundColor = color(100, 50, 150); - * } - * - * function draw() { - * backgroundColor.setRed(128 + 128 * sin(millis() / 1000)); - * background(backgroundColor); - * describe('canvas with gradually changing background color'); - * } - * - *
- */ -p5.Color.prototype.setRed = function(new_red) { - this._array[0] = new_red / this.maxes[constants.RGB][0]; - this._calculateLevels(); -}; - -/** - * The setGreen method sets the green component of a color. - * The range depends on your color mode, in the default RGB mode it's between 0 and 255. - * @method setGreen - * @param {Number} green the new green value - * @example - *
- * - * let backgroundColor = color(100, 50, 150); - * function draw() { - * backgroundColor.setGreen(128 + 128 * sin(millis() / 1000)); - * background(backgroundColor); - * describe('canvas with gradually changing background color'); - * } - * - *
- * - **/ -p5.Color.prototype.setGreen = function(new_green) { - this._array[1] = new_green / this.maxes[constants.RGB][1]; - this._calculateLevels(); -}; - -/** - * The setBlue method sets the blue component of a color. - * The range depends on your color mode, in the default RGB mode it's between 0 and 255. - * @method setBlue - * @param {Number} blue the new blue value - * @example - *
- * - * let backgroundColor = color(100, 50, 150); - * function draw() { - * backgroundColor.setBlue(128 + 128 * sin(millis() / 1000)); - * background(backgroundColor); - * describe('canvas with gradually changing background color'); - * } - * - *
- * - **/ -p5.Color.prototype.setBlue = function(new_blue) { - this._array[2] = new_blue / this.maxes[constants.RGB][2]; - this._calculateLevels(); -}; - -/** - * The setAlpha method sets the transparency (alpha) value of a color. - * The range depends on your color mode, in the default RGB mode it's between 0 and 255. - * @method setAlpha - * @param {Number} alpha the new alpha value - * @example - *
- * - * function draw() { - * clear(); - * background(200); - * squareColor = color(100, 50, 100); - * squareColor.setAlpha(128 + 128 * sin(millis() / 1000)); - * fill(squareColor); - * rect(13, 13, width - 26, height - 26); - * describe('a square with gradually changing opacity on a gray background'); - * } - * - *
- **/ -p5.Color.prototype.setAlpha = function(new_alpha) { - this._array[3] = new_alpha / this.maxes[this.mode][3]; - this._calculateLevels(); -}; - -// calculates and stores the closest screen levels -p5.Color.prototype._calculateLevels = function() { - const array = this._array; - // (loop backwards for performance) - const levels = (this.levels = new Array(array.length)); - for (let i = array.length - 1; i >= 0; --i) { - levels[i] = Math.round(array[i] * 255); - } - - // Clear cached HSL/HSB values - this.hsla = null; - this.hsba = null; -}; - -p5.Color.prototype._getAlpha = function() { - return this._array[3] * this.maxes[this.mode][3]; -}; - -// stores the color mode and maxes in this instance of Color -// for later use (by _parseInputs()) -p5.Color.prototype._storeModeAndMaxes = function(new_mode, new_maxes) { - this.mode = new_mode; - this.maxes = new_maxes; -}; - -p5.Color.prototype._getMode = function() { - return this.mode; -}; - -p5.Color.prototype._getMaxes = function() { - return this.maxes; -}; - -p5.Color.prototype._getBlue = function() { - return this._array[2] * this.maxes[constants.RGB][2]; -}; - -p5.Color.prototype._getBrightness = function() { - if (!this.hsba) { - this.hsba = color_conversion._rgbaToHSBA(this._array); - } - return this.hsba[2] * this.maxes[constants.HSB][2]; -}; - -p5.Color.prototype._getGreen = function() { - return this._array[1] * this.maxes[constants.RGB][1]; -}; - -/** - * Hue is the same in HSB and HSL, but the maximum value may be different. - * This function will return the HSB-normalized saturation when supplied with - * an HSB color object, but will default to the HSL-normalized saturation - * otherwise. - */ -p5.Color.prototype._getHue = function() { - if (this.mode === constants.HSB) { - if (!this.hsba) { - this.hsba = color_conversion._rgbaToHSBA(this._array); - } - return this.hsba[0] * this.maxes[constants.HSB][0]; - } else { - if (!this.hsla) { - this.hsla = color_conversion._rgbaToHSLA(this._array); - } - return this.hsla[0] * this.maxes[constants.HSL][0]; - } -}; - -p5.Color.prototype._getLightness = function() { - if (!this.hsla) { - this.hsla = color_conversion._rgbaToHSLA(this._array); - } - return this.hsla[2] * this.maxes[constants.HSL][2]; -}; - -p5.Color.prototype._getRed = function() { - return this._array[0] * this.maxes[constants.RGB][0]; -}; - -/** - * Saturation is scaled differently in HSB and HSL. This function will return - * the HSB saturation when supplied with an HSB color object, but will default - * to the HSL saturation otherwise. - */ -p5.Color.prototype._getSaturation = function() { - if (this.mode === constants.HSB) { - if (!this.hsba) { - this.hsba = color_conversion._rgbaToHSBA(this._array); - } - return this.hsba[1] * this.maxes[constants.HSB][1]; - } else { - if (!this.hsla) { - this.hsla = color_conversion._rgbaToHSLA(this._array); - } - return this.hsla[1] * this.maxes[constants.HSL][1]; - } -}; - /** * CSS named colors. */ @@ -761,6 +316,446 @@ const colorPatterns = { }; /** + * Each color stores the color mode and level maxes that were applied at the + * time of its construction. These are used to interpret the input arguments + * (at construction and later for that instance of color) and to format the + * output e.g. when saturation() is requested. + * + * Internally, we store an array representing the ideal RGBA values in floating + * point form, normalized from 0 to 1. From this we calculate the closest + * screen color (RGBA levels from 0 to 255) and expose this to the renderer. + * + * We also cache normalized, floating-point components of the color in various + * representations as they are calculated. This is done to prevent repeating a + * conversion that has already been performed. + * + * color() is the recommended way to create an instance + * of this class. However, one can also create a color instace from the constructor + * using the parameters below. + * + * @class p5.Color + * @constructor + * @param {p5} [pInst] pointer to p5 instance. + * + * @param {Number[]|String} vals an array containing the color values + * for red, green, blue and alpha channel + * or CSS color. + */ +p5.Color = class Color { + constructor(pInst, vals) { + // Record color mode and maxes at time of construction. + this._storeModeAndMaxes(pInst._colorMode, pInst._colorMaxes); + + // Calculate normalized RGBA values. + if (![constants.RGB, constants.HSL, constants.HSB].includes(this.mode)) { + throw new Error(`${this.mode} is an invalid colorMode.`); + } else { + this._array = Color._parseInputs.apply(this, vals); + } + + // Expose closest screen color. + this._calculateLevels(); + } + + /** + * This method returns the color formatted as a string. This can be useful + * for debugging, or for using p5.js with other libraries. + * + * @method toString + * @param {String} [format] How the color string will be formatted. + * Leaving this empty formats the string as rgba(r, g, b, a). + * '#rgb' '#rgba' '#rrggbb' and '#rrggbbaa' format as hexadecimal color codes. + * 'rgb' 'hsb' and 'hsl' return the color formatted in the specified color mode. + * 'rgba' 'hsba' and 'hsla' are the same as above but with alpha channels. + * 'rgb%' 'hsb%' 'hsl%' 'rgba%' 'hsba%' and 'hsla%' format as percentages. + * @return {String} the formatted string + * + * @example + *
+ * + * createCanvas(200, 100); + * let myColor; + * stroke(255); + * myColor = color(100, 100, 250); + * fill(myColor); + * rotate(HALF_PI); + * text(myColor.toString(), 0, -5); + * text(myColor.toString('#rrggbb'), 0, -30); + * text(myColor.toString('rgba%'), 0, -55); + * describe('A canvas with 3 text representation of their color.'); + * + *
+ * + *
+ * + * let myColor = color(100, 130, 250); + * text(myColor.toString('#rrggbb'), 25, 25); + * + *
+ */ + toString(format) { + const a = this.levels; + const f = this._array; + const alpha = f[3]; // String representation uses normalized alpha + + switch (format) { + case '#rrggbb': + return '#'.concat( + a[0] < 16 ? '0'.concat(a[0].toString(16)) : a[0].toString(16), + a[1] < 16 ? '0'.concat(a[1].toString(16)) : a[1].toString(16), + a[2] < 16 ? '0'.concat(a[2].toString(16)) : a[2].toString(16) + ); + + case '#rrggbbaa': + return '#'.concat( + a[0] < 16 ? '0'.concat(a[0].toString(16)) : a[0].toString(16), + a[1] < 16 ? '0'.concat(a[1].toString(16)) : a[1].toString(16), + a[2] < 16 ? '0'.concat(a[2].toString(16)) : a[2].toString(16), + a[3] < 16 ? '0'.concat(a[3].toString(16)) : a[3].toString(16) + ); + + case '#rgb': + return '#'.concat( + Math.round(f[0] * 15).toString(16), + Math.round(f[1] * 15).toString(16), + Math.round(f[2] * 15).toString(16) + ); + + case '#rgba': + return '#'.concat( + Math.round(f[0] * 15).toString(16), + Math.round(f[1] * 15).toString(16), + Math.round(f[2] * 15).toString(16), + Math.round(f[3] * 15).toString(16) + ); + + case 'rgb': + return 'rgb('.concat(a[0], ', ', a[1], ', ', a[2], ')'); + + case 'rgb%': + return 'rgb('.concat( + (100 * f[0]).toPrecision(3), + '%, ', + (100 * f[1]).toPrecision(3), + '%, ', + (100 * f[2]).toPrecision(3), + '%)' + ); + + case 'rgba%': + return 'rgba('.concat( + (100 * f[0]).toPrecision(3), + '%, ', + (100 * f[1]).toPrecision(3), + '%, ', + (100 * f[2]).toPrecision(3), + '%, ', + (100 * f[3]).toPrecision(3), + '%)' + ); + + case 'hsb': + case 'hsv': + if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array); + return 'hsb('.concat( + this.hsba[0] * this.maxes[constants.HSB][0], + ', ', + this.hsba[1] * this.maxes[constants.HSB][1], + ', ', + this.hsba[2] * this.maxes[constants.HSB][2], + ')' + ); + + case 'hsb%': + case 'hsv%': + if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array); + return 'hsb('.concat( + (100 * this.hsba[0]).toPrecision(3), + '%, ', + (100 * this.hsba[1]).toPrecision(3), + '%, ', + (100 * this.hsba[2]).toPrecision(3), + '%)' + ); + + case 'hsba': + case 'hsva': + if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array); + return 'hsba('.concat( + this.hsba[0] * this.maxes[constants.HSB][0], + ', ', + this.hsba[1] * this.maxes[constants.HSB][1], + ', ', + this.hsba[2] * this.maxes[constants.HSB][2], + ', ', + alpha, + ')' + ); + + case 'hsba%': + case 'hsva%': + if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array); + return 'hsba('.concat( + (100 * this.hsba[0]).toPrecision(3), + '%, ', + (100 * this.hsba[1]).toPrecision(3), + '%, ', + (100 * this.hsba[2]).toPrecision(3), + '%, ', + (100 * alpha).toPrecision(3), + '%)' + ); + + case 'hsl': + if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array); + return 'hsl('.concat( + this.hsla[0] * this.maxes[constants.HSL][0], + ', ', + this.hsla[1] * this.maxes[constants.HSL][1], + ', ', + this.hsla[2] * this.maxes[constants.HSL][2], + ')' + ); + + case 'hsl%': + if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array); + return 'hsl('.concat( + (100 * this.hsla[0]).toPrecision(3), + '%, ', + (100 * this.hsla[1]).toPrecision(3), + '%, ', + (100 * this.hsla[2]).toPrecision(3), + '%)' + ); + + case 'hsla': + if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array); + return 'hsla('.concat( + this.hsla[0] * this.maxes[constants.HSL][0], + ', ', + this.hsla[1] * this.maxes[constants.HSL][1], + ', ', + this.hsla[2] * this.maxes[constants.HSL][2], + ', ', + alpha, + ')' + ); + + case 'hsla%': + if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array); + return 'hsl('.concat( + (100 * this.hsla[0]).toPrecision(3), + '%, ', + (100 * this.hsla[1]).toPrecision(3), + '%, ', + (100 * this.hsla[2]).toPrecision(3), + '%, ', + (100 * alpha).toPrecision(3), + '%)' + ); + + case 'rgba': + default: + return 'rgba('.concat(a[0], ',', a[1], ',', a[2], ',', alpha, ')'); + } + } + + /** + * The setRed method sets the red component of a color. + * The range depends on your color mode, in the default RGB mode it's between 0 and 255. + * @method setRed + * @param {Number} red the new red value + * @example + *
+ * + * let backgroundColor; + * + * function setup() { + * backgroundColor = color(100, 50, 150); + * } + * + * function draw() { + * backgroundColor.setRed(128 + 128 * sin(millis() / 1000)); + * background(backgroundColor); + * describe('canvas with gradually changing background color'); + * } + * + *
+ */ + setRed(new_red) { + this._array[0] = new_red / this.maxes[constants.RGB][0]; + this._calculateLevels(); + } + + /** + * The setGreen method sets the green component of a color. + * The range depends on your color mode, in the default RGB mode it's between 0 and 255. + * @method setGreen + * @param {Number} green the new green value + * @example + *
+ * + * let backgroundColor = color(100, 50, 150); + * function draw() { + * backgroundColor.setGreen(128 + 128 * sin(millis() / 1000)); + * background(backgroundColor); + * describe('canvas with gradually changing background color'); + * } + * + *
+ * + **/ + setGreen(new_green) { + this._array[1] = new_green / this.maxes[constants.RGB][1]; + this._calculateLevels(); + } + + /** + * The setBlue method sets the blue component of a color. + * The range depends on your color mode, in the default RGB mode it's between 0 and 255. + * @method setBlue + * @param {Number} blue the new blue value + * @example + *
+ * + * let backgroundColor = color(100, 50, 150); + * function draw() { + * backgroundColor.setBlue(128 + 128 * sin(millis() / 1000)); + * background(backgroundColor); + * describe('canvas with gradually changing background color'); + * } + * + *
+ * + **/ + setBlue(new_blue) { + this._array[2] = new_blue / this.maxes[constants.RGB][2]; + this._calculateLevels(); + } + + /** + * The setAlpha method sets the transparency (alpha) value of a color. + * The range depends on your color mode, in the default RGB mode it's between 0 and 255. + * @method setAlpha + * @param {Number} alpha the new alpha value + * @example + *
+ * + * function draw() { + * clear(); + * background(200); + * squareColor = color(100, 50, 100); + * squareColor.setAlpha(128 + 128 * sin(millis() / 1000)); + * fill(squareColor); + * rect(13, 13, width - 26, height - 26); + * describe('a square with gradually changing opacity on a gray background'); + * } + * + *
+ **/ + setAlpha(new_alpha) { + this._array[3] = new_alpha / this.maxes[this.mode][3]; + this._calculateLevels(); + } + + // calculates and stores the closest screen levels + _calculateLevels() { + const array = this._array; + // (loop backwards for performance) + const levels = (this.levels = new Array(array.length)); + for (let i = array.length - 1; i >= 0; --i) { + levels[i] = Math.round(array[i] * 255); + } + + // Clear cached HSL/HSB values + this.hsla = null; + this.hsba = null; + } + + _getAlpha() { + return this._array[3] * this.maxes[this.mode][3]; + } + + // stores the color mode and maxes in this instance of Color + // for later use (by _parseInputs()) + _storeModeAndMaxes(new_mode, new_maxes) { + this.mode = new_mode; + this.maxes = new_maxes; + } + + _getMode() { + return this.mode; + } + + _getMaxes() { + return this.maxes; + } + + _getBlue() { + return this._array[2] * this.maxes[constants.RGB][2]; + } + + _getBrightness() { + if (!this.hsba) { + this.hsba = color_conversion._rgbaToHSBA(this._array); + } + return this.hsba[2] * this.maxes[constants.HSB][2]; + } + + _getGreen() { + return this._array[1] * this.maxes[constants.RGB][1]; + } + + /** + * Hue is the same in HSB and HSL, but the maximum value may be different. + * This function will return the HSB-normalized saturation when supplied with + * an HSB color object, but will default to the HSL-normalized saturation + * otherwise. + */ + _getHue() { + if (this.mode === constants.HSB) { + if (!this.hsba) { + this.hsba = color_conversion._rgbaToHSBA(this._array); + } + return this.hsba[0] * this.maxes[constants.HSB][0]; + } else { + if (!this.hsla) { + this.hsla = color_conversion._rgbaToHSLA(this._array); + } + return this.hsla[0] * this.maxes[constants.HSL][0]; + } + } + + _getLightness() { + if (!this.hsla) { + this.hsla = color_conversion._rgbaToHSLA(this._array); + } + return this.hsla[2] * this.maxes[constants.HSL][2]; + } + + _getRed() { + return this._array[0] * this.maxes[constants.RGB][0]; + } + + /** + * Saturation is scaled differently in HSB and HSL. This function will return + * the HSB saturation when supplied with an HSB color object, but will default + * to the HSL saturation otherwise. + */ + _getSaturation() { + if (this.mode === constants.HSB) { + if (!this.hsba) { + this.hsba = color_conversion._rgbaToHSBA(this._array); + } + return this.hsba[1] * this.maxes[constants.HSB][1]; + } else { + if (!this.hsla) { + this.hsla = color_conversion._rgbaToHSLA(this._array); + } + return this.hsla[1] * this.maxes[constants.HSL][1]; + } + } + /** * For a number of different inputs, returns a color formatted as [r, g, b, a] * arrays, with each component normalized between 0 and 1. * @@ -787,212 +782,213 @@ const colorPatterns = { * * */ -p5.Color._parseInputs = function(r, g, b, a) { - const numArgs = arguments.length; - const mode = this.mode; - const maxes = this.maxes[mode]; - let results = []; - let i; - - if (numArgs >= 3) { - // Argument is a list of component values. - - results[0] = r / maxes[0]; - results[1] = g / maxes[1]; - results[2] = b / maxes[2]; - - // Alpha may be undefined, so default it to 100%. - if (typeof a === 'number') { - results[3] = a / maxes[3]; - } else { - results[3] = 1; - } + static _parseInputs(r, g, b, a) { + const numArgs = arguments.length; + const mode = this.mode; + const maxes = this.maxes[mode]; + let results = []; + let i; + + if (numArgs >= 3) { + // Argument is a list of component values. + + results[0] = r / maxes[0]; + results[1] = g / maxes[1]; + results[2] = b / maxes[2]; + + // Alpha may be undefined, so default it to 100%. + if (typeof a === 'number') { + results[3] = a / maxes[3]; + } else { + results[3] = 1; + } - // Constrain components to the range [0,1]. - // (loop backwards for performance) - for (i = results.length - 1; i >= 0; --i) { - const result = results[i]; - if (result < 0) { - results[i] = 0; - } else if (result > 1) { - results[i] = 1; + // Constrain components to the range [0,1]. + // (loop backwards for performance) + for (i = results.length - 1; i >= 0; --i) { + const result = results[i]; + if (result < 0) { + results[i] = 0; + } else if (result > 1) { + results[i] = 1; + } } - } - // Convert to RGBA and return. - if (mode === constants.HSL) { - return color_conversion._hslaToRGBA(results); - } else if (mode === constants.HSB) { - return color_conversion._hsbaToRGBA(results); - } else { - return results; - } - } else if (numArgs === 1 && typeof r === 'string') { - const str = r.trim().toLowerCase(); + // Convert to RGBA and return. + if (mode === constants.HSL) { + return color_conversion._hslaToRGBA(results); + } else if (mode === constants.HSB) { + return color_conversion._hsbaToRGBA(results); + } else { + return results; + } + } else if (numArgs === 1 && typeof r === 'string') { + const str = r.trim().toLowerCase(); - // Return if string is a named colour. - if (namedColors[str]) { - return p5.Color._parseInputs.call(this, namedColors[str]); - } + // Return if string is a named colour. + if (namedColors[str]) { + return Color._parseInputs.call(this, namedColors[str]); + } - // Try RGBA pattern matching. - if (colorPatterns.HEX3.test(str)) { - // #rgb - results = colorPatterns.HEX3.exec(str) - .slice(1) - .map(color => parseInt(color + color, 16) / 255); - results[3] = 1; - return results; - } else if (colorPatterns.HEX6.test(str)) { - // #rrggbb - results = colorPatterns.HEX6.exec(str) - .slice(1) - .map(color => parseInt(color, 16) / 255); - results[3] = 1; - return results; - } else if (colorPatterns.HEX4.test(str)) { - // #rgba - results = colorPatterns.HEX4.exec(str) - .slice(1) - .map(color => parseInt(color + color, 16) / 255); - return results; - } else if (colorPatterns.HEX8.test(str)) { - // #rrggbbaa - results = colorPatterns.HEX8.exec(str) - .slice(1) - .map(color => parseInt(color, 16) / 255); - return results; - } else if (colorPatterns.RGB.test(str)) { - // rgb(R,G,B) - results = colorPatterns.RGB.exec(str) - .slice(1) - .map(color => color / 255); - results[3] = 1; - return results; - } else if (colorPatterns.RGB_PERCENT.test(str)) { - // rgb(R%,G%,B%) - results = colorPatterns.RGB_PERCENT.exec(str) - .slice(1) - .map(color => parseFloat(color) / 100); - results[3] = 1; - return results; - } else if (colorPatterns.RGBA.test(str)) { - // rgba(R,G,B,A) - results = colorPatterns.RGBA.exec(str) - .slice(1) - .map((color, idx) => { - if (idx === 3) { - return parseFloat(color); - } - return color / 255; - }); - return results; - } else if (colorPatterns.RGBA_PERCENT.test(str)) { - // rgba(R%,G%,B%,A%) - results = colorPatterns.RGBA_PERCENT.exec(str) - .slice(1) - .map((color, idx) => { - if (idx === 3) { - return parseFloat(color); - } - return parseFloat(color) / 100; - }); - return results; - } + // Try RGBA pattern matching. + if (colorPatterns.HEX3.test(str)) { + // #rgb + results = colorPatterns.HEX3.exec(str) + .slice(1) + .map(color => parseInt(color + color, 16) / 255); + results[3] = 1; + return results; + } else if (colorPatterns.HEX6.test(str)) { + // #rrggbb + results = colorPatterns.HEX6.exec(str) + .slice(1) + .map(color => parseInt(color, 16) / 255); + results[3] = 1; + return results; + } else if (colorPatterns.HEX4.test(str)) { + // #rgba + results = colorPatterns.HEX4.exec(str) + .slice(1) + .map(color => parseInt(color + color, 16) / 255); + return results; + } else if (colorPatterns.HEX8.test(str)) { + // #rrggbbaa + results = colorPatterns.HEX8.exec(str) + .slice(1) + .map(color => parseInt(color, 16) / 255); + return results; + } else if (colorPatterns.RGB.test(str)) { + // rgb(R,G,B) + results = colorPatterns.RGB.exec(str) + .slice(1) + .map(color => color / 255); + results[3] = 1; + return results; + } else if (colorPatterns.RGB_PERCENT.test(str)) { + // rgb(R%,G%,B%) + results = colorPatterns.RGB_PERCENT.exec(str) + .slice(1) + .map(color => parseFloat(color) / 100); + results[3] = 1; + return results; + } else if (colorPatterns.RGBA.test(str)) { + // rgba(R,G,B,A) + results = colorPatterns.RGBA.exec(str) + .slice(1) + .map((color, idx) => { + if (idx === 3) { + return parseFloat(color); + } + return color / 255; + }); + return results; + } else if (colorPatterns.RGBA_PERCENT.test(str)) { + // rgba(R%,G%,B%,A%) + results = colorPatterns.RGBA_PERCENT.exec(str) + .slice(1) + .map((color, idx) => { + if (idx === 3) { + return parseFloat(color); + } + return parseFloat(color) / 100; + }); + return results; + } - // Try HSLA pattern matching. - if (colorPatterns.HSL.test(str)) { - // hsl(H,S,L) - results = colorPatterns.HSL.exec(str) - .slice(1) - .map((color, idx) => { - if (idx === 0) { - return parseInt(color, 10) / 360; - } - return parseInt(color, 10) / 100; - }); - results[3] = 1; - } else if (colorPatterns.HSLA.test(str)) { - // hsla(H,S,L,A) - results = colorPatterns.HSLA.exec(str) - .slice(1) - .map((color, idx) => { - if (idx === 0) { - return parseInt(color, 10) / 360; - } else if (idx === 3) { - return parseFloat(color); - } - return parseInt(color, 10) / 100; - }); - } - results = results.map(value => Math.max(Math.min(value, 1), 0)); - if (results.length) { - return color_conversion._hslaToRGBA(results); - } + // Try HSLA pattern matching. + if (colorPatterns.HSL.test(str)) { + // hsl(H,S,L) + results = colorPatterns.HSL.exec(str) + .slice(1) + .map((color, idx) => { + if (idx === 0) { + return parseInt(color, 10) / 360; + } + return parseInt(color, 10) / 100; + }); + results[3] = 1; + } else if (colorPatterns.HSLA.test(str)) { + // hsla(H,S,L,A) + results = colorPatterns.HSLA.exec(str) + .slice(1) + .map((color, idx) => { + if (idx === 0) { + return parseInt(color, 10) / 360; + } else if (idx === 3) { + return parseFloat(color); + } + return parseInt(color, 10) / 100; + }); + } + results = results.map(value => Math.max(Math.min(value, 1), 0)); + if (results.length) { + return color_conversion._hslaToRGBA(results); + } - // Try HSBA pattern matching. - if (colorPatterns.HSB.test(str)) { - // hsb(H,S,B) - results = colorPatterns.HSB.exec(str) - .slice(1) - .map((color, idx) => { - if (idx === 0) { - return parseInt(color, 10) / 360; - } - return parseInt(color, 10) / 100; - }); - results[3] = 1; - } else if (colorPatterns.HSBA.test(str)) { - // hsba(H,S,B,A) - results = colorPatterns.HSBA.exec(str) - .slice(1) - .map((color, idx) => { - if (idx === 0) { - return parseInt(color, 10) / 360; - } else if (idx === 3) { - return parseFloat(color); - } - return parseInt(color, 10) / 100; - }); - } + // Try HSBA pattern matching. + if (colorPatterns.HSB.test(str)) { + // hsb(H,S,B) + results = colorPatterns.HSB.exec(str) + .slice(1) + .map((color, idx) => { + if (idx === 0) { + return parseInt(color, 10) / 360; + } + return parseInt(color, 10) / 100; + }); + results[3] = 1; + } else if (colorPatterns.HSBA.test(str)) { + // hsba(H,S,B,A) + results = colorPatterns.HSBA.exec(str) + .slice(1) + .map((color, idx) => { + if (idx === 0) { + return parseInt(color, 10) / 360; + } else if (idx === 3) { + return parseFloat(color); + } + return parseInt(color, 10) / 100; + }); + } - if (results.length) { - // (loop backwards for performance) - for (i = results.length - 1; i >= 0; --i) { - results[i] = Math.max(Math.min(results[i], 1), 0); + if (results.length) { + // (loop backwards for performance) + for (i = results.length - 1; i >= 0; --i) { + results[i] = Math.max(Math.min(results[i], 1), 0); + } + + return color_conversion._hsbaToRGBA(results); } - return color_conversion._hsbaToRGBA(results); - } + // Input did not match any CSS color pattern: default to white. + results = [1, 1, 1, 1]; + } else if ((numArgs === 1 || numArgs === 2) && typeof r === 'number') { + // 'Grayscale' mode. + + /** + * For HSB and HSL, interpret the gray level as a brightness/lightness + * value (they are equivalent when chroma is zero). For RGB, normalize the + * gray level according to the blue maximum. + */ + results[0] = r / maxes[2]; + results[1] = r / maxes[2]; + results[2] = r / maxes[2]; + + // Alpha may be undefined, so default it to 100%. + if (typeof g === 'number') { + results[3] = g / maxes[3]; + } else { + results[3] = 1; + } - // Input did not match any CSS color pattern: default to white. - results = [1, 1, 1, 1]; - } else if ((numArgs === 1 || numArgs === 2) && typeof r === 'number') { - // 'Grayscale' mode. - - /** - * For HSB and HSL, interpret the gray level as a brightness/lightness - * value (they are equivalent when chroma is zero). For RGB, normalize the - * gray level according to the blue maximum. - */ - results[0] = r / maxes[2]; - results[1] = r / maxes[2]; - results[2] = r / maxes[2]; - - // Alpha may be undefined, so default it to 100%. - if (typeof g === 'number') { - results[3] = g / maxes[3]; + // Constrain components to the range [0,1]. + results = results.map(value => Math.max(Math.min(value, 1), 0)); } else { - results[3] = 1; + throw new Error(`${arguments}is not a valid color representation.`); } - // Constrain components to the range [0,1]. - results = results.map(value => Math.max(Math.min(value, 1), 0)); - } else { - throw new Error(`${arguments}is not a valid color representation.`); + return results; } - - return results; }; export default p5.Color; diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index ecb1680326..af2d77d1ec 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -19,7 +19,7 @@ import * as constants from '../core/constants'; * @param {p5} [pInst] pointer to p5 instance * @param {Boolean} [isMainCanvas] whether we're using it as main canvas */ -p5.Renderer = function(elt, pInst, isMainCanvas) { +p5.Renderer = function (elt, pInst, isMainCanvas) { p5.Element.call(this, elt, pInst); this.canvas = elt; this._pixelsState = pInst; @@ -63,7 +63,7 @@ p5.Renderer.prototype = Object.create(p5.Element.prototype); // the renderer should return a 'style' object that it wishes to // store on the push stack. -p5.Renderer.prototype.push = function() { +p5.Renderer.prototype.push = function () { return { properties: { _doStroke: this._doStroke, @@ -89,7 +89,7 @@ p5.Renderer.prototype.push = function() { // a pop() operation is in progress // the renderer is passed the 'style' object that it returned // from its push() method. -p5.Renderer.prototype.pop = function(style) { +p5.Renderer.prototype.pop = function (style) { if (style.properties) { // copy the style properties back into the renderer Object.assign(this, style.properties); @@ -99,7 +99,7 @@ p5.Renderer.prototype.pop = function(style) { /** * Resize our canvas element. */ -p5.Renderer.prototype.resize = function(w, h) { +p5.Renderer.prototype.resize = function (w, h) { this.width = w; this.height = h; this.elt.width = w * this._pInst._pixelDensity; @@ -112,7 +112,7 @@ p5.Renderer.prototype.resize = function(w, h) { } }; -p5.Renderer.prototype.get = function(x, y, w, h) { +p5.Renderer.prototype.get = function (x, y, w, h) { const pixelsState = this._pixelsState; const pd = pixelsState._pixelDensity; const canvas = this.canvas; @@ -145,7 +145,7 @@ p5.Renderer.prototype.get = function(x, y, w, h) { return region; }; -p5.Renderer.prototype.textLeading = function(l) { +p5.Renderer.prototype.textLeading = function (l) { if (typeof l === 'number') { this._setProperty('_leadingSet', true); this._setProperty('_textLeading', l); @@ -155,7 +155,7 @@ p5.Renderer.prototype.textLeading = function(l) { return this._textLeading; }; -p5.Renderer.prototype.textSize = function(s) { +p5.Renderer.prototype.textSize = function (s) { if (typeof s === 'number') { this._setProperty('_textSize', s); if (!this._leadingSet) { @@ -168,7 +168,7 @@ p5.Renderer.prototype.textSize = function(s) { return this._textSize; }; -p5.Renderer.prototype.textStyle = function(s) { +p5.Renderer.prototype.textStyle = function (s) { if (s) { if ( s === constants.NORMAL || @@ -185,21 +185,21 @@ p5.Renderer.prototype.textStyle = function(s) { return this._textStyle; }; -p5.Renderer.prototype.textAscent = function() { +p5.Renderer.prototype.textAscent = function () { if (this._textAscent === null) { this._updateTextMetrics(); } return this._textAscent; }; -p5.Renderer.prototype.textDescent = function() { +p5.Renderer.prototype.textDescent = function () { if (this._textDescent === null) { this._updateTextMetrics(); } return this._textDescent; }; -p5.Renderer.prototype.textAlign = function(h, v) { +p5.Renderer.prototype.textAlign = function (h, v) { if (typeof h !== 'undefined') { this._setProperty('_textAlign', h); @@ -216,12 +216,12 @@ p5.Renderer.prototype.textAlign = function(h, v) { } }; -p5.Renderer.prototype.textWrap = function(wrapStyle) { +p5.Renderer.prototype.textWrap = function (wrapStyle) { this._setProperty('_textWrap', wrapStyle); return this._textWrap; }; -p5.Renderer.prototype.text = function(str, x, y, maxWidth, maxHeight) { +p5.Renderer.prototype.text = function (str, x, y, maxWidth, maxHeight) { const p = this._pInst; const textWrapStyle = this._textWrap; @@ -452,18 +452,18 @@ p5.Renderer.prototype.text = function(str, x, y, maxWidth, maxHeight) { return p; }; -p5.Renderer.prototype._applyDefaults = function() { +p5.Renderer.prototype._applyDefaults = function () { return this; }; /** * Helper function to check font type (system or otf) */ -p5.Renderer.prototype._isOpenType = function(f = this._textFont) { +p5.Renderer.prototype._isOpenType = function (f = this._textFont) { return typeof f === 'object' && f.font && f.font.supported; }; -p5.Renderer.prototype._updateTextMetrics = function() { +p5.Renderer.prototype._updateTextMetrics = function () { if (this._isOpenType()) { this._setProperty('_textAscent', this._textFont._textAscent()); this._setProperty('_textDescent', this._textFont._textDescent()); diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index a6f0ed534c..1e0875f8da 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -11,7 +11,7 @@ import './p5.Renderer'; const styleEmpty = 'rgba(0,0,0,0)'; // const alphaThreshold = 0.00125; // minimum visible -p5.Renderer2D = function(elt, pInst, isMainCanvas) { +p5.Renderer2D = function (elt, pInst, isMainCanvas) { p5.Renderer.call(this, elt, pInst, isMainCanvas); this.drawingContext = this.canvas.getContext('2d'); this._pInst._setProperty('drawingContext', this.drawingContext); @@ -20,7 +20,7 @@ p5.Renderer2D = function(elt, pInst, isMainCanvas) { p5.Renderer2D.prototype = Object.create(p5.Renderer.prototype); -p5.Renderer2D.prototype._applyDefaults = function() { +p5.Renderer2D.prototype._applyDefaults = function () { this._cachedFillStyle = this._cachedStrokeStyle = undefined; this._cachedBlendMode = constants.BLEND; this._setFill(constants._DEFAULT_FILL); @@ -29,7 +29,7 @@ p5.Renderer2D.prototype._applyDefaults = function() { this.drawingContext.font = 'normal 12px sans-serif'; }; -p5.Renderer2D.prototype.resize = function(w, h) { +p5.Renderer2D.prototype.resize = function (w, h) { p5.Renderer.prototype.resize.call(this, w, h); this.drawingContext.scale( this._pInst._pixelDensity, @@ -41,7 +41,7 @@ p5.Renderer2D.prototype.resize = function(w, h) { // COLOR | Setting ////////////////////////////////////////////// -p5.Renderer2D.prototype.background = function(...args) { +p5.Renderer2D.prototype.background = function (...args) { this.drawingContext.save(); this.resetMatrix(); @@ -82,14 +82,14 @@ p5.Renderer2D.prototype.background = function(...args) { this.drawingContext.restore(); }; -p5.Renderer2D.prototype.clear = function() { +p5.Renderer2D.prototype.clear = function () { this.drawingContext.save(); this.resetMatrix(); this.drawingContext.clearRect(0, 0, this.width, this.height); this.drawingContext.restore(); }; -p5.Renderer2D.prototype.fill = function(...args) { +p5.Renderer2D.prototype.fill = function (...args) { const color = this._pInst.color(...args); this._setFill(color.toString()); @@ -99,7 +99,7 @@ p5.Renderer2D.prototype.fill = function(...args) { } }; -p5.Renderer2D.prototype.stroke = function(...args) { +p5.Renderer2D.prototype.stroke = function (...args) { const color = this._pInst.color(...args); this._setStroke(color.toString()); @@ -109,7 +109,7 @@ p5.Renderer2D.prototype.stroke = function(...args) { } }; -p5.Renderer2D.prototype.erase = function(opacityFill, opacityStroke) { +p5.Renderer2D.prototype.erase = function (opacityFill, opacityStroke) { if (!this._isErasing) { // cache the fill style this._cachedFillStyle = this.drawingContext.fillStyle; @@ -130,7 +130,7 @@ p5.Renderer2D.prototype.erase = function(opacityFill, opacityStroke) { } }; -p5.Renderer2D.prototype.noErase = function() { +p5.Renderer2D.prototype.noErase = function () { if (this._isErasing) { this.drawingContext.fillStyle = this._cachedFillStyle; this.drawingContext.strokeStyle = this._cachedStrokeStyle; @@ -144,7 +144,7 @@ p5.Renderer2D.prototype.noErase = function() { // IMAGE | Loading & Displaying ////////////////////////////////////////////// -p5.Renderer2D.prototype.image = function( +p5.Renderer2D.prototype.image = function ( img, sx, sy, @@ -198,7 +198,7 @@ p5.Renderer2D.prototype.image = function( } }; -p5.Renderer2D.prototype._getTintedImageCanvas = function(img) { +p5.Renderer2D.prototype._getTintedImageCanvas = function (img) { if (!img.canvas) { return img; } @@ -268,7 +268,7 @@ p5.Renderer2D.prototype._getTintedImageCanvas = function(img) { // IMAGE | Pixels ////////////////////////////////////////////// -p5.Renderer2D.prototype.blendMode = function(mode) { +p5.Renderer2D.prototype.blendMode = function (mode) { if (mode === constants.SUBTRACT) { console.warn('blendMode(SUBTRACT) only works in WEBGL mode.'); } else if ( @@ -295,7 +295,7 @@ p5.Renderer2D.prototype.blendMode = function(mode) { } }; -p5.Renderer2D.prototype.blend = function(...args) { +p5.Renderer2D.prototype.blend = function (...args) { const currBlend = this.drawingContext.globalCompositeOperation; const blendMode = args[args.length - 1]; @@ -312,7 +312,7 @@ p5.Renderer2D.prototype.blend = function(...args) { // .get() is not overridden // x,y are canvas-relative (pre-scaled by _pixelDensity) -p5.Renderer2D.prototype._getPixel = function(x, y) { +p5.Renderer2D.prototype._getPixel = function (x, y) { let imageData, index; imageData = this.drawingContext.getImageData(x, y, 1, 1).data; index = 0; @@ -324,7 +324,7 @@ p5.Renderer2D.prototype._getPixel = function(x, y) { ]; }; -p5.Renderer2D.prototype.loadPixels = function() { +p5.Renderer2D.prototype.loadPixels = function () { const pixelsState = this._pixelsState; // if called by p5.Image const pd = pixelsState._pixelDensity; @@ -337,7 +337,7 @@ p5.Renderer2D.prototype.loadPixels = function() { pixelsState._setProperty('pixels', imageData.data); }; -p5.Renderer2D.prototype.set = function(x, y, imgOrCol) { +p5.Renderer2D.prototype.set = function (x, y, imgOrCol) { // round down to get integer numbers x = Math.floor(x); y = Math.floor(y); @@ -413,7 +413,7 @@ p5.Renderer2D.prototype.set = function(x, y, imgOrCol) { } }; -p5.Renderer2D.prototype.updatePixels = function(x, y, w, h) { +p5.Renderer2D.prototype.updatePixels = function (x, y, w, h) { const pixelsState = this._pixelsState; const pd = pixelsState._pixelDensity; if ( @@ -487,7 +487,7 @@ p5.Renderer2D.prototype._acuteArcToBezier = function _acuteArcToBezier( * * start <= stop < start + TWO_PI */ -p5.Renderer2D.prototype.arc = function(x, y, w, h, start, stop, mode) { +p5.Renderer2D.prototype.arc = function (x, y, w, h, start, stop, mode) { const ctx = this.drawingContext; const rx = w / 2.0; const ry = h / 2.0; @@ -514,8 +514,8 @@ p5.Renderer2D.prototype.arc = function(x, y, w, h, start, stop, mode) { } /* eslint-disable indent */ ctx.bezierCurveTo(x + curve.bx * rx, y + curve.by * ry, - x + curve.cx * rx, y + curve.cy * ry, - x + curve.dx * rx, y + curve.dy * ry); + x + curve.cx * rx, y + curve.cy * ry, + x + curve.dx * rx, y + curve.dy * ry); /* eslint-enable indent */ }); if (mode === constants.PIE || mode == null) { @@ -534,8 +534,8 @@ p5.Renderer2D.prototype.arc = function(x, y, w, h, start, stop, mode) { } /* eslint-disable indent */ ctx.bezierCurveTo(x + curve.bx * rx, y + curve.by * ry, - x + curve.cx * rx, y + curve.cy * ry, - x + curve.dx * rx, y + curve.dy * ry); + x + curve.cx * rx, y + curve.cy * ry, + x + curve.dx * rx, y + curve.dy * ry); /* eslint-enable indent */ }); if (mode === constants.PIE) { @@ -549,7 +549,7 @@ p5.Renderer2D.prototype.arc = function(x, y, w, h, start, stop, mode) { return this; }; -p5.Renderer2D.prototype.ellipse = function(args) { +p5.Renderer2D.prototype.ellipse = function (args) { const ctx = this.drawingContext; const doFill = this._doFill, doStroke = this._doStroke; @@ -592,7 +592,7 @@ p5.Renderer2D.prototype.ellipse = function(args) { } }; -p5.Renderer2D.prototype.line = function(x1, y1, x2, y2) { +p5.Renderer2D.prototype.line = function (x1, y1, x2, y2) { const ctx = this.drawingContext; if (!this._doStroke) { return this; @@ -606,7 +606,7 @@ p5.Renderer2D.prototype.line = function(x1, y1, x2, y2) { return this; }; -p5.Renderer2D.prototype.point = function(x, y) { +p5.Renderer2D.prototype.point = function (x, y) { const ctx = this.drawingContext; if (!this._doStroke) { return this; @@ -623,7 +623,7 @@ p5.Renderer2D.prototype.point = function(x, y) { this._setFill(f); }; -p5.Renderer2D.prototype.quad = function(x1, y1, x2, y2, x3, y3, x4, y4) { +p5.Renderer2D.prototype.quad = function (x1, y1, x2, y2, x3, y3, x4, y4) { const ctx = this.drawingContext; const doFill = this._doFill, doStroke = this._doStroke; @@ -651,7 +651,7 @@ p5.Renderer2D.prototype.quad = function(x1, y1, x2, y2, x3, y3, x4, y4) { return this; }; -p5.Renderer2D.prototype.rect = function(args) { +p5.Renderer2D.prototype.rect = function (args) { const x = args[0]; const y = args[1]; const w = args[2]; @@ -740,7 +740,7 @@ p5.Renderer2D.prototype.rect = function(args) { return this; }; -p5.Renderer2D.prototype.triangle = function(args) { +p5.Renderer2D.prototype.triangle = function (args) { const ctx = this.drawingContext; const doFill = this._doFill, doStroke = this._doStroke; @@ -772,7 +772,7 @@ p5.Renderer2D.prototype.triangle = function(args) { } }; -p5.Renderer2D.prototype.endShape = function( +p5.Renderer2D.prototype.endShape = function ( mode, vertices, isCurve, @@ -809,7 +809,7 @@ p5.Renderer2D.prototype.endShape = function( ]; b[2] = [ vertices[i + 1][0] + - (s * vertices[i][0] - s * vertices[i + 2][0]) / 6, + (s * vertices[i][0] - s * vertices[i + 2][0]) / 6, vertices[i + 1][1] + (s * vertices[i][1] - s * vertices[i + 2][1]) / 6 ]; b[3] = [vertices[i + 1][0], vertices[i + 1][1]]; @@ -1034,7 +1034,7 @@ p5.Renderer2D.prototype.endShape = function( // SHAPE | Attributes ////////////////////////////////////////////// -p5.Renderer2D.prototype.strokeCap = function(cap) { +p5.Renderer2D.prototype.strokeCap = function (cap) { if ( cap === constants.ROUND || cap === constants.SQUARE || @@ -1045,7 +1045,7 @@ p5.Renderer2D.prototype.strokeCap = function(cap) { return this; }; -p5.Renderer2D.prototype.strokeJoin = function(join) { +p5.Renderer2D.prototype.strokeJoin = function (join) { if ( join === constants.ROUND || join === constants.BEVEL || @@ -1056,7 +1056,7 @@ p5.Renderer2D.prototype.strokeJoin = function(join) { return this; }; -p5.Renderer2D.prototype.strokeWeight = function(w) { +p5.Renderer2D.prototype.strokeWeight = function (w) { if (typeof w === 'undefined' || w === 0) { // hack because lineWidth 0 doesn't work this.drawingContext.lineWidth = 0.0001; @@ -1066,28 +1066,28 @@ p5.Renderer2D.prototype.strokeWeight = function(w) { return this; }; -p5.Renderer2D.prototype._getFill = function() { +p5.Renderer2D.prototype._getFill = function () { if (!this._cachedFillStyle) { this._cachedFillStyle = this.drawingContext.fillStyle; } return this._cachedFillStyle; }; -p5.Renderer2D.prototype._setFill = function(fillStyle) { +p5.Renderer2D.prototype._setFill = function (fillStyle) { if (fillStyle !== this._cachedFillStyle) { this.drawingContext.fillStyle = fillStyle; this._cachedFillStyle = fillStyle; } }; -p5.Renderer2D.prototype._getStroke = function() { +p5.Renderer2D.prototype._getStroke = function () { if (!this._cachedStrokeStyle) { this._cachedStrokeStyle = this.drawingContext.strokeStyle; } return this._cachedStrokeStyle; }; -p5.Renderer2D.prototype._setStroke = function(strokeStyle) { +p5.Renderer2D.prototype._setStroke = function (strokeStyle) { if (strokeStyle !== this._cachedStrokeStyle) { this.drawingContext.strokeStyle = strokeStyle; this._cachedStrokeStyle = strokeStyle; @@ -1097,7 +1097,7 @@ p5.Renderer2D.prototype._setStroke = function(strokeStyle) { ////////////////////////////////////////////// // SHAPE | Curves ////////////////////////////////////////////// -p5.Renderer2D.prototype.bezier = function(x1, y1, x2, y2, x3, y3, x4, y4) { +p5.Renderer2D.prototype.bezier = function (x1, y1, x2, y2, x3, y3, x4, y4) { this._pInst.beginShape(); this._pInst.vertex(x1, y1); this._pInst.bezierVertex(x2, y2, x3, y3, x4, y4); @@ -1105,7 +1105,7 @@ p5.Renderer2D.prototype.bezier = function(x1, y1, x2, y2, x3, y3, x4, y4) { return this; }; -p5.Renderer2D.prototype.curve = function(x1, y1, x2, y2, x3, y3, x4, y4) { +p5.Renderer2D.prototype.curve = function (x1, y1, x2, y2, x3, y3, x4, y4) { this._pInst.beginShape(); this._pInst.curveVertex(x1, y1); this._pInst.curveVertex(x2, y2); @@ -1119,7 +1119,7 @@ p5.Renderer2D.prototype.curve = function(x1, y1, x2, y2, x3, y3, x4, y4) { // SHAPE | Vertex ////////////////////////////////////////////// -p5.Renderer2D.prototype._doFillStrokeClose = function(closeShape) { +p5.Renderer2D.prototype._doFillStrokeClose = function (closeShape) { if (closeShape) { this.drawingContext.closePath(); } @@ -1135,11 +1135,11 @@ p5.Renderer2D.prototype._doFillStrokeClose = function(closeShape) { // TRANSFORM ////////////////////////////////////////////// -p5.Renderer2D.prototype.applyMatrix = function(a, b, c, d, e, f) { +p5.Renderer2D.prototype.applyMatrix = function (a, b, c, d, e, f) { this.drawingContext.transform(a, b, c, d, e, f); }; -p5.Renderer2D.prototype.resetMatrix = function() { +p5.Renderer2D.prototype.resetMatrix = function () { this.drawingContext.setTransform(1, 0, 0, 1, 0, 0); this.drawingContext.scale( this._pInst._pixelDensity, @@ -1148,16 +1148,16 @@ p5.Renderer2D.prototype.resetMatrix = function() { return this; }; -p5.Renderer2D.prototype.rotate = function(rad) { +p5.Renderer2D.prototype.rotate = function (rad) { this.drawingContext.rotate(rad); }; -p5.Renderer2D.prototype.scale = function(x, y) { +p5.Renderer2D.prototype.scale = function (x, y) { this.drawingContext.scale(x, y); return this; }; -p5.Renderer2D.prototype.translate = function(x, y) { +p5.Renderer2D.prototype.translate = function (x, y) { // support passing a vector as the 1st parameter if (x instanceof p5.Vector) { y = x.y; @@ -1172,7 +1172,7 @@ p5.Renderer2D.prototype.translate = function(x, y) { // ////////////////////////////////////////////// -p5.Renderer2D.prototype.text = function(str, x, y, maxWidth, maxHeight) { +p5.Renderer2D.prototype.text = function (str, x, y, maxWidth, maxHeight) { let baselineHacked; // baselineHacked: (HACK) @@ -1195,7 +1195,7 @@ p5.Renderer2D.prototype.text = function(str, x, y, maxWidth, maxHeight) { return p; }; -p5.Renderer2D.prototype._renderText = function(p, line, x, y, maxY, minY) { +p5.Renderer2D.prototype._renderText = function (p, line, x, y, maxY, minY) { if (y < minY || y >= maxY) { return; // don't render lines beyond our minY/maxY bounds (see #5785) } @@ -1228,7 +1228,7 @@ p5.Renderer2D.prototype._renderText = function(p, line, x, y, maxY, minY) { return p; }; -p5.Renderer2D.prototype.textWidth = function(s) { +p5.Renderer2D.prototype.textWidth = function (s) { if (this._isOpenType()) { return this._textFont._textWidth(s, this._textSize); } @@ -1236,7 +1236,7 @@ p5.Renderer2D.prototype.textWidth = function(s) { return this.drawingContext.measureText(s).width; }; -p5.Renderer2D.prototype._applyTextProperties = function() { +p5.Renderer2D.prototype._applyTextProperties = function () { let font; const p = this._pInst; @@ -1272,7 +1272,7 @@ p5.Renderer2D.prototype._applyTextProperties = function() { // store on the push stack. // derived renderers should call the base class' push() method // to fetch the base style object. -p5.Renderer2D.prototype.push = function() { +p5.Renderer2D.prototype.push = function () { this.drawingContext.save(); // get the base renderer style @@ -1284,7 +1284,7 @@ p5.Renderer2D.prototype.push = function() { // from its push() method. // derived renderers should pass this object to their base // class' pop method -p5.Renderer2D.prototype.pop = function(style) { +p5.Renderer2D.prototype.pop = function (style) { this.drawingContext.restore(); // Re-cache the fill / stroke state this._cachedFillStyle = this.drawingContext.fillStyle; diff --git a/src/data/p5.TypedDict.js b/src/data/p5.TypedDict.js index 6df8d1d01f..449887157b 100644 --- a/src/data/p5.TypedDict.js +++ b/src/data/p5.TypedDict.js @@ -40,7 +40,7 @@ import p5 from '../core/main'; * @return {p5.StringDict} */ -p5.prototype.createStringDict = function(key, value) { +p5.prototype.createStringDict = function (key, value) { p5._validateParameters('createStringDict', arguments); return new p5.StringDict(key, value); }; @@ -74,7 +74,7 @@ p5.prototype.createStringDict = function(key, value) { * @return {p5.NumberDict} */ -p5.prototype.createNumberDict = function(key, value) { +p5.prototype.createNumberDict = function (key, value) { p5._validateParameters('createNumberDict', arguments); return new p5.NumberDict(key, value); }; @@ -88,309 +88,312 @@ p5.prototype.createNumberDict = function(key, value) { * @constructor */ -p5.TypedDict = function(key, value) { - if (key instanceof Object) { - this.data = key; - } else { - this.data = {}; - this.data[key] = value; +p5.TypedDict = class TypedDict { + constructor(key, value) { + if (key instanceof Object) { + this.data = key; + } else { + this.data = {}; + this.data[key] = value; + } + return this; } - return this; -}; - -/** - * Returns the number of key-value pairs currently stored in the Dictionary. - * - * @method size - * @return {Integer} the number of key-value pairs in the Dictionary - * - * @example - *
- * - * function setup() { - * let myDictionary = createNumberDict(1, 10); - * myDictionary.create(2, 20); - * myDictionary.create(3, 30); - * print(myDictionary.size()); // logs 3 to the console - * } - *
- */ -p5.TypedDict.prototype.size = function() { - return Object.keys(this.data).length; -}; - -/** - * Returns true if the given key exists in the Dictionary, - * otherwise returns false. - * - * @method hasKey - * @param {Number|String} key that you want to look up - * @return {Boolean} whether that key exists in Dictionary - * - * @example - *
- * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * print(myDictionary.hasKey('p5')); // logs true to console - * } - *
- */ -p5.TypedDict.prototype.hasKey = function(key) { - return this.data.hasOwnProperty(key); -}; - -/** - * Returns the value stored at the given key. - * - * @method get - * @param {Number|String} the key you want to access - * @return {Number|String} the value stored at that key - * - * @example - *
- * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * let myValue = myDictionary.get('p5'); - * print(myValue === 'js'); // logs true to console - * } - *
- */ + /** + * Returns the number of key-value pairs currently stored in the Dictionary. + * + * @method size + * @return {Integer} the number of key-value pairs in the Dictionary + * + * @example + *
+ * + * function setup() { + * let myDictionary = createNumberDict(1, 10); + * myDictionary.create(2, 20); + * myDictionary.create(3, 30); + * print(myDictionary.size()); // logs 3 to the console + * } + *
+ */ + size() { + return Object.keys(this.data).length; + } -p5.TypedDict.prototype.get = function(key) { - if (this.data.hasOwnProperty(key)) { - return this.data[key]; - } else { - console.log(`${key} does not exist in this Dictionary`); + /** + * Returns true if the given key exists in the Dictionary, + * otherwise returns false. + * + * @method hasKey + * @param {Number|String} key that you want to look up + * @return {Boolean} whether that key exists in Dictionary + * + * @example + *
+ * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * print(myDictionary.hasKey('p5')); // logs true to console + * } + *
+ */ + + hasKey(key) { + return this.data.hasOwnProperty(key); } -}; -/** - * Updates the value associated with the given key in case it already exists - * in the Dictionary. Otherwise a new key-value pair is added. - * - * @method set - * @param {Number|String} key - * @param {Number|String} value - * - * @example - *
- * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * myDictionary.set('p5', 'JS'); - * myDictionary.print(); // logs "key: p5 - value: JS" to console - * } - *
- */ + /** + * Returns the value stored at the given key. + * + * @method get + * @param {Number|String} the key you want to access + * @return {Number|String} the value stored at that key + * + * @example + *
+ * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * let myValue = myDictionary.get('p5'); + * print(myValue === 'js'); // logs true to console + * } + *
+ */ + + get(key) { + if (this.data.hasOwnProperty(key)) { + return this.data[key]; + } else { + console.log(`${key} does not exist in this Dictionary`); + } + } -p5.TypedDict.prototype.set = function(key, value) { - if (this._validate(value)) { - this.data[key] = value; - } else { - console.log('Those values dont work for this dictionary type.'); + /** + * Updates the value associated with the given key in case it already exists + * in the Dictionary. Otherwise a new key-value pair is added. + * + * @method set + * @param {Number|String} key + * @param {Number|String} value + * + * @example + *
+ * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * myDictionary.set('p5', 'JS'); + * myDictionary.print(); // logs "key: p5 - value: JS" to console + * } + *
+ */ + + set(key, value) { + if (this._validate(value)) { + this.data[key] = value; + } else { + console.log('Those values dont work for this dictionary type.'); + } } -}; -/** - * private helper function to handle the user passing in objects - * during construction or calls to create() - */ + /** + * private helper function to handle the user passing in objects + * during construction or calls to create() + */ -p5.TypedDict.prototype._addObj = function(obj) { - for (const key in obj) { - this.set(key, obj[key]); + _addObj(obj) { + for (const key in obj) { + this.set(key, obj[key]); + } } -}; - -/** - * Creates a new key-value pair in the Dictionary. - * - * @method create - * @param {Number|String} key - * @param {Number|String} value - * - * @example - *
- * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * myDictionary.create('happy', 'coding'); - * myDictionary.print(); - * // above logs "key: p5 - value: js, key: happy - value: coding" to console - * } - *
- */ -/** - * @method create - * @param {Object} obj key/value pair - */ -p5.TypedDict.prototype.create = function(key, value) { - if (key instanceof Object && typeof value === 'undefined') { - this._addObj(key); - } else if (typeof key !== 'undefined') { - this.set(key, value); - } else { - console.log( - 'In order to create a new Dictionary entry you must pass ' + + /** + * Creates a new key-value pair in the Dictionary. + * + * @method create + * @param {Number|String} key + * @param {Number|String} value + * + * @example + *
+ * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * myDictionary.create('happy', 'coding'); + * myDictionary.print(); + * // above logs "key: p5 - value: js, key: happy - value: coding" to console + * } + *
+ */ + /** + * @method create + * @param {Object} obj key/value pair + */ + + create(key, value) { + if (key instanceof Object && typeof value === 'undefined') { + this._addObj(key); + } else if (typeof key !== 'undefined') { + this.set(key, value); + } else { + console.log( + 'In order to create a new Dictionary entry you must pass ' + 'an object or a key, value pair' - ); + ); + } } -}; - -/** - * Removes all previously stored key-value pairs from the Dictionary. - * - * @method clear - * @example - *
- * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * print(myDictionary.hasKey('p5')); // prints 'true' - * myDictionary.clear(); - * print(myDictionary.hasKey('p5')); // prints 'false' - * } - * - *
- */ - -p5.TypedDict.prototype.clear = function() { - this.data = {}; -}; - -/** - * Removes the key-value pair stored at the given key from the Dictionary. - * - * @method remove - * @param {Number|String} key for the pair to remove - * - * @example - *
- * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * myDictionary.create('happy', 'coding'); - * myDictionary.print(); - * // above logs "key: p5 - value: js, key: happy - value: coding" to console - * myDictionary.remove('p5'); - * myDictionary.print(); - * // above logs "key: happy value: coding" to console - * } - *
- */ -p5.TypedDict.prototype.remove = function(key) { - if (this.data.hasOwnProperty(key)) { - delete this.data[key]; - } else { - throw new Error(`${key} does not exist in this Dictionary`); + /** + * Removes all previously stored key-value pairs from the Dictionary. + * + * @method clear + * @example + *
+ * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * print(myDictionary.hasKey('p5')); // prints 'true' + * myDictionary.clear(); + * print(myDictionary.hasKey('p5')); // prints 'false' + * } + * + *
+ */ + + clear() { + this.data = {}; } -}; -/** - * Logs the set of items currently stored in the Dictionary to the console. - * - * @method print - * - * @example - *
- * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * myDictionary.create('happy', 'coding'); - * myDictionary.print(); - * // above logs "key: p5 - value: js, key: happy - value: coding" to console - * } - * - *
- */ + /** + * Removes the key-value pair stored at the given key from the Dictionary. + * + * @method remove + * @param {Number|String} key for the pair to remove + * + * @example + *
+ * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * myDictionary.create('happy', 'coding'); + * myDictionary.print(); + * // above logs "key: p5 - value: js, key: happy - value: coding" to console + * myDictionary.remove('p5'); + * myDictionary.print(); + * // above logs "key: happy value: coding" to console + * } + *
+ */ + + remove(key) { + if (this.data.hasOwnProperty(key)) { + delete this.data[key]; + } else { + throw new Error(`${key} does not exist in this Dictionary`); + } + } -p5.TypedDict.prototype.print = function() { - for (const item in this.data) { - console.log(`key:${item} value:${this.data[item]}`); + /** + * Logs the set of items currently stored in the Dictionary to the console. + * + * @method print + * + * @example + *
+ * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * myDictionary.create('happy', 'coding'); + * myDictionary.print(); + * // above logs "key: p5 - value: js, key: happy - value: coding" to console + * } + * + *
+ */ + + print() { + for (const item in this.data) { + console.log(`key:${item} value:${this.data[item]}`); + } } -}; -/** - * Converts the Dictionary into a CSV file for local download. - * - * @method saveTable - * @example - *
- * - * function setup() { - * createCanvas(100, 100); - * background(200); - * text('click here to save', 10, 10, 70, 80); - * } - * - * function mousePressed() { - * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { - * createStringDict({ - * john: 1940, - * paul: 1942, - * george: 1943, - * ringo: 1940 - * }).saveTable('beatles'); - * } - * } - * - *
- */ + /** + * Converts the Dictionary into a CSV file for local download. + * + * @method saveTable + * @example + *
+ * + * function setup() { + * createCanvas(100, 100); + * background(200); + * text('click here to save', 10, 10, 70, 80); + * } + * + * function mousePressed() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * createStringDict({ + * john: 1940, + * paul: 1942, + * george: 1943, + * ringo: 1940 + * }).saveTable('beatles'); + * } + * } + * + *
+ */ + + saveTable(filename) { + let output = ''; -p5.TypedDict.prototype.saveTable = function(filename) { - let output = ''; + for (const key in this.data) { + output += `${key},${this.data[key]}\n`; + } - for (const key in this.data) { - output += `${key},${this.data[key]}\n`; + const blob = new Blob([output], { type: 'text/csv' }); + p5.prototype.downloadFile(blob, filename || 'mycsv', 'csv'); } - const blob = new Blob([output], { type: 'text/csv' }); - p5.prototype.downloadFile(blob, filename || 'mycsv', 'csv'); -}; - -/** - * Converts the Dictionary into a JSON file for local download. - * - * @method saveJSON - * @example - *
- * - * function setup() { - * createCanvas(100, 100); - * background(200); - * text('click here to save', 10, 10, 70, 80); - * } - * - * function mousePressed() { - * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { - * createStringDict({ - * john: 1940, - * paul: 1942, - * george: 1943, - * ringo: 1940 - * }).saveJSON('beatles'); - * } - * } - * - *
- */ + /** + * Converts the Dictionary into a JSON file for local download. + * + * @method saveJSON + * @example + *
+ * + * function setup() { + * createCanvas(100, 100); + * background(200); + * text('click here to save', 10, 10, 70, 80); + * } + * + * function mousePressed() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * createStringDict({ + * john: 1940, + * paul: 1942, + * george: 1943, + * ringo: 1940 + * }).saveJSON('beatles'); + * } + * } + * + *
+ */ + + saveJSON(filename, opt) { + p5.prototype.saveJSON(this.data, filename, opt); + } -p5.TypedDict.prototype.saveJSON = function(filename, opt) { - p5.prototype.saveJSON(this.data, filename, opt); + /** + * private helper function to ensure that the user passed in valid + * values for the Dictionary type + */ + _validate(value) { + return true; + } }; -/** - * private helper function to ensure that the user passed in valid - * values for the Dictionary type - */ - -p5.TypedDict.prototype._validate = value => true; - /** * * A simple Dictionary class for Strings. @@ -399,13 +402,15 @@ p5.TypedDict.prototype._validate = value => true; * @extends p5.TypedDict */ -p5.StringDict = function(...args) { - p5.TypedDict.apply(this, args); -}; - -p5.StringDict.prototype = Object.create(p5.TypedDict.prototype); +p5.StringDict = class StringDict extends p5.TypedDict { + constructor(...args) { + super(...args); + } -p5.StringDict.prototype._validate = value => typeof value === 'string'; + _validate(value) { + return typeof value === 'string'; + } +}; /** * @@ -416,247 +421,250 @@ p5.StringDict.prototype._validate = value => typeof value === 'string'; * @extends p5.TypedDict */ -p5.NumberDict = function(...args) { - p5.TypedDict.apply(this, args); -}; - -p5.NumberDict.prototype = Object.create(p5.TypedDict.prototype); - -/** - * private helper function to ensure that the user passed in valid - * values for the Dictionary type - */ - -p5.NumberDict.prototype._validate = value => typeof value === 'number'; - -/** - * Add the given number to the value currently stored at the given key. - * The sum then replaces the value previously stored in the Dictionary. - * - * @method add - * @param {Number} Key for the value you wish to add to - * @param {Number} Number to add to the value - * @example - *
- * - * function setup() { - * let myDictionary = createNumberDict(2, 5); - * myDictionary.add(2, 2); - * print(myDictionary.get(2)); // logs 7 to console. - * } - *
- * - */ - -p5.NumberDict.prototype.add = function(key, amount) { - if (this.data.hasOwnProperty(key)) { - this.data[key] += amount; - } else { - console.log(`The key - ${key} does not exist in this dictionary.`); +p5.NumberDict = class NumberDict extends p5.TypedDict { + constructor(...args) { + super(...args); } -}; -/** - * Subtract the given number from the value currently stored at the given key. - * The difference then replaces the value previously stored in the Dictionary. - * - * @method sub - * @param {Number} Key for the value you wish to subtract from - * @param {Number} Number to subtract from the value - * @example - *
- * - * function setup() { - * let myDictionary = createNumberDict(2, 5); - * myDictionary.sub(2, 2); - * print(myDictionary.get(2)); // logs 3 to console. - * } - *
- * - */ -p5.NumberDict.prototype.sub = function(key, amount) { - this.add(key, -amount); -}; + /** + * private helper function to ensure that the user passed in valid + * values for the Dictionary type + */ -/** - * Multiply the given number with the value currently stored at the given key. - * The product then replaces the value previously stored in the Dictionary. - * - * @method mult - * @param {Number} Key for value you wish to multiply - * @param {Number} Amount to multiply the value by - * @example - *
- * - * function setup() { - * let myDictionary = createNumberDict(2, 4); - * myDictionary.mult(2, 2); - * print(myDictionary.get(2)); // logs 8 to console. - * } - *
- * - */ + _validate(value) { + return typeof value === 'number'; + } -p5.NumberDict.prototype.mult = function(key, amount) { - if (this.data.hasOwnProperty(key)) { - this.data[key] *= amount; - } else { - console.log(`The key - ${key} does not exist in this dictionary.`); + /** + * Add the given number to the value currently stored at the given key. + * The sum then replaces the value previously stored in the Dictionary. + * + * @method add + * @param {Number} Key for the value you wish to add to + * @param {Number} Number to add to the value + * @example + *
+ * + * function setup() { + * let myDictionary = createNumberDict(2, 5); + * myDictionary.add(2, 2); + * print(myDictionary.get(2)); // logs 7 to console. + * } + *
+ * + */ + + add(key, amount) { + if (this.data.hasOwnProperty(key)) { + this.data[key] += amount; + } else { + console.log(`The key - ${key} does not exist in this dictionary.`); + } } -}; -/** - * Divide the given number with the value currently stored at the given key. - * The quotient then replaces the value previously stored in the Dictionary. - * - * @method div - * @param {Number} Key for value you wish to divide - * @param {Number} Amount to divide the value by - * @example - *
- * - * function setup() { - * let myDictionary = createNumberDict(2, 8); - * myDictionary.div(2, 2); - * print(myDictionary.get(2)); // logs 4 to console. - * } - *
- * - */ + /** + * Subtract the given number from the value currently stored at the given key. + * The difference then replaces the value previously stored in the Dictionary. + * + * @method sub + * @param {Number} Key for the value you wish to subtract from + * @param {Number} Number to subtract from the value + * @example + *
+ * + * function setup() { + * let myDictionary = createNumberDict(2, 5); + * myDictionary.sub(2, 2); + * print(myDictionary.get(2)); // logs 3 to console. + * } + *
+ * + */ + + sub(key, amount) { + this.add(key, -amount); + } -p5.NumberDict.prototype.div = function(key, amount) { - if (this.data.hasOwnProperty(key)) { - this.data[key] /= amount; - } else { - console.log(`The key - ${key} does not exist in this dictionary.`); + /** + * Multiply the given number with the value currently stored at the given key. + * The product then replaces the value previously stored in the Dictionary. + * + * @method mult + * @param {Number} Key for value you wish to multiply + * @param {Number} Amount to multiply the value by + * @example + *
+ * + * function setup() { + * let myDictionary = createNumberDict(2, 4); + * myDictionary.mult(2, 2); + * print(myDictionary.get(2)); // logs 8 to console. + * } + *
+ * + */ + + mult(key, amount) { + if (this.data.hasOwnProperty(key)) { + this.data[key] *= amount; + } else { + console.log(`The key - ${key} does not exist in this dictionary.`); + } } -}; -/** - * private helper function for finding lowest or highest value - * the argument 'flip' is used to flip the comparison arrow - * from 'less than' to 'greater than' - */ + /** + * Divide the given number with the value currently stored at the given key. + * The quotient then replaces the value previously stored in the Dictionary. + * + * @method div + * @param {Number} Key for value you wish to divide + * @param {Number} Amount to divide the value by + * @example + *
+ * + * function setup() { + * let myDictionary = createNumberDict(2, 8); + * myDictionary.div(2, 2); + * print(myDictionary.get(2)); // logs 4 to console. + * } + *
+ * + */ + + div(key, amount) { + if (this.data.hasOwnProperty(key)) { + this.data[key] /= amount; + } else { + console.log(`The key - ${key} does not exist in this dictionary.`); + } + } -p5.NumberDict.prototype._valueTest = function(flip) { - if (Object.keys(this.data).length === 0) { - throw new Error( - 'Unable to search for a minimum or maximum value on an empty NumberDict' - ); - } else if (Object.keys(this.data).length === 1) { - return this.data[Object.keys(this.data)[0]]; - } else { - let result = this.data[Object.keys(this.data)[0]]; - for (const key in this.data) { - if (this.data[key] * flip < result * flip) { - result = this.data[key]; + /** + * private helper function for finding lowest or highest value + * the argument 'flip' is used to flip the comparison arrow + * from 'less than' to 'greater than' + */ + + _valueTest(flip) { + if (Object.keys(this.data).length === 0) { + throw new Error( + 'Unable to search for a minimum or maximum value on an empty NumberDict' + ); + } else if (Object.keys(this.data).length === 1) { + return this.data[Object.keys(this.data)[0]]; + } else { + let result = this.data[Object.keys(this.data)[0]]; + for (const key in this.data) { + if (this.data[key] * flip < result * flip) { + result = this.data[key]; + } } + return result; } - return result; } -}; - -/** - * Return the lowest number currently stored in the Dictionary. - * - * @method minValue - * @return {Number} - * @example - *
- * - * function setup() { - * let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 }); - * let lowestValue = myDictionary.minValue(); // value is -10 - * print(lowestValue); - * } - *
- */ - -p5.NumberDict.prototype.minValue = function() { - return this._valueTest(1); -}; - -/** - * Return the highest number currently stored in the Dictionary. - * - * @method maxValue - * @return {Number} - * @example - *
- * - * function setup() { - * let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 }); - * let highestValue = myDictionary.maxValue(); // value is 3 - * print(highestValue); - * } - *
- */ -p5.NumberDict.prototype.maxValue = function() { - return this._valueTest(-1); -}; + /** + * Return the lowest number currently stored in the Dictionary. + * + * @method minValue + * @return {Number} + * @example + *
+ * + * function setup() { + * let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 }); + * let lowestValue = myDictionary.minValue(); // value is -10 + * print(lowestValue); + * } + *
+ */ + + minValue() { + return this._valueTest(1); + } -/** - * private helper function for finding lowest or highest key - * the argument 'flip' is used to flip the comparison arrow - * from 'less than' to 'greater than' - */ + /** + * Return the highest number currently stored in the Dictionary. + * + * @method maxValue + * @return {Number} + * @example + *
+ * + * function setup() { + * let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 }); + * let highestValue = myDictionary.maxValue(); // value is 3 + * print(highestValue); + * } + *
+ */ + + maxValue() { + return this._valueTest(-1); + } -p5.NumberDict.prototype._keyTest = function(flip) { - if (Object.keys(this.data).length === 0) { - throw new Error('Unable to use minValue on an empty NumberDict'); - } else if (Object.keys(this.data).length === 1) { - return Object.keys(this.data)[0]; - } else { - let result = Object.keys(this.data)[0]; - for (let i = 1; i < Object.keys(this.data).length; i++) { - if (Object.keys(this.data)[i] * flip < result * flip) { - result = Object.keys(this.data)[i]; + /** + * private helper function for finding lowest or highest key + * the argument 'flip' is used to flip the comparison arrow + * from 'less than' to 'greater than' + */ + + _keyTest(flip) { + if (Object.keys(this.data).length === 0) { + throw new Error('Unable to use minValue on an empty NumberDict'); + } else if (Object.keys(this.data).length === 1) { + return Object.keys(this.data)[0]; + } else { + let result = Object.keys(this.data)[0]; + for (let i = 1; i < Object.keys(this.data).length; i++) { + if (Object.keys(this.data)[i] * flip < result * flip) { + result = Object.keys(this.data)[i]; + } } + return result; } - return result; } -}; - -/** - * Return the lowest key currently used in the Dictionary. - * - * @method minKey - * @return {Number} - * @example - *
- * - * function setup() { - * let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 }); - * let lowestKey = myDictionary.minKey(); // value is 1.2 - * print(lowestKey); - * } - *
- */ -p5.NumberDict.prototype.minKey = function() { - return this._keyTest(1); -}; - -/** - * Return the highest key currently used in the Dictionary. - * - * @method maxKey - * @return {Number} - * @example - *
- * - * function setup() { - * let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 }); - * let highestKey = myDictionary.maxKey(); // value is 4 - * print(highestKey); - * } - *
- */ + /** + * Return the lowest key currently used in the Dictionary. + * + * @method minKey + * @return {Number} + * @example + *
+ * + * function setup() { + * let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 }); + * let lowestKey = myDictionary.minKey(); // value is 1.2 + * print(lowestKey); + * } + *
+ */ + + minKey() { + return this._keyTest(1); + } -p5.NumberDict.prototype.maxKey = function() { - return this._keyTest(-1); + /** + * Return the highest key currently used in the Dictionary. + * + * @method maxKey + * @return {Number} + * @example + *
+ * + * function setup() { + * let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 }); + * let highestKey = myDictionary.maxKey(); // value is 4 + * print(highestKey); + * } + *
+ */ + + maxKey() { + return this._keyTest(-1); + } }; export default p5.TypedDict; diff --git a/src/dom/dom.js b/src/dom/dom.js index 10c8f0cc59..9d28420c9f 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -54,7 +54,7 @@ import p5 from '../core/main'; * [a, b, c, d, e]; // unused * */ -p5.prototype.select = function(e, p) { +p5.prototype.select = function (e, p) { p5._validateParameters('select', arguments); const container = this._getContainer(p); const res = container.querySelector(e); @@ -110,7 +110,7 @@ p5.prototype.select = function(e, p) { * console.log(a); * */ -p5.prototype.selectAll = function(e, p) { +p5.prototype.selectAll = function (e, p) { p5._validateParameters('selectAll', arguments); const arr = []; const container = this._getContainer(p); @@ -127,7 +127,7 @@ p5.prototype.selectAll = function(e, p) { /** * Helper function for select and selectAll */ -p5.prototype._getContainer = function(p) { +p5.prototype._getContainer = function (p) { let container = document; if (typeof p === 'string') { container = document.querySelector(p) || document; @@ -142,11 +142,11 @@ p5.prototype._getContainer = function(p) { /** * Helper function for getElement and getElements. */ -p5.prototype._wrapElement = function(elt) { +p5.prototype._wrapElement = function (elt) { const children = Array.prototype.slice.call(elt.children); if (elt.tagName === 'INPUT' && elt.type === 'checkbox') { let converted = new p5.Element(elt, this); - converted.checked = function() { + converted.checked = function () { if (arguments.length === 0) { return this.elt.checked; } else if (arguments[0]) { @@ -163,7 +163,7 @@ p5.prototype._wrapElement = function(elt) { return this.createSelect(new p5.Element(elt, this)); } else if ( children.length > 0 && - children.every(function(c) { + children.every(function (c) { return c.tagName === 'INPUT' || c.tagName === 'LABEL'; }) ) { @@ -193,7 +193,7 @@ p5.prototype._wrapElement = function(elt) { * } * */ -p5.prototype.removeElements = function(e) { +p5.prototype.removeElements = function (e) { p5._validateParameters('removeElements', arguments); // el.remove splices from this._elements, so don't mix iteration with it const isNotCanvasElement = el => !(el.elt instanceof HTMLCanvasElement); @@ -263,7 +263,7 @@ p5.prototype.removeElements = function(e) { * @alt * dropdown: pear, kiwi, grape. When selected text "it's a" + selection shown. */ -p5.Element.prototype.changed = function(fxn) { +p5.Element.prototype.changed = function (fxn) { p5.Element._adjustListener('change', fxn, this); return this; }; @@ -301,7 +301,7 @@ p5.Element.prototype.changed = function(fxn) { * @alt * no display. */ -p5.Element.prototype.input = function(fxn) { +p5.Element.prototype.input = function (fxn) { p5.Element._adjustListener('input', fxn, this); return this; }; @@ -332,7 +332,7 @@ function addElement(elt, pInst, media) { * div.position(10, 0); * */ -p5.prototype.createDiv = function(html = '') { +p5.prototype.createDiv = function (html = '') { let elt = document.createElement('div'); elt.innerHTML = html; return addElement(elt, this); @@ -352,7 +352,7 @@ p5.prototype.createDiv = function(html = '') { * p.position(10, 0); * */ -p5.prototype.createP = function(html = '') { +p5.prototype.createP = function (html = '') { let elt = document.createElement('p'); elt.innerHTML = html; return addElement(elt, this); @@ -370,7 +370,7 @@ p5.prototype.createP = function(html = '') { * span.position(0, 0); * */ -p5.prototype.createSpan = function(html = '') { +p5.prototype.createSpan = function (html = '') { let elt = document.createElement('span'); elt.innerHTML = html; return addElement(elt, this); @@ -401,7 +401,7 @@ p5.prototype.createSpan = function(html = '') { * @param {Function} [successCallback] callback to be called once image data is loaded with the p5.Element as argument * @return {p5.Element} pointer to p5.Element holding created node */ -p5.prototype.createImg = function() { +p5.prototype.createImg = function () { p5._validateParameters('createImg', arguments); const elt = document.createElement('img'); const args = arguments; @@ -414,7 +414,7 @@ p5.prototype.createImg = function() { } elt.src = args[0]; self = addElement(elt, this); - elt.addEventListener('load', function() { + elt.addEventListener('load', function () { self.width = elt.offsetWidth || elt.width; self.height = elt.offsetHeight || elt.height; const last = args[args.length - 1]; @@ -438,7 +438,7 @@ p5.prototype.createImg = function() { * a.position(0, 0); * */ -p5.prototype.createA = function(href, html, target) { +p5.prototype.createA = function (href, html, target) { p5._validateParameters('createA', arguments); const elt = document.createElement('a'); elt.href = href; @@ -489,7 +489,7 @@ p5.prototype.createA = function(href, html, target) { * } * */ -p5.prototype.createSlider = function(min, max, value, step) { +p5.prototype.createSlider = function (min, max, value, step) { p5._validateParameters('createSlider', arguments); const elt = document.createElement('input'); elt.type = 'range'; @@ -530,7 +530,7 @@ p5.prototype.createSlider = function(min, max, value, step) { * } * */ -p5.prototype.createButton = function(label, value) { +p5.prototype.createButton = function (label, value) { p5._validateParameters('createButton', arguments); const elt = document.createElement('button'); elt.innerHTML = label; @@ -565,7 +565,7 @@ p5.prototype.createButton = function(label, value) { * } * */ -p5.prototype.createCheckbox = function() { +p5.prototype.createCheckbox = function () { p5._validateParameters('createCheckbox', arguments); // Create a container element @@ -585,7 +585,7 @@ p5.prototype.createCheckbox = function() { //checkbox must be wrapped in p5.Element before label so that label appears after const self = addElement(elt, this); - self.checked = function() { + self.checked = function () { const cb = self.elt.firstElementChild.getElementsByTagName('input')[0]; if (cb) { if (arguments.length === 0) { @@ -599,7 +599,7 @@ p5.prototype.createCheckbox = function() { return self; }; - this.value = function(val) { + this.value = function (val) { self.value = val; return this; }; @@ -697,7 +697,7 @@ p5.prototype.createCheckbox = function() { * @return {p5.Element} */ -p5.prototype.createSelect = function() { +p5.prototype.createSelect = function () { p5._validateParameters('createSelect', arguments); let self; let arg = arguments[0]; @@ -716,7 +716,7 @@ p5.prototype.createSelect = function() { self = addElement(elt, this); this.elt = elt; } - self.option = function(name, value) { + self.option = function (name, value) { let index; // if no name is passed, return @@ -749,7 +749,7 @@ p5.prototype.createSelect = function() { } }; - self.selected = function(value) { + self.selected = function (value) { // Update selected status of option if (value !== undefined) { for (let i = 0; i < this.elt.length; i += 1) { @@ -771,7 +771,7 @@ p5.prototype.createSelect = function() { } }; - self.disable = function(value) { + self.disable = function (value) { if (typeof value === 'string') { for (let i = 0; i < this.elt.length; i++) { if (this.elt[i].value.toString() === value) { @@ -785,7 +785,7 @@ p5.prototype.createSelect = function() { return this; }; - self.enable = function(value) { + self.enable = function (value) { if (typeof value === 'string') { for (let i = 0; i < this.elt.length; i++) { if (this.elt[i].value.toString() === value) { @@ -877,7 +877,7 @@ p5.prototype.createSelect = function() { * @method createRadio * @return {p5.Element} pointer to p5.Element holding created node */ -p5.prototype.createRadio = function() { +p5.prototype.createRadio = function () { // Creates a div, adds each option as an individual input inside it. // If already given with a containerEl, will search for all input[radio] // it, create a p5.Element out of it, add options to it and return the p5.Element. @@ -916,7 +916,7 @@ p5.prototype.createRadio = function() { const isLabelElement = el => el instanceof HTMLLabelElement; const isSpanElement = el => el instanceof HTMLSpanElement; - self._getOptionsArray = function() { + self._getOptionsArray = function () { return Array.from(this.elt.children) .filter( el => @@ -926,7 +926,7 @@ p5.prototype.createRadio = function() { .map(el => (isRadioInput(el) ? el : el.firstElementChild)); }; - self.option = function(value, label) { + self.option = function (value, label) { // return an option with this value, create if not exists. let optionEl; for (const option of self._getOptionsArray()) { @@ -972,7 +972,7 @@ p5.prototype.createRadio = function() { return optionEl; }; - self.remove = function(value) { + self.remove = function (value) { for (const optionEl of self._getOptionsArray()) { if (optionEl.value === value) { if (isLabelElement(optionEl.parentElement)) { @@ -987,7 +987,7 @@ p5.prototype.createRadio = function() { } }; - self.value = function() { + self.value = function () { let result = ''; for (const option of self._getOptionsArray()) { if (option.checked) { @@ -998,7 +998,7 @@ p5.prototype.createRadio = function() { return result; }; - self.selected = function(value) { + self.selected = function (value) { let result = null; if (value === undefined) { for (const option of self._getOptionsArray()) { @@ -1026,7 +1026,7 @@ p5.prototype.createRadio = function() { return result; }; - self.disable = function(shouldDisable = true) { + self.disable = function (shouldDisable = true) { for (const radioInput of self._getOptionsArray()) { radioInput.setAttribute('disabled', shouldDisable); } @@ -1088,7 +1088,7 @@ p5.prototype.createRadio = function() { * } * */ -p5.prototype.createColorPicker = function(value) { +p5.prototype.createColorPicker = function (value) { p5._validateParameters('createColorPicker', arguments); const elt = document.createElement('input'); let self; @@ -1110,7 +1110,7 @@ p5.prototype.createColorPicker = function(value) { } self = addElement(elt, this); // Method to return a p5.Color object for the given color. - self.color = function() { + self.color = function () { if (value) { if (value.mode) { p5.prototype._colorMode = value.mode; @@ -1154,7 +1154,7 @@ p5.prototype.createColorPicker = function(value) { * @param {String} [value] * @return {p5.Element} */ -p5.prototype.createInput = function(value = '', type = 'text') { +p5.prototype.createInput = function (value = '', type = 'text') { p5._validateParameters('createInput', arguments); let elt = document.createElement('input'); elt.setAttribute('value', value); @@ -1198,10 +1198,10 @@ p5.prototype.createInput = function(value = '', type = 'text') { * } * */ -p5.prototype.createFileInput = function(callback, multiple = false) { +p5.prototype.createFileInput = function (callback, multiple = false) { p5._validateParameters('createFileInput', arguments); - const handleFileSelect = function(event) { + const handleFileSelect = function (event) { for (const file of event.target.files) { p5.File._load(file, callback); } @@ -1310,7 +1310,7 @@ function createMedia(pInst, type, src, callback) { * } * */ -p5.prototype.createVideo = function(src, callback) { +p5.prototype.createVideo = function (src, callback) { p5._validateParameters('createVideo', arguments); return createMedia(this, 'video', src, callback); }; @@ -1349,7 +1349,7 @@ p5.prototype.createVideo = function(src, callback) { * } * */ -p5.prototype.createAudio = function(src, callback) { +p5.prototype.createAudio = function (src, callback) { p5._validateParameters('createAudio', arguments); return createMedia(this, 'audio', src, callback); }; @@ -1370,7 +1370,7 @@ if (navigator.mediaDevices === undefined) { // with getUserMedia as it would overwrite existing properties. // Here, we will just add the getUserMedia property if it's missing. if (navigator.mediaDevices.getUserMedia === undefined) { - navigator.mediaDevices.getUserMedia = function(constraints) { + navigator.mediaDevices.getUserMedia = function (constraints) { // First get ahold of the legacy getUserMedia, if present const getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; @@ -1384,7 +1384,7 @@ if (navigator.mediaDevices.getUserMedia === undefined) { } // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { getUserMedia.call(navigator, constraints, resolve, reject); }); }; @@ -1472,7 +1472,7 @@ if (navigator.mediaDevices.getUserMedia === undefined) { * * */ -p5.prototype.createCapture = function() { +p5.prototype.createCapture = function () { p5._validateParameters('createCapture', arguments); // return if getUserMedia is not supported by browser @@ -1496,7 +1496,7 @@ p5.prototype.createCapture = function() { // required to work in iOS 11 & up: domElement.setAttribute('playsinline', ''); - navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { + navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { try { if ('srcObject' in domElement) { domElement.srcObject = stream; @@ -1511,7 +1511,7 @@ p5.prototype.createCapture = function() { const videoEl = addElement(domElement, this, true); videoEl.loadedmetadata = false; // set width and height onload metadata - domElement.addEventListener('loadedmetadata', function() { + domElement.addEventListener('loadedmetadata', function () { domElement.play(); if (domElement.width) { videoEl.width = domElement.width; @@ -1541,7 +1541,7 @@ p5.prototype.createCapture = function() { * h5.position(0, 0); * */ -p5.prototype.createElement = function(tag, content) { +p5.prototype.createElement = function (tag, content) { p5._validateParameters('createElement', arguments); const elt = document.createElement(tag); if (typeof content !== 'undefined') { @@ -1567,7 +1567,7 @@ p5.prototype.createElement = function(tag, content) { * div.addClass('myClass'); * */ -p5.Element.prototype.addClass = function(c) { +p5.Element.prototype.addClass = function (c) { if (this.elt.className) { if (!this.hasClass(c)) { this.elt.className = this.elt.className + ' ' + c; @@ -1603,7 +1603,7 @@ p5.Element.prototype.addClass = function(c) { * } * */ -p5.Element.prototype.removeClass = function(c) { +p5.Element.prototype.removeClass = function (c) { // Note: Removing a class that does not exist does NOT throw an error in classList.remove method this.elt.classList.remove(c); return this; @@ -1634,7 +1634,7 @@ p5.Element.prototype.removeClass = function(c) { * } * */ -p5.Element.prototype.hasClass = function(c) { +p5.Element.prototype.hasClass = function (c) { return this.elt.classList.contains(c); }; @@ -1659,7 +1659,7 @@ p5.Element.prototype.hasClass = function(c) { * } * */ -p5.Element.prototype.toggleClass = function(c) { +p5.Element.prototype.toggleClass = function (c) { // classList also has a toggle() method, but we cannot use that yet as support is unclear. // See https://github.com/processing/p5.js/issues/3631 // this.elt.classList.toggle(c); @@ -1705,7 +1705,7 @@ p5.Element.prototype.toggleClass = function(c) { * to add to the current element * @chainable */ -p5.Element.prototype.child = function(childNode) { +p5.Element.prototype.child = function (childNode) { if (typeof childNode === 'undefined') { return this.elt.childNodes; } @@ -1743,7 +1743,7 @@ p5.Element.prototype.child = function(childNode) { * } * */ -p5.Element.prototype.center = function(align) { +p5.Element.prototype.center = function (align) { const style = this.elt.style.display; const hidden = this.elt.style.display === 'none'; const parentHidden = this.parent().style.display === 'none'; @@ -1802,7 +1802,7 @@ p5.Element.prototype.center = function(align) { * @param {boolean} [append] whether to append HTML to existing * @chainable */ -p5.Element.prototype.html = function() { +p5.Element.prototype.html = function () { if (arguments.length === 0) { return this.elt.innerHTML; } else if (arguments[1]) { @@ -1854,7 +1854,7 @@ p5.Element.prototype.html = function() { * @param {String} [positionType] it can be static, fixed, relative, sticky, initial or inherit (optional) * @chainable */ -p5.Element.prototype.position = function() { +p5.Element.prototype.position = function () { if (arguments.length === 0) { return { x: this.elt.offsetLeft, y: this.elt.offsetTop }; } else { @@ -1879,7 +1879,7 @@ p5.Element.prototype.position = function() { }; /* Helper method called by p5.Element.style() */ -p5.Element.prototype._translate = function() { +p5.Element.prototype._translate = function () { this.elt.style.position = 'absolute'; // save out initial non-translate transform styling let transform = ''; @@ -1911,7 +1911,7 @@ p5.Element.prototype._translate = function() { }; /* Helper method called by p5.Element.style() */ -p5.Element.prototype._rotate = function() { +p5.Element.prototype._rotate = function () { // save out initial non-rotate transform styling let transform = ''; if (this.elt.style.transform) { @@ -1979,7 +1979,7 @@ p5.Element.prototype._rotate = function() { * @return {String} current value of property, if no value is given as second argument * @chainable */ -p5.Element.prototype.style = function(prop, val) { +p5.Element.prototype.style = function (prop, val) { const self = this; if (val instanceof p5.Color) { @@ -2050,7 +2050,7 @@ p5.Element.prototype.style = function(prop, val) { * @param {String} value value to assign to attribute * @chainable */ -p5.Element.prototype.attribute = function(attr, value) { +p5.Element.prototype.attribute = function (attr, value) { //handling for checkboxes and radios to ensure options get //attributes not divs if ( @@ -2104,7 +2104,7 @@ p5.Element.prototype.attribute = function(attr, value) { * } * */ -p5.Element.prototype.removeAttribute = function(attr) { +p5.Element.prototype.removeAttribute = function (attr) { if ( this.elt.firstChild != null && (this.elt.firstChild.type === 'checkbox' || @@ -2153,7 +2153,7 @@ p5.Element.prototype.removeAttribute = function(attr) { * @param {String|Number} value * @chainable */ -p5.Element.prototype.value = function() { +p5.Element.prototype.value = function () { if (arguments.length > 0) { this.elt.value = arguments[0]; return this; @@ -2177,7 +2177,7 @@ p5.Element.prototype.value = function() { * div.show(); // turns display to block * */ -p5.Element.prototype.show = function() { +p5.Element.prototype.show = function () { this.elt.style.display = 'block'; return this; }; @@ -2193,7 +2193,7 @@ p5.Element.prototype.show = function() { * div.hide(); * */ -p5.Element.prototype.hide = function() { +p5.Element.prototype.hide = function () { this.elt.style.display = 'none'; return this; }; @@ -2228,7 +2228,7 @@ p5.Element.prototype.hide = function() { * @param {Number|Constant} [h] height of the element, either AUTO, or a number * @chainable */ -p5.Element.prototype.size = function(w, h) { +p5.Element.prototype.size = function (w, h) { if (arguments.length === 0) { return { width: this.elt.offsetWidth, height: this.elt.offsetHeight }; } else { @@ -2288,7 +2288,7 @@ p5.Element.prototype.size = function(w, h) { * myDiv.remove(); * */ -p5.Element.prototype.remove = function() { +p5.Element.prototype.remove = function () { // stop all audios/videos and detach all devices like microphone/camera etc // used as input/output for audios/videos. if (this instanceof p5.MediaElement) { @@ -2376,13 +2376,13 @@ p5.Element.prototype.remove = function() { * @alt * Canvas turns into whatever image is dragged/dropped onto it. */ -p5.Element.prototype.drop = function(callback, fxn) { +p5.Element.prototype.drop = function (callback, fxn) { // Is the file stuff supported? if (window.File && window.FileReader && window.FileList && window.Blob) { if (!this._dragDisabled) { this._dragDisabled = true; - const preventDefault = function(evt) { + const preventDefault = function (evt) { evt.preventDefault(); }; @@ -2398,7 +2398,7 @@ p5.Element.prototype.drop = function(callback, fxn) { // Deal with the files p5.Element._attachListener( 'drop', - function(evt) { + function (evt) { evt.preventDefault(); // Call the second argument as a callback that receives the raw drop event if (typeof fxn === 'function') { @@ -2422,6 +2422,20 @@ p5.Element.prototype.drop = function(callback, fxn) { return this; }; +/*** SCHEDULE EVENTS ***/ + +// Cue inspired by JavaScript setTimeout, and the +// Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org +// eslint-disable-next-line no-unused-vars +class Cue { + constructor(callback, time, id, val) { + this.callback = callback; + this.time = time; + this.id = id; + this.val = val; + } +} + // ============================================================================= // p5.MediaElement additions // ============================================================================= @@ -2436,38 +2450,104 @@ p5.Element.prototype.drop = function(callback, fxn) { * @constructor * @param {String} elt DOM node that is wrapped */ -p5.MediaElement = function(elt, pInst) { - p5.Element.call(this, elt, pInst); +p5.MediaElement = class MediaElement extends p5.Element { + constructor(elt, pInst) { + super(elt, pInst); + + const self = this; + this.elt.crossOrigin = 'anonymous'; + + this._prevTime = 0; + this._cueIDCounter = 0; + this._cues = []; + this.pixels = []; + this._pixelsState = this; + this._pixelDensity = 1; + this._modified = false; + + // Media has an internal canvas that is used when drawing it to the main + // canvas. It will need to be updated each frame as the video itself plays. + // We don't want to update it every time we draw, however, in case the user + // has used load/updatePixels. To handle this, we record the frame drawn to + // the internal canvas so we only update it if the frame has changed. + this._frameOnCanvas = -1; + + /** + * Path to the media element source. + * + * @property src + * @return {String} src + * @example + *
+ * let ele; + * + * function setup() { + * background(250); + * + * //p5.MediaElement objects are usually created + * //by calling the createAudio(), createVideo(), + * //and createCapture() functions. + * + * //In this example we create + * //a new p5.MediaElement via createAudio(). + * ele = createAudio('assets/beat.mp3'); + * + * //We'll set up our example so that + * //when you click on the text, + * //an alert box displays the MediaElement's + * //src field. + * textAlign(CENTER); + * text('Click Me!', width / 2, height / 2); + * } + * + * function mouseClicked() { + * //here we test if the mouse is over the + * //canvas element when it's clicked + * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { + * //Show our p5.MediaElement's src field + * alert(ele.src); + * } + * } + *
+ */ + Object.defineProperty(self, 'src', { + get() { + const firstChildSrc = self.elt.children[0].src; + const srcVal = self.elt.src === window.location.href ? '' : self.elt.src; + const ret = + firstChildSrc === window.location.href ? srcVal : firstChildSrc; + return ret; + }, + set(newValue) { + for (let i = 0; i < self.elt.children.length; i++) { + self.elt.removeChild(self.elt.children[i]); + } + const source = document.createElement('source'); + source.src = newValue; + elt.appendChild(source); + self.elt.src = newValue; + self.modified = true; + } + }); + + // private _onended callback, set by the method: onended(callback) + self._onended = function () { }; + self.elt.onended = function () { + self._onended(self); + }; + } - const self = this; - this.elt.crossOrigin = 'anonymous'; - - this._prevTime = 0; - this._cueIDCounter = 0; - this._cues = []; - this._pixelsState = this; - this._pixelDensity = 1; - this._modified = false; - - // Media has an internal canvas that is used when drawing it to the main - // canvas. It will need to be updated each frame as the video itself plays. - // We don't want to update it every time we draw, however, in case the user - // has used load/updatePixels. To handle this, we record the frame drawn to - // the internal canvas so we only update it if the frame has changed. - this._frameOnCanvas = -1; /** - * Path to the media element source. + * Play an HTML5 media element. * - * @property src - * @return {String} src + * @method play + * @chainable * @example *
* let ele; * * function setup() { - * background(250); - * * //p5.MediaElement objects are usually created * //by calling the createAudio(), createVideo(), * //and createCapture() functions. @@ -2476,973 +2556,899 @@ p5.MediaElement = function(elt, pInst) { * //a new p5.MediaElement via createAudio(). * ele = createAudio('assets/beat.mp3'); * - * //We'll set up our example so that - * //when you click on the text, - * //an alert box displays the MediaElement's - * //src field. + * background(250); * textAlign(CENTER); - * text('Click Me!', width / 2, height / 2); + * text('Click to Play!', width / 2, height / 2); * } * * function mouseClicked() { * //here we test if the mouse is over the * //canvas element when it's clicked * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { - * //Show our p5.MediaElement's src field - * alert(ele.src); + * //Here we call the play() function on + * //the p5.MediaElement we created above. + * //This will start the audio sample. + * ele.play(); + * + * background(200); + * text('You clicked Play!', width / 2, height / 2); * } * } *
*/ - Object.defineProperty(self, 'src', { - get: function() { - const firstChildSrc = self.elt.children[0].src; - const srcVal = self.elt.src === window.location.href ? '' : self.elt.src; - const ret = - firstChildSrc === window.location.href ? srcVal : firstChildSrc; - return ret; - }, - set: function(newValue) { - for (let i = 0; i < self.elt.children.length; i++) { - self.elt.removeChild(self.elt.children[i]); - } - const source = document.createElement('source'); - source.src = newValue; - elt.appendChild(source); - self.elt.src = newValue; - self.modified = true; + play() { + if (this.elt.currentTime === this.elt.duration) { + this.elt.currentTime = 0; } - }); - - // private _onended callback, set by the method: onended(callback) - self._onended = function() {}; - self.elt.onended = function() { - self._onended(self); - }; -}; -p5.MediaElement.prototype = Object.create(p5.Element.prototype); + let promise; + if (this.elt.readyState > 1) { + promise = this.elt.play(); + } else { + // in Chrome, playback cannot resume after being stopped and must reload + this.elt.load(); + promise = this.elt.play(); + } + if (promise && promise.catch) { + promise.catch(e => { + // if it's an autoplay failure error + if (e.name === 'NotAllowedError') { + if (typeof IS_MINIFIED === 'undefined') { + p5._friendlyAutoplayError(this.src); + } else { + console.error(e); + } + } else { + // any other kind of error + console.error('Media play method encountered an unexpected error', e); + } + }); + } + return this; + } -/** - * Play an HTML5 media element. - * - * @method play - * @chainable - * @example - *
- * let ele; - * - * function setup() { - * //p5.MediaElement objects are usually created - * //by calling the createAudio(), createVideo(), - * //and createCapture() functions. - * - * //In this example we create - * //a new p5.MediaElement via createAudio(). - * ele = createAudio('assets/beat.mp3'); - * - * background(250); - * textAlign(CENTER); - * text('Click to Play!', width / 2, height / 2); - * } - * - * function mouseClicked() { - * //here we test if the mouse is over the - * //canvas element when it's clicked - * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { - * //Here we call the play() function on - * //the p5.MediaElement we created above. - * //This will start the audio sample. - * ele.play(); - * - * background(200); - * text('You clicked Play!', width / 2, height / 2); - * } - * } - *
- */ -p5.MediaElement.prototype.play = function() { - if (this.elt.currentTime === this.elt.duration) { + /** + * Stops an HTML5 media element (sets current time to zero). + * + * @method stop + * @chainable + * @example + *
+ * //This example both starts + * //and stops a sound sample + * //when the user clicks the canvas + * + * //We will store the p5.MediaElement + * //object in here + * let ele; + * + * //while our audio is playing, + * //this will be set to true + * let sampleIsPlaying = false; + * + * function setup() { + * //Here we create a p5.MediaElement object + * //using the createAudio() function. + * ele = createAudio('assets/beat.mp3'); + * background(200); + * textAlign(CENTER); + * text('Click to play!', width / 2, height / 2); + * } + * + * function mouseClicked() { + * //here we test if the mouse is over the + * //canvas element when it's clicked + * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { + * background(200); + * + * if (sampleIsPlaying) { + * //if the sample is currently playing + * //calling the stop() function on + * //our p5.MediaElement will stop + * //it and reset its current + * //time to 0 (i.e. it will start + * //at the beginning the next time + * //you play it) + * ele.stop(); + * + * sampleIsPlaying = false; + * text('Click to play!', width / 2, height / 2); + * } else { + * //loop our sound element until we + * //call ele.stop() on it. + * ele.loop(); + * + * sampleIsPlaying = true; + * text('Click to stop!', width / 2, height / 2); + * } + * } + * } + *
+ */ + stop() { + this.elt.pause(); this.elt.currentTime = 0; + return this; } - let promise; - if (this.elt.readyState > 1) { - promise = this.elt.play(); - } else { - // in Chrome, playback cannot resume after being stopped and must reload - this.elt.load(); - promise = this.elt.play(); - } - if (promise && promise.catch) { - promise.catch(e => { - // if it's an autoplay failure error - if (e.name === 'NotAllowedError') { - if (typeof IS_MINIFIED === 'undefined') { - p5._friendlyAutoplayError(this.src); - } else { - console.error(e); - } + + /** + * Pauses an HTML5 media element. + * + * @method pause + * @chainable + * @example + *
+ * //This example both starts + * //and pauses a sound sample + * //when the user clicks the canvas + * + * //We will store the p5.MediaElement + * //object in here + * let ele; + * + * //while our audio is playing, + * //this will be set to true + * let sampleIsPlaying = false; + * + * function setup() { + * //Here we create a p5.MediaElement object + * //using the createAudio() function. + * ele = createAudio('assets/lucky_dragons.mp3'); + * background(200); + * textAlign(CENTER); + * text('Click to play!', width / 2, height / 2); + * } + * + * function mouseClicked() { + * //here we test if the mouse is over the + * //canvas element when it's clicked + * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { + * background(200); + * + * if (sampleIsPlaying) { + * //Calling pause() on our + * //p5.MediaElement will stop it + * //playing, but when we call the + * //loop() or play() functions + * //the sample will start from + * //where we paused it. + * ele.pause(); + * + * sampleIsPlaying = false; + * text('Click to resume!', width / 2, height / 2); + * } else { + * //loop our sound element until we + * //call ele.pause() on it. + * ele.loop(); + * + * sampleIsPlaying = true; + * text('Click to pause!', width / 2, height / 2); + * } + * } + * } + *
+ */ + pause() { + this.elt.pause(); + return this; + } + + /** + * Set 'loop' to true for an HTML5 media element, and starts playing. + * + * @method loop + * @chainable + * @example + *
+ * //Clicking the canvas will loop + * //the audio sample until the user + * //clicks again to stop it + * + * //We will store the p5.MediaElement + * //object in here + * let ele; + * + * //while our audio is playing, + * //this will be set to true + * let sampleIsLooping = false; + * + * function setup() { + * //Here we create a p5.MediaElement object + * //using the createAudio() function. + * ele = createAudio('assets/lucky_dragons.mp3'); + * background(200); + * textAlign(CENTER); + * text('Click to loop!', width / 2, height / 2); + * } + * + * function mouseClicked() { + * //here we test if the mouse is over the + * //canvas element when it's clicked + * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { + * background(200); + * + * if (!sampleIsLooping) { + * //loop our sound element until we + * //call ele.stop() on it. + * ele.loop(); + * + * sampleIsLooping = true; + * text('Click to stop!', width / 2, height / 2); + * } else { + * ele.stop(); + * + * sampleIsLooping = false; + * text('Click to loop!', width / 2, height / 2); + * } + * } + * } + *
+ */ + loop() { + this.elt.setAttribute('loop', true); + this.play(); + return this; + } + /** + * Set 'loop' to false for an HTML5 media element. Element will stop + * when it reaches the end. + * + * @method noLoop + * @chainable + * @example + *
+ * //This example both starts + * //and stops loop of sound sample + * //when the user clicks the canvas + * + * //We will store the p5.MediaElement + * //object in here + * let ele; + * //while our audio is playing, + * //this will be set to true + * let sampleIsPlaying = false; + * + * function setup() { + * //Here we create a p5.MediaElement object + * //using the createAudio() function. + * ele = createAudio('assets/beat.mp3'); + * background(200); + * textAlign(CENTER); + * text('Click to play!', width / 2, height / 2); + * } + * + * function mouseClicked() { + * //here we test if the mouse is over the + * //canvas element when it's clicked + * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { + * background(200); + * + * if (sampleIsPlaying) { + * ele.noLoop(); + * sampleIsPlaying = false; + * text('No more Loops!', width / 2, height / 2); + * } else { + * ele.loop(); + * sampleIsPlaying = true; + * text('Click to stop looping!', width / 2, height / 2); + * } + * } + * } + *
+ */ + noLoop() { + this.elt.removeAttribute('loop'); + return this; + } + + /** + * Sets up logic to check that autoplay succeeded. + * + * @method setupAutoplayFailDetection + * @private + */ + _setupAutoplayFailDetection() { + const timeout = setTimeout(() => { + if (typeof IS_MINIFIED === 'undefined') { + p5._friendlyAutoplayError(this.src); } else { - // any other kind of error - console.error('Media play method encountered an unexpected error', e); + console.error(e); } + }, 500); + this.elt.addEventListener('play', () => clearTimeout(timeout), { + passive: true, + once: true }); } - return this; -}; - -/** - * Stops an HTML5 media element (sets current time to zero). - * - * @method stop - * @chainable - * @example - *
- * //This example both starts - * //and stops a sound sample - * //when the user clicks the canvas - * - * //We will store the p5.MediaElement - * //object in here - * let ele; - * - * //while our audio is playing, - * //this will be set to true - * let sampleIsPlaying = false; - * - * function setup() { - * //Here we create a p5.MediaElement object - * //using the createAudio() function. - * ele = createAudio('assets/beat.mp3'); - * background(200); - * textAlign(CENTER); - * text('Click to play!', width / 2, height / 2); - * } - * - * function mouseClicked() { - * //here we test if the mouse is over the - * //canvas element when it's clicked - * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { - * background(200); - * - * if (sampleIsPlaying) { - * //if the sample is currently playing - * //calling the stop() function on - * //our p5.MediaElement will stop - * //it and reset its current - * //time to 0 (i.e. it will start - * //at the beginning the next time - * //you play it) - * ele.stop(); - * - * sampleIsPlaying = false; - * text('Click to play!', width / 2, height / 2); - * } else { - * //loop our sound element until we - * //call ele.stop() on it. - * ele.loop(); - * - * sampleIsPlaying = true; - * text('Click to stop!', width / 2, height / 2); - * } - * } - * } - *
- */ -p5.MediaElement.prototype.stop = function() { - this.elt.pause(); - this.elt.currentTime = 0; - return this; -}; -/** - * Pauses an HTML5 media element. - * - * @method pause - * @chainable - * @example - *
- * //This example both starts - * //and pauses a sound sample - * //when the user clicks the canvas - * - * //We will store the p5.MediaElement - * //object in here - * let ele; - * - * //while our audio is playing, - * //this will be set to true - * let sampleIsPlaying = false; - * - * function setup() { - * //Here we create a p5.MediaElement object - * //using the createAudio() function. - * ele = createAudio('assets/lucky_dragons.mp3'); - * background(200); - * textAlign(CENTER); - * text('Click to play!', width / 2, height / 2); - * } - * - * function mouseClicked() { - * //here we test if the mouse is over the - * //canvas element when it's clicked - * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { - * background(200); - * - * if (sampleIsPlaying) { - * //Calling pause() on our - * //p5.MediaElement will stop it - * //playing, but when we call the - * //loop() or play() functions - * //the sample will start from - * //where we paused it. - * ele.pause(); - * - * sampleIsPlaying = false; - * text('Click to resume!', width / 2, height / 2); - * } else { - * //loop our sound element until we - * //call ele.pause() on it. - * ele.loop(); - * - * sampleIsPlaying = true; - * text('Click to pause!', width / 2, height / 2); - * } - * } - * } - *
- */ -p5.MediaElement.prototype.pause = function() { - this.elt.pause(); - return this; -}; - -/** - * Set 'loop' to true for an HTML5 media element, and starts playing. - * - * @method loop - * @chainable - * @example - *
- * //Clicking the canvas will loop - * //the audio sample until the user - * //clicks again to stop it - * - * //We will store the p5.MediaElement - * //object in here - * let ele; - * - * //while our audio is playing, - * //this will be set to true - * let sampleIsLooping = false; - * - * function setup() { - * //Here we create a p5.MediaElement object - * //using the createAudio() function. - * ele = createAudio('assets/lucky_dragons.mp3'); - * background(200); - * textAlign(CENTER); - * text('Click to loop!', width / 2, height / 2); - * } - * - * function mouseClicked() { - * //here we test if the mouse is over the - * //canvas element when it's clicked - * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { - * background(200); - * - * if (!sampleIsLooping) { - * //loop our sound element until we - * //call ele.stop() on it. - * ele.loop(); - * - * sampleIsLooping = true; - * text('Click to stop!', width / 2, height / 2); - * } else { - * ele.stop(); - * - * sampleIsLooping = false; - * text('Click to loop!', width / 2, height / 2); - * } - * } - * } - *
- */ -p5.MediaElement.prototype.loop = function() { - this.elt.setAttribute('loop', true); - this.play(); - return this; -}; -/** - * Set 'loop' to false for an HTML5 media element. Element will stop - * when it reaches the end. - * - * @method noLoop - * @chainable - * @example - *
- * //This example both starts - * //and stops loop of sound sample - * //when the user clicks the canvas - * - * //We will store the p5.MediaElement - * //object in here - * let ele; - * //while our audio is playing, - * //this will be set to true - * let sampleIsPlaying = false; - * - * function setup() { - * //Here we create a p5.MediaElement object - * //using the createAudio() function. - * ele = createAudio('assets/beat.mp3'); - * background(200); - * textAlign(CENTER); - * text('Click to play!', width / 2, height / 2); - * } - * - * function mouseClicked() { - * //here we test if the mouse is over the - * //canvas element when it's clicked - * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { - * background(200); - * - * if (sampleIsPlaying) { - * ele.noLoop(); - * sampleIsPlaying = false; - * text('No more Loops!', width / 2, height / 2); - * } else { - * ele.loop(); - * sampleIsPlaying = true; - * text('Click to stop looping!', width / 2, height / 2); - * } - * } - * } - *
- */ -p5.MediaElement.prototype.noLoop = function() { - this.elt.removeAttribute('loop'); - return this; -}; + /** + * Set HTML5 media element to autoplay or not. If no argument is specified, by + * default it will autoplay. + * + * @method autoplay + * @param {Boolean} shouldAutoplay whether the element should autoplay + * @chainable + * @example + *
+ * let videoElement; + * function setup() { + * noCanvas(); + * videoElement = createVideo(['assets/small.mp4'], onVideoLoad); + * } + * function onVideoLoad() { + * // The media will play as soon as it is loaded. + * videoElement.autoplay(); + * videoElement.volume(0); + * videoElement.size(100, 100); + * } + *
+ * + *
+ * let videoElement; + * function setup() { + * noCanvas(); + * videoElement = createVideo(['assets/small.mp4'], onVideoLoad); + * } + * function onVideoLoad() { + * // The media will not play until some explicitly triggered. + * videoElement.autoplay(false); + * videoElement.volume(0); + * videoElement.size(100, 100); + * } + * + * function mouseClicked() { + * videoElement.play(); + * } + *
+ * + * @alt + * An example of a video element which autoplays after it is loaded. + * An example of a video element which waits for a trigger for playing. + */ -/** - * Sets up logic to check that autoplay succeeded. - * - * @method setupAutoplayFailDetection - * @private - */ -p5.MediaElement.prototype._setupAutoplayFailDetection = function() { - const timeout = setTimeout(() => { - if (typeof IS_MINIFIED === 'undefined') { - p5._friendlyAutoplayError(this.src); - } else { - console.error(e); + autoplay(val) { + const oldVal = this.elt.getAttribute('autoplay'); + this.elt.setAttribute('autoplay', val); + // if we turned on autoplay + if (val && !oldVal) { + // bind method to this scope + const setupAutoplayFailDetection = + () => this._setupAutoplayFailDetection(); + // if media is ready to play, schedule check now + if (this.elt.readyState === 4) { + setupAutoplayFailDetection(); + } else { + // otherwise, schedule check whenever it is ready + this.elt.addEventListener('canplay', setupAutoplayFailDetection, { + passive: true, + once: true + }); + } } - }, 500); - this.elt.addEventListener('play', () => clearTimeout(timeout), { - passive: true, - once: true - }); -}; -/** - * Set HTML5 media element to autoplay or not. If no argument is specified, by - * default it will autoplay. - * - * @method autoplay - * @param {Boolean} shouldAutoplay whether the element should autoplay - * @chainable - * @example - *
- * let videoElement; - * function setup() { - * noCanvas(); - * videoElement = createVideo(['assets/small.mp4'], onVideoLoad); - * } - * function onVideoLoad() { - * // The media will play as soon as it is loaded. - * videoElement.autoplay(); - * videoElement.volume(0); - * videoElement.size(100, 100); - * } - *
- * - *
- * let videoElement; - * function setup() { - * noCanvas(); - * videoElement = createVideo(['assets/small.mp4'], onVideoLoad); - * } - * function onVideoLoad() { - * // The media will not play until some explicitly triggered. - * videoElement.autoplay(false); - * videoElement.volume(0); - * videoElement.size(100, 100); - * } - * - * function mouseClicked() { - * videoElement.play(); - * } - *
- * - * @alt - * An example of a video element which autoplays after it is loaded. - * An example of a video element which waits for a trigger for playing. - */ + return this; + } -p5.MediaElement.prototype.autoplay = function(val) { - const oldVal = this.elt.getAttribute('autoplay'); - this.elt.setAttribute('autoplay', val); - // if we turned on autoplay - if (val && !oldVal) { - // bind method to this scope - const setupAutoplayFailDetection = () => this._setupAutoplayFailDetection(); - // if media is ready to play, schedule check now - if (this.elt.readyState === 4) { - setupAutoplayFailDetection(); + /** + * Sets volume for this HTML5 media element. If no argument is given, + * returns the current volume. + * + * @method volume + * @return {Number} current volume + * + * @example + *
+ * let ele; + * function setup() { + * // p5.MediaElement objects are usually created + * // by calling the createAudio(), createVideo(), + * // and createCapture() functions. + * // In this example we create + * // a new p5.MediaElement via createAudio(). + * ele = createAudio('assets/lucky_dragons.mp3'); + * background(250); + * textAlign(CENTER); + * text('Click to Play!', width / 2, height / 2); + * } + * function mouseClicked() { + * // Here we call the volume() function + * // on the sound element to set its volume + * // Volume must be between 0.0 and 1.0 + * ele.volume(0.2); + * ele.play(); + * background(200); + * text('You clicked Play!', width / 2, height / 2); + * } + *
+ *
+ * let audio; + * let counter = 0; + * + * function loaded() { + * audio.play(); + * } + * + * function setup() { + * audio = createAudio('assets/lucky_dragons.mp3', loaded); + * textAlign(CENTER); + * } + * + * function draw() { + * if (counter === 0) { + * background(0, 255, 0); + * text('volume(0.9)', width / 2, height / 2); + * } else if (counter === 1) { + * background(255, 255, 0); + * text('volume(0.5)', width / 2, height / 2); + * } else if (counter === 2) { + * background(255, 0, 0); + * text('volume(0.1)', width / 2, height / 2); + * } + * } + * + * function mousePressed() { + * counter++; + * if (counter === 0) { + * audio.volume(0.9); + * } else if (counter === 1) { + * audio.volume(0.5); + * } else if (counter === 2) { + * audio.volume(0.1); + * } else { + * counter = 0; + * audio.volume(0.9); + * } + * } + * + *
+ */ + /** + * @method volume + * @param {Number} val volume between 0.0 and 1.0 + * @chainable + */ + volume(val) { + if (typeof val === 'undefined') { + return this.elt.volume; } else { - // otherwise, schedule check whenever it is ready - this.elt.addEventListener('canplay', setupAutoplayFailDetection, { - passive: true, - once: true - }); + this.elt.volume = val; } } - return this; -}; - -/** - * Sets volume for this HTML5 media element. If no argument is given, - * returns the current volume. - * - * @method volume - * @return {Number} current volume - * - * @example - *
- * let ele; - * function setup() { - * // p5.MediaElement objects are usually created - * // by calling the createAudio(), createVideo(), - * // and createCapture() functions. - * // In this example we create - * // a new p5.MediaElement via createAudio(). - * ele = createAudio('assets/lucky_dragons.mp3'); - * background(250); - * textAlign(CENTER); - * text('Click to Play!', width / 2, height / 2); - * } - * function mouseClicked() { - * // Here we call the volume() function - * // on the sound element to set its volume - * // Volume must be between 0.0 and 1.0 - * ele.volume(0.2); - * ele.play(); - * background(200); - * text('You clicked Play!', width / 2, height / 2); - * } - *
- *
- * let audio; - * let counter = 0; - * - * function loaded() { - * audio.play(); - * } - * - * function setup() { - * audio = createAudio('assets/lucky_dragons.mp3', loaded); - * textAlign(CENTER); - * } - * - * function draw() { - * if (counter === 0) { - * background(0, 255, 0); - * text('volume(0.9)', width / 2, height / 2); - * } else if (counter === 1) { - * background(255, 255, 0); - * text('volume(0.5)', width / 2, height / 2); - * } else if (counter === 2) { - * background(255, 0, 0); - * text('volume(0.1)', width / 2, height / 2); - * } - * } - * - * function mousePressed() { - * counter++; - * if (counter === 0) { - * audio.volume(0.9); - * } else if (counter === 1) { - * audio.volume(0.5); - * } else if (counter === 2) { - * audio.volume(0.1); - * } else { - * counter = 0; - * audio.volume(0.9); - * } - * } - * - *
- */ -/** - * @method volume - * @param {Number} val volume between 0.0 and 1.0 - * @chainable - */ -p5.MediaElement.prototype.volume = function(val) { - if (typeof val === 'undefined') { - return this.elt.volume; - } else { - this.elt.volume = val; - } -}; - -/** - * If no arguments are given, returns the current playback speed of the - * element. The speed parameter sets the speed where 2.0 will play the - * element twice as fast, 0.5 will play at half the speed, and -1 will play - * the element in normal speed in reverse.(Note that not all browsers support - * backward playback and even if they do, playback might not be smooth.) - * - * @method speed - * @return {Number} current playback speed of the element - * - * @example - *
- * //Clicking the canvas will loop - * //the audio sample until the user - * //clicks again to stop it - * - * //We will store the p5.MediaElement - * //object in here - * let ele; - * let button; - * - * function setup() { - * createCanvas(710, 400); - * //Here we create a p5.MediaElement object - * //using the createAudio() function. - * ele = createAudio('assets/beat.mp3'); - * ele.loop(); - * background(200); - * - * button = createButton('2x speed'); - * button.position(100, 68); - * button.mousePressed(twice_speed); - * - * button = createButton('half speed'); - * button.position(200, 68); - * button.mousePressed(half_speed); - * - * button = createButton('reverse play'); - * button.position(300, 68); - * button.mousePressed(reverse_speed); - * - * button = createButton('STOP'); - * button.position(400, 68); - * button.mousePressed(stop_song); - * - * button = createButton('PLAY!'); - * button.position(500, 68); - * button.mousePressed(play_speed); - * } - * - * function twice_speed() { - * ele.speed(2); - * } - * - * function half_speed() { - * ele.speed(0.5); - * } - * - * function reverse_speed() { - * ele.speed(-1); - * } - * - * function stop_song() { - * ele.stop(); - * } - * - * function play_speed() { - * ele.play(); - * } - *
- */ + /** + * If no arguments are given, returns the current playback speed of the + * element. The speed parameter sets the speed where 2.0 will play the + * element twice as fast, 0.5 will play at half the speed, and -1 will play + * the element in normal speed in reverse.(Note that not all browsers support + * backward playback and even if they do, playback might not be smooth.) + * + * @method speed + * @return {Number} current playback speed of the element + * + * @example + *
+ * //Clicking the canvas will loop + * //the audio sample until the user + * //clicks again to stop it + * + * //We will store the p5.MediaElement + * //object in here + * let ele; + * let button; + * + * function setup() { + * createCanvas(710, 400); + * //Here we create a p5.MediaElement object + * //using the createAudio() function. + * ele = createAudio('assets/beat.mp3'); + * ele.loop(); + * background(200); + * + * button = createButton('2x speed'); + * button.position(100, 68); + * button.mousePressed(twice_speed); + * + * button = createButton('half speed'); + * button.position(200, 68); + * button.mousePressed(half_speed); + * + * button = createButton('reverse play'); + * button.position(300, 68); + * button.mousePressed(reverse_speed); + * + * button = createButton('STOP'); + * button.position(400, 68); + * button.mousePressed(stop_song); + * + * button = createButton('PLAY!'); + * button.position(500, 68); + * button.mousePressed(play_speed); + * } + * + * function twice_speed() { + * ele.speed(2); + * } + * + * function half_speed() { + * ele.speed(0.5); + * } + * + * function reverse_speed() { + * ele.speed(-1); + * } + * + * function stop_song() { + * ele.stop(); + * } + * + * function play_speed() { + * ele.play(); + * } + *
+ */ -/** - * @method speed - * @param {Number} speed speed multiplier for element playback - * @chainable - */ -p5.MediaElement.prototype.speed = function(val) { - if (typeof val === 'undefined') { - return this.presetPlaybackRate || this.elt.playbackRate; - } else { - if (this.loadedmetadata) { - this.elt.playbackRate = val; + /** + * @method speed + * @param {Number} speed speed multiplier for element playback + * @chainable + */ + speed(val) { + if (typeof val === 'undefined') { + return this.presetPlaybackRate || this.elt.playbackRate; } else { - this.presetPlaybackRate = val; + if (this.loadedmetadata) { + this.elt.playbackRate = val; + } else { + this.presetPlaybackRate = val; + } } } -}; -/** - * If no arguments are given, returns the current time of the element. - * If an argument is given the current time of the element is set to it. - * - * @method time - * @return {Number} current time (in seconds) - * - * @example - *
- * let ele; - * let beginning = true; - * function setup() { - * //p5.MediaElement objects are usually created - * //by calling the createAudio(), createVideo(), - * //and createCapture() functions. - * - * //In this example we create - * //a new p5.MediaElement via createAudio(). - * ele = createAudio('assets/lucky_dragons.mp3'); - * background(250); - * textAlign(CENTER); - * text('start at beginning', width / 2, height / 2); - * } - * - * // this function fires with click anywhere - * function mousePressed() { - * if (beginning === true) { - * // here we start the sound at the beginning - * // time(0) is not necessary here - * // as this produces the same result as - * // play() - * ele.play().time(0); - * background(200); - * text('jump 2 sec in', width / 2, height / 2); - * beginning = false; - * } else { - * // here we jump 2 seconds into the sound - * ele.play().time(2); - * background(250); - * text('start at beginning', width / 2, height / 2); - * beginning = true; - * } - * } - *
- */ -/** - * @method time - * @param {Number} time time to jump to (in seconds) - * @chainable - */ -p5.MediaElement.prototype.time = function(val) { - if (typeof val === 'undefined') { - return this.elt.currentTime; - } else { - this.elt.currentTime = val; - return this; + /** + * If no arguments are given, returns the current time of the element. + * If an argument is given the current time of the element is set to it. + * + * @method time + * @return {Number} current time (in seconds) + * + * @example + *
+ * let ele; + * let beginning = true; + * function setup() { + * //p5.MediaElement objects are usually created + * //by calling the createAudio(), createVideo(), + * //and createCapture() functions. + * + * //In this example we create + * //a new p5.MediaElement via createAudio(). + * ele = createAudio('assets/lucky_dragons.mp3'); + * background(250); + * textAlign(CENTER); + * text('start at beginning', width / 2, height / 2); + * } + * + * // this function fires with click anywhere + * function mousePressed() { + * if (beginning === true) { + * // here we start the sound at the beginning + * // time(0) is not necessary here + * // as this produces the same result as + * // play() + * ele.play().time(0); + * background(200); + * text('jump 2 sec in', width / 2, height / 2); + * beginning = false; + * } else { + * // here we jump 2 seconds into the sound + * ele.play().time(2); + * background(250); + * text('start at beginning', width / 2, height / 2); + * beginning = true; + * } + * } + *
+ */ + /** + * @method time + * @param {Number} time time to jump to (in seconds) + * @chainable + */ + time(val) { + if (typeof val === 'undefined') { + return this.elt.currentTime; + } else { + this.elt.currentTime = val; + return this; + } } -}; -/** - * Returns the duration of the HTML5 media element. - * - * @method duration - * @return {Number} duration - * - * @example - *
- * let ele; - * function setup() { - * //p5.MediaElement objects are usually created - * //by calling the createAudio(), createVideo(), - * //and createCapture() functions. - * //In this example we create - * //a new p5.MediaElement via createAudio(). - * ele = createAudio('assets/doorbell.mp3'); - * background(250); - * textAlign(CENTER); - * text('Click to know the duration!', 10, 25, 70, 80); - * } - * function mouseClicked() { - * ele.play(); - * background(200); - * //ele.duration dislpays the duration - * text(ele.duration() + ' seconds', width / 2, height / 2); - * } - *
- */ -p5.MediaElement.prototype.duration = function() { - return this.elt.duration; -}; -p5.MediaElement.prototype.pixels = []; -p5.MediaElement.prototype._ensureCanvas = function() { - if (!this.canvas) { - this.canvas = document.createElement('canvas'); - this.drawingContext = this.canvas.getContext('2d'); - this.setModified(true); + /** + * Returns the duration of the HTML5 media element. + * + * @method duration + * @return {Number} duration + * + * @example + *
+ * let ele; + * function setup() { + * //p5.MediaElement objects are usually created + * //by calling the createAudio(), createVideo(), + * //and createCapture() functions. + * //In this example we create + * //a new p5.MediaElement via createAudio(). + * ele = createAudio('assets/doorbell.mp3'); + * background(250); + * textAlign(CENTER); + * text('Click to know the duration!', 10, 25, 70, 80); + * } + * function mouseClicked() { + * ele.play(); + * background(200); + * //ele.duration dislpays the duration + * text(ele.duration() + ' seconds', width / 2, height / 2); + * } + *
+ */ + duration() { + return this.elt.duration; } - // Don't update the canvas again if we have already updated the canvas with - // the current frame - const needsRedraw = this._frameOnCanvas !== this._pInst.frameCount; - if (this.loadedmetadata && needsRedraw) { - // wait for metadata for w/h - if (this.canvas.width !== this.elt.width) { - this.canvas.width = this.elt.width; - this.canvas.height = this.elt.height; - this.width = this.canvas.width; - this.height = this.canvas.height; + _ensureCanvas() { + if (!this.canvas) { + this.canvas = document.createElement('canvas'); + this.drawingContext = this.canvas.getContext('2d'); + this.setModified(true); } - this.drawingContext.drawImage( - this.elt, - 0, - 0, - this.canvas.width, - this.canvas.height - ); + // Don't update the canvas again if we have already updated the canvas with + // the current frame + const needsRedraw = this._frameOnCanvas !== this._pInst.frameCount; + if (this.loadedmetadata && needsRedraw) { + // wait for metadata for w/h + if (this.canvas.width !== this.elt.width) { + this.canvas.width = this.elt.width; + this.canvas.height = this.elt.height; + this.width = this.canvas.width; + this.height = this.canvas.height; + } + + this.drawingContext.drawImage( + this.elt, + 0, + 0, + this.canvas.width, + this.canvas.height + ); + this.setModified(true); + this._frameOnCanvas = this._pInst.frameCount; + } + } + loadPixels() { + this._ensureCanvas(); + return p5.Renderer2D.prototype.loadPixels.apply(this, arguments); + } + updatePixels(x, y, w, h) { + if (this.loadedmetadata) { + // wait for metadata + this._ensureCanvas(); + p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h); + } this.setModified(true); - this._frameOnCanvas = this._pInst.frameCount; + return this; } -}; -p5.MediaElement.prototype.loadPixels = function() { - this._ensureCanvas(); - return p5.Renderer2D.prototype.loadPixels.apply(this, arguments); -}; -p5.MediaElement.prototype.updatePixels = function(x, y, w, h) { - if (this.loadedmetadata) { - // wait for metadata + get() { this._ensureCanvas(); - p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h); + return p5.Renderer2D.prototype.get.apply(this, arguments); + } + _getPixel() { + this.loadPixels(); + return p5.Renderer2D.prototype._getPixel.apply(this, arguments); } - this.setModified(true); - return this; -}; -p5.MediaElement.prototype.get = function() { - this._ensureCanvas(); - return p5.Renderer2D.prototype.get.apply(this, arguments); -}; -p5.MediaElement.prototype._getPixel = function() { - this.loadPixels(); - return p5.Renderer2D.prototype._getPixel.apply(this, arguments); -}; -p5.MediaElement.prototype.set = function(x, y, imgOrCol) { - if (this.loadedmetadata) { - // wait for metadata + set(x, y, imgOrCol) { + if (this.loadedmetadata) { + // wait for metadata + this._ensureCanvas(); + p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol); + this.setModified(true); + } + } + copy() { this._ensureCanvas(); - p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol); + p5.prototype.copy.apply(this, arguments); + } + mask() { + this.loadPixels(); this.setModified(true); + p5.Image.prototype.mask.apply(this, arguments); + } + /** + * helper method for web GL mode to figure out if the element + * has been modified and might need to be re-uploaded to texture + * memory between frames. + * @method isModified + * @private + * @return {boolean} a boolean indicating whether or not the + * image has been updated or modified since last texture upload. + */ + isModified() { + return this._modified; + } + /** + * helper method for web GL mode to indicate that an element has been + * changed or unchanged since last upload. gl texture upload will + * set this value to false after uploading the texture; or might set + * it to true if metadata has become available but there is no actual + * texture data available yet.. + * @method setModified + * @param {boolean} val sets whether or not the element has been + * modified. + * @private + */ + setModified(value) { + this._modified = value; + } + /** + * Schedule an event to be called when the audio or video + * element reaches the end. If the element is looping, + * this will not be called. The element is passed in + * as the argument to the onended callback. + * + * @method onended + * @param {Function} callback function to call when the + * soundfile has ended. The + * media element will be passed + * in as the argument to the + * callback. + * @chainable + * @example + *
+ * function setup() { + * let audioEl = createAudio('assets/beat.mp3'); + * audioEl.showControls(); + * audioEl.onended(sayDone); + * } + * + * function sayDone(elt) { + * alert('done playing ' + elt.src); + * } + *
+ */ + onended(callback) { + this._onended = callback; + return this; } -}; -p5.MediaElement.prototype.copy = function() { - this._ensureCanvas(); - p5.prototype.copy.apply(this, arguments); -}; -p5.MediaElement.prototype.mask = function() { - this.loadPixels(); - this.setModified(true); - p5.Image.prototype.mask.apply(this, arguments); -}; -/** - * helper method for web GL mode to figure out if the element - * has been modified and might need to be re-uploaded to texture - * memory between frames. - * @method isModified - * @private - * @return {boolean} a boolean indicating whether or not the - * image has been updated or modified since last texture upload. - */ -p5.MediaElement.prototype.isModified = function() { - return this._modified; -}; -/** - * helper method for web GL mode to indicate that an element has been - * changed or unchanged since last upload. gl texture upload will - * set this value to false after uploading the texture; or might set - * it to true if metadata has become available but there is no actual - * texture data available yet.. - * @method setModified - * @param {boolean} val sets whether or not the element has been - * modified. - * @private - */ -p5.MediaElement.prototype.setModified = function(value) { - this._modified = value; -}; -/** - * Schedule an event to be called when the audio or video - * element reaches the end. If the element is looping, - * this will not be called. The element is passed in - * as the argument to the onended callback. - * - * @method onended - * @param {Function} callback function to call when the - * soundfile has ended. The - * media element will be passed - * in as the argument to the - * callback. - * @chainable - * @example - *
- * function setup() { - * let audioEl = createAudio('assets/beat.mp3'); - * audioEl.showControls(); - * audioEl.onended(sayDone); - * } - * - * function sayDone(elt) { - * alert('done playing ' + elt.src); - * } - *
- */ -p5.MediaElement.prototype.onended = function(callback) { - this._onended = callback; - return this; -}; -/*** CONNECT TO WEB AUDIO API / p5.sound.js ***/ + /*** CONNECT TO WEB AUDIO API / p5.sound.js ***/ -/** - * Send the audio output of this element to a specified audioNode or - * p5.sound object. If no element is provided, connects to p5's main - * output. That connection is established when this method is first called. - * All connections are removed by the .disconnect() method. - * - * This method is meant to be used with the p5.sound.js addon library. - * - * @method connect - * @param {AudioNode|Object} audioNode AudioNode from the Web Audio API, - * or an object from the p5.sound library - */ -p5.MediaElement.prototype.connect = function(obj) { - let audioContext, mainOutput; + /** + * Send the audio output of this element to a specified audioNode or + * p5.sound object. If no element is provided, connects to p5's main + * output. That connection is established when this method is first called. + * All connections are removed by the .disconnect() method. + * + * This method is meant to be used with the p5.sound.js addon library. + * + * @method connect + * @param {AudioNode|Object} audioNode AudioNode from the Web Audio API, + * or an object from the p5.sound library + */ + connect(obj) { + let audioContext, mainOutput; - // if p5.sound exists, same audio context - if (typeof p5.prototype.getAudioContext === 'function') { - audioContext = p5.prototype.getAudioContext(); - mainOutput = p5.soundOut.input; - } else { - try { - audioContext = obj.context; - mainOutput = audioContext.destination; - } catch (e) { - throw 'connect() is meant to be used with Web Audio API or p5.sound.js'; + // if p5.sound exists, same audio context + if (typeof p5.prototype.getAudioContext === 'function') { + audioContext = p5.prototype.getAudioContext(); + mainOutput = p5.soundOut.input; + } else { + try { + audioContext = obj.context; + mainOutput = audioContext.destination; + } catch (e) { + throw 'connect() is meant to be used with Web Audio API or p5.sound.js'; + } } - } - // create a Web Audio MediaElementAudioSourceNode if none already exists - if (!this.audioSourceNode) { - this.audioSourceNode = audioContext.createMediaElementSource(this.elt); + // create a Web Audio MediaElementAudioSourceNode if none already exists + if (!this.audioSourceNode) { + this.audioSourceNode = audioContext.createMediaElementSource(this.elt); - // connect to main output when this method is first called - this.audioSourceNode.connect(mainOutput); - } + // connect to main output when this method is first called + this.audioSourceNode.connect(mainOutput); + } - // connect to object if provided - if (obj) { - if (obj.input) { - this.audioSourceNode.connect(obj.input); + // connect to object if provided + if (obj) { + if (obj.input) { + this.audioSourceNode.connect(obj.input); + } else { + this.audioSourceNode.connect(obj); + } } else { - this.audioSourceNode.connect(obj); + // otherwise connect to main output of p5.sound / AudioContext + this.audioSourceNode.connect(mainOutput); } - } else { - // otherwise connect to main output of p5.sound / AudioContext - this.audioSourceNode.connect(mainOutput); } -}; -/** - * Disconnect all Web Audio routing, including to main output. - * This is useful if you want to re-route the output through - * audio effects, for example. - * - * @method disconnect - */ -p5.MediaElement.prototype.disconnect = function() { - if (this.audioSourceNode) { - this.audioSourceNode.disconnect(); - } else { - throw 'nothing to disconnect'; + /** + * Disconnect all Web Audio routing, including to main output. + * This is useful if you want to re-route the output through + * audio effects, for example. + * + * @method disconnect + */ + disconnect() { + if (this.audioSourceNode) { + this.audioSourceNode.disconnect(); + } else { + throw 'nothing to disconnect'; + } } -}; - -/*** SHOW / HIDE CONTROLS ***/ - -/** - * Show the default MediaElement controls, as determined by the web browser. - * - * @method showControls - * @example - *
- * let ele; - * function setup() { - * //p5.MediaElement objects are usually created - * //by calling the createAudio(), createVideo(), - * //and createCapture() functions. - * //In this example we create - * //a new p5.MediaElement via createAudio() - * ele = createAudio('assets/lucky_dragons.mp3'); - * background(200); - * textAlign(CENTER); - * text('Click to Show Controls!', 10, 25, 70, 80); - * } - * function mousePressed() { - * ele.showControls(); - * background(200); - * text('Controls Shown', width / 2, height / 2); - * } - *
- */ -p5.MediaElement.prototype.showControls = function() { - // must set style for the element to show on the page - this.elt.style['text-align'] = 'inherit'; - this.elt.controls = true; -}; -/** - * Hide the default mediaElement controls. - * @method hideControls - * @example - *
- * let ele; - * function setup() { - * //p5.MediaElement objects are usually created - * //by calling the createAudio(), createVideo(), - * //and createCapture() functions. - * //In this example we create - * //a new p5.MediaElement via createAudio() - * ele = createAudio('assets/lucky_dragons.mp3'); - * ele.showControls(); - * background(200); - * textAlign(CENTER); - * text('Click to hide Controls!', 10, 25, 70, 80); - * } - * function mousePressed() { - * ele.hideControls(); - * background(200); - * text('Controls hidden', width / 2, height / 2); - * } - *
- */ -p5.MediaElement.prototype.hideControls = function() { - this.elt.controls = false; -}; + /*** SHOW / HIDE CONTROLS ***/ -/*** SCHEDULE EVENTS ***/ + /** + * Show the default MediaElement controls, as determined by the web browser. + * + * @method showControls + * @example + *
+ * let ele; + * function setup() { + * //p5.MediaElement objects are usually created + * //by calling the createAudio(), createVideo(), + * //and createCapture() functions. + * //In this example we create + * //a new p5.MediaElement via createAudio() + * ele = createAudio('assets/lucky_dragons.mp3'); + * background(200); + * textAlign(CENTER); + * text('Click to Show Controls!', 10, 25, 70, 80); + * } + * function mousePressed() { + * ele.showControls(); + * background(200); + * text('Controls Shown', width / 2, height / 2); + * } + *
+ */ + showControls() { + // must set style for the element to show on the page + this.elt.style['text-align'] = 'inherit'; + this.elt.controls = true; + } -// Cue inspired by JavaScript setTimeout, and the -// Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org -const Cue = function(callback, time, id, val) { - this.callback = callback; - this.time = time; - this.id = id; - this.val = val; -}; + /** + * Hide the default mediaElement controls. + * @method hideControls + * @example + *
+ * let ele; + * function setup() { + * //p5.MediaElement objects are usually created + * //by calling the createAudio(), createVideo(), + * //and createCapture() functions. + * //In this example we create + * //a new p5.MediaElement via createAudio() + * ele = createAudio('assets/lucky_dragons.mp3'); + * ele.showControls(); + * background(200); + * textAlign(CENTER); + * text('Click to hide Controls!', 10, 25, 70, 80); + * } + * function mousePressed() { + * ele.hideControls(); + * background(200); + * text('Controls hidden', width / 2, height / 2); + * } + *
+ */ + hideControls() { + this.elt.controls = false; + } -/** + /** * Schedule events to trigger every time a MediaElement * (audio/video) reaches a playback cue point. * @@ -3492,20 +3498,20 @@ const Cue = function(callback, time, id, val) { * } * */ -p5.MediaElement.prototype.addCue = function(time, callback, val) { - const id = this._cueIDCounter++; + addCue(time, callback, val) { + const id = this._cueIDCounter++; - const cue = new Cue(callback, time, id, val); - this._cues.push(cue); + const cue = new Cue(callback, time, id, val); + this._cues.push(cue); - if (!this.elt.ontimeupdate) { - this.elt.ontimeupdate = this._onTimeUpdate.bind(this); - } + if (!this.elt.ontimeupdate) { + this.elt.ontimeupdate = this._onTimeUpdate.bind(this); + } - return id; -}; + return id; + } -/** + /** * Remove a callback based on its ID. The ID is returned by the * addCue method. * @method removeCue @@ -3534,20 +3540,20 @@ p5.MediaElement.prototype.addCue = function(time, callback, val) { * } * */ -p5.MediaElement.prototype.removeCue = function(id) { - for (let i = 0; i < this._cues.length; i++) { - if (this._cues[i].id === id) { - console.log(id); - this._cues.splice(i, 1); + removeCue(id) { + for (let i = 0; i < this._cues.length; i++) { + if (this._cues[i].id === id) { + console.log(id); + this._cues.splice(i, 1); + } } - } - if (this._cues.length === 0) { - this.elt.ontimeupdate = null; + if (this._cues.length === 0) { + this.elt.ontimeupdate = null; + } } -}; -/** + /** * Remove all of the callbacks that had originally been scheduled * via the addCue method. * @method clearCues @@ -3581,27 +3587,28 @@ p5.MediaElement.prototype.removeCue = function(id) { * } * */ -p5.MediaElement.prototype.clearCues = function() { - this._cues = []; - this.elt.ontimeupdate = null; -}; + clearCues() { + this._cues = []; + this.elt.ontimeupdate = null; + } -// private method that checks for cues to be fired if events -// have been scheduled using addCue(callback, time). -p5.MediaElement.prototype._onTimeUpdate = function() { - const playbackTime = this.time(); + // private method that checks for cues to be fired if events + // have been scheduled using addCue(callback, time). + _onTimeUpdate() { + const playbackTime = this.time(); - for (let i = 0; i < this._cues.length; i++) { - const callbackTime = this._cues[i].time; - const val = this._cues[i].val; + for (let i = 0; i < this._cues.length; i++) { + const callbackTime = this._cues[i].time; + const val = this._cues[i].val; - if (this._prevTime < callbackTime && callbackTime <= playbackTime) { - // pass the scheduled callbackTime as parameter to the callback - this._cues[i].callback(val); + if (this._prevTime < callbackTime && callbackTime <= playbackTime) { + // pass the scheduled callbackTime as parameter to the callback + this._cues[i].callback(val); + } } - } - this._prevTime = playbackTime; + this._prevTime = playbackTime; + } }; /** @@ -3612,84 +3619,87 @@ p5.MediaElement.prototype._onTimeUpdate = function() { * @constructor * @param {File} file File that is wrapped */ -p5.File = function(file, pInst) { - /** - * Underlying File object. All normal File methods can be called on this. - * - * @property file - */ - this.file = file; - - this._pInst = pInst; +p5.File = class File { + constructor(file, pInst) { + /** + * Underlying File object. All normal File methods can be called on this. + * + * @property file + */ + this.file = file; + + this._pInst = pInst; + + // Splitting out the file type into two components + // This makes determining if image or text etc simpler + const typeList = file.type.split('/'); + /** + * File type (image, text, etc.) + * + * @property type + */ + this.type = typeList[0]; + /** + * File subtype (usually the file extension jpg, png, xml, etc.) + * + * @property subtype + */ + this.subtype = typeList[1]; + /** + * File name + * + * @property name + */ + this.name = file.name; + /** + * File size + * + * @property size + */ + this.size = file.size; + + /** + * URL string containing either image data, the text contents of the file or + * a parsed object if file is JSON and p5.XML if XML + * + * @property data + */ + this.data = undefined; + } - // Splitting out the file type into two components - // This makes determining if image or text etc simpler - const typeList = file.type.split('/'); - /** - * File type (image, text, etc.) - * - * @property type - */ - this.type = typeList[0]; - /** - * File subtype (usually the file extension jpg, png, xml, etc.) - * - * @property subtype - */ - this.subtype = typeList[1]; - /** - * File name - * - * @property name - */ - this.name = file.name; - /** - * File size - * - * @property size - */ - this.size = file.size; - /** - * URL string containing either image data, the text contents of the file or - * a parsed object if file is JSON and p5.XML if XML - * - * @property data - */ - this.data = undefined; -}; + static _createLoader(theFile, callback) { + const reader = new FileReader(); + reader.onload = function (e) { + const p5file = new p5.File(theFile); + if (p5file.file.type === 'application/json') { + // Parse JSON and store the result in data + p5file.data = JSON.parse(e.target.result); + } else if (p5file.file.type === 'text/xml') { + // Parse XML, wrap it in p5.XML and store the result in data + const parser = new DOMParser(); + const xml = parser.parseFromString(e.target.result, 'text/xml'); + p5file.data = new p5.XML(xml.documentElement); + } else { + p5file.data = e.target.result; + } + callback(p5file); + }; + return reader; + } -p5.File._createLoader = function(theFile, callback) { - const reader = new FileReader(); - reader.onload = function(e) { - const p5file = new p5.File(theFile); - if (p5file.file.type === 'application/json') { - // Parse JSON and store the result in data - p5file.data = JSON.parse(e.target.result); - } else if (p5file.file.type === 'text/xml') { - // Parse XML, wrap it in p5.XML and store the result in data - const parser = new DOMParser(); - const xml = parser.parseFromString(e.target.result, 'text/xml'); - p5file.data = new p5.XML(xml.documentElement); + static _load(f, callback) { + // Text or data? + // This should likely be improved + if (/^text\//.test(f.type) || f.type === 'application/json') { + p5.File._createLoader(f, callback).readAsText(f); + } else if (!/^(video|audio)\//.test(f.type)) { + p5.File._createLoader(f, callback).readAsDataURL(f); } else { - p5file.data = e.target.result; + const file = new p5.File(f); + file.data = URL.createObjectURL(f); + callback(file); } - callback(p5file); - }; - return reader; -}; - -p5.File._load = function(f, callback) { - // Text or data? - // This should likely be improved - if (/^text\//.test(f.type) || f.type === 'application/json') { - p5.File._createLoader(f, callback).readAsText(f); - } else if (!/^(video|audio)\//.test(f.type)) { - p5.File._createLoader(f, callback).readAsDataURL(f); - } else { - const file = new p5.File(f); - file.data = URL.createObjectURL(f); - callback(file); } }; diff --git a/src/webgl/p5.Camera.js b/src/webgl/p5.Camera.js index bc3be9ae72..7a81de3172 100644 --- a/src/webgl/p5.Camera.js +++ b/src/webgl/p5.Camera.js @@ -109,7 +109,7 @@ import p5 from '../core/main'; * An interactive example of a red cube with 3 sliders for moving it across x, y, * z axis and 3 sliders for shifting its center. */ -p5.prototype.camera = function(...args) { +p5.prototype.camera = function (...args) { this._assert3d('camera'); p5._validateParameters('camera', args); this._renderer._curCamera.camera(...args); @@ -173,7 +173,7 @@ p5.prototype.camera = function(...args) { * @alt * two colored 3D boxes move back and forth, rotating as mouse is dragged. */ -p5.prototype.perspective = function(...args) { +p5.prototype.perspective = function (...args) { this._assert3d('perspective'); p5._validateParameters('perspective', args); this._renderer._curCamera.perspective(...args); @@ -236,7 +236,7 @@ p5.prototype.perspective = function(...args) { * @alt * two 3D boxes move back and forth along same plane, rotating as mouse is dragged. */ -p5.prototype.ortho = function(...args) { +p5.prototype.ortho = function (...args) { this._assert3d('ortho'); p5._validateParameters('ortho', args); this._renderer._curCamera.ortho(...args); @@ -303,7 +303,7 @@ p5.prototype.ortho = function(...args) { * @alt * two 3D boxes move back and forth along same plane, rotating as mouse is dragged. */ -p5.prototype.frustum = function(...args) { +p5.prototype.frustum = function (...args) { this._assert3d('frustum'); p5._validateParameters('frustum', args); this._renderer._curCamera.frustum(...args); @@ -368,7 +368,7 @@ p5.prototype.frustum = function(...args) { * @alt * An example that creates a camera and moves it around the box. */ -p5.prototype.createCamera = function() { +p5.prototype.createCamera = function () { this._assert3d('createCamera'); const _cam = new p5.Camera(this._renderer); @@ -464,16 +464,17 @@ p5.prototype.createCamera = function() { * @alt * camera view pans left and right across a series of rotating 3D boxes. */ -p5.Camera = function(renderer) { - this._renderer = renderer; +p5.Camera = class Camera { + constructor(renderer) { + this._renderer = renderer; - this.cameraType = 'default'; + this.cameraType = 'default'; - this.cameraMatrix = new p5.Matrix(); - this.projMatrix = new p5.Matrix(); - this.yScale = 1; -}; -/** + this.cameraMatrix = new p5.Matrix(); + this.projMatrix = new p5.Matrix(); + this.yScale = 1; + } + /** * camera position value on x axis * @property {Number} eyeX * @readonly @@ -502,7 +503,7 @@ p5.Camera = function(renderer) { * */ -/** + /** * camera position value on y axis * @property {Number} eyeY * @readonly @@ -530,7 +531,7 @@ p5.Camera = function(renderer) { * */ -/** + /** * camera position value on z axis * @property {Number} eyeZ * @readonly @@ -558,7 +559,7 @@ p5.Camera = function(renderer) { * */ -/** + /** * x coordinate representing center of the sketch * @property {Number} centerX * @readonly @@ -587,7 +588,7 @@ p5.Camera = function(renderer) { * */ -/** + /** * y coordinate representing center of the sketch * @property {Number} centerY * @readonly @@ -616,7 +617,7 @@ p5.Camera = function(renderer) { * */ -/** + /** * z coordinate representing center of the sketch * @property {Number} centerZ * @readonly @@ -645,7 +646,7 @@ p5.Camera = function(renderer) { * */ -/** + /** * x component of direction 'up' from camera * @property {Number} upX * @readonly @@ -669,7 +670,7 @@ p5.Camera = function(renderer) { * */ -/** + /** * y component of direction 'up' from camera * @property {Number} upY * @readonly @@ -693,7 +694,7 @@ p5.Camera = function(renderer) { * */ -/** + /** * z component of direction 'up' from camera * @property {Number} upZ * @readonly @@ -717,11 +718,11 @@ p5.Camera = function(renderer) { * */ -//////////////////////////////////////////////////////////////////////////////// -// Camera Projection Methods -//////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + // Camera Projection Methods + //////////////////////////////////////////////////////////////////////////////// -/** + /** * Sets a perspective projection. * Accepts the same parameters as the global * perspective(). @@ -766,81 +767,81 @@ p5.Camera = function(renderer) { * @alt * two colored 3D boxes move back and forth, rotating as mouse is dragged. */ -p5.Camera.prototype.perspective = function(fovy, aspect, near, far) { - this.cameraType = arguments.length > 0 ? 'custom' : 'default'; - if (typeof fovy === 'undefined') { - fovy = this.defaultCameraFOV; - // this avoids issue where setting angleMode(DEGREES) before calling - // perspective leads to a smaller than expected FOV (because - // _computeCameraDefaultSettings computes in radians) - this.cameraFOV = fovy; - } else { - this.cameraFOV = this._renderer._pInst._toRadians(fovy); - } - if (typeof aspect === 'undefined') { - aspect = this.defaultAspectRatio; - } - if (typeof near === 'undefined') { - near = this.defaultCameraNear; - } - if (typeof far === 'undefined') { - far = this.defaultCameraFar; - } - - if (near <= 0.0001) { - near = 0.01; - console.log( - 'Avoid perspective near plane values close to or below 0. ' + + perspective(fovy, aspect, near, far) { + this.cameraType = arguments.length > 0 ? 'custom' : 'default'; + if (typeof fovy === 'undefined') { + fovy = this.defaultCameraFOV; + // this avoids issue where setting angleMode(DEGREES) before calling + // perspective leads to a smaller than expected FOV (because + // _computeCameraDefaultSettings computes in radians) + this.cameraFOV = fovy; + } else { + this.cameraFOV = this._renderer._pInst._toRadians(fovy); + } + if (typeof aspect === 'undefined') { + aspect = this.defaultAspectRatio; + } + if (typeof near === 'undefined') { + near = this.defaultCameraNear; + } + if (typeof far === 'undefined') { + far = this.defaultCameraFar; + } + + if (near <= 0.0001) { + near = 0.01; + console.log( + 'Avoid perspective near plane values close to or below 0. ' + 'Setting value to 0.01.' - ); - } + ); + } - if (far < near) { - console.log( - 'Perspective far plane value is less than near plane value. ' + + if (far < near) { + console.log( + 'Perspective far plane value is less than near plane value. ' + 'Nothing will be shown.' - ); + ); + } + + this.aspectRatio = aspect; + this.cameraNear = near; + this.cameraFar = far; + + this.projMatrix = p5.Matrix.identity(); + + const f = 1.0 / Math.tan(this.cameraFOV / 2); + const nf = 1.0 / (this.cameraNear - this.cameraFar); + + /* eslint-disable indent */ + this.projMatrix.set(f / aspect, 0, 0, 0, + 0, -f * this.yScale, 0, 0, + 0, 0, (far + near) * nf, -1, + 0, 0, (2 * far * near) * nf, 0); + /* eslint-enable indent */ + + if (this._isActive()) { + this._renderer.uPMatrix.set( + this.projMatrix.mat4[0], + this.projMatrix.mat4[1], + this.projMatrix.mat4[2], + this.projMatrix.mat4[3], + this.projMatrix.mat4[4], + this.projMatrix.mat4[5], + this.projMatrix.mat4[6], + this.projMatrix.mat4[7], + this.projMatrix.mat4[8], + this.projMatrix.mat4[9], + this.projMatrix.mat4[10], + this.projMatrix.mat4[11], + this.projMatrix.mat4[12], + this.projMatrix.mat4[13], + this.projMatrix.mat4[14], + this.projMatrix.mat4[15] + ); + } } - this.aspectRatio = aspect; - this.cameraNear = near; - this.cameraFar = far; - - this.projMatrix = p5.Matrix.identity(); - - const f = 1.0 / Math.tan(this.cameraFOV / 2); - const nf = 1.0 / (this.cameraNear - this.cameraFar); - - /* eslint-disable indent */ - this.projMatrix.set(f / aspect, 0, 0, 0, - 0, -f * this.yScale, 0, 0, - 0, 0, (far + near) * nf, -1, - 0, 0, (2 * far * near) * nf, 0); - /* eslint-enable indent */ - - if (this._isActive()) { - this._renderer.uPMatrix.set( - this.projMatrix.mat4[0], - this.projMatrix.mat4[1], - this.projMatrix.mat4[2], - this.projMatrix.mat4[3], - this.projMatrix.mat4[4], - this.projMatrix.mat4[5], - this.projMatrix.mat4[6], - this.projMatrix.mat4[7], - this.projMatrix.mat4[8], - this.projMatrix.mat4[9], - this.projMatrix.mat4[10], - this.projMatrix.mat4[11], - this.projMatrix.mat4[12], - this.projMatrix.mat4[13], - this.projMatrix.mat4[14], - this.projMatrix.mat4[15] - ); - } -}; - -/** + /** * Sets an orthographic projection. * Accepts the same parameters as the global * ortho(). @@ -883,64 +884,64 @@ p5.Camera.prototype.perspective = function(fovy, aspect, near, far) { * @alt * two 3D boxes move back and forth along same plane, rotating as mouse is dragged. */ -p5.Camera.prototype.ortho = function(left, right, bottom, top, near, far) { - if (left === undefined) left = -this._renderer.width / 2; - if (right === undefined) right = +this._renderer.width / 2; - if (bottom === undefined) bottom = -this._renderer.height / 2; - if (top === undefined) top = +this._renderer.height / 2; - if (near === undefined) near = 0; - if (far === undefined) - far = Math.max(this._renderer.width, this._renderer.height); - - this.cameraNear = near; - this.cameraFar = far; - - const w = right - left; - const h = top - bottom; - const d = far - near; - - const x = +2.0 / w; - const y = +2.0 / h * this.yScale; - const z = -2.0 / d; - - const tx = -(right + left) / w; - const ty = -(top + bottom) / h; - const tz = -(far + near) / d; - - this.projMatrix = p5.Matrix.identity(); - - /* eslint-disable indent */ - this.projMatrix.set( x, 0, 0, 0, - 0, -y, 0, 0, - 0, 0, z, 0, - tx, ty, tz, 1); - /* eslint-enable indent */ - - if (this._isActive()) { - this._renderer.uPMatrix.set( - this.projMatrix.mat4[0], - this.projMatrix.mat4[1], - this.projMatrix.mat4[2], - this.projMatrix.mat4[3], - this.projMatrix.mat4[4], - this.projMatrix.mat4[5], - this.projMatrix.mat4[6], - this.projMatrix.mat4[7], - this.projMatrix.mat4[8], - this.projMatrix.mat4[9], - this.projMatrix.mat4[10], - this.projMatrix.mat4[11], - this.projMatrix.mat4[12], - this.projMatrix.mat4[13], - this.projMatrix.mat4[14], - this.projMatrix.mat4[15] - ); + ortho(left, right, bottom, top, near, far) { + if (left === undefined) left = -this._renderer.width / 2; + if (right === undefined) right = +this._renderer.width / 2; + if (bottom === undefined) bottom = -this._renderer.height / 2; + if (top === undefined) top = +this._renderer.height / 2; + if (near === undefined) near = 0; + if (far === undefined) + far = Math.max(this._renderer.width, this._renderer.height); + + this.cameraNear = near; + this.cameraFar = far; + + const w = right - left; + const h = top - bottom; + const d = far - near; + + const x = +2.0 / w; + const y = +2.0 / h * this.yScale; + const z = -2.0 / d; + + const tx = -(right + left) / w; + const ty = -(top + bottom) / h; + const tz = -(far + near) / d; + + this.projMatrix = p5.Matrix.identity(); + + /* eslint-disable indent */ + this.projMatrix.set( x, 0, 0, 0, + 0, -y, 0, 0, + 0, 0, z, 0, + tx, ty, tz, 1); + /* eslint-enable indent */ + + if (this._isActive()) { + this._renderer.uPMatrix.set( + this.projMatrix.mat4[0], + this.projMatrix.mat4[1], + this.projMatrix.mat4[2], + this.projMatrix.mat4[3], + this.projMatrix.mat4[4], + this.projMatrix.mat4[5], + this.projMatrix.mat4[6], + this.projMatrix.mat4[7], + this.projMatrix.mat4[8], + this.projMatrix.mat4[9], + this.projMatrix.mat4[10], + this.projMatrix.mat4[11], + this.projMatrix.mat4[12], + this.projMatrix.mat4[13], + this.projMatrix.mat4[14], + this.projMatrix.mat4[15] + ); + } + + this.cameraType = 'custom'; } - this.cameraType = 'custom'; -}; - -/** + /** * Sets the camera's frustum. * Accepts the same parameters as the global * frustum(). @@ -982,112 +983,112 @@ p5.Camera.prototype.ortho = function(left, right, bottom, top, near, far) { * @alt * two 3D boxes move back and forth along same plane, rotating as mouse is dragged. */ -p5.Camera.prototype.frustum = function(left, right, bottom, top, near, far) { - if (left === undefined) left = -this._renderer.width * 0.05; - if (right === undefined) right = +this._renderer.width * 0.05; - if (bottom === undefined) bottom = +this._renderer.height * 0.05; - if (top === undefined) top = -this._renderer.height * 0.05; - if (near === undefined) near = this.defaultCameraNear; - if (far === undefined) far = this.defaultCameraFar; - - this.cameraNear = near; - this.cameraFar = far; - - const w = right - left; - const h = top - bottom; - const d = far - near; - - const x = +(2.0 * near) / w; - const y = +(2.0 * near) / h * this.yScale; - const z = -(2.0 * far * near) / d; - - const tx = (right + left) / w; - const ty = (top + bottom) / h; - const tz = -(far + near) / d; - - this.projMatrix = p5.Matrix.identity(); - - /* eslint-disable indent */ - this.projMatrix.set( x, 0, 0, 0, - 0, -y, 0, 0, - tx, ty, tz, -1, - 0, 0, z, 0); - /* eslint-enable indent */ - - if (this._isActive()) { - this._renderer.uPMatrix.set( - this.projMatrix.mat4[0], - this.projMatrix.mat4[1], - this.projMatrix.mat4[2], - this.projMatrix.mat4[3], - this.projMatrix.mat4[4], - this.projMatrix.mat4[5], - this.projMatrix.mat4[6], - this.projMatrix.mat4[7], - this.projMatrix.mat4[8], - this.projMatrix.mat4[9], - this.projMatrix.mat4[10], - this.projMatrix.mat4[11], - this.projMatrix.mat4[12], - this.projMatrix.mat4[13], - this.projMatrix.mat4[14], - this.projMatrix.mat4[15] - ); + frustum(left, right, bottom, top, near, far) { + if (left === undefined) left = -this._renderer.width * 0.05; + if (right === undefined) right = +this._renderer.width * 0.05; + if (bottom === undefined) bottom = +this._renderer.height * 0.05; + if (top === undefined) top = -this._renderer.height * 0.05; + if (near === undefined) near = this.defaultCameraNear; + if (far === undefined) far = this.defaultCameraFar; + + this.cameraNear = near; + this.cameraFar = far; + + const w = right - left; + const h = top - bottom; + const d = far - near; + + const x = +(2.0 * near) / w; + const y = +(2.0 * near) / h * this.yScale; + const z = -(2.0 * far * near) / d; + + const tx = (right + left) / w; + const ty = (top + bottom) / h; + const tz = -(far + near) / d; + + this.projMatrix = p5.Matrix.identity(); + + /* eslint-disable indent */ + this.projMatrix.set( x, 0, 0, 0, + 0, -y, 0, 0, + tx, ty, tz, -1, + 0, 0, z, 0); + /* eslint-enable indent */ + + if (this._isActive()) { + this._renderer.uPMatrix.set( + this.projMatrix.mat4[0], + this.projMatrix.mat4[1], + this.projMatrix.mat4[2], + this.projMatrix.mat4[3], + this.projMatrix.mat4[4], + this.projMatrix.mat4[5], + this.projMatrix.mat4[6], + this.projMatrix.mat4[7], + this.projMatrix.mat4[8], + this.projMatrix.mat4[9], + this.projMatrix.mat4[10], + this.projMatrix.mat4[11], + this.projMatrix.mat4[12], + this.projMatrix.mat4[13], + this.projMatrix.mat4[14], + this.projMatrix.mat4[15] + ); + } + + this.cameraType = 'custom'; } - this.cameraType = 'custom'; -}; - -//////////////////////////////////////////////////////////////////////////////// -// Camera Orientation Methods -//////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + // Camera Orientation Methods + //////////////////////////////////////////////////////////////////////////////// -/** + /** * Rotate camera view about arbitrary axis defined by x,y,z * based on http://learnwebgl.brown37.net/07_cameras/camera_rotating_motion.html * @method _rotateView * @private */ -p5.Camera.prototype._rotateView = function(a, x, y, z) { - let centerX = this.centerX; - let centerY = this.centerY; - let centerZ = this.centerZ; - - // move center by eye position such that rotation happens around eye position - centerX -= this.eyeX; - centerY -= this.eyeY; - centerZ -= this.eyeZ; - - const rotation = p5.Matrix.identity(this._renderer._pInst); - rotation.rotate(this._renderer._pInst._toRadians(a), x, y, z); - - /* eslint-disable max-len */ - const rotatedCenter = [ - centerX * rotation.mat4[0] + centerY * rotation.mat4[4] + centerZ * rotation.mat4[8], - centerX * rotation.mat4[1] + centerY * rotation.mat4[5] + centerZ * rotation.mat4[9], - centerX * rotation.mat4[2] + centerY * rotation.mat4[6] + centerZ * rotation.mat4[10] - ]; - /* eslint-enable max-len */ - - // add eye position back into center - rotatedCenter[0] += this.eyeX; - rotatedCenter[1] += this.eyeY; - rotatedCenter[2] += this.eyeZ; - - this.camera( - this.eyeX, - this.eyeY, - this.eyeZ, - rotatedCenter[0], - rotatedCenter[1], - rotatedCenter[2], - this.upX, - this.upY, - this.upZ - ); -}; + _rotateView(a, x, y, z) { + let centerX = this.centerX; + let centerY = this.centerY; + let centerZ = this.centerZ; + + // move center by eye position such that rotation happens around eye position + centerX -= this.eyeX; + centerY -= this.eyeY; + centerZ -= this.eyeZ; + + const rotation = p5.Matrix.identity(this._renderer._pInst); + rotation.rotate(this._renderer._pInst._toRadians(a), x, y, z); + + /* eslint-disable max-len */ + const rotatedCenter = [ + centerX * rotation.mat4[0] + centerY * rotation.mat4[4] + centerZ * rotation.mat4[8], + centerX * rotation.mat4[1] + centerY * rotation.mat4[5] + centerZ * rotation.mat4[9], + centerX * rotation.mat4[2] + centerY * rotation.mat4[6] + centerZ * rotation.mat4[10] + ]; + /* eslint-enable max-len */ + + // add eye position back into center + rotatedCenter[0] += this.eyeX; + rotatedCenter[1] += this.eyeY; + rotatedCenter[2] += this.eyeZ; + + this.camera( + this.eyeX, + this.eyeY, + this.eyeZ, + rotatedCenter[0], + rotatedCenter[1], + rotatedCenter[2], + this.upX, + this.upY, + this.upZ + ); + } -/** + /** * Panning rotates the camera view to the left and right. * @method pan * @param {Number} angle amount to rotate camera in current @@ -1140,12 +1141,12 @@ p5.Camera.prototype._rotateView = function(a, x, y, z) { * @alt * camera view pans left and right across a series of rotating 3D boxes. */ -p5.Camera.prototype.pan = function(amount) { - const local = this._getLocalAxes(); - this._rotateView(amount, local.y[0], local.y[1], local.y[2]); -}; + pan(amount) { + const local = this._getLocalAxes(); + this._rotateView(amount, local.y[0], local.y[1], local.y[2]); + } -/** + /** * Tilting rotates the camera view up and down. * @method tilt * @param {Number} angle amount to rotate camera in current @@ -1198,12 +1199,12 @@ p5.Camera.prototype.pan = function(amount) { * @alt * camera view tilts up and down across a series of rotating 3D boxes. */ -p5.Camera.prototype.tilt = function(amount) { - const local = this._getLocalAxes(); - this._rotateView(amount, local.x[0], local.x[1], local.x[2]); -}; + tilt(amount) { + const local = this._getLocalAxes(); + this._rotateView(amount, local.x[0], local.x[1], local.x[2]); + } -/** + /** * Reorients the camera to look at a position in world space. * @method lookAt * @for p5.Camera @@ -1252,25 +1253,25 @@ p5.Camera.prototype.tilt = function(amount) { * camera view of rotating 3D cubes changes to look at a new random * point every second . */ -p5.Camera.prototype.lookAt = function(x, y, z) { - this.camera( - this.eyeX, - this.eyeY, - this.eyeZ, - x, - y, - z, - this.upX, - this.upY, - this.upZ - ); -}; + lookAt(x, y, z) { + this.camera( + this.eyeX, + this.eyeY, + this.eyeZ, + x, + y, + z, + this.upX, + this.upY, + this.upZ + ); + } -//////////////////////////////////////////////////////////////////////////////// -// Camera Position Methods -//////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + // Camera Position Methods + //////////////////////////////////////////////////////////////////////////////// -/** + /** * Sets the camera's position and orientation. * Accepts the same parameters as the global * camera(). @@ -1354,87 +1355,87 @@ p5.Camera.prototype.lookAt = function(x, y, z) { * An interactive example of a red cube with 3 sliders for moving it across x, y, * z axis and 3 sliders for shifting its center. */ -p5.Camera.prototype.camera = function( - eyeX, - eyeY, - eyeZ, - centerX, - centerY, - centerZ, - upX, - upY, - upZ -) { - if (typeof eyeX === 'undefined') { - eyeX = this.defaultEyeX; - eyeY = this.defaultEyeY; - eyeZ = this.defaultEyeZ; - centerX = eyeX; - centerY = eyeY; - centerZ = 0; - upX = 0; - upY = 1; - upZ = 0; + camera( + eyeX, + eyeY, + eyeZ, + centerX, + centerY, + centerZ, + upX, + upY, + upZ + ) { + if (typeof eyeX === 'undefined') { + eyeX = this.defaultEyeX; + eyeY = this.defaultEyeY; + eyeZ = this.defaultEyeZ; + centerX = eyeX; + centerY = eyeY; + centerZ = 0; + upX = 0; + upY = 1; + upZ = 0; + } + + this.eyeX = eyeX; + this.eyeY = eyeY; + this.eyeZ = eyeZ; + + if (typeof centerX !== 'undefined') { + this.centerX = centerX; + this.centerY = centerY; + this.centerZ = centerZ; + } + + if (typeof upX !== 'undefined') { + this.upX = upX; + this.upY = upY; + this.upZ = upZ; + } + + const local = this._getLocalAxes(); + + // the camera affects the model view matrix, insofar as it + // inverse translates the world to the eye position of the camera + // and rotates it. + /* eslint-disable indent */ + this.cameraMatrix.set(local.x[0], local.y[0], local.z[0], 0, + local.x[1], local.y[1], local.z[1], 0, + local.x[2], local.y[2], local.z[2], 0, + 0, 0, 0, 1); + /* eslint-enable indent */ + + const tx = -eyeX; + const ty = -eyeY; + const tz = -eyeZ; + + this.cameraMatrix.translate([tx, ty, tz]); + + if (this._isActive()) { + this._renderer.uMVMatrix.set( + this.cameraMatrix.mat4[0], + this.cameraMatrix.mat4[1], + this.cameraMatrix.mat4[2], + this.cameraMatrix.mat4[3], + this.cameraMatrix.mat4[4], + this.cameraMatrix.mat4[5], + this.cameraMatrix.mat4[6], + this.cameraMatrix.mat4[7], + this.cameraMatrix.mat4[8], + this.cameraMatrix.mat4[9], + this.cameraMatrix.mat4[10], + this.cameraMatrix.mat4[11], + this.cameraMatrix.mat4[12], + this.cameraMatrix.mat4[13], + this.cameraMatrix.mat4[14], + this.cameraMatrix.mat4[15] + ); + } + return this; } - this.eyeX = eyeX; - this.eyeY = eyeY; - this.eyeZ = eyeZ; - - if (typeof centerX !== 'undefined') { - this.centerX = centerX; - this.centerY = centerY; - this.centerZ = centerZ; - } - - if (typeof upX !== 'undefined') { - this.upX = upX; - this.upY = upY; - this.upZ = upZ; - } - - const local = this._getLocalAxes(); - - // the camera affects the model view matrix, insofar as it - // inverse translates the world to the eye position of the camera - // and rotates it. - /* eslint-disable indent */ - this.cameraMatrix.set(local.x[0], local.y[0], local.z[0], 0, - local.x[1], local.y[1], local.z[1], 0, - local.x[2], local.y[2], local.z[2], 0, - 0, 0, 0, 1); - /* eslint-enable indent */ - - const tx = -eyeX; - const ty = -eyeY; - const tz = -eyeZ; - - this.cameraMatrix.translate([tx, ty, tz]); - - if (this._isActive()) { - this._renderer.uMVMatrix.set( - this.cameraMatrix.mat4[0], - this.cameraMatrix.mat4[1], - this.cameraMatrix.mat4[2], - this.cameraMatrix.mat4[3], - this.cameraMatrix.mat4[4], - this.cameraMatrix.mat4[5], - this.cameraMatrix.mat4[6], - this.cameraMatrix.mat4[7], - this.cameraMatrix.mat4[8], - this.cameraMatrix.mat4[9], - this.cameraMatrix.mat4[10], - this.cameraMatrix.mat4[11], - this.cameraMatrix.mat4[12], - this.cameraMatrix.mat4[13], - this.cameraMatrix.mat4[14], - this.cameraMatrix.mat4[15] - ); - } - return this; -}; - -/** + /** * Move camera along its local axes while maintaining current camera orientation. * @method move * @param {Number} x amount to move along camera's left-right axis @@ -1484,29 +1485,29 @@ p5.Camera.prototype.camera = function( * camera view moves along a series of 3D boxes, maintaining the same * orientation throughout the move */ -p5.Camera.prototype.move = function(x, y, z) { - const local = this._getLocalAxes(); - - // scale local axes by movement amounts - // based on http://learnwebgl.brown37.net/07_cameras/camera_linear_motion.html - const dx = [local.x[0] * x, local.x[1] * x, local.x[2] * x]; - const dy = [local.y[0] * y, local.y[1] * y, local.y[2] * y]; - const dz = [local.z[0] * z, local.z[1] * z, local.z[2] * z]; - - this.camera( - this.eyeX + dx[0] + dy[0] + dz[0], - this.eyeY + dx[1] + dy[1] + dz[1], - this.eyeZ + dx[2] + dy[2] + dz[2], - this.centerX + dx[0] + dy[0] + dz[0], - this.centerY + dx[1] + dy[1] + dz[1], - this.centerZ + dx[2] + dy[2] + dz[2], - this.upX, - this.upY, - this.upZ - ); -}; + move(x, y, z) { + const local = this._getLocalAxes(); + + // scale local axes by movement amounts + // based on http://learnwebgl.brown37.net/07_cameras/camera_linear_motion.html + const dx = [local.x[0] * x, local.x[1] * x, local.x[2] * x]; + const dy = [local.y[0] * y, local.y[1] * y, local.y[2] * y]; + const dz = [local.z[0] * z, local.z[1] * z, local.z[2] * z]; + + this.camera( + this.eyeX + dx[0] + dy[0] + dz[0], + this.eyeY + dx[1] + dy[1] + dz[1], + this.eyeZ + dx[2] + dy[2] + dz[2], + this.centerX + dx[0] + dy[0] + dz[0], + this.centerY + dx[1] + dy[1] + dz[1], + this.centerZ + dx[2] + dy[2] + dz[2], + this.upX, + this.upY, + this.upZ + ); + } -/** + /** * Set camera position in world-space while maintaining current camera * orientation. * @method setPosition @@ -1550,168 +1551,168 @@ p5.Camera.prototype.move = function(x, y, z) { * @alt * camera position changes as the user presses keys, altering view of a 3D box */ -p5.Camera.prototype.setPosition = function(x, y, z) { - const diffX = x - this.eyeX; - const diffY = y - this.eyeY; - const diffZ = z - this.eyeZ; - - this.camera( - x, - y, - z, - this.centerX + diffX, - this.centerY + diffY, - this.centerZ + diffZ, - this.upX, - this.upY, - this.upZ - ); -}; - -//////////////////////////////////////////////////////////////////////////////// -// Camera Helper Methods -//////////////////////////////////////////////////////////////////////////////// + setPosition(x, y, z) { + const diffX = x - this.eyeX; + const diffY = y - this.eyeY; + const diffZ = z - this.eyeZ; + + this.camera( + x, + y, + z, + this.centerX + diffX, + this.centerY + diffY, + this.centerZ + diffZ, + this.upX, + this.upY, + this.upZ + ); + } -// @TODO: combine this function with _setDefaultCamera to compute these values -// as-needed -p5.Camera.prototype._computeCameraDefaultSettings = function() { - this.defaultCameraFOV = 60 / 180 * Math.PI; - this.defaultAspectRatio = this._renderer.width / this._renderer.height; - this.defaultEyeX = 0; - this.defaultEyeY = 0; - this.defaultEyeZ = - this._renderer.height / 2.0 / Math.tan(this.defaultCameraFOV / 2.0); - this.defaultCenterX = 0; - this.defaultCenterY = 0; - this.defaultCenterZ = 0; - this.defaultCameraNear = this.defaultEyeZ * 0.1; - this.defaultCameraFar = this.defaultEyeZ * 10; -}; + //////////////////////////////////////////////////////////////////////////////// + // Camera Helper Methods + //////////////////////////////////////////////////////////////////////////////// + + // @TODO: combine this function with _setDefaultCamera to compute these values + // as-needed + _computeCameraDefaultSettings() { + this.defaultCameraFOV = 60 / 180 * Math.PI; + this.defaultAspectRatio = this._renderer.width / this._renderer.height; + this.defaultEyeX = 0; + this.defaultEyeY = 0; + this.defaultEyeZ = + this._renderer.height / 2.0 / Math.tan(this.defaultCameraFOV / 2.0); + this.defaultCenterX = 0; + this.defaultCenterY = 0; + this.defaultCenterZ = 0; + this.defaultCameraNear = this.defaultEyeZ * 0.1; + this.defaultCameraFar = this.defaultEyeZ * 10; + } -//detect if user didn't set the camera -//then call this function below -p5.Camera.prototype._setDefaultCamera = function() { - this.cameraFOV = this.defaultCameraFOV; - this.aspectRatio = this.defaultAspectRatio; - this.eyeX = this.defaultEyeX; - this.eyeY = this.defaultEyeY; - this.eyeZ = this.defaultEyeZ; - this.centerX = this.defaultCenterX; - this.centerY = this.defaultCenterY; - this.centerZ = this.defaultCenterZ; - this.upX = 0; - this.upY = 1; - this.upZ = 0; - this.cameraNear = this.defaultCameraNear; - this.cameraFar = this.defaultCameraFar; - - this.perspective(); - this.camera(); - - this.cameraType = 'default'; -}; + //detect if user didn't set the camera + //then call this function below + _setDefaultCamera() { + this.cameraFOV = this.defaultCameraFOV; + this.aspectRatio = this.defaultAspectRatio; + this.eyeX = this.defaultEyeX; + this.eyeY = this.defaultEyeY; + this.eyeZ = this.defaultEyeZ; + this.centerX = this.defaultCenterX; + this.centerY = this.defaultCenterY; + this.centerZ = this.defaultCenterZ; + this.upX = 0; + this.upY = 1; + this.upZ = 0; + this.cameraNear = this.defaultCameraNear; + this.cameraFar = this.defaultCameraFar; + + this.perspective(); + this.camera(); + + this.cameraType = 'default'; + } -p5.Camera.prototype._resize = function() { - // If we're using the default camera, update the aspect ratio - if (this.cameraType === 'default') { - this._computeCameraDefaultSettings(); - this._setDefaultCamera(); - } else { - this.perspective( - this.cameraFOV, - this._renderer.width / this._renderer.height - ); + _resize() { + // If we're using the default camera, update the aspect ratio + if (this.cameraType === 'default') { + this._computeCameraDefaultSettings(); + this._setDefaultCamera(); + } else { + this.perspective( + this.cameraFOV, + this._renderer.width / this._renderer.height + ); + } } -}; -/** + /** * Returns a copy of a camera. * @method copy * @private */ -p5.Camera.prototype.copy = function() { - const _cam = new p5.Camera(this._renderer); - _cam.cameraFOV = this.cameraFOV; - _cam.aspectRatio = this.aspectRatio; - _cam.eyeX = this.eyeX; - _cam.eyeY = this.eyeY; - _cam.eyeZ = this.eyeZ; - _cam.centerX = this.centerX; - _cam.centerY = this.centerY; - _cam.centerZ = this.centerZ; - _cam.upX = this.upX; - _cam.upY = this.upY; - _cam.upZ = this.upZ; - _cam.cameraNear = this.cameraNear; - _cam.cameraFar = this.cameraFar; - - _cam.cameraType = this.cameraType; - - _cam.cameraMatrix = this.cameraMatrix.copy(); - _cam.projMatrix = this.projMatrix.copy(); - - return _cam; -}; + copy() { + const _cam = new p5.Camera(this._renderer); + _cam.cameraFOV = this.cameraFOV; + _cam.aspectRatio = this.aspectRatio; + _cam.eyeX = this.eyeX; + _cam.eyeY = this.eyeY; + _cam.eyeZ = this.eyeZ; + _cam.centerX = this.centerX; + _cam.centerY = this.centerY; + _cam.centerZ = this.centerZ; + _cam.upX = this.upX; + _cam.upY = this.upY; + _cam.upZ = this.upZ; + _cam.cameraNear = this.cameraNear; + _cam.cameraFar = this.cameraFar; + + _cam.cameraType = this.cameraType; + + _cam.cameraMatrix = this.cameraMatrix.copy(); + _cam.projMatrix = this.projMatrix.copy(); + + return _cam; + } -/** + /** * Returns a camera's local axes: left-right, up-down, and forward-backward, * as defined by vectors in world-space. * @method _getLocalAxes * @private */ -p5.Camera.prototype._getLocalAxes = function() { - // calculate camera local Z vector - let z0 = this.eyeX - this.centerX; - let z1 = this.eyeY - this.centerY; - let z2 = this.eyeZ - this.centerZ; - - // normalize camera local Z vector - const eyeDist = Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2); - if (eyeDist !== 0) { - z0 /= eyeDist; - z1 /= eyeDist; - z2 /= eyeDist; - } - - // calculate camera Y vector - let y0 = this.upX; - let y1 = this.upY; - let y2 = this.upZ; - - // compute camera local X vector as up vector (local Y) cross local Z - let x0 = y1 * z2 - y2 * z1; - let x1 = -y0 * z2 + y2 * z0; - let x2 = y0 * z1 - y1 * z0; - - // recompute y = z cross x - y0 = z1 * x2 - z2 * x1; - y1 = -z0 * x2 + z2 * x0; - y2 = z0 * x1 - z1 * x0; - - // cross product gives area of parallelogram, which is < 1.0 for - // non-perpendicular unit-length vectors; so normalize x, y here: - const xmag = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2); - if (xmag !== 0) { - x0 /= xmag; - x1 /= xmag; - x2 /= xmag; + _getLocalAxes() { + // calculate camera local Z vector + let z0 = this.eyeX - this.centerX; + let z1 = this.eyeY - this.centerY; + let z2 = this.eyeZ - this.centerZ; + + // normalize camera local Z vector + const eyeDist = Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2); + if (eyeDist !== 0) { + z0 /= eyeDist; + z1 /= eyeDist; + z2 /= eyeDist; + } + + // calculate camera Y vector + let y0 = this.upX; + let y1 = this.upY; + let y2 = this.upZ; + + // compute camera local X vector as up vector (local Y) cross local Z + let x0 = y1 * z2 - y2 * z1; + let x1 = -y0 * z2 + y2 * z0; + let x2 = y0 * z1 - y1 * z0; + + // recompute y = z cross x + y0 = z1 * x2 - z2 * x1; + y1 = -z0 * x2 + z2 * x0; + y2 = z0 * x1 - z1 * x0; + + // cross product gives area of parallelogram, which is < 1.0 for + // non-perpendicular unit-length vectors; so normalize x, y here: + const xmag = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2); + if (xmag !== 0) { + x0 /= xmag; + x1 /= xmag; + x2 /= xmag; + } + + const ymag = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); + if (ymag !== 0) { + y0 /= ymag; + y1 /= ymag; + y2 /= ymag; + } + + return { + x: [x0, x1, x2], + y: [y0, y1, y2], + z: [z0, z1, z2] + }; } - const ymag = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); - if (ymag !== 0) { - y0 /= ymag; - y1 /= ymag; - y2 /= ymag; - } - - return { - x: [x0, x1, x2], - y: [y0, y1, y2], - z: [z0, z1, z2] - }; -}; - -/** + /** * Orbits the camera about center point. For use with orbitControl(). * @method _orbit * @private @@ -1719,74 +1720,75 @@ p5.Camera.prototype._getLocalAxes = function() { * @param {Number} dPhi change in spherical coordinate phi * @param {Number} dRadius change in radius */ -p5.Camera.prototype._orbit = function(dTheta, dPhi, dRadius) { - // Calculate the vector and its magnitude from the center to the viewpoint - const diffX = this.eyeX - this.centerX; - const diffY = this.eyeY - this.centerY; - const diffZ = this.eyeZ - this.centerZ; - let camRadius = Math.hypot(diffX, diffY, diffZ); - // front vector. unit vector from center to eye. - const front = new p5.Vector(diffX, diffY, diffZ).normalize(); - // up vector. normalized camera's up vector. - const up = new p5.Vector(this.upX, this.upY, this.upZ).normalize(); // y-axis - // side vector. Right when viewed from the front - const side = new p5.Vector.cross(up, front).normalize(); // x-axis - // vertical vector. normalized vector of projection of front vector. - const vertical = new p5.Vector.cross(side, up); // z-axis - - // update camRadius - camRadius *= Math.pow(10, dRadius); - // prevent zooming through the center: - if (camRadius < this.cameraNear) { - camRadius = this.cameraNear; - } - if (camRadius > this.cameraFar) { - camRadius = this.cameraFar; - } - - // calculate updated camera angle - // Find the angle between the "up" and the "front", add dPhi to that. - // angleBetween() may return negative value. Since this specification is subject to change - // due to version updates, it cannot be adopted, so here we calculate using a method - // that directly obtains the absolute value. - const camPhi = - Math.acos(Math.max(-1, Math.min(1, p5.Vector.dot(front, up)))) + dPhi; - // Rotate by dTheta in the shortest direction from "vertical" to "side" - const camTheta = dTheta; - - // Invert camera's upX, upY, upZ if dPhi is below 0 or above PI - if(camPhi <= 0 || camPhi >= Math.PI){ - this.upX *= -1; - this.upY *= -1; - this.upZ *= -1; + _orbit(dTheta, dPhi, dRadius) { + // Calculate the vector and its magnitude from the center to the viewpoint + const diffX = this.eyeX - this.centerX; + const diffY = this.eyeY - this.centerY; + const diffZ = this.eyeZ - this.centerZ; + let camRadius = Math.hypot(diffX, diffY, diffZ); + // front vector. unit vector from center to eye. + const front = new p5.Vector(diffX, diffY, diffZ).normalize(); + // up vector. normalized camera's up vector. + const up = new p5.Vector(this.upX, this.upY, this.upZ).normalize(); // y-axis + // side vector. Right when viewed from the front + const side = new p5.Vector.cross(up, front).normalize(); // x-axis + // vertical vector. normalized vector of projection of front vector. + const vertical = new p5.Vector.cross(side, up); // z-axis + + // update camRadius + camRadius *= Math.pow(10, dRadius); + // prevent zooming through the center: + if (camRadius < this.cameraNear) { + camRadius = this.cameraNear; + } + if (camRadius > this.cameraFar) { + camRadius = this.cameraFar; + } + + // calculate updated camera angle + // Find the angle between the "up" and the "front", add dPhi to that. + // angleBetween() may return negative value. Since this specification is subject to change + // due to version updates, it cannot be adopted, so here we calculate using a method + // that directly obtains the absolute value. + const camPhi = + Math.acos(Math.max(-1, Math.min(1, p5.Vector.dot(front, up)))) + dPhi; + // Rotate by dTheta in the shortest direction from "vertical" to "side" + const camTheta = dTheta; + + // Invert camera's upX, upY, upZ if dPhi is below 0 or above PI + if (camPhi <= 0 || camPhi >= Math.PI) { + this.upX *= -1; + this.upY *= -1; + this.upZ *= -1; + } + + // update eye vector by calculate new front vector + up.mult(Math.cos(camPhi)); + vertical.mult(Math.cos(camTheta) * Math.sin(camPhi)); + side.mult(Math.sin(camTheta) * Math.sin(camPhi)); + + front.set(up).add(vertical).add(side); + + this.eyeX = camRadius * front.x + this.centerX; + this.eyeY = camRadius * front.y + this.centerY; + this.eyeZ = camRadius * front.z + this.centerZ; + + // update camera + this.camera( + this.eyeX, this.eyeY, this.eyeZ, + this.centerX, this.centerY, this.centerZ, + this.upX, this.upY, this.upZ + ); } - // update eye vector by calculate new front vector - up.mult(Math.cos(camPhi)); - vertical.mult(Math.cos(camTheta) * Math.sin(camPhi)); - side.mult(Math.sin(camTheta) * Math.sin(camPhi)); - - front.set(up).add(vertical).add(side); - - this.eyeX = camRadius * front.x + this.centerX; - this.eyeY = camRadius * front.y + this.centerY; - this.eyeZ = camRadius * front.z + this.centerZ; - - // update camera - this.camera( - this.eyeX, this.eyeY, this.eyeZ, - this.centerX, this.centerY, this.centerZ, - this.upX, this.upY, this.upZ - ); -}; - -/** + /** * Returns true if camera is currently attached to renderer. * @method _isActive * @private */ -p5.Camera.prototype._isActive = function() { - return this === this._renderer._curCamera; + _isActive() { + return this === this._renderer._curCamera; + } }; /** @@ -1864,7 +1866,7 @@ p5.Camera.prototype._isActive = function() { * Canvas switches between two camera views, each showing a series of spinning * 3D boxes. */ -p5.prototype.setCamera = function(cam) { +p5.prototype.setCamera = function (cam) { this._renderer._curCamera = cam; // set the projection matrix (which is not normally updated each frame) diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index fd6e0e2d2b..f5f28e60f7 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -11,11 +11,11 @@ import { join } from 'path'; const STROKE_CAP_ENUM = {}; const STROKE_JOIN_ENUM = {}; let lineDefs = ''; -const defineStrokeCapEnum = function(key, val) { +const defineStrokeCapEnum = function (key, val) { lineDefs += `#define STROKE_CAP_${key} ${val}\n`; STROKE_CAP_ENUM[constants[key]] = val; }; -const defineStrokeJoinEnum = function(key, val) { +const defineStrokeJoinEnum = function (key, val) { lineDefs += `#define STROKE_JOIN_${key} ${val}\n`; STROKE_JOIN_ENUM[constants[key]] = val; }; @@ -86,7 +86,7 @@ const defaultShaders = { * @todo extend class to include public method for offscreen * rendering (FBO). */ -p5.RendererGL = function(elt, pInst, isMainCanvas, attr) { +p5.RendererGL = function (elt, pInst, isMainCanvas, attr) { p5.Renderer.call(this, elt, pInst, isMainCanvas); this._setAttributeDefaults(pInst); this._initContext(); @@ -138,6 +138,7 @@ p5.RendererGL = function(elt, pInst, isMainCanvas, attr) { } this._isBlending = false; + this._hasSetAmbient = false; this._useSpecularMaterial = false; this._useEmissiveMaterial = false; @@ -157,9 +158,9 @@ p5.RendererGL = function(elt, pInst, isMainCanvas, attr) { this.quadraticAttenuation = 0; /** - * model view, projection, & normal - * matrices - */ + * model view, projection, & normal + * matrices + */ this.uMVMatrix = new p5.Matrix(); this.uPMatrix = new p5.Matrix(); this.uNMatrix = new p5.Matrix('mat3'); @@ -214,7 +215,7 @@ p5.RendererGL = function(elt, pInst, isMainCanvas, attr) { new p5.RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten) ], text: [ - new p5.RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition',this, this._vToNArray), + new p5.RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray), new p5.RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten) ] } @@ -292,7 +293,7 @@ p5.RendererGL.prototype = Object.create(p5.Renderer.prototype); // Setting ////////////////////////////////////////////// -p5.RendererGL.prototype._setAttributeDefaults = function(pInst) { +p5.RendererGL.prototype._setAttributeDefaults = function (pInst) { // See issue #3850, safer to enable AA in Safari const applyAA = navigator.userAgent.toLowerCase().includes('safari'); const defaults = { @@ -313,7 +314,7 @@ p5.RendererGL.prototype._setAttributeDefaults = function(pInst) { return; }; -p5.RendererGL.prototype._initContext = function() { +p5.RendererGL.prototype._initContext = function () { if (this._pInst._glAttributes.version !== 1) { // Unless WebGL1 is explicitly asked for, try to create a WebGL2 context this.drawingContext = @@ -350,7 +351,7 @@ p5.RendererGL.prototype._initContext = function() { //This is helper function to reset the context anytime the attributes //are changed with setAttributes() -p5.RendererGL.prototype._resetContext = function(options, callback) { +p5.RendererGL.prototype._resetContext = function (options, callback) { const w = this.width; const h = this.height; const defaultId = this.canvas.id; @@ -557,11 +558,11 @@ p5.RendererGL.prototype._resetContext = function(options, callback) { * @param {Object} obj object with key-value pairs */ -p5.prototype.setAttributes = function(key, value) { +p5.prototype.setAttributes = function (key, value) { if (typeof this._glAttributes === 'undefined') { console.log( 'You are trying to use setAttributes on a p5.Graphics object ' + - 'that does not use a WEBGL renderer.' + 'that does not use a WEBGL renderer.' ); return; } @@ -593,7 +594,7 @@ p5.prototype.setAttributes = function(key, value) { if (this._renderer.retainedMode.geometry.hasOwnProperty(x)) { p5._friendlyError( 'Sorry, Could not set the attributes, you need to call setAttributes() ' + - 'before calling the other drawing methods in setup()' + 'before calling the other drawing methods in setup()' ); return; } @@ -613,7 +614,7 @@ p5.prototype.setAttributes = function(key, value) { * @class p5.RendererGL */ -p5.RendererGL.prototype._update = function() { +p5.RendererGL.prototype._update = function () { // reset model view and apply initial camera transform // (containing only look at info; no projection). this.uMVMatrix.set( @@ -667,7 +668,7 @@ p5.RendererGL.prototype._update = function() { /** * [background description] */ -p5.RendererGL.prototype.background = function(...args) { +p5.RendererGL.prototype.background = function (...args) { const _col = this._pInst.color(...args); const _r = _col.levels[0] / 255; const _g = _col.levels[1] / 255; @@ -711,7 +712,7 @@ p5.RendererGL.prototype.background = function(...args) { * @alt * black canvas with purple cube spinning */ -p5.RendererGL.prototype.fill = function(v1, v2, v3, a) { +p5.RendererGL.prototype.fill = function (v1, v2, v3, a) { //see material.js for more info on color blending in webgl const color = p5.prototype.color.apply(this._pInst, arguments); this.curFillColor = color._array; @@ -750,27 +751,27 @@ p5.RendererGL.prototype.fill = function(v1, v2, v3, a) { * @alt * black canvas with purple cube with pink outline spinning */ -p5.RendererGL.prototype.stroke = function(r, g, b, a) { +p5.RendererGL.prototype.stroke = function (r, g, b, a) { const color = p5.prototype.color.apply(this._pInst, arguments); this.curStrokeColor = color._array; }; -p5.RendererGL.prototype.strokeCap = function(cap) { +p5.RendererGL.prototype.strokeCap = function (cap) { this.curStrokeCap = cap; }; -p5.RendererGL.prototype.strokeJoin = function(join) { +p5.RendererGL.prototype.strokeJoin = function (join) { this.curStrokeJoin = join; }; -p5.RendererGL.prototype.filter = function(filterType) { +p5.RendererGL.prototype.filter = function (filterType) { // filter can be achieved using custom shaders. // https://github.com/aferriss/p5jsShaderExamples // https://itp-xstory.github.io/p5js-shaders/#/ p5._friendlyError('filter() does not work in WEBGL mode'); }; -p5.RendererGL.prototype.blendMode = function(mode) { +p5.RendererGL.prototype.blendMode = function (mode) { if ( mode === constants.DARKEST || mode === constants.LIGHTEST || @@ -797,7 +798,7 @@ p5.RendererGL.prototype.blendMode = function(mode) { } }; -p5.RendererGL.prototype.erase = function(opacityFill, opacityStroke) { +p5.RendererGL.prototype.erase = function (opacityFill, opacityStroke) { if (!this._isErasing) { this._applyBlendMode(constants.REMOVE); this._isErasing = true; @@ -810,7 +811,7 @@ p5.RendererGL.prototype.erase = function(opacityFill, opacityStroke) { } }; -p5.RendererGL.prototype.noErase = function() { +p5.RendererGL.prototype.noErase = function () { if (this._isErasing) { this._isErasing = false; this.curFillColor = this._cachedFillStyle.slice(); @@ -858,7 +859,7 @@ p5.RendererGL.prototype.noErase = function() { * black canvas with two purple rotating spheres with pink * outlines the sphere on top has much heavier outlines, */ -p5.RendererGL.prototype.strokeWeight = function(w) { +p5.RendererGL.prototype.strokeWeight = function (w) { if (this.curStrokeWeight !== w) { this.pointSize = w; this.curStrokeWeight = w; @@ -866,7 +867,7 @@ p5.RendererGL.prototype.strokeWeight = function(w) { }; // x,y are canvas-relative (pre-scaled by _pixelDensity) -p5.RendererGL.prototype._getPixel = function(x, y) { +p5.RendererGL.prototype._getPixel = function (x, y) { const gl = this.GL; return readPixelWebGL( gl, @@ -888,7 +889,7 @@ p5.RendererGL.prototype._getPixel = function(x, y) { * @method loadPixels */ -p5.RendererGL.prototype.loadPixels = function() { +p5.RendererGL.prototype.loadPixels = function () { const pixelsState = this._pixelsState; //@todo_FES @@ -919,7 +920,7 @@ p5.RendererGL.prototype.loadPixels = function() { ); }; -p5.RendererGL.prototype.updatePixels = function() { +p5.RendererGL.prototype.updatePixels = function () { const fbo = this._getTempFramebuffer(); fbo.pixels = this._pixelsState.pixels; fbo.updatePixels(); @@ -939,7 +940,7 @@ p5.RendererGL.prototype.updatePixels = function() { * of the renderer's canvas. It will be created if it does not yet exist, and * reused if it does. */ -p5.RendererGL.prototype._getTempFramebuffer = function() { +p5.RendererGL.prototype._getTempFramebuffer = function () { if (!this._tempFramebuffer) { this._tempFramebuffer = this._pInst.createFramebuffer({ format: constants.UNSIGNED_BYTE, @@ -1067,7 +1068,7 @@ export function readPixelWebGL( // HASH | for geometry ////////////////////////////////////////////// -p5.RendererGL.prototype.geometryInHash = function(gId) { +p5.RendererGL.prototype.geometryInHash = function (gId) { return this.retainedMode.geometry[gId] !== undefined; }; @@ -1077,7 +1078,7 @@ p5.RendererGL.prototype.geometryInHash = function(gId) { * @param {Number} w [description] * @param {Number} h [description] */ -p5.RendererGL.prototype.resize = function(w, h) { +p5.RendererGL.prototype.resize = function (w, h) { p5.Renderer.prototype.resize.call(this, w, h); this.GL.viewport( 0, @@ -1116,7 +1117,7 @@ p5.RendererGL.prototype.resize = function(w, h) { * @param {Number} b normalized blue val. * @param {Number} a normalized alpha val. */ -p5.RendererGL.prototype.clear = function(...args) { +p5.RendererGL.prototype.clear = function (...args) { const _r = args[0] || 0; const _g = args[1] || 0; const _b = args[2] || 0; @@ -1127,7 +1128,7 @@ p5.RendererGL.prototype.clear = function(...args) { this.GL.clear(this.GL.COLOR_BUFFER_BIT | this.GL.DEPTH_BUFFER_BIT); }; -p5.RendererGL.prototype.applyMatrix = function(a, b, c, d, e, f) { +p5.RendererGL.prototype.applyMatrix = function (a, b, c, d, e, f) { if (arguments.length === 16) { p5.Matrix.prototype.apply.apply(this.uMVMatrix, arguments); } else { @@ -1149,7 +1150,7 @@ p5.RendererGL.prototype.applyMatrix = function(a, b, c, d, e, f) { * @chainable * @todo implement handle for components or vector as args */ -p5.RendererGL.prototype.translate = function(x, y, z) { +p5.RendererGL.prototype.translate = function (x, y, z) { if (x instanceof p5.Vector) { z = x.z; y = x.y; @@ -1167,12 +1168,12 @@ p5.RendererGL.prototype.translate = function(x, y, z) { * @param {Number} [z] z-axis scalar * @chainable */ -p5.RendererGL.prototype.scale = function(x, y, z) { +p5.RendererGL.prototype.scale = function (x, y, z) { this.uMVMatrix.scale(x, y, z); return this; }; -p5.RendererGL.prototype.rotate = function(rad, axis) { +p5.RendererGL.prototype.rotate = function (rad, axis) { if (typeof axis === 'undefined') { return this.rotateZ(rad); } @@ -1180,22 +1181,22 @@ p5.RendererGL.prototype.rotate = function(rad, axis) { return this; }; -p5.RendererGL.prototype.rotateX = function(rad) { +p5.RendererGL.prototype.rotateX = function (rad) { this.rotate(rad, 1, 0, 0); return this; }; -p5.RendererGL.prototype.rotateY = function(rad) { +p5.RendererGL.prototype.rotateY = function (rad) { this.rotate(rad, 0, 1, 0); return this; }; -p5.RendererGL.prototype.rotateZ = function(rad) { +p5.RendererGL.prototype.rotateZ = function (rad) { this.rotate(rad, 0, 0, 1); return this; }; -p5.RendererGL.prototype.push = function() { +p5.RendererGL.prototype.push = function () { // get the base renderer style const style = p5.Renderer.prototype.push.apply(this); @@ -1263,7 +1264,7 @@ p5.RendererGL.prototype.push = function() { return style; }; -p5.RendererGL.prototype.resetMatrix = function() { +p5.RendererGL.prototype.resetMatrix = function () { this.uMVMatrix.set( this._curCamera.cameraMatrix.mat4[0], this._curCamera.cameraMatrix.mat4[1], @@ -1295,7 +1296,7 @@ p5.RendererGL.prototype.resetMatrix = function() { * and the shader must be valid in that context. */ -p5.RendererGL.prototype._getImmediateStrokeShader = function() { +p5.RendererGL.prototype._getImmediateStrokeShader = function () { // select the stroke shader to use const stroke = this.userStrokeShader; if (!stroke || !stroke.isStrokeShader()) { @@ -1311,7 +1312,7 @@ p5.RendererGL.prototype._getRetainedStrokeShader = * selects which fill shader should be used based on renderer state, * for use with begin/endShape and immediate vertex mode. */ -p5.RendererGL.prototype._getImmediateFillShader = function() { +p5.RendererGL.prototype._getImmediateFillShader = function () { const fill = this.userFillShader; if (this._useNormalMaterial) { if (!fill || !fill.isNormalShader()) { @@ -1336,7 +1337,7 @@ p5.RendererGL.prototype._getImmediateFillShader = function() { * selects which fill shader should be used based on renderer state * for retained mode. */ -p5.RendererGL.prototype._getRetainedFillShader = function() { +p5.RendererGL.prototype._getRetainedFillShader = function () { if (this._useNormalMaterial) { return this._getNormalShader(); } @@ -1356,7 +1357,7 @@ p5.RendererGL.prototype._getRetainedFillShader = function() { return fill; }; -p5.RendererGL.prototype._getImmediatePointShader = function() { +p5.RendererGL.prototype._getImmediatePointShader = function () { // select the point shader to use const point = this.userPointShader; if (!point || !point.isPointShader()) { @@ -1368,7 +1369,7 @@ p5.RendererGL.prototype._getImmediatePointShader = function() { p5.RendererGL.prototype._getRetainedLineShader = p5.RendererGL.prototype._getImmediateLineShader; -p5.RendererGL.prototype._getLightShader = function() { +p5.RendererGL.prototype._getLightShader = function () { if (!this._defaultLightShader) { if (this._pInst._glAttributes.perPixelLighting) { this._defaultLightShader = new p5.Shader( @@ -1388,7 +1389,7 @@ p5.RendererGL.prototype._getLightShader = function() { return this._defaultLightShader; }; -p5.RendererGL.prototype._getImmediateModeShader = function() { +p5.RendererGL.prototype._getImmediateModeShader = function () { if (!this._defaultImmediateModeShader) { this._defaultImmediateModeShader = new p5.Shader( this, @@ -1400,7 +1401,7 @@ p5.RendererGL.prototype._getImmediateModeShader = function() { return this._defaultImmediateModeShader; }; -p5.RendererGL.prototype._getNormalShader = function() { +p5.RendererGL.prototype._getNormalShader = function () { if (!this._defaultNormalShader) { this._defaultNormalShader = new p5.Shader( this, @@ -1412,7 +1413,7 @@ p5.RendererGL.prototype._getNormalShader = function() { return this._defaultNormalShader; }; -p5.RendererGL.prototype._getColorShader = function() { +p5.RendererGL.prototype._getColorShader = function () { if (!this._defaultColorShader) { this._defaultColorShader = new p5.Shader( this, @@ -1424,7 +1425,7 @@ p5.RendererGL.prototype._getColorShader = function() { return this._defaultColorShader; }; -p5.RendererGL.prototype._getPointShader = function() { +p5.RendererGL.prototype._getPointShader = function () { if (!this._defaultPointShader) { this._defaultPointShader = new p5.Shader( this, @@ -1435,7 +1436,7 @@ p5.RendererGL.prototype._getPointShader = function() { return this._defaultPointShader; }; -p5.RendererGL.prototype._getLineShader = function() { +p5.RendererGL.prototype._getLineShader = function () { if (!this._defaultLineShader) { this._defaultLineShader = new p5.Shader( this, @@ -1447,7 +1448,7 @@ p5.RendererGL.prototype._getLineShader = function() { return this._defaultLineShader; }; -p5.RendererGL.prototype._getFontShader = function() { +p5.RendererGL.prototype._getFontShader = function () { if (!this._defaultFontShader) { if (this.webglVersion === constants.WEBGL) { this.GL.getExtension('OES_standard_derivatives'); @@ -1455,15 +1456,15 @@ p5.RendererGL.prototype._getFontShader = function() { this._defaultFontShader = new p5.Shader( this, this._webGL2CompatibilityPrefix('vert', 'mediump') + - defaultShaders.fontVert, + defaultShaders.fontVert, this._webGL2CompatibilityPrefix('frag', 'mediump') + - defaultShaders.fontFrag + defaultShaders.fontFrag ); } return this._defaultFontShader; }; -p5.RendererGL.prototype._webGL2CompatibilityPrefix = function( +p5.RendererGL.prototype._webGL2CompatibilityPrefix = function ( shaderType, floatPrecision ) { @@ -1482,7 +1483,7 @@ p5.RendererGL.prototype._webGL2CompatibilityPrefix = function( return code; }; -p5.RendererGL.prototype._getEmptyTexture = function() { +p5.RendererGL.prototype._getEmptyTexture = function () { if (!this._emptyTexture) { // a plain white texture RGBA, full alpha, single pixel. const im = new p5.Image(1, 1); @@ -1492,7 +1493,7 @@ p5.RendererGL.prototype._getEmptyTexture = function() { return this._emptyTexture; }; -p5.RendererGL.prototype.getTexture = function(input) { +p5.RendererGL.prototype.getTexture = function (input) { let src = input; if (src instanceof p5.Framebuffer) { src = src.color; @@ -1508,11 +1509,11 @@ p5.RendererGL.prototype.getTexture = function(input) { return tex; }; -p5.RendererGL.prototype.createFramebuffer = function(options) { +p5.RendererGL.prototype.createFramebuffer = function (options) { return new p5.Framebuffer(this, options); }; -p5.RendererGL.prototype._setStrokeUniforms = function(strokeShader) { +p5.RendererGL.prototype._setStrokeUniforms = function (strokeShader) { strokeShader.bindShader(); // set the uniform values @@ -1523,7 +1524,7 @@ p5.RendererGL.prototype._setStrokeUniforms = function(strokeShader) { strokeShader.setUniform('uStrokeJoin', STROKE_JOIN_ENUM[this.curStrokeJoin]); }; -p5.RendererGL.prototype._setFillUniforms = function(fillShader) { +p5.RendererGL.prototype._setFillUniforms = function (fillShader) { fillShader.bindShader(); // TODO: optimize @@ -1593,7 +1594,7 @@ p5.RendererGL.prototype._setFillUniforms = function(fillShader) { fillShader.bindTextures(); }; -p5.RendererGL.prototype._setPointUniforms = function(pointShader) { +p5.RendererGL.prototype._setPointUniforms = function (pointShader) { pointShader.bindShader(); // set the uniform values @@ -1610,7 +1611,7 @@ p5.RendererGL.prototype._setPointUniforms = function(pointShader) { * when passed more than two arguments it also updates or initializes * the data associated with the buffer */ -p5.RendererGL.prototype._bindBuffer = function( +p5.RendererGL.prototype._bindBuffer = function ( buffer, target, values, @@ -1628,7 +1629,7 @@ p5.RendererGL.prototype._bindBuffer = function( /////////////////////////////// //// UTILITY FUNCTIONS ////////////////////////////// -p5.RendererGL.prototype._arraysEqual = function(a, b) { +p5.RendererGL.prototype._arraysEqual = function (a, b) { const aLength = a.length; if (aLength !== b.length) return false; for (let i = 0; i < aLength; i++) { @@ -1637,7 +1638,7 @@ p5.RendererGL.prototype._arraysEqual = function(a, b) { return true; }; -p5.RendererGL.prototype._isTypedArray = function(arr) { +p5.RendererGL.prototype._isTypedArray = function (arr) { let res = false; res = arr instanceof Float32Array; res = arr instanceof Float64Array; @@ -1653,7 +1654,7 @@ p5.RendererGL.prototype._isTypedArray = function(arr) { * @return {Array} 1-dimensional array * [[1, 2, 3],[4, 5, 6]] -> [1, 2, 3, 4, 5, 6] */ -p5.RendererGL.prototype._flatten = function(arr) { +p5.RendererGL.prototype._flatten = function (arr) { //when empty, return empty if (arr.length === 0) { return []; @@ -1691,7 +1692,7 @@ p5.RendererGL.prototype._flatten = function(arr) { * [p5.Vector(1, 2, 3), p5.Vector(4, 5, 6)] -> * [1, 2, 3, 4, 5, 6] */ -p5.RendererGL.prototype._vToNArray = function(arr) { +p5.RendererGL.prototype._vToNArray = function (arr) { const ret = []; for (const item of arr) { @@ -1704,7 +1705,7 @@ p5.RendererGL.prototype._vToNArray = function(arr) { /** * ensures that p5 is using a 3d renderer. throws an error if not. */ -p5.prototype._assert3d = function(name) { +p5.prototype._assert3d = function (name) { if (!this._renderer.isP3D) throw new Error( `${name}() is only supported in WEBGL mode. If you'd like to use 3D graphics and WebGL, see https://p5js.org/examples/form-3d-primitives.html for more information.` @@ -1758,7 +1759,7 @@ p5.RendererGL.prototype._initTessy = function initTesselator() { return tessy; }; -p5.RendererGL.prototype._triangulate = function(contours) { +p5.RendererGL.prototype._triangulate = function (contours) { // libtess will take 3d verts and flatten to a plane for tesselation. // libtess is capable of calculating a plane to tesselate on, but // if all of the vertices have the same z values, we'll just @@ -1812,7 +1813,7 @@ p5.RendererGL.prototype._triangulate = function(contours) { }; // function to calculate BezierVertex Coefficients -p5.RendererGL.prototype._bezierCoefficients = function(t) { +p5.RendererGL.prototype._bezierCoefficients = function (t) { const t2 = t * t; const t3 = t2 * t; const mt = 1 - t; @@ -1822,7 +1823,7 @@ p5.RendererGL.prototype._bezierCoefficients = function(t) { }; // function to calculate QuadraticVertex Coefficients -p5.RendererGL.prototype._quadraticCoefficients = function(t) { +p5.RendererGL.prototype._quadraticCoefficients = function (t) { const t2 = t * t; const mt = 1 - t; const mt2 = mt * mt; @@ -1830,7 +1831,7 @@ p5.RendererGL.prototype._quadraticCoefficients = function(t) { }; // function to convert Bezier coordinates to Catmull Rom Splines -p5.RendererGL.prototype._bezierToCatmull = function(w) { +p5.RendererGL.prototype._bezierToCatmull = function (w) { const p1 = w[1]; const p2 = w[1] + (w[2] - w[0]) / this._curveTightness; const p3 = w[2] - (w[3] - w[1]) / this._curveTightness; diff --git a/src/webgl/text.js b/src/webgl/text.js index 6d589474af..276b1a5d25 100644 --- a/src/webgl/text.js +++ b/src/webgl/text.js @@ -44,21 +44,22 @@ const cellImageHeight = 64; * * the ImageInfos class holds a list of ImageDatas of a given size. */ -function ImageInfos(width, height) { - this.width = width; - this.height = height; - this.infos = []; // the list of images - +class ImageInfos { + constructor(width, height) { + this.width = width; + this.height = height; + this.infos = []; // the list of images + } /** - * - * @method findImage - * @param {Integer} space - * @return {Object} contains the ImageData, and pixel index into that - * ImageData where the free space was allocated. - * - * finds free space of a given size in the ImageData list - */ - this.findImage = function(space) { + * + * @method findImage + * @param {Integer} space + * @return {Object} contains the ImageData, and pixel index into that + * ImageData where the free space was allocated. + * + * finds free space of a given size in the ImageData list + */ + findImage (space) { const imageSize = this.width * this.height; if (space > imageSize) throw new Error('font is too complex to render in 3D'); @@ -109,7 +110,7 @@ function ImageInfos(width, height) { imageInfo.index += space; // move to the start of the next image imageData._dirty = true; return { imageData, index }; - }; + } } /** @@ -141,30 +142,30 @@ const SQRT3 = Math.sqrt(3); * * contains cached images and glyph information for an opentype font */ -const FontInfo = function(font) { - this.font = font; - // the bezier curve coordinates - this.strokeImageInfos = new ImageInfos(strokeImageWidth, strokeImageHeight); - // lists of curve indices for each row/column slice - this.colDimImageInfos = new ImageInfos(gridImageWidth, gridImageHeight); - this.rowDimImageInfos = new ImageInfos(gridImageWidth, gridImageHeight); - // the offset & length of each row/col slice in the glyph - this.colCellImageInfos = new ImageInfos(cellImageWidth, cellImageHeight); - this.rowCellImageInfos = new ImageInfos(cellImageWidth, cellImageHeight); - - // the cached information for each glyph - this.glyphInfos = {}; - +class FontInfo { + constructor(font) { + this.font = font; + // the bezier curve coordinates + this.strokeImageInfos = new ImageInfos(strokeImageWidth, strokeImageHeight); + // lists of curve indices for each row/column slice + this.colDimImageInfos = new ImageInfos(gridImageWidth, gridImageHeight); + this.rowDimImageInfos = new ImageInfos(gridImageWidth, gridImageHeight); + // the offset & length of each row/col slice in the glyph + this.colCellImageInfos = new ImageInfos(cellImageWidth, cellImageHeight); + this.rowCellImageInfos = new ImageInfos(cellImageWidth, cellImageHeight); + + // the cached information for each glyph + this.glyphInfos = {}; + } /** - * @method getGlyphInfo - * @param {Glyph} glyph the x positions of points in the curve - * @returns {Object} the glyphInfo for that glyph - * - * calculates rendering info for a glyph, including the curve information, - * row & column stripes compiled into textures. - */ - - this.getGlyphInfo = function(glyph) { + * @method getGlyphInfo + * @param {Glyph} glyph the x positions of points in the curve + * @returns {Object} the glyphInfo for that glyph + * + * calculates rendering info for a glyph, including the curve information, + * row & column stripes compiled into textures. + */ + getGlyphInfo (glyph) { // check the cache let gi = this.glyphInfos[glyph.index]; if (gi) return gi; @@ -189,25 +190,25 @@ const FontInfo = function(font) { for (i = charGridHeight - 1; i >= 0; --i) rows.push([]); /** - * @function push - * @param {Number[]} xs the x positions of points in the curve - * @param {Number[]} ys the y positions of points in the curve - * @param {Object} v the curve information - * - * adds a curve to the rows & columns that it intersects with - */ + * @function push + * @param {Number[]} xs the x positions of points in the curve + * @param {Number[]} ys the y positions of points in the curve + * @param {Object} v the curve information + * + * adds a curve to the rows & columns that it intersects with + */ function push(xs, ys, v) { const index = strokes.length; // the index of this stroke strokes.push(v); // add this stroke to the list /** - * @function minMax - * @param {Number[]} rg the list of values to compare - * @param {Number} min the initial minimum value - * @param {Number} max the initial maximum value - * - * find the minimum & maximum value in a list of values - */ + * @function minMax + * @param {Number[]} rg the list of values to compare + * @param {Number} min the initial minimum value + * @param {Number} max the initial maximum value + * + * find the minimum & maximum value in a list of values + */ function minMax(rg, min, max) { for (let i = rg.length; i-- > 0; ) { const v = rg[i]; @@ -250,13 +251,13 @@ const FontInfo = function(font) { } /** - * @function clamp - * @param {Number} v the value to clamp - * @param {Number} min the minimum value - * @param {Number} max the maxmimum value - * - * clamps a value between a minimum & maximum value - */ + * @function clamp + * @param {Number} v the value to clamp + * @param {Number} min the minimum value + * @param {Number} max the maxmimum value + * + * clamps a value between a minimum & maximum value + */ function clamp(v, min, max) { if (v < min) return min; if (v > max) return max; @@ -264,39 +265,40 @@ const FontInfo = function(font) { } /** - * @function byte - * @param {Number} v the value to scale - * - * converts a floating-point number in the range 0-1 to a byte 0-255 - */ + * @function byte + * @param {Number} v the value to scale + * + * converts a floating-point number in the range 0-1 to a byte 0-255 + */ function byte(v) { return clamp(255 * v, 0, 255); } /** - * @private - * @class Cubic - * @param {Number} p0 the start point of the curve - * @param {Number} c0 the first control point - * @param {Number} c1 the second control point - * @param {Number} p1 the end point - * - * a cubic curve - */ - function Cubic(p0, c0, c1, p1) { - this.p0 = p0; - this.c0 = c0; - this.c1 = c1; - this.p1 = p1; - - /** - * @method toQuadratic - * @return {Object} the quadratic approximation + * @private + * @class Cubic + * @param {Number} p0 the start point of the curve + * @param {Number} c0 the first control point + * @param {Number} c1 the second control point + * @param {Number} p1 the end point * - * converts the cubic to a quadtratic approximation by - * picking an appropriate quadratic control point + * a cubic curve */ - this.toQuadratic = function() { + class Cubic { + constructor(p0, c0, c1, p1) { + this.p0 = p0; + this.c0 = c0; + this.c1 = c1; + this.p1 = p1; + } + /** + * @method toQuadratic + * @return {Object} the quadratic approximation + * + * converts the cubic to a quadtratic approximation by + * picking an appropriate quadratic control point + */ + toQuadratic () { return { x: this.p0.x, y: this.p0.y, @@ -305,34 +307,34 @@ const FontInfo = function(font) { cx: ((this.c0.x + this.c1.x) * 3 - (this.p0.x + this.p1.x)) / 4, cy: ((this.c0.y + this.c1.y) * 3 - (this.p0.y + this.p1.y)) / 4 }; - }; + } /** - * @method quadError - * @return {Number} the error - * - * calculates the magnitude of error of this curve's - * quadratic approximation. - */ - this.quadError = function() { + * @method quadError + * @return {Number} the error + * + * calculates the magnitude of error of this curve's + * quadratic approximation. + */ + quadError () { return ( p5.Vector.sub( p5.Vector.sub(this.p1, this.p0), p5.Vector.mult(p5.Vector.sub(this.c1, this.c0), 3) ).mag() / 2 ); - }; + } /** - * @method split - * @param {Number} t the value (0-1) at which to split - * @return {Cubic} the second part of the curve - * - * splits the cubic into two parts at a point 't' along the curve. - * this cubic keeps its start point and its end point becomes the - * point at 't'. the 'end half is returned. - */ - this.split = function(t) { + * @method split + * @param {Number} t the value (0-1) at which to split + * @return {Cubic} the second part of the curve + * + * splits the cubic into two parts at a point 't' along the curve. + * this cubic keeps its start point and its end point becomes the + * point at 't'. the 'end half is returned. + */ + split (t) { const m1 = p5.Vector.lerp(this.p0, this.c0, t); const m2 = p5.Vector.lerp(this.c0, this.c1, t); const mm1 = p5.Vector.lerp(m1, m2, t); @@ -343,17 +345,17 @@ const FontInfo = function(font) { const part1 = new Cubic(this.p0, m1, mm1, pt); this.p0 = pt; return part1; - }; + } /** - * @method splitInflections - * @return {Cubic[]} the non-inflecting pieces of this cubic - * - * returns an array containing 0, 1 or 2 cubics split resulting - * from splitting this cubic at its inflection points. - * this cubic is (potentially) altered and returned in the list. - */ - this.splitInflections = function() { + * @method splitInflections + * @return {Cubic[]} the non-inflecting pieces of this cubic + * + * returns an array containing 0, 1 or 2 cubics split resulting + * from splitting this cubic at its inflection points. + * this cubic is (potentially) altered and returned in the list. + */ + splitInflections () { const a = p5.Vector.sub(this.c0, this.p0); const b = p5.Vector.sub(p5.Vector.sub(this.c1, this.c0), a); const c = p5.Vector.sub( @@ -398,24 +400,24 @@ const FontInfo = function(font) { cubics.push(this); return cubics; - }; + } } /** - * @function cubicToQuadratics - * @param {Number} x0 - * @param {Number} y0 - * @param {Number} cx0 - * @param {Number} cy0 - * @param {Number} cx1 - * @param {Number} cy1 - * @param {Number} x1 - * @param {Number} y1 - * @returns {Cubic[]} an array of cubics whose quadratic approximations - * closely match the civen cubic. - * - * converts a cubic curve to a list of quadratics. - */ + * @function cubicToQuadratics + * @param {Number} x0 + * @param {Number} y0 + * @param {Number} cx0 + * @param {Number} cy0 + * @param {Number} cx1 + * @param {Number} cy1 + * @param {Number} x1 + * @param {Number} y1 + * @returns {Cubic[]} an array of cubics whose quadratic approximations + * closely match the civen cubic. + * + * converts a cubic curve to a list of quadratics. + */ function cubicToQuadratics(x0, y0, cx0, cy0, cx1, cy1, x1, y1) { // create the Cubic object and split it at its inflections const cubics = new Cubic( @@ -470,14 +472,14 @@ const FontInfo = function(font) { } /** - * @function pushLine - * @param {Number} x0 - * @param {Number} y0 - * @param {Number} x1 - * @param {Number} y1 - * - * add a straight line to the row/col grid of a glyph - */ + * @function pushLine + * @param {Number} x0 + * @param {Number} y0 + * @param {Number} x1 + * @param {Number} y1 + * + * add a straight line to the row/col grid of a glyph + */ function pushLine(x0, y0, x1, y1) { const mx = (x0 + x1) / 2; const my = (y0 + y1) / 2; @@ -485,15 +487,15 @@ const FontInfo = function(font) { } /** - * @function samePoint - * @param {Number} x0 - * @param {Number} y0 - * @param {Number} x1 - * @param {Number} y1 - * @return {Boolean} true if the two points are sufficiently close - * - * tests if two points are close enough to be considered the same - */ + * @function samePoint + * @param {Number} x0 + * @param {Number} y0 + * @param {Number} x1 + * @param {Number} y1 + * @return {Boolean} true if the two points are sufficiently close + * + * tests if two points are close enough to be considered the same + */ function samePoint(x0, y0, x1, y1) { return Math.abs(x1 - x0) < 0.00001 && Math.abs(y1 - y0) < 0.00001; } @@ -570,16 +572,16 @@ const FontInfo = function(font) { } /** - * @function layout - * @param {Number[][]} dim - * @param {ImageInfo[]} dimImageInfos - * @param {ImageInfo[]} cellImageInfos - * @return {Object} - * - * lays out the curves in a dimension (row or col) into two - * images, one for the indices of the curves themselves, and - * one containing the offset and length of those index spans. - */ + * @function layout + * @param {Number[][]} dim + * @param {ImageInfo[]} dimImageInfos + * @param {ImageInfo[]} cellImageInfos + * @return {Object} + * + * lays out the curves in a dimension (row or col) into two + * images, one for the indices of the curves themselves, and + * one containing the offset and length of those index spans. + */ function layout(dim, dimImageInfos, cellImageInfos) { const dimLength = dim.length; // the number of slices in this dimension const dimImageInfo = dimImageInfos.findImage(dimLength); @@ -634,8 +636,8 @@ const FontInfo = function(font) { }; gi.uGridOffset = [gi.colInfo.dimOffset, gi.rowInfo.dimOffset]; return gi; - }; -}; + } +} p5.RendererGL.prototype._renderText = function(p, line, x, y, maxY) { if (!this._textFont || typeof this._textFont === 'string') { diff --git a/test/manual-test-examples/learningprocessing/chp10/example_10_1/catcher.js b/test/manual-test-examples/learningprocessing/chp10/example_10_1/catcher.js index 8033cf0dc1..dd4bbe3fe1 100644 --- a/test/manual-test-examples/learningprocessing/chp10/example_10_1/catcher.js +++ b/test/manual-test-examples/learningprocessing/chp10/example_10_1/catcher.js @@ -1,18 +1,18 @@ // Catch class -function Catcher(tempR) { - this.r = tempR; // radius - this.x = 0; // location - this.y = 0; +class Catcher { + constructor(tempR) { + this.r = tempR; // radius + this.x = 0; // location + this.y = 0; + } + setLocation(tempX, tempY) { + this.x = tempX; + this.y = tempY; + } + display() { + stroke(0); + fill(175); + ellipse(this.x, this.y, this.r * 2, this.r * 2); + } } - -Catcher.prototype.setLocation = function(tempX, tempY) { - this.x = tempX; - this.y = tempY; -}; - -Catcher.prototype.display = function() { - stroke(0); - fill(175); - ellipse(this.x, this.y, this.r * 2, this.r * 2); -}; diff --git a/test/manual-test-examples/learningprocessing/chp10/example_10_10/catcher.js b/test/manual-test-examples/learningprocessing/chp10/example_10_10/catcher.js index 0250bd6abb..85b72ae3e1 100644 --- a/test/manual-test-examples/learningprocessing/chp10/example_10_10/catcher.js +++ b/test/manual-test-examples/learningprocessing/chp10/example_10_10/catcher.js @@ -6,34 +6,33 @@ // Example 10-10: The raindrop catching game -function Catcher(tempR) { - this.r = tempR; // radius - this.col = [50, 10, 10, 150]; // color - this.x = 0; // location - this.y = 0; -} - -Catcher.prototype.setLocation = function(tempX, tempY) { - this.x = tempX; - this.y = tempY; -}; - -Catcher.prototype.display = function() { - stroke(0); - fill(this.col); - ellipse(this.x, this.y, this.r * 2, this.r * 2); -}; - -// A function that returns true or false based on -// if the catcher intersects a raindrop -Catcher.prototype.intersect = function(d) { - // Calculate distance - var distance = dist(this.x, this.y, d.x, d.y); +class Catcher { + constructor(tempR) { + this.r = tempR; // radius + this.col = [50, 10, 10, 150]; // color + this.x = 0; // location + this.y = 0; + } + setLocation(tempX, tempY) { + this.x = tempX; + this.y = tempY; + } + display() { + stroke(0); + fill(this.col); + ellipse(this.x, this.y, this.r * 2, this.r * 2); + } + // A function that returns true or false based on + // if the catcher intersects a raindrop + intersect(d) { + // Calculate distance + var distance = dist(this.x, this.y, d.x, d.y); - // Compare distance to sum of radii - if (distance < this.r + d.r) { - return true; - } else { - return false; + // Compare distance to sum of radii + if (distance < this.r + d.r) { + return true; + } else { + return false; + } } -}; +} diff --git a/test/manual-test-examples/learningprocessing/chp10/example_10_10/drop.js b/test/manual-test-examples/learningprocessing/chp10/example_10_10/drop.js index b7848d487b..7be3571399 100644 --- a/test/manual-test-examples/learningprocessing/chp10/example_10_10/drop.js +++ b/test/manual-test-examples/learningprocessing/chp10/example_10_10/drop.js @@ -6,44 +6,42 @@ // Example 10-10: The raindrop catching game -function Drop() { - this.r = 8; // All raindrops are the same size - this.x = random(width); // Start with a random x location - this.y = -this.r * 4; // Start a little above the window - this.speed = random(1, 5); // Speed of raindrop - this.c = [50, 100, 150]; -} - -// Move the raindrop down -Drop.prototype.move = function() { - // Increment by speed - this.y += this.speed; -}; - -// Check if it hits the bottom -Drop.prototype.reachedBottom = function() { - // If we go a little beyond the bottom - if (this.y > height + this.r * 4) { - return true; - } else { - return false; +class Drop { + constructor() { + this.r = 8; // All raindrops are the same size + this.x = random(width); // Start with a random x location + this.y = -this.r * 4; // Start a little above the window + this.speed = random(1, 5); // Speed of raindrop + this.c = [50, 100, 150]; } -}; - -// Display the raindrop -Drop.prototype.display = function() { - // Display the drop - fill(this.c); - noStroke(); - for (var i = 2; i < this.r; i++) { - ellipse(this.x, this.y + i * 4, i * 2, i * 2); + // Move the raindrop down + move() { + // Increment by speed + this.y += this.speed; } -}; - -// If the drop is caught -Drop.prototype.caught = function() { - // Stop it from moving by setting speed equal to zero - this.speed = 0; - // Set the location to somewhere way off-screen - this.y = -1000; -}; + // Check if it hits the bottom + reachedBottom() { + // If we go a little beyond the bottom + if (this.y > height + this.r * 4) { + return true; + } else { + return false; + } + } + // Display the raindrop + display() { + // Display the drop + fill(this.c); + noStroke(); + for (var i = 2; i < this.r; i++) { + ellipse(this.x, this.y + i * 4, i * 2, i * 2); + } + } + // If the drop is caught + caught() { + // Stop it from moving by setting speed equal to zero + this.speed = 0; + // Set the location to somewhere way off-screen + this.y = -1000; + } +} diff --git a/test/manual-test-examples/learningprocessing/chp10/example_10_10/timer.js b/test/manual-test-examples/learningprocessing/chp10/example_10_10/timer.js index 4fb0d15d60..66a28358ce 100644 --- a/test/manual-test-examples/learningprocessing/chp10/example_10_10/timer.js +++ b/test/manual-test-examples/learningprocessing/chp10/example_10_10/timer.js @@ -6,25 +6,25 @@ // Example 10-5: Object-oriented timer -function Timer(tempTotalTime) { - this.savedTime = 0; // When Timer started - this.totalTime = tempTotalTime; // How long Timer should last -} - -// Starting the timer -Timer.prototype.start = function() { - // When the timer starts it stores the current time in milliseconds. - this.savedTime = millis(); -}; - -// The function isFinished() returns true if 5,000 ms have passed. -// The work of the timer is farmed out to this method. -Timer.prototype.isFinished = function() { - // Check how much time has passed - var passedTime = millis() - this.savedTime; - if (passedTime > this.totalTime) { - return true; - } else { - return false; +class Timer { + constructor(tempTotalTime) { + this.savedTime = 0; // When Timer started + this.totalTime = tempTotalTime; // How long Timer should last } -}; + // Starting the timer + start() { + // When the timer starts it stores the current time in milliseconds. + this.savedTime = millis(); + } + // The function isFinished() returns true if 5,000 ms have passed. + // The work of the timer is farmed out to this method. + isFinished() { + // Check how much time has passed + var passedTime = millis() - this.savedTime; + if (passedTime > this.totalTime) { + return true; + } else { + return false; + } + } +} diff --git a/test/manual-test-examples/learningprocessing/chp10/example_10_2/ball.js b/test/manual-test-examples/learningprocessing/chp10/example_10_2/ball.js index 6176226609..c98c93e79c 100644 --- a/test/manual-test-examples/learningprocessing/chp10/example_10_2/ball.js +++ b/test/manual-test-examples/learningprocessing/chp10/example_10_2/ball.js @@ -7,31 +7,31 @@ // Example 10-2: Bouncing ball class -function Ball(tempR) { - this.r = tempR; - this.x = random(width); - this.y = random(height); - this.xspeed = random(-5, 5); - this.yspeed = random(-5, 5); -} - -Ball.prototype.move = function() { - this.x += this.xspeed; // Increment x - this.y += this.yspeed; // Increment y +class Ball { + constructor(tempR) { + this.r = tempR; + this.x = random(width); + this.y = random(height); + this.xspeed = random(-5, 5); + this.yspeed = random(-5, 5); + } + move() { + this.x += this.xspeed; // Increment x + this.y += this.yspeed; // Increment y - // Check horizontal edges - if (this.x > width || this.x < 0) { - this.xspeed *= -1; + // Check horizontal edges + if (this.x > width || this.x < 0) { + this.xspeed *= -1; + } + //Check vertical edges + if (this.y > height || this.y < 0) { + this.yspeed *= -1; + } } - //Check vertical edges - if (this.y > height || this.y < 0) { - this.yspeed *= -1; + // Draw the ball + display() { + stroke(0); + fill(0, 50); + ellipse(this.x, this.y, this.r * 2, this.r * 2); } -}; - -// Draw the ball -Ball.prototype.display = function() { - stroke(0); - fill(0, 50); - ellipse(this.x, this.y, this.r * 2, this.r * 2); -}; +} diff --git a/test/manual-test-examples/learningprocessing/chp10/example_10_3/ball.js b/test/manual-test-examples/learningprocessing/chp10/example_10_3/ball.js index 75a0c3e781..cbc5f6a2fc 100644 --- a/test/manual-test-examples/learningprocessing/chp10/example_10_3/ball.js +++ b/test/manual-test-examples/learningprocessing/chp10/example_10_3/ball.js @@ -7,55 +7,53 @@ // Example 10-3: Bouncing ball with intersection -function Ball(tempR) { - this.r = tempR; // radius - this.x = random(width); - this.y = random(height); - this.xspeed = random(-5, 5); - this.yspeed = random(-5, 5); - this.c = [100, 50]; -} - -Ball.prototype.move = function() { - this.x += this.xspeed; // Increment x - this.y += this.yspeed; // Increment y - - // Check horizontal edges - if (this.x > width || this.x < 0) { - this.xspeed *= -1; +class Ball { + constructor(tempR) { + this.r = tempR; // radius + this.x = random(width); + this.y = random(height); + this.xspeed = random(-5, 5); + this.yspeed = random(-5, 5); + this.c = [100, 50]; } - - // Check vertical edges - if (this.y > height || this.y < 0) { - this.yspeed *= -1; + move() { + this.x += this.xspeed; // Increment x + this.y += this.yspeed; // Increment y + + // Check horizontal edges + if (this.x > width || this.x < 0) { + this.xspeed *= -1; + } + + // Check vertical edges + if (this.y > height || this.y < 0) { + this.yspeed *= -1; + } } -}; - -// Whenever the balls are touching, this highlight() function is called -// and the color is darkened. -Ball.prototype.highlight = function() { - this.c = [0, 150]; -}; - -// Draw the ball -Ball.prototype.display = function() { - stroke(0); - fill(this.c); - ellipse(this.x, this.y, this.r * 2, this.r * 2); - // After the ball is displayed, the color is reset back to a darker gray. - this.c = [100, 50]; -}; - -// A function that returns true or false based on whether two circles intersect -// If distance is less than the sum of radii the circles touch -Ball.prototype.intersect = function(b) { - // Objects can be passed into functions as arguments too! - var distance = dist(this.x, this.y, b.x, b.y); // Calculate distance - - // Compare distance to sum of radii - if (distance < this.r + b.r) { - return true; - } else { - return false; + // Whenever the balls are touching, this highlight() function is called + // and the color is darkened. + highlight() { + this.c = [0, 150]; + } + // Draw the ball + display() { + stroke(0); + fill(this.c); + ellipse(this.x, this.y, this.r * 2, this.r * 2); + // After the ball is displayed, the color is reset back to a darker gray. + this.c = [100, 50]; } -}; + // A function that returns true or false based on whether two circles intersect + // If distance is less than the sum of radii the circles touch + intersect(b) { + // Objects can be passed into functions as arguments too! + var distance = dist(this.x, this.y, b.x, b.y); // Calculate distance + + // Compare distance to sum of radii + if (distance < this.r + b.r) { + return true; + } else { + return false; + } + } +} diff --git a/test/manual-test-examples/learningprocessing/chp10/example_10_5/timer.js b/test/manual-test-examples/learningprocessing/chp10/example_10_5/timer.js index 0328a2ee77..1d3164a3bc 100644 --- a/test/manual-test-examples/learningprocessing/chp10/example_10_5/timer.js +++ b/test/manual-test-examples/learningprocessing/chp10/example_10_5/timer.js @@ -1,24 +1,24 @@ // Timer class -function Timer(tempTotalTime) { - this.savedTime = 0; // When Timer started - this.totalTime = tempTotalTime; // How long Timer should last -} - -// Starting the timer -Timer.prototype.start = function() { - // When the timer starts it stores the current time in milliseconds. - this.savedTime = millis(); -}; - -// The function isFinished() returns true if 5,000 ms have passed. -// The work of the timer is farmed out to this method. -Timer.prototype.isFinished = function() { - // Check how much time has passed - var passedTime = millis() - this.savedTime; - if (passedTime > this.totalTime) { - return true; - } else { - return false; +class Timer { + constructor(tempTotalTime) { + this.savedTime = 0; // When Timer started + this.totalTime = tempTotalTime; // How long Timer should last } -}; + // Starting the timer + start() { + // When the timer starts it stores the current time in milliseconds. + this.savedTime = millis(); + } + // The function isFinished() returns true if 5,000 ms have passed. + // The work of the timer is farmed out to this method. + isFinished() { + // Check how much time has passed + var passedTime = millis() - this.savedTime; + if (passedTime > this.totalTime) { + return true; + } else { + return false; + } + } +} diff --git a/test/manual-test-examples/learningprocessing/chp10/example_10_7/drop.js b/test/manual-test-examples/learningprocessing/chp10/example_10_7/drop.js index 95143a0ce4..960311f59e 100644 --- a/test/manual-test-examples/learningprocessing/chp10/example_10_7/drop.js +++ b/test/manual-test-examples/learningprocessing/chp10/example_10_7/drop.js @@ -8,44 +8,42 @@ // Drop class -function Drop() { - this.r = 8; // All raindrops are the same size - this.x = random(width); // Start with a random x location - this.y = -this.r * 4; // Start a little above the window - this.speed = random(1, 5); // Pick a random speed - this.c = [50, 100, 150]; // Color -} - -// Move the raindrop down -Drop.prototype.move = function() { - // Increment by speed - this.y += this.speed; -}; - -// Check if it hits the bottom -Drop.prototype.reachedBottom = function() { - // If we go a little beyond the bottom - if (this.y > height + this.r * 4) { - return true; - } else { - return false; +class Drop { + constructor() { + this.r = 8; // All raindrops are the same size + this.x = random(width); // Start with a random x location + this.y = -this.r * 4; // Start a little above the window + this.speed = random(1, 5); // Pick a random speed + this.c = [50, 100, 150]; // Color } -}; - -// Display the raindrop -Drop.prototype.display = function() { - // Display the drop - fill(this.c); - noStroke(); - for (var i = 2; i < this.r; i++) { - ellipse(this.x, this.y + i * 4, i * 2, i * 2); + // Move the raindrop down + move() { + // Increment by speed + this.y += this.speed; } -}; - -// If the drop is caught -Drop.prototype.caught = function() { - // Stop it from moving by setting speed equal to zero - this.speed = 0; - // Set the location to somewhere way off-screen - this.y = -1000; -}; + // Check if it hits the bottom + reachedBottom() { + // If we go a little beyond the bottom + if (this.y > height + this.r * 4) { + return true; + } else { + return false; + } + } + // Display the raindrop + display() { + // Display the drop + fill(this.c); + noStroke(); + for (var i = 2; i < this.r; i++) { + ellipse(this.x, this.y + i * 4, i * 2, i * 2); + } + } + // If the drop is caught + caught() { + // Stop it from moving by setting speed equal to zero + this.speed = 0; + // Set the location to somewhere way off-screen + this.y = -1000; + } +} diff --git a/test/manual-test-examples/learningprocessing/chp10/example_10_9/catcher.js b/test/manual-test-examples/learningprocessing/chp10/example_10_9/catcher.js index 7402c4dfc1..cfeeba0642 100644 --- a/test/manual-test-examples/learningprocessing/chp10/example_10_9/catcher.js +++ b/test/manual-test-examples/learningprocessing/chp10/example_10_9/catcher.js @@ -6,19 +6,19 @@ // Example 10-1: Catcher -function Catcher(tempR) { - this.r = tempR; // radius - this.x = 0; // location - this.y = 0; +class Catcher { + constructor(tempR) { + this.r = tempR; // radius + this.x = 0; // location + this.y = 0; + } + setLocation(tempX, tempY) { + this.x = tempX; + this.y = tempY; + } + display() { + stroke(0); + fill(175); + ellipse(this.x, this.y, this.r * 2, this.r * 2); + } } - -Catcher.prototype.setLocation = function(tempX, tempY) { - this.x = tempX; - this.y = tempY; -}; - -Catcher.prototype.display = function() { - stroke(0); - fill(175); - ellipse(this.x, this.y, this.r * 2, this.r * 2); -}; diff --git a/test/manual-test-examples/learningprocessing/chp10/example_10_9/drop.js b/test/manual-test-examples/learningprocessing/chp10/example_10_9/drop.js index aa3c427ef0..ea54d69f74 100644 --- a/test/manual-test-examples/learningprocessing/chp10/example_10_9/drop.js +++ b/test/manual-test-examples/learningprocessing/chp10/example_10_9/drop.js @@ -6,47 +6,45 @@ // Example 10-7: Drops one at a time -function Drop() { - this.r = 8; // Radius of raindrop (all raindrops are the same size) +class Drop { + constructor() { + this.r = 8; // Radius of raindrop (all raindrops are the same size) - // Variables for location of raindrop - this.x = random(width); - this.y = -this.r * 4; + // Variables for location of raindrop + this.x = random(width); + this.y = -this.r * 4; - this.speed = random(1, 5); // Speed of raindrop - this.c = [50, 100, 150]; -} - -// Move the raindrop down -Drop.prototype.move = function() { - // Increment by speed - this.y += this.speed; -}; - -// Check if it hits the bottom -Drop.prototype.reachedBottom = function() { - // If we go a little beyond the bottom - if (this.y > height + this.r * 4) { - return true; - } else { - return false; + this.speed = random(1, 5); // Speed of raindrop + this.c = [50, 100, 150]; } -}; - -// Display the raindrop -Drop.prototype.display = function() { - // Display the drop - fill(this.c); - noStroke(); - for (var i = 2; i < this.r; i++) { - ellipse(this.x, this.y + i * 4, i * 2, i * 2); + // Move the raindrop down + move() { + // Increment by speed + this.y += this.speed; } -}; - -// If the drop is caught -Drop.prototype.caught = function() { - // Stop it from moving by setting speed equal to zero - this.speed = 0; - // Set the location to somewhere way off-screen - this.y = -1000; -}; + // Check if it hits the bottom + reachedBottom() { + // If we go a little beyond the bottom + if (this.y > height + this.r * 4) { + return true; + } else { + return false; + } + } + // Display the raindrop + display() { + // Display the drop + fill(this.c); + noStroke(); + for (var i = 2; i < this.r; i++) { + ellipse(this.x, this.y + i * 4, i * 2, i * 2); + } + } + // If the drop is caught + caught() { + // Stop it from moving by setting speed equal to zero + this.speed = 0; + // Set the location to somewhere way off-screen + this.y = -1000; + } +} diff --git a/test/manual-test-examples/learningprocessing/chp10/example_10_9/timer.js b/test/manual-test-examples/learningprocessing/chp10/example_10_9/timer.js index 58e10ee54e..d4ded395db 100644 --- a/test/manual-test-examples/learningprocessing/chp10/example_10_9/timer.js +++ b/test/manual-test-examples/learningprocessing/chp10/example_10_9/timer.js @@ -6,25 +6,25 @@ // Example 10-5: Object-oriented timer -function Timer(tempTotalTime) { - this.savedTime = 0; // When Timer started - this.totalTime = tempTotalTime; // How long Timer should last -} - -// Starting the timer -Timer.prototype.start = function() { - // When the timer starts it stores the current time in milliseconds. - this.savedTime = millis(); -}; - -// The function isFinished() returns true if 5,000 ms have passed. -// The work of the timer is farmed out to this method. -Timer.prototype.isFinished = function() { - // Check how much time has passed - var passedTime = millis() - this.savedTime; - if (passedTime > this.totalTime) { - return true; - } else { - return false; +class Timer { + constructor(tempTotalTime) { + this.savedTime = 0; // When Timer started + this.totalTime = tempTotalTime; // How long Timer should last } -}; + // Starting the timer + start() { + // When the timer starts it stores the current time in milliseconds. + this.savedTime = millis(); + } + // The function isFinished() returns true if 5,000 ms have passed. + // The work of the timer is farmed out to this method. + isFinished() { + // Check how much time has passed + var passedTime = millis() - this.savedTime; + if (passedTime > this.totalTime) { + return true; + } else { + return false; + } + } +} diff --git a/test/manual-test-examples/learningprocessing/chp8/example_8_1.js b/test/manual-test-examples/learningprocessing/chp8/example_8_1.js index 643a6d8ea0..a173cddb72 100644 --- a/test/manual-test-examples/learningprocessing/chp8/example_8_1.js +++ b/test/manual-test-examples/learningprocessing/chp8/example_8_1.js @@ -6,6 +6,31 @@ // Example 8-1: A Car class and a Car object +// Define a class below the rest of the program. +class Car { + constructor() { + this.c = 175; + this.xpos = width / 2; + this.ypos = height / 2; + this.xspeed = 1; + } + display() { + // Function. + // The car is just a square + rectMode(CENTER); + stroke(0); + fill(this.c); + rect(this.xpos, this.ypos, 20, 10); + } + move() { + // Function. + this.xpos = this.xpos + this.xspeed; + if (this.xpos > width) { + this.xpos = 0; + } + } +} + var myCar; // Declare car object as a globle variable. function setup() { @@ -20,28 +45,3 @@ function draw() { myCar.move(); // Operate the car object in draw( ) by calling object methods using the dots syntax. myCar.display(); } - -// Define a class below the rest of the program. -function Car() { - this.c = 175; - this.xpos = width / 2; - this.ypos = height / 2; - this.xspeed = 1; -} - -Car.prototype.display = function() { - // Function. - // The car is just a square - rectMode(CENTER); - stroke(0); - fill(this.c); - rect(this.xpos, this.ypos, 20, 10); -}; - -Car.prototype.move = function() { - // Function. - this.xpos = this.xpos + this.xspeed; - if (this.xpos > width) { - this.xpos = 0; - } -}; diff --git a/test/manual-test-examples/learningprocessing/chp8/example_8_2.js b/test/manual-test-examples/learningprocessing/chp8/example_8_2.js index e94c4a4741..a29e80d21d 100644 --- a/test/manual-test-examples/learningprocessing/chp8/example_8_2.js +++ b/test/manual-test-examples/learningprocessing/chp8/example_8_2.js @@ -8,6 +8,27 @@ var myCar1; var myCar2; // Two objects! +class Car { + constructor(tempC, tempXpos, tempYpos, tempXspeed) { + // Even though there are multiple objects, we still only need one class. No matter how many cookies we make, only one cookie cutter is needed.Isn’t object-oriented programming swell? + this.c = tempC; + this.xpos = tempXpos; + this.ypos = tempYpos; + this.xspeed = tempXspeed; + } + display() { + stroke(0); + fill(this.c); + rectMode(CENTER); + rect(this.xpos, this.ypos, 20, 10); + } + move() { + this.xpos = this.xpos + this.xspeed; + if (this.xpos > width) { + this.xpos = 0; + } + } +} function setup() { createCanvas(200, 200); @@ -22,25 +43,3 @@ function draw() { myCar2.move(); myCar2.display(); } - -function Car(tempC, tempXpos, tempYpos, tempXspeed) { - // Even though there are multiple objects, we still only need one class. No matter how many cookies we make, only one cookie cutter is needed.Isn’t object-oriented programming swell? - this.c = tempC; - this.xpos = tempXpos; - this.ypos = tempYpos; - this.xspeed = tempXspeed; -} - -Car.prototype.display = function() { - stroke(0); - fill(this.c); - rectMode(CENTER); - rect(this.xpos, this.ypos, 20, 10); -}; - -Car.prototype.move = function() { - this.xpos = this.xpos + this.xspeed; - if (this.xpos > width) { - this.xpos = 0; - } -}; diff --git a/test/manual-test-examples/learningprocessing/chp8/example_8_3.js b/test/manual-test-examples/learningprocessing/chp8/example_8_3.js index db51f84cb0..efbd2942ab 100644 --- a/test/manual-test-examples/learningprocessing/chp8/example_8_3.js +++ b/test/manual-test-examples/learningprocessing/chp8/example_8_3.js @@ -5,6 +5,73 @@ // Ported by Lauren McCarthy // Example 8-3 +class Zoog { + constructor(tempX, tempY, tempW, tempH, tempEyeSize) { + // Zoog's variables + this.x = tempX; + this.y = tempY; + this.w = tempW; + this.h = tempH; + this.eyeSize = tempEyeSize; + } + // Move Zoog + jiggle(speed) { + // Change the location of Zoog randomly + this.x = this.x + random(-1, 1) * speed; + this.y = this.y + random(-1, 1) * speed; + // Constrain Zoog to window + this.x = constrain(this.x, 0, width); + this.y = constrain(this.y, 0, height); + } + // Display Zoog + display() { + // Set ellipses and rects to CENTER mode + ellipseMode(CENTER); + rectMode(CENTER); + // Draw Zoog's arms with a for loop + for (var i = this.y - this.h / 3; i < this.y + this.h / 2; i += 10) { + stroke(0); + line(this.x - this.w / 4, i, this.x + this.w / 4, i); + } + // Draw Zoog's body + stroke(0); + fill(175); + rect(this.x, this.y, this.w / 6, this.h); + // Draw Zoog's head + stroke(0); + fill(255); + ellipse(this.x, this.y - this.h, this.w, this.h); + // Draw Zoog's eyes + fill(0); + ellipse( + this.x - this.w / 3, + this.y - this.h, + this.eyeSize, + this.eyeSize * 2 + ); + ellipse( + this.x + this.w / 3, + this.y - this.h, + this.eyeSize, + this.eyeSize * 2 + ); + // Draw Zoog's legs + stroke(0); + line( + this.x - this.w / 12, + this.y + this.h / 2, + this.x - this.w / 4, + this.y + this.h / 2 + 10 + ); + line( + this.x + this.w / 12, + this.y + this.h / 2, + this.x + this.w / 4, + this.y + this.h / 2 + 10 + ); + } +} + var zoog; function setup() { @@ -20,60 +87,3 @@ function draw() { zoog.jiggle(factor); zoog.display(); } - -function Zoog(tempX, tempY, tempW, tempH, tempEyeSize) { - // Zoog's variables - this.x = tempX; - this.y = tempY; - this.w = tempW; - this.h = tempH; - this.eyeSize = tempEyeSize; -} - -// Move Zoog -Zoog.prototype.jiggle = function(speed) { - // Change the location of Zoog randomly - this.x = this.x + random(-1, 1) * speed; - this.y = this.y + random(-1, 1) * speed; - // Constrain Zoog to window - this.x = constrain(this.x, 0, width); - this.y = constrain(this.y, 0, height); -}; - -// Display Zoog -Zoog.prototype.display = function() { - // Set ellipses and rects to CENTER mode - ellipseMode(CENTER); - rectMode(CENTER); - // Draw Zoog's arms with a for loop - for (var i = this.y - this.h / 3; i < this.y + this.h / 2; i += 10) { - stroke(0); - line(this.x - this.w / 4, i, this.x + this.w / 4, i); - } - // Draw Zoog's body - stroke(0); - fill(175); - rect(this.x, this.y, this.w / 6, this.h); - // Draw Zoog's head - stroke(0); - fill(255); - ellipse(this.x, this.y - this.h, this.w, this.h); - // Draw Zoog's eyes - fill(0); - ellipse(this.x - this.w / 3, this.y - this.h, this.eyeSize, this.eyeSize * 2); - ellipse(this.x + this.w / 3, this.y - this.h, this.eyeSize, this.eyeSize * 2); - // Draw Zoog's legs - stroke(0); - line( - this.x - this.w / 12, - this.y + this.h / 2, - this.x - this.w / 4, - this.y + this.h / 2 + 10 - ); - line( - this.x + this.w / 12, - this.y + this.h / 2, - this.x + this.w / 4, - this.y + this.h / 2 + 10 - ); -}; diff --git a/test/manual-test-examples/learningprocessing/chp9/example_9_10.js b/test/manual-test-examples/learningprocessing/chp9/example_9_10.js index 2528538066..51ff5b1f09 100644 --- a/test/manual-test-examples/learningprocessing/chp9/example_9_10.js +++ b/test/manual-test-examples/learningprocessing/chp9/example_9_10.js @@ -7,6 +7,47 @@ // Example 9-10: Interactive stripes // An array of stripes + +class Stripe { + constructor() { + // All stripes start at 0 + this.x = 0; // horizontal location of stripe + + // All stripes have a random positive speed + this.speed = random(1); // speed of stripe + this.w = random(10, 30); // width of stripe + + // A boolean variable keeps track of the object's state. + this.mouse = false; // state of stripe (mouse is over or not?) + } + // Draw stripe + display() { + // Boolean variable determines Stripe color. + if (this.mouse) { + fill(255); + } else { + fill(255, 100); + } + + noStroke(); + rect(this.x, 0, this.w, height); + } + // Move stripe + move() { + this.x += this.speed; + if (this.x > width + 20) this.x = -20; + } + // Check to see if point (mx,my) is inside the Stripe. + rollover(mx, my) { + // Left edge is x, Right edge is x + w + if (mx > this.x && mx < this.x + this.w) { + this.mouse = true; + } else { + this.mouse = false; + } + } +} + var stripes = []; function setup() { @@ -28,42 +69,3 @@ function draw() { stripes[i].display(); } } - -function Stripe() { - // All stripes start at 0 - this.x = 0; // horizontal location of stripe - // All stripes have a random positive speed - this.speed = random(1); // speed of stripe - this.w = random(10, 30); // width of stripe - // A boolean variable keeps track of the object's state. - this.mouse = false; // state of stripe (mouse is over or not?) -} - -// Draw stripe -Stripe.prototype.display = function() { - // Boolean variable determines Stripe color. - if (this.mouse) { - fill(255); - } else { - fill(255, 100); - } - - noStroke(); - rect(this.x, 0, this.w, height); -}; - -// Move stripe -Stripe.prototype.move = function() { - this.x += this.speed; - if (this.x > width + 20) this.x = -20; -}; - -// Check to see if point (mx,my) is inside the Stripe. -Stripe.prototype.rollover = function(mx, my) { - // Left edge is x, Right edge is x + w - if (mx > this.x && mx < this.x + this.w) { - this.mouse = true; - } else { - this.mouse = false; - } -}; diff --git a/test/manual-test-examples/learningprocessing/chp9/example_9_11.js b/test/manual-test-examples/learningprocessing/chp9/example_9_11.js index a18ab24b95..ca11a64669 100644 --- a/test/manual-test-examples/learningprocessing/chp9/example_9_11.js +++ b/test/manual-test-examples/learningprocessing/chp9/example_9_11.js @@ -6,6 +6,34 @@ var balls = []; var gravity = 0.1; +class Ball { + constructor(tempX, tempY, tempW) { + this.x = tempX; + this.y = tempY; + this.w = tempW; + this.speed = 0; + } + gravity() { + // Add gravity to speed + this.speed = this.speed + gravity; + } + move() { + // Add speed to y location + this.y = this.y + this.speed; + // If square reaches the bottom + // Reverse speed + if (this.y > height) { + this.speed = this.speed * -0.95; + this.y = height; + } + } + display() { + // Display the circle + fill(175); + stroke(0); + ellipse(this.x, this.y, this.w, this.w); + } +} function setup() { createCanvas(200, 200); @@ -40,33 +68,3 @@ function mousePressed() { // In addition, the append() function requires that you explicitly state the type of data in the array again by putting the // array data type in parentheses: (Ball[]) This is known as casting. } - -function Ball(tempX, tempY, tempW) { - this.x = tempX; - this.y = tempY; - this.w = tempW; - this.speed = 0; -} - -Ball.prototype.gravity = function() { - // Add gravity to speed - this.speed = this.speed + gravity; -}; - -Ball.prototype.move = function() { - // Add speed to y location - this.y = this.y + this.speed; - // If square reaches the bottom - // Reverse speed - if (this.y > height) { - this.speed = this.speed * -0.95; - this.y = height; - } -}; - -Ball.prototype.display = function() { - // Display the circle - fill(175); - stroke(0); - ellipse(this.x, this.y, this.w, this.w); -}; diff --git a/test/manual-test-examples/learningprocessing/chp9/example_9_12.js b/test/manual-test-examples/learningprocessing/chp9/example_9_12.js index f310263d44..5535bdbdf2 100644 --- a/test/manual-test-examples/learningprocessing/chp9/example_9_12.js +++ b/test/manual-test-examples/learningprocessing/chp9/example_9_12.js @@ -8,6 +8,75 @@ // The only difference between this example and the previous chapter (Example 8-3) // is the use of an array for multiple Zoog objects. +class Zoog { + constructor(tempX, tempY, tempW, tempH, tempEyeSize) { + // Zoog's variables + this.x = tempX; + this.y = tempY; + this.w = tempW; + this.h = tempH; + this.eyeSize = tempEyeSize; + } + // Move Zoog + jiggle() { + // For simplicity we have also removed the “speed” argument from the jiggle() function. Try adding it back in as an exercise. + // Change the location + this.x = this.x + random(-1, 1); + this.y = this.y + random(-1, 1); + // Constrain Zoog to window + this.x = constrain(this.x, 0, width); + this.y = constrain(this.y, 0, height); + } + // Display Zoog + display() { + // Set ellipses and rects to CENTER mode + ellipseMode(CENTER); + rectMode(CENTER); + // Draw Zoog's arms with a for loop + for (var i = this.y - this.h / 3; i < this.y + this.h / 2; i += 10) { + stroke(0); + line(this.x - this.w / 4, i, this.x + this.w / 4, i); + } + // Draw Zoog's body + stroke(0); + fill(175); + rect(this.x, this.y, this.w / 6, this.h); + // Draw Zoog's head + stroke(0); + fill(255); + ellipse(this.x, this.y - this.h, this.w, this.h); + // Draw Zoog's eyes + fill(0); + ellipse( + this.x - this.w / 3, + this.y - this.h, + this.eyeSize, + this.eyeSize * 2 + ); + ellipse( + this.x + this.w / 3, + this.y - this.h, + this.eyeSize, + this.eyeSize * 2 + ); + // Draw Zoog's legs + stroke(0); + line( + this.x - this.w / 12, + this.y + this.h / 2, + this.x - this.w / 4, + this.y + this.h / 2 + 10 + ); + line( + this.x + this.w / 12, + this.y + this.h / 2, + this.x + this.w / 4, + this.y + this.h / 2 + 10 + ); + } +} + + var zoogies = []; function setup() { @@ -26,60 +95,3 @@ function draw() { } } -function Zoog(tempX, tempY, tempW, tempH, tempEyeSize) { - // Zoog's variables - this.x = tempX; - this.y = tempY; - this.w = tempW; - this.h = tempH; - this.eyeSize = tempEyeSize; -} - -// Move Zoog -Zoog.prototype.jiggle = function() { - // For simplicity we have also removed the “speed” argument from the jiggle() function. Try adding it back in as an exercise. - // Change the location - this.x = this.x + random(-1, 1); - this.y = this.y + random(-1, 1); - // Constrain Zoog to window - this.x = constrain(this.x, 0, width); - this.y = constrain(this.y, 0, height); -}; - -// Display Zoog -Zoog.prototype.display = function() { - // Set ellipses and rects to CENTER mode - ellipseMode(CENTER); - rectMode(CENTER); - // Draw Zoog's arms with a for loop - for (var i = this.y - this.h / 3; i < this.y + this.h / 2; i += 10) { - stroke(0); - line(this.x - this.w / 4, i, this.x + this.w / 4, i); - } - // Draw Zoog's body - stroke(0); - fill(175); - rect(this.x, this.y, this.w / 6, this.h); - // Draw Zoog's head - stroke(0); - fill(255); - ellipse(this.x, this.y - this.h, this.w, this.h); - // Draw Zoog's eyes - fill(0); - ellipse(this.x - this.w / 3, this.y - this.h, this.eyeSize, this.eyeSize * 2); - ellipse(this.x + this.w / 3, this.y - this.h, this.eyeSize, this.eyeSize * 2); - // Draw Zoog's legs - stroke(0); - line( - this.x - this.w / 12, - this.y + this.h / 2, - this.x - this.w / 4, - this.y + this.h / 2 + 10 - ); - line( - this.x + this.w / 12, - this.y + this.h / 2, - this.x + this.w / 4, - this.y + this.h / 2 + 10 - ); -}; diff --git a/test/manual-test-examples/learningprocessing/chp9/example_9_9.js b/test/manual-test-examples/learningprocessing/chp9/example_9_9.js index be39507dd4..d51317457b 100644 --- a/test/manual-test-examples/learningprocessing/chp9/example_9_9.js +++ b/test/manual-test-examples/learningprocessing/chp9/example_9_9.js @@ -6,6 +6,28 @@ // Example 9-9: An array of Car objects +// The Car class does not change whether we are making one car, 100 cars or 1,000 cars! +class Car { + constructor(c, xpos, ypos, xspeed) { + this.c = c; + this.xpos = xpos; + this.ypos = ypos; + this.xspeed = xspeed; + } + display() { + rectMode(CENTER); + stroke(0); + fill(this.c); + rect(this.xpos, this.ypos, 20, 10); + } + move() { + this.xpos = this.xpos + this.xspeed; + if (this.xpos > width) { + this.xpos = 0; + } + } +} + var cars = []; // An array for Car objects function setup() { @@ -25,25 +47,3 @@ function draw() { cars[i].display(); } } - -// The Car class does not change whether we are making one car, 100 cars or 1,000 cars! -function Car(c, xpos, ypos, xspeed) { - this.c = c; - this.xpos = xpos; - this.ypos = ypos; - this.xspeed = xspeed; -} - -Car.prototype.display = function() { - rectMode(CENTER); - stroke(0); - fill(this.c); - rect(this.xpos, this.ypos, 20, 10); -}; - -Car.prototype.move = function() { - this.xpos = this.xpos + this.xspeed; - if (this.xpos > width) { - this.xpos = 0; - } -}; diff --git a/test/manual-test-examples/p5.Font/pathpoints/boids.js b/test/manual-test-examples/p5.Font/pathpoints/boids.js index 9fa0eb3bfc..c868aa9c33 100644 --- a/test/manual-test-examples/p5.Font/pathpoints/boids.js +++ b/test/manual-test-examples/p5.Font/pathpoints/boids.js @@ -1,32 +1,34 @@ // adapted from Shiffman's The Nature of Code // http://natureofcode.com -function Boid(target) { - this.acceleration = createVector(0, 0); - this.velocity = createVector(random(-1, 1), random(-1, 1)); - this.position = createVector(width / 2, height / 2); - - this.r = 3.0; - this.maxspeed = 3; // Maximum speed - this.maxforce = 0.05; // Maximum steering force - - this.theta = - p5.Vector.fromAngle(radians(target.alpha)).heading() + radians(90); - this.target = createVector(target.x, target.y); - this.arrived = false; - this.hidden = true; - - this.place = function(x, y) { +class Boid { + constructor(target) { + this.acceleration = createVector(0, 0); + this.velocity = createVector(random(-1, 1), random(-1, 1)); + this.position = createVector(width / 2, height / 2); + + this.r = 3.0; + this.maxspeed = 3; // Maximum speed + this.maxforce = 0.05; // Maximum steering force + + this.theta = + p5.Vector.fromAngle(radians(target.alpha)).heading() + radians(90); + this.target = createVector(target.x, target.y); + this.arrived = false; + this.hidden = true; + } + place (x, y) { this.position = createVector(mouseX, mouseY); this.velocity = p5.Vector.sub( createVector(mouseX, mouseY), createVector(pmouseX, pmouseY) ); this.hidden = false; - }; + } - this.run = function(boids) { - if (this.hidden) return; + run (boids) { + if (this.hidden) + return; if (flock.assemble) { this.arrive(this.target); @@ -36,18 +38,19 @@ function Boid(target) { this.update(); this.borders(); this.render(); - }; + } - this.applyForce = function(force) { + applyForce (force) { // We could add mass here if we want A = F / M this.acceleration.add(force); - }; + } // We accumulate a new acceleration each time based on three rules - this.flock = function(boids) { + flock (boids) { var sep = this.separate(boids); // Separation var ali = this.align(boids); // Alignment var coh = this.cohesion(boids); // Cohesion + // Arbitrarily weight these forces sep.mult(1.5); ali.mult(1.0); @@ -56,15 +59,13 @@ function Boid(target) { this.applyForce(sep); this.applyForce(ali); this.applyForce(coh); - }; + } // Method to update location - this.update = function() { - if ( - flock.assemble && - !this.arrived && - this.target.dist(this.position) < 1 - ) { + update () { + if (flock.assemble && + !this.arrived && + this.target.dist(this.position) < 1) { this.arrived = true; this.velocity = p5.Vector.fromAngle(this.theta + radians(90)); } else { @@ -73,9 +74,9 @@ function Boid(target) { this.position.add(this.velocity); this.acceleration.mult(0); } - }; + } - this.seek = function(target) { + seek (target) { var desired = p5.Vector.sub(target, this.position); // Normalize desired and scale to maximum speed desired.normalize(); @@ -84,9 +85,9 @@ function Boid(target) { var steer = p5.Vector.sub(desired, this.velocity); steer.limit(this.maxforce); // Limit to maximum steering force return steer; - }; + } - this.render = function() { + render () { // Draw a triangle rotated in the direction of velocity var theta = this.velocity.heading() + radians(90); fill(255); @@ -100,19 +101,23 @@ function Boid(target) { vertex(this.r, this.r * 2); endShape(CLOSE); pop(); - }; + } // Wraparound - this.borders = function() { - if (this.position.x < -this.r) this.position.x = width + this.r; - if (this.position.y < -this.r) this.position.y = height + this.r; - if (this.position.x > width + this.r) this.position.x = -this.r; - if (this.position.y > height + this.r) this.position.y = -this.r; - }; + borders () { + if (this.position.x < -this.r) + this.position.x = width + this.r; + if (this.position.y < -this.r) + this.position.y = height + this.r; + if (this.position.x > width + this.r) + this.position.x = -this.r; + if (this.position.y > height + this.r) + this.position.y = -this.r; + } // Separation // Method checks for nearby boids and steers away - this.separate = function(boids) { + separate (boids) { var desiredseparation = 25.0; var steer = createVector(0, 0); var count = 0; @@ -143,11 +148,11 @@ function Boid(target) { steer.limit(this.maxforce); } return steer; - }; + } // Alignment // For every nearby boid in the system, calculate the average velocity - this.align = function(boids) { + align (boids) { var neighbordist = 50; var sum = createVector(0, 0); var count = 0; @@ -168,11 +173,11 @@ function Boid(target) { } else { return createVector(0, 0); } - }; + } // Cohesion // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location - this.cohesion = function(boids) { + cohesion (boids) { var neighbordist = 50; var sum = createVector(0, 0); // Start with empty vector to accumulate all locations var num = 0; @@ -188,12 +193,11 @@ function Boid(target) { } else { return createVector(0, 0); } - }; + } - Boid.prototype.arrive = function(target) { + arrive (target) { // A vector pointing from the location to the target - var desired = p5.Vector.sub(target, this.position), - d = desired.mag(); + var desired = p5.Vector.sub(target, this.position), d = desired.mag(); // Scale with arbitrary damping within 100 pixels desired.setMag(d < 100 ? map(d, 0, 100, 0, this.maxspeed) : this.maxspeed); @@ -202,19 +206,21 @@ function Boid(target) { var steer = p5.Vector.sub(desired, this.velocity); steer.limit(this.maxforce); // Limit to maximum steering force this.applyForce(steer); - }; + } + } function mouseOnScreen() { return mouseX && mouseX <= width && mouseY && mouseY <= height; } -function Flock() { - this.count = 0; - this.boids = []; - this.assemble = false; - - this.arrived = function() { +class Flock { + constructor() { + this.count = 0; + this.boids = []; + this.assemble = false; + } + arrived() { var i; if (arguments.length) { for (i = 0; i < this.boids.length; i++) @@ -225,14 +231,14 @@ function Flock() { if (!this.boids[i].arrived) return false; return true; } - }; + } - this.run = function() { + run() { this.assemble = this.count === flock.boids.length; if (!this.assemble && mouseOnScreen()) this.boids[this.count++].place(mouseX, mouseY); for (var i = 0; i < this.boids.length; i++) this.boids[i].run(this.boids); - }; + } } diff --git a/test/manual-test-examples/p5.Vector/NOC_2_5_fluidresistance/mover.js b/test/manual-test-examples/p5.Vector/NOC_2_5_fluidresistance/mover.js index c201b3e3fd..abe950e69d 100644 --- a/test/manual-test-examples/p5.Vector/NOC_2_5_fluidresistance/mover.js +++ b/test/manual-test-examples/p5.Vector/NOC_2_5_fluidresistance/mover.js @@ -2,40 +2,38 @@ // Daniel Shiffman // http://natureofcode.com -function Mover(m, x, y) { - this.mass = m; - this.position = createVector(x, y); - this.velocity = createVector(0, 0); - this.acceleration = createVector(0, 0); -} - -// Newton's 2nd law: F = M * A -// or A = F / M -Mover.prototype.applyForce = function(force) { - var f = p5.Vector.div(force, this.mass); - this.acceleration.add(f); -}; - -Mover.prototype.update = function() { - // Velocity changes according to acceleration - this.velocity.add(this.acceleration); - // position changes by velocity - this.position.add(this.velocity); - // We must clear acceleration each frame - this.acceleration.mult(0); -}; - -Mover.prototype.display = function() { - stroke(0); - strokeWeight(2); - fill(255, 127); - ellipse(this.position.x, this.position.y, this.mass * 16, this.mass * 16); -}; - -// Bounce off bottom of window -Mover.prototype.checkEdges = function() { - if (this.position.y > height) { - this.velocity.y *= -0.9; // A little dampening when hitting the bottom - this.position.y = height; +class Mover { + constructor(m, x, y) { + this.mass = m; + this.position = createVector(x, y); + this.velocity = createVector(0, 0); + this.acceleration = createVector(0, 0); } -}; + // Newton's 2nd law: F = M * A + // or A = F / M + applyForce(force) { + var f = p5.Vector.div(force, this.mass); + this.acceleration.add(f); + } + update() { + // Velocity changes according to acceleration + this.velocity.add(this.acceleration); + // position changes by velocity + this.position.add(this.velocity); + // We must clear acceleration each frame + this.acceleration.mult(0); + } + display() { + stroke(0); + strokeWeight(2); + fill(255, 127); + ellipse(this.position.x, this.position.y, this.mass * 16, this.mass * 16); + } + // Bounce off bottom of window + checkEdges() { + if (this.position.y > height) { + this.velocity.y *= -0.9; // A little dampening when hitting the bottom + this.position.y = height; + } + } +} diff --git a/test/manual-test-examples/p5.Vector/NOC_6_09_Flocking/boid.js b/test/manual-test-examples/p5.Vector/NOC_6_09_Flocking/boid.js index ed1f796dbe..b1c411ed0d 100644 --- a/test/manual-test-examples/p5.Vector/NOC_6_09_Flocking/boid.js +++ b/test/manual-test-examples/p5.Vector/NOC_6_09_Flocking/boid.js @@ -5,167 +5,165 @@ // Boid class // Methods for Separation, Cohesion, Alignment added -function Boid(x, y) { - this.acceleration = createVector(0, 0); - this.velocity = createVector(random(-1, 1), random(-1, 1)); - this.position = createVector(x, y); - this.r = 3.0; - this.maxspeed = 3; // Maximum speed - this.maxforce = 0.05; // Maximum steering force -} - -Boid.prototype.run = function(boids) { - this.flock(boids); - this.update(); - this.borders(); - this.render(); -}; - -Boid.prototype.applyForce = function(force) { - // We could add mass here if we want A = F / M - this.acceleration.add(force); -}; - -// We accumulate a new acceleration each time based on three rules -Boid.prototype.flock = function(boids) { - var sep = this.separate(boids); // Separation - var ali = this.align(boids); // Alignment - var coh = this.cohesion(boids); // Cohesion - // Arbitrarily weight these forces - sep.mult(1.5); - ali.mult(1.0); - coh.mult(1.0); - // Add the force vectors to acceleration - this.applyForce(sep); - this.applyForce(ali); - this.applyForce(coh); -}; - -// Method to update location -Boid.prototype.update = function() { - // Update velocity - this.velocity.add(this.acceleration); - // Limit speed - this.velocity.limit(this.maxspeed); - this.position.add(this.velocity); - // Reset accelertion to 0 each cycle - this.acceleration.mult(0); -}; - -// A method that calculates and applies a steering force towards a target -// STEER = DESIRED MINUS VELOCITY -Boid.prototype.seek = function(target) { - var desired = p5.Vector.sub(target, this.position); // A vector pointing from the location to the target - // Normalize desired and scale to maximum speed - desired.normalize(); - desired.mult(this.maxspeed); - // Steering = Desired minus Velocity - var steer = p5.Vector.sub(desired, this.velocity); - steer.limit(this.maxforce); // Limit to maximum steering force - return steer; -}; - -Boid.prototype.render = function() { - // Draw a triangle rotated in the direction of velocity - var theta = this.velocity.heading() + radians(90); - fill(127); - stroke(200); - push(); - translate(this.position.x, this.position.y); - rotate(theta); - beginShape(); - vertex(0, -this.r * 2); - vertex(-this.r, this.r * 2); - vertex(this.r, this.r * 2); - endShape(CLOSE); - pop(); -}; - -// Wraparound -Boid.prototype.borders = function() { - if (this.position.x < -this.r) this.position.x = width + this.r; - if (this.position.y < -this.r) this.position.y = height + this.r; - if (this.position.x > width + this.r) location.x = -this.r; - if (this.position.y > height + this.r) location.y = -this.r; -}; +class Boid { + constructor(x, y) { + this.acceleration = createVector(0, 0); + this.velocity = createVector(random(-1, 1), random(-1, 1)); + this.position = createVector(x, y); + this.r = 3.0; + this.maxspeed = 3; // Maximum speed + this.maxforce = 0.05; // Maximum steering force + } + run(boids) { + this.flock(boids); + this.update(); + this.borders(); + this.render(); + } + applyForce(force) { + // We could add mass here if we want A = F / M + this.acceleration.add(force); + } + // We accumulate a new acceleration each time based on three rules + flock(boids) { + var sep = this.separate(boids); // Separation + var ali = this.align(boids); // Alignment + var coh = this.cohesion(boids); // Cohesion -// Separation -// Method checks for nearby boids and steers away -Boid.prototype.separate = function(boids) { - var desiredseparation = 25.0; - var steer = createVector(0, 0); - var count = 0; - // For every boid in the system, check if it's too close - for (var i = 0; i < boids.length; i++) { - var d = p5.Vector.dist(this.position, boids[i].position); - // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself) - if (d > 0 && d < desiredseparation) { - // Calculate vector pointing away from neighbor - var diff = p5.Vector.sub(this.position, boids[i].position); - diff.normalize(); - diff.div(d); // Weight by distance - steer.add(diff); - count++; // Keep track of how many - } + // Arbitrarily weight these forces + sep.mult(1.5); + ali.mult(1.0); + coh.mult(1.0); + // Add the force vectors to acceleration + this.applyForce(sep); + this.applyForce(ali); + this.applyForce(coh); } - // Average -- divide by how many - if (count > 0) { - steer.div(count); + // Method to update location + update() { + // Update velocity + this.velocity.add(this.acceleration); + // Limit speed + this.velocity.limit(this.maxspeed); + this.position.add(this.velocity); + // Reset accelertion to 0 each cycle + this.acceleration.mult(0); } + // A method that calculates and applies a steering force towards a target + // STEER = DESIRED MINUS VELOCITY + seek(target) { + var desired = p5.Vector.sub(target, this.position); // A vector pointing from the location to the target - // As long as the vector is greater than 0 - if (steer.mag() > 0) { - // Implement Reynolds: Steering = Desired - Velocity - steer.normalize(); - steer.mult(this.maxspeed); - steer.sub(this.velocity); - steer.limit(this.maxforce); + // Normalize desired and scale to maximum speed + desired.normalize(); + desired.mult(this.maxspeed); + // Steering = Desired minus Velocity + var steer = p5.Vector.sub(desired, this.velocity); + steer.limit(this.maxforce); // Limit to maximum steering force + return steer; + } + render() { + // Draw a triangle rotated in the direction of velocity + var theta = this.velocity.heading() + radians(90); + fill(127); + stroke(200); + push(); + translate(this.position.x, this.position.y); + rotate(theta); + beginShape(); + vertex(0, -this.r * 2); + vertex(-this.r, this.r * 2); + vertex(this.r, this.r * 2); + endShape(CLOSE); + pop(); + } + // Wraparound + borders() { + if (this.position.x < -this.r) + this.position.x = width + this.r; + if (this.position.y < -this.r) + this.position.y = height + this.r; + if (this.position.x > width + this.r) + location.x = -this.r; + if (this.position.y > height + this.r) + location.y = -this.r; } - return steer; -}; + // Separation + // Method checks for nearby boids and steers away + separate(boids) { + var desiredseparation = 25.0; + var steer = createVector(0, 0); + var count = 0; + // For every boid in the system, check if it's too close + for (var i = 0; i < boids.length; i++) { + var d = p5.Vector.dist(this.position, boids[i].position); + // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself) + if (d > 0 && d < desiredseparation) { + // Calculate vector pointing away from neighbor + var diff = p5.Vector.sub(this.position, boids[i].position); + diff.normalize(); + diff.div(d); // Weight by distance + steer.add(diff); + count++; // Keep track of how many + } + } + // Average -- divide by how many + if (count > 0) { + steer.div(count); + } -// Alignment -// For every nearby boid in the system, calculate the average velocity -Boid.prototype.align = function(boids) { - var neighbordist = 50; - var sum = createVector(0, 0); - var count = 0; - for (var i = 0; i < boids.length; i++) { - var d = p5.Vector.dist(this.position, boids[i].position); - if (d > 0 && d < neighbordist) { - sum.add(boids[i].velocity); - count++; + // As long as the vector is greater than 0 + if (steer.mag() > 0) { + // Implement Reynolds: Steering = Desired - Velocity + steer.normalize(); + steer.mult(this.maxspeed); + steer.sub(this.velocity); + steer.limit(this.maxforce); } - } - if (count > 0) { - sum.div(count); - sum.normalize(); - sum.mult(this.maxspeed); - var steer = p5.Vector.sub(sum, this.velocity); - steer.limit(this.maxforce); return steer; - } else { - return createVector(0, 0); } -}; - -// Cohesion -// For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location -Boid.prototype.cohesion = function(boids) { - var neighbordist = 50; - var sum = createVector(0, 0); // Start with empty vector to accumulate all locations - var count = 0; - for (var i = 0; i < boids.length; i++) { - var d = p5.Vector.dist(this.position, boids[i].position); - if (d > 0 && d < neighbordist) { - sum.add(boids[i].position); // Add location - count++; + // Alignment + // For every nearby boid in the system, calculate the average velocity + align(boids) { + var neighbordist = 50; + var sum = createVector(0, 0); + var count = 0; + for (var i = 0; i < boids.length; i++) { + var d = p5.Vector.dist(this.position, boids[i].position); + if (d > 0 && d < neighbordist) { + sum.add(boids[i].velocity); + count++; + } + } + if (count > 0) { + sum.div(count); + sum.normalize(); + sum.mult(this.maxspeed); + var steer = p5.Vector.sub(sum, this.velocity); + steer.limit(this.maxforce); + return steer; + } else { + return createVector(0, 0); } } - if (count > 0) { - sum.div(count); - return this.seek(sum); // Steer towards the location - } else { - return createVector(0, 0); + // Cohesion + // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location + cohesion(boids) { + var neighbordist = 50; + var sum = createVector(0, 0); // Start with empty vector to accumulate all locations + var count = 0; + for (var i = 0; i < boids.length; i++) { + var d = p5.Vector.dist(this.position, boids[i].position); + if (d > 0 && d < neighbordist) { + sum.add(boids[i].position); // Add location + count++; + } + } + if (count > 0) { + sum.div(count); + return this.seek(sum); // Steer towards the location + } else { + return createVector(0, 0); + } } -}; +} diff --git a/test/manual-test-examples/p5.Vector/NOC_6_09_Flocking/flock.js b/test/manual-test-examples/p5.Vector/NOC_6_09_Flocking/flock.js index 3cd00323de..1708e485c6 100644 --- a/test/manual-test-examples/p5.Vector/NOC_6_09_Flocking/flock.js +++ b/test/manual-test-examples/p5.Vector/NOC_6_09_Flocking/flock.js @@ -5,17 +5,17 @@ // Flock object // Does very little, simply manages the array of all the boids -function Flock() { - // An array for all the boids - this.boids = []; // Initialize the array -} - -Flock.prototype.run = function() { - for (var i = 0; i < this.boids.length; i++) { - this.boids[i].run(this.boids); // Passing the entire list of boids to each boid individually +class Flock { + constructor() { + // An array for all the boids + this.boids = []; // Initialize the array } -}; - -Flock.prototype.addBoid = function(b) { - this.boids.push(b); -}; + run() { + for (var i = 0; i < this.boids.length; i++) { + this.boids[i].run(this.boids); // Passing the entire list of boids to each boid individually + } + } + addBoid(b) { + this.boids.push(b); + } +} diff --git a/test/manual-test-examples/webgl/performance/points/sketch.js b/test/manual-test-examples/webgl/performance/points/sketch.js index f7fb3fc4f5..aa4de2b297 100644 --- a/test/manual-test-examples/webgl/performance/points/sketch.js +++ b/test/manual-test-examples/webgl/performance/points/sketch.js @@ -1,20 +1,21 @@ -function Particle(x, y) { - this.pos = createVector(x, y); - this.vel = p5.Vector.random2D(); - this.vel.setMag(random(2, 5)); - this.acc = createVector(); - - this.update = function() { +class Particle { + constructor(x, y) { + this.pos = createVector(x, y); + this.vel = p5.Vector.random2D(); + this.vel.setMag(random(2, 5)); + this.acc = createVector(); + } + update () { this.pos.add(this.vel); this.vel.add(this.acc); this.acc.mult(0); - }; + } - this.show = function() { + show () { vertex(this.pos.x, this.pos.y); - }; + } - this.attracted = function(target) { + attracted (target) { var force = p5.Vector.sub(target, this.pos); var dsquared = force.magSq(); dsquared = constrain(dsquared, 25, 500); @@ -22,7 +23,8 @@ function Particle(x, y) { var strength = G / dsquared; force.setMag(strength); this.acc.add(force); - }; + } + } var attractor_1;