From 62567a92de5a4526dc0efdbd17edaa590b75e38a Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 8 Nov 2024 20:11:32 -0700 Subject: [PATCH 001/111] commit message --- src/app.js | 4 ++++ src/shape/custom_shapes.js | 32 ++++++++++++++++++++++++++++++++ src/shape/index.js | 5 +++++ 3 files changed, 41 insertions(+) create mode 100644 src/shape/custom_shapes.js create mode 100644 src/shape/index.js diff --git a/src/app.js b/src/app.js index a0f677bfb8..19d2329242 100644 --- a/src/app.js +++ b/src/app.js @@ -22,6 +22,10 @@ import './core/shape/attributes'; import './core/shape/curves'; import './core/shape/vertex'; +// shapes +import customShapes from './shape'; +customShapes(p5); + //accessibility import accessibility from './accessibility'; accessibility(p5); diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js new file mode 100644 index 0000000000..fac21ddd9d --- /dev/null +++ b/src/shape/custom_shapes.js @@ -0,0 +1,32 @@ +/** + * @module Shape + * @submodule Custom Shapes + * @for p5 + * @requires core + * @requires constants + */ + +// declare MyClass + +function customShapes(p5, fn) { + + // ---- FUNCTIONS ---- + + // documentation here + + // fn.myFunction = function() { + // this.background('yellow'); // call an existing p5 function + // }; + + // ---- CLASSES ---- + + // documentation here + + // p5.MyClass = MyClass; +} + +export default customShapes; + +if (typeof p5 !== 'undefined') { + customShapes(p5, p5.prototype); +} \ No newline at end of file diff --git a/src/shape/index.js b/src/shape/index.js new file mode 100644 index 0000000000..b0930f9214 --- /dev/null +++ b/src/shape/index.js @@ -0,0 +1,5 @@ +import customShapes from './custom_shapes.js'; + +export default function(p5){ + p5.registerAddon(customShapes); +} From e38b3b98ea9f4d3a8e62975a21d271aea5ba5eb7 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Mon, 11 Nov 2024 23:03:32 -0700 Subject: [PATCH 002/111] Set up files for custom shape refactor --- src/shape/custom_shapes.js | 405 ++++++++++++++++++++++++++++++++++++- 1 file changed, 397 insertions(+), 8 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index fac21ddd9d..1b950d3ecd 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -6,26 +6,415 @@ * @requires constants */ -// declare MyClass +// uncomment the following once you need it (otherwise VS Code complains): +// import * as constants from '../core/constants'; + +// ---- GENERAL CLASSES ---- + +class Shape { + constructor() { + + } +} + +class Contour { + constructor() { + + } +} + +// abstract class +class ShapePrimitive { + constructor() { + + } +} + +class Vertex { + constructor() { + + } +} + +// ---- PATH PRIMITIVES ---- + +class Anchor { + constructor() { + + } +} + +// abstract class +class Segment extends ShapePrimitive { + constructor() { + + } +} + +class LineSegment extends Segment { + constructor() { + + } +} + +class BezierSegment extends Segment { + constructor() { + + } +} + +// consider type and end modes -- see #6766) +// may want to use separate classes, but maybe not +class SplineSegment extends Segment { + constructor() { + + } +} + +// ---- ISOLATED PRIMITIVES ---- + +class Point extends ShapePrimitive { + constructor() { + + } +} + +class Line extends ShapePrimitive { + constructor() { + + } +} + +class Triangle extends ShapePrimitive { + constructor() { + + } +} + +class Quad extends ShapePrimitive { + constructor() { + + } +} + +// ---- TESSELLATION PRIMITIVES ---- + +class TriangleFan extends ShapePrimitive { + constructor() { + + } +} + +class TriangleStrip extends ShapePrimitive { + constructor() { + + } +} + +class QuadStrip extends ShapePrimitive { + constructor() { + + } +} + +// ---- PRIMITIVE VISITORS ---- + +// abstract class +class PrimitiveVisitor { + constructor() { + + } +} + +// using this instead of PrimitiveToContext2DConverter for now +class PrimitiveToPath2DConverter extends PrimitiveVisitor { + constructor() { + + } +} + +class PrimitiveToVerticesConverter extends PrimitiveVisitor { + constructor() { + + } +} + +class PointAtLengthGetter extends PrimitiveVisitor { + constructor() { + + } +} function customShapes(p5, fn) { + // ---- GENERAL CLASSES ---- + + /** + * @private + * A class responsible for... + */ + + p5.Shape = Shape; + + /** + * @private + * A class responsible for... + */ + + p5.Contour = Contour; + + /** + * @private + * A class responsible for... + */ + + p5.ShapePrimitive = ShapePrimitive; + + /** + * @private + * A class responsible for... + */ + + p5.Vertex = Vertex; + + // ---- PATH PRIMITIVES ---- + + /** + * @private + * A class responsible for... + */ + + p5.Anchor = Anchor; + + /** + * @private + * A class responsible for... + */ + + p5.Segment = Segment; + + /** + * @private + * A class responsible for... + */ + + p5.LineSegment = LineSegment; + + /** + * @private + * A class responsible for... + */ + + p5.BezierSegment = BezierSegment; + + /** + * @private + * A class responsible for... + */ + + p5.SplineSegment = SplineSegment; + + // ---- ISOLATED PRIMITIVES ---- + + /** + * @private + * A class responsible for... + */ + + p5.Point = Point; + + /** + * @private + * A class responsible for... + */ + + p5.Line = Line; + + /** + * @private + * A class responsible for... + */ + + p5.Triangle = Triangle; + + /** + * @private + * A class responsible for... + */ + + p5.Quad = Quad; + + // ---- TESSELLATION PRIMITIVES ---- + + /** + * @private + * A class responsible for... + */ + + p5.TriangleFan = TriangleFan; + + /** + * @private + * A class responsible for... + */ + + p5.TriangleStrip = TriangleStrip; + + /** + * @private + * A class responsible for... + */ + + p5.QuadStrip = QuadStrip; + + // ---- PRIMITIVE VISITORS ---- + + /** + * @private + * A class responsible for... + */ + + p5.PrimitiveVisitor = PrimitiveVisitor; + + /** + * @private + * A class responsible for... + */ + + p5.PrimitiveToPath2DConverter = PrimitiveToPath2DConverter; + + /** + * @private + * A class responsible for... + */ + + p5.PrimitiveToVerticesConverter = PrimitiveToVerticesConverter; + + /** + * @private + * A class responsible for... + */ + + p5.PointAtLengthGetter = PointAtLengthGetter; // ---- FUNCTIONS ---- + + // Note: Code is commented out for now, to avoid conflicts with the existing implementation. + + /** + * Top-line description + * + * More details... + */ + + // fn.beginContour = function() { + // // example of how to call an existing p5 function: + // // this.background('yellow'); + // }; - // documentation here + /** + * Top-line description + * + * More details... + */ + + // fn.beginShape = function() { + + // }; + + /** + * Top-line description + * + * More details... + */ + + // fn.bezierVertex = function() { + + // }; + + /** + * Top-line description + * + * More details... + */ + + // fn.curveVertex = function() { + + // }; + + /** + * Top-line description + * + * More details... + */ + + // fn.endContour = function() { + + // }; + + /** + * Top-line description + * + * More details... + */ + + // fn.endShape = function() { + + // }; + + /** + * Top-line description + * + * More details... + */ + + // fn.vertex = function() { + + // }; + + /** + * Top-line description + * + * More details... + */ + + // fn.normal = function() { - // fn.myFunction = function() { - // this.background('yellow'); // call an existing p5 function - // }; + // }; - // ---- CLASSES ---- + /** + * Top-line description + * + * More details... + */ - // documentation here + // fn.vertexProperty = function() { - // p5.MyClass = MyClass; + // }; } export default customShapes; +export { + Shape, + Contour, + ShapePrimitive, + Vertex, + Anchor, + Segment, + LineSegment, + BezierSegment, + SplineSegment, + Point, + Line, + Triangle, + Quad, + TriangleFan, + TriangleStrip, + QuadStrip, + PrimitiveVisitor, + PrimitiveToPath2DConverter, + PrimitiveToVerticesConverter, + PointAtLengthGetter +}; if (typeof p5 !== 'undefined') { customShapes(p5, p5.prototype); From b7787846dc3c776d7144ab1965b2090dbc94d938 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 12 Nov 2024 05:16:03 -0700 Subject: [PATCH 003/111] Add inline documentation for Shape class --- src/shape/custom_shapes.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 1b950d3ecd..c9179dd5cf 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -150,7 +150,28 @@ function customShapes(p5, fn) { /** * @private - * A class responsible for... + * A class to describe a custom shape made with + * beginShape()/endShape(). + * + * Every `Shape` has a `kind`. The kind takes any value that + * can be passed to beginShape(): + * + * - `PATH` + * - `POINTS` + * - `LINES` + * - `TRIANGLES` + * - `QUADS` + * - `TRIANGLE_FAN` + * - `TRIANGLE_STRIP` + * - `QUAD_STRIP` + * + * All `Shapes` consist of `contours`, which can be thought of as + * subshapes (shapes inside another shape). + * + * @class p5.Shape + * @constructor + * @param {Constant} [kind] either PATH, POINTS, LINES, TRIANGLES, QUADS, TRIANGLE_FAN, + * TRIANGLE_STRIP, or QUAD_STRIP. */ p5.Shape = Shape; From 19e8c48d524d3300f31bb3d13fa94ea5f292cb1d Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 12 Nov 2024 05:50:10 -0700 Subject: [PATCH 004/111] Fix documentation of default parameter for Shape constructor --- src/shape/custom_shapes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index c9179dd5cf..38c3ce0c0b 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -170,8 +170,8 @@ function customShapes(p5, fn) { * * @class p5.Shape * @constructor - * @param {Constant} [kind] either PATH, POINTS, LINES, TRIANGLES, QUADS, TRIANGLE_FAN, - * TRIANGLE_STRIP, or QUAD_STRIP. + * @param {(PATH|POINTS|LINES|TRIANGLES|QUADS|TRIANGLE_FAN|TRIANGLE_STRIP|QUAD_STRIP)} [kind=PATH] either + * PATH, POINTS, LINES, TRIANGLES, QUADS, TRIANGLE_FAN, TRIANGLE_STRIP, or QUAD_STRIP. */ p5.Shape = Shape; From 8eb74e652654cdb54db7ff26491e5eee9e2aab8b Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Wed, 13 Nov 2024 04:35:35 -0700 Subject: [PATCH 005/111] Update inline documentation for p5.Shape --- src/shape/custom_shapes.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 38c3ce0c0b..fdcdacdaa0 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -165,9 +165,12 @@ function customShapes(p5, fn) { * - `TRIANGLE_STRIP` * - `QUAD_STRIP` * - * All `Shapes` consist of `contours`, which can be thought of as + * A `Shape` of any kind consists of `contours`, which can be thought of as * subshapes (shapes inside another shape). * + * To construct a `Shape` called `myShape`, methods such as `myShape.beginShape()`, + * `myShape.vertex()`, and `myShape.endShape()` may be called. + * * @class p5.Shape * @constructor * @param {(PATH|POINTS|LINES|TRIANGLES|QUADS|TRIANGLE_FAN|TRIANGLE_STRIP|QUAD_STRIP)} [kind=PATH] either From f0fc9086e648f25438384bbab55a7bc045dd3dd7 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Thu, 14 Nov 2024 03:10:57 -0700 Subject: [PATCH 006/111] Document p5.Vertex --- src/shape/custom_shapes.js | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index fdcdacdaa0..2ca4c6ed3e 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -195,7 +195,41 @@ function customShapes(p5, fn) { /** * @private - * A class responsible for... + * A class to describe a vertex (a point on a shape), in 2D or 3D. + * + * Vertices are the basic building blocks of all `p5.Shape` objects, including + * shapes made with vertex(), arcVertex(), + * bezierVertex(), and splineVertex(). + * + * Like a point on an object in the real world, a vertex has different properties. These include coordinate + * properties `position`, `textureCoordinates`, and `normal`, as well as color properties `fill` and `stroke`. + * + * A vertex called `myVertex` with position coordinates `(2, 3, 5)` and a green stroke may be created like this: + * + *

+     * let myVertex = new p5.Vertex({
+     *   position: createVector(2, 3, 5),
+     *   stroke: color('green')
+     * });
+     * 
+ * + * Note: + * - Coordinate properties are `p5.Vector` objects. + * - Color properties are `p5.Color` objects. + * + * Properties may be specified in all the ways supported by `createVector()` + * and `color()`. For example, a vertex position can be set with two coordinates, as in `createVector(2, 3)`. + * The position is then a `p5.Vector` object with coordinates `(2, 3, 0)`. + * + * In general, the vertices that make up a shape contain the same data, in the same format, regardless of + * how the shape is drawn. Like an artist who decides to ignore color or depth, a renderer can decide + * which of the vertex data to use. This data may be be reformatted using methods of `p5.Vector` and + * `p5.Color`. For easier use with certain renderers, vertices also have a `toArray()` method that converts + * a full vertex object to a one-dimensional array. + * + * @class p5.Vertex + * @constructor + * @param {Object} [properties] vertex properties. */ p5.Vertex = Vertex; From 1a05d51f1e09854c1cb207d76e191a889886d69b Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Thu, 14 Nov 2024 15:17:46 -0700 Subject: [PATCH 007/111] Fix code snippet in p5.Vertex documentation --- src/shape/custom_shapes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 2ca4c6ed3e..71cd0f117f 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -206,12 +206,12 @@ function customShapes(p5, fn) { * * A vertex called `myVertex` with position coordinates `(2, 3, 5)` and a green stroke may be created like this: * - *

+     * ```js
      * let myVertex = new p5.Vertex({
      *   position: createVector(2, 3, 5),
      *   stroke: color('green')
      * });
-     * 
+ * ``` * * Note: * - Coordinate properties are `p5.Vector` objects. From 45591b08e22af87c31f46a61d53136eaba97e833 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 15 Nov 2024 01:41:12 -0700 Subject: [PATCH 008/111] Revise p5.Vertex documentation and add todo comment about a new implementation --- src/shape/custom_shapes.js | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 71cd0f117f..cb604f322f 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -30,6 +30,34 @@ class ShapePrimitive { } } +/* +TODO: + +If we can rename the p5.Vector method `array()` as `toArray()` for clarity and +consistency with `toString()`, and if we can make the private p5.Color method for +converting to an array public, with the name `toArray()`, then we'll be able to +make Vertex more flexible. + +We can modify the Vertex constructor so that a vertex has only the data it needs, by dynamically +generating properties based on a required properties object: + +``` +class Vertex { + constructor(properties) { + for (const [key, value] of Object.entries(properties)) { + this[key] = value; + } + } + + toArray() { + //convert to 1D array + } +} +``` +Any property names or values may be used, but values that are objects must have +a `toArray()` method. +*/ + class Vertex { constructor() { @@ -218,13 +246,10 @@ function customShapes(p5, fn) { * - Color properties are `p5.Color` objects. * * Properties may be specified in all the ways supported by `createVector()` - * and `color()`. For example, a vertex position can be set with two coordinates, as in `createVector(2, 3)`. - * The position is then a `p5.Vector` object with coordinates `(2, 3, 0)`. + * and `color()`. For example, a vertex position can be set with two coordinates, + * as in `createVector(2, 3)`. * - * In general, the vertices that make up a shape contain the same data, in the same format, regardless of - * how the shape is drawn. Like an artist who decides to ignore color or depth, a renderer can decide - * which of the vertex data to use. This data may be be reformatted using methods of `p5.Vector` and - * `p5.Color`. For easier use with certain renderers, vertices also have a `toArray()` method that converts + * Vertices also have a `toArray()` method that converts * a full vertex object to a one-dimensional array. * * @class p5.Vertex From 37c2db4cd9edef5682840a8783f0670919f7bc66 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 15 Nov 2024 03:26:23 -0700 Subject: [PATCH 009/111] Move TODO comments out of the code (and into project timeline) --- src/shape/custom_shapes.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index cb604f322f..cb23a0c40d 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -30,34 +30,6 @@ class ShapePrimitive { } } -/* -TODO: - -If we can rename the p5.Vector method `array()` as `toArray()` for clarity and -consistency with `toString()`, and if we can make the private p5.Color method for -converting to an array public, with the name `toArray()`, then we'll be able to -make Vertex more flexible. - -We can modify the Vertex constructor so that a vertex has only the data it needs, by dynamically -generating properties based on a required properties object: - -``` -class Vertex { - constructor(properties) { - for (const [key, value] of Object.entries(properties)) { - this[key] = value; - } - } - - toArray() { - //convert to 1D array - } -} -``` -Any property names or values may be used, but values that are objects must have -a `toArray()` method. -*/ - class Vertex { constructor() { From 9663eda60383f3e870b18318151c8e2e353d3e05 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 15 Nov 2024 04:52:07 -0700 Subject: [PATCH 010/111] Document p5.Contour --- src/shape/custom_shapes.js | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index cb23a0c40d..97ad05b559 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -11,9 +11,9 @@ // ---- GENERAL CLASSES ---- -class Shape { +class Shape { constructor() { - + } } @@ -181,7 +181,35 @@ function customShapes(p5, fn) { /** * @private - * A class responsible for... + * A class to describe a contour made with + * beginContour()/endContour(). + * + * Contours are subshapes: they're made inside of shapes created with + * beginShape()/endShape(). + * For example, a contour may be used to create a hole in a shape. + * + * Contours can have any `kind` that a shape can have: + * + * - `PATH` + * - `POINTS` + * - `LINES` + * - `TRIANGLES` + * - `QUADS` + * - `TRIANGLE_FAN` + * - `TRIANGLE_STRIP` + * - `QUAD_STRIP` + * + * By default, a contour has the same kind as the shape that contains it, + * but different kinds of contours can be made inside the same shape. + * + * A `Contour` of any kind consists of `primitives`, which are the most basic + * shapes that can be drawn. For example, if a contour is a hexagon, then + * it's made from six line-segment primitives. + * + * @class p5.Contour + * @constructor + * @param {(PATH|POINTS|LINES|TRIANGLES|QUADS|TRIANGLE_FAN|TRIANGLE_STRIP|QUAD_STRIP)} [kind=PATH] either + * PATH, POINTS, LINES, TRIANGLES, QUADS, TRIANGLE_FAN, TRIANGLE_STRIP, or QUAD_STRIP. */ p5.Contour = Contour; From 5cd71a2b3cf30a6fdc94b718a3bf82fa770e1ea3 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 15 Nov 2024 06:18:32 -0700 Subject: [PATCH 011/111] Document p5.ShapePrimitive (and revise some other documentation). --- src/shape/custom_shapes.js | 39 ++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 97ad05b559..5de4134c60 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -150,8 +150,7 @@ function customShapes(p5, fn) { /** * @private - * A class to describe a custom shape made with - * beginShape()/endShape(). + * A class to describe a custom shape made with `beginShape()`/`endShape()`. * * Every `Shape` has a `kind`. The kind takes any value that * can be passed to beginShape(): @@ -181,12 +180,12 @@ function customShapes(p5, fn) { /** * @private - * A class to describe a contour made with - * beginContour()/endContour(). + * A class to describe a contour made with `beginContour()`/`endContour()`. * * Contours are subshapes: they're made inside of shapes created with * beginShape()/endShape(). - * For example, a contour may be used to create a hole in a shape. + * For example, a contour may be used to create a hole in a shape. Multiple contours + * may be used to create multiple holes. * * Contours can have any `kind` that a shape can have: * @@ -199,8 +198,8 @@ function customShapes(p5, fn) { * - `TRIANGLE_STRIP` * - `QUAD_STRIP` * - * By default, a contour has the same kind as the shape that contains it, - * but different kinds of contours can be made inside the same shape. + * By default, a contour has the same kind as the shape that contains it, but this + * may be changed by passing a different `kind` to beginContour(). * * A `Contour` of any kind consists of `primitives`, which are the most basic * shapes that can be drawn. For example, if a contour is a hexagon, then @@ -216,7 +215,31 @@ function customShapes(p5, fn) { /** * @private - * A class responsible for... + * A base class to describe a shape primitive (a basic shape drawn with + * `beginShape()`/`endShape()`). + * + * Shape primitives are the most basic shapes that can be drawn with + * beginShape()/endShape(): + * + * - segment primitives: line segments, bezier segments, spline segments, and arc segments + * - isolated primitives: points, lines, triangles, and quads + * - tessellation primitives: triangle fans, triangle strips, and quad strips + * + * More complex shapes may be created by combining many primitives, possibly of different kinds. + * + * In a similar way, every shape primitive is built from one or more vertices. + * For example, a point consists of a single vertex, while a triangle consists of three vertices. + * + * Each primitive can add itself to a shape, with an `addToShape()` method. + * + * It can also accept visitor objects with an `accept()` method. When a primitive accepts a visitor, + * it gives the visitor access to its vertex data. For example, one visitor to a segment might turn + * the data into 2D drawing instructions. Another might find a point at a given distance + * along the segment. + * + * @class p5.ShapePrimitive + * @constructor + * @param {p5.Vertex} vertex the first vertex to include in the primitive. */ p5.ShapePrimitive = ShapePrimitive; From 84a0fde58b5685c62cf3073f182cb9ed360dfbb9 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 15 Nov 2024 06:34:53 -0700 Subject: [PATCH 012/111] Revise docs for clarity. --- src/shape/custom_shapes.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 5de4134c60..d3c094ad87 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -182,10 +182,10 @@ function customShapes(p5, fn) { * @private * A class to describe a contour made with `beginContour()`/`endContour()`. * - * Contours are subshapes: they're made inside of shapes created with - * beginShape()/endShape(). - * For example, a contour may be used to create a hole in a shape. Multiple contours - * may be used to create multiple holes. + * Contours may be thought of as shapes inside of other shapes. + * For example, a contour may be used to create a hole in a shape that is created + * with beginShape()/endShape(). + * Multiple contours may be used to create multiple holes. * * Contours can have any `kind` that a shape can have: * @@ -225,7 +225,8 @@ function customShapes(p5, fn) { * - isolated primitives: points, lines, triangles, and quads * - tessellation primitives: triangle fans, triangle strips, and quad strips * - * More complex shapes may be created by combining many primitives, possibly of different kinds. + * More complex shapes may be created by combining many primitives, possibly of different kinds, + * into a single shape. * * In a similar way, every shape primitive is built from one or more vertices. * For example, a point consists of a single vertex, while a triangle consists of three vertices. From 0b117cc84bfd914c959e56dbaa9b68aa07a9a491 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Thu, 21 Nov 2024 22:48:21 -0700 Subject: [PATCH 013/111] Add TODO comment about Vertex interface --- src/shape/custom_shapes.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index d3c094ad87..db337cb614 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -34,6 +34,10 @@ class Vertex { constructor() { } + + // TODO: make sure name of array conversion method is + // consistent with any modifications to the names of corresponding + // properties of p5.Vector and p5.Color } // ---- PATH PRIMITIVES ---- From 2fcc2b08c72fbeabbfed4883396908a78cd8a93b Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 22 Nov 2024 01:52:46 -0700 Subject: [PATCH 014/111] Describe custom vertex properties in Shape docs --- src/shape/custom_shapes.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index db337cb614..4e52804608 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -169,15 +169,33 @@ function customShapes(p5, fn) { * - `QUAD_STRIP` * * A `Shape` of any kind consists of `contours`, which can be thought of as - * subshapes (shapes inside another shape). + * subshapes (shapes inside another shape). Each `contour` is built from + * basic shapes called primitives, and each primitive consists of one or more vertices. * - * To construct a `Shape` called `myShape`, methods such as `myShape.beginShape()`, - * `myShape.vertex()`, and `myShape.endShape()` may be called. + * For example, a square can be made from a single path contour with four line segment + * primitives. Each line segment contains a vertex that indicates its endpoint. A square + * with a circular hole in it contains the circle in a separate contour. + * + * By default, each vertex only has a position, but a shape's vertices may have other + * properties such as texture coordinates, a normal vector, a fill color, and a stroke color. + * The properties every vertex should have may be customized by passing `vertexProperties` to + * `createShape()`. + * + * Once a shape is created and given a name like `myShape`, it can be built up with + * methods such as `myShape.beginShape()`, `myShape.vertex()`, and `myShape.endShape()`. + * + * Vertex functions such as `vertex()` or `bezierVertex()` are used to set the `position` + * property of vertices, as well as the `textureCoordinates` property if applicable. Those + * properties only apply to a single vertex. + * + * If `vertexProperties` includes other properties, they are each set by a method of the + * same name. For example, if vertices in `myShape` have a `fill`, then that is set with + * `myShape.fill()`. In the same way that a fill() may be applied + * to one or more shapes, `myShape.fill()` may be applied to one or more vertices. * * @class p5.Shape * @constructor - * @param {(PATH|POINTS|LINES|TRIANGLES|QUADS|TRIANGLE_FAN|TRIANGLE_STRIP|QUAD_STRIP)} [kind=PATH] either - * PATH, POINTS, LINES, TRIANGLES, QUADS, TRIANGLE_FAN, TRIANGLE_STRIP, or QUAD_STRIP. + * @param {Object} [vertexProperties={position: createVector(0, 0)}] vertex properties and their initial values. */ p5.Shape = Shape; From d8625910926e929305e81a1587c2564771bba257 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 22 Nov 2024 02:07:03 -0700 Subject: [PATCH 015/111] Revise p5.Contour docs based on new design --- src/shape/custom_shapes.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 4e52804608..9e03b3e9f5 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -207,7 +207,7 @@ function customShapes(p5, fn) { * Contours may be thought of as shapes inside of other shapes. * For example, a contour may be used to create a hole in a shape that is created * with beginShape()/endShape(). - * Multiple contours may be used to create multiple holes. + * Multiple contours may be included inside a single shape. * * Contours can have any `kind` that a shape can have: * @@ -229,8 +229,6 @@ function customShapes(p5, fn) { * * @class p5.Contour * @constructor - * @param {(PATH|POINTS|LINES|TRIANGLES|QUADS|TRIANGLE_FAN|TRIANGLE_STRIP|QUAD_STRIP)} [kind=PATH] either - * PATH, POINTS, LINES, TRIANGLES, QUADS, TRIANGLE_FAN, TRIANGLE_STRIP, or QUAD_STRIP. */ p5.Contour = Contour; From 8ea67c58d45f582ebccd8105221bb8ee5473452d Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 22 Nov 2024 02:10:32 -0700 Subject: [PATCH 016/111] Remove @constructor tags (YUIDoc syntax) --- src/shape/custom_shapes.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 9e03b3e9f5..68de0564bd 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -194,7 +194,6 @@ function customShapes(p5, fn) { * to one or more shapes, `myShape.fill()` may be applied to one or more vertices. * * @class p5.Shape - * @constructor * @param {Object} [vertexProperties={position: createVector(0, 0)}] vertex properties and their initial values. */ @@ -228,7 +227,6 @@ function customShapes(p5, fn) { * it's made from six line-segment primitives. * * @class p5.Contour - * @constructor */ p5.Contour = Contour; @@ -259,7 +257,6 @@ function customShapes(p5, fn) { * along the segment. * * @class p5.ShapePrimitive - * @constructor * @param {p5.Vertex} vertex the first vertex to include in the primitive. */ @@ -297,7 +294,6 @@ function customShapes(p5, fn) { * a full vertex object to a one-dimensional array. * * @class p5.Vertex - * @constructor * @param {Object} [properties] vertex properties. */ From 1443b53420972fd0e73cb313e3a1f1bb2fd75b3f Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 22 Nov 2024 02:20:55 -0700 Subject: [PATCH 017/111] Fix typo in Shape docs --- src/shape/custom_shapes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 68de0564bd..bf4de3d5de 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -172,7 +172,7 @@ function customShapes(p5, fn) { * subshapes (shapes inside another shape). Each `contour` is built from * basic shapes called primitives, and each primitive consists of one or more vertices. * - * For example, a square can be made from a single path contour with four line segment + * For example, a square can be made from a single path contour with four line-segment * primitives. Each line segment contains a vertex that indicates its endpoint. A square * with a circular hole in it contains the circle in a separate contour. * From 78940ff72d27879c69c6b8bf4b3cb1505a350526 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 22 Nov 2024 02:45:39 -0700 Subject: [PATCH 018/111] Update p5.ShapePrimitive docs with new design --- src/shape/custom_shapes.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index bf4de3d5de..3cd5bb7878 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -247,9 +247,12 @@ function customShapes(p5, fn) { * into a single shape. * * In a similar way, every shape primitive is built from one or more vertices. - * For example, a point consists of a single vertex, while a triangle consists of three vertices. + * For example, a point consists of a single vertex, while a triangle consists of three vertices. + * Each type of shape primitive has a `vertexCapacity`, which may be `Infinity` (for example, a + * spline may consist of any number of vertices). A primitive's `vertexCount` is the number of + * vertices it currently contains. * - * Each primitive can add itself to a shape, with an `addToShape()` method. + * Each primitive can add itself to a shape with an `addToShape()` method. * * It can also accept visitor objects with an `accept()` method. When a primitive accepts a visitor, * it gives the visitor access to its vertex data. For example, one visitor to a segment might turn @@ -257,7 +260,7 @@ function customShapes(p5, fn) { * along the segment. * * @class p5.ShapePrimitive - * @param {p5.Vertex} vertex the first vertex to include in the primitive. + * @param {...p5.Vertex} vertices the vertices to include in the primitive. */ p5.ShapePrimitive = ShapePrimitive; From 4dac39eaff08a9de31df130f8e37be39766c4141 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 22 Nov 2024 03:23:19 -0700 Subject: [PATCH 019/111] Update p5.Vertex docs to reflect new design --- src/shape/custom_shapes.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 3cd5bb7878..fd5ee92f76 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -273,10 +273,12 @@ function customShapes(p5, fn) { * shapes made with vertex(), arcVertex(), * bezierVertex(), and splineVertex(). * - * Like a point on an object in the real world, a vertex has different properties. These include coordinate - * properties `position`, `textureCoordinates`, and `normal`, as well as color properties `fill` and `stroke`. + * Like a point on an object in the real world, a vertex may have different properties. + * These may include coordinate properties such as `position`, `textureCoordinates`, and `normal`, + * color properties such as `fill` and `stroke`, and more. * - * A vertex called `myVertex` with position coordinates `(2, 3, 5)` and a green stroke may be created like this: + * A vertex called `myVertex` with position coordinates `(2, 3, 5)` and a green stroke may be created + * like this: * * ```js * let myVertex = new p5.Vertex({ @@ -285,16 +287,14 @@ function customShapes(p5, fn) { * }); * ``` * - * Note: - * - Coordinate properties are `p5.Vector` objects. - * - Color properties are `p5.Color` objects. + * Any property names may be used. Property values may be any + * JavaScript primitive, any + * object literal, + * or any object with an `array` property. * - * Properties may be specified in all the ways supported by `createVector()` - * and `color()`. For example, a vertex position can be set with two coordinates, - * as in `createVector(2, 3)`. - * - * Vertices also have a `toArray()` method that converts - * a full vertex object to a one-dimensional array. + * For example, if a position is stored as a `p5.Vector` object and a stroke is stored as a `p5.Color` object, + * then the `array` properties of those objects will be used by the vertex's own `array` property, which provides + * all the vertex data in a single array. * * @class p5.Vertex * @param {Object} [properties] vertex properties. From 567ff259f18607e5d1adbeaf1a857d768b20890e Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 22 Nov 2024 03:27:47 -0700 Subject: [PATCH 020/111] Make p5.Vertex and p5.Shape constructors consistent --- src/shape/custom_shapes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index fd5ee92f76..18348c8114 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -297,7 +297,7 @@ function customShapes(p5, fn) { * all the vertex data in a single array. * * @class p5.Vertex - * @param {Object} [properties] vertex properties. + * @param {Object} [properties={position: createVector(0, 0)}] vertex properties. */ p5.Vertex = Vertex; From 727ff60866ec2cfde73304dcc080790b72d2cbf2 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Mon, 18 Nov 2024 16:44:59 -0500 Subject: [PATCH 021/111] Move fill and stroke into the base renderer --- src/color/setting.js | 4 +- src/core/p5.Renderer.js | 23 ++++++--- src/core/p5.Renderer2D.js | 74 ++++++++++++++-------------- src/shape/2d_primitives.js | 14 +++--- src/shape/curves.js | 4 +- src/shape/vertex.js | 2 +- src/typography/loading_displaying.js | 2 +- src/typography/p5.Font.js | 4 +- src/webgl/3d_primitives.js | 16 +++--- src/webgl/GeometryBuilder.js | 6 +-- src/webgl/ShapeBuilder.js | 4 +- src/webgl/material.js | 8 +-- src/webgl/p5.Framebuffer.js | 2 +- src/webgl/p5.RendererGL.js | 26 +++++----- src/webgl/text.js | 8 +-- test/unit/webgl/p5.RendererGL.js | 10 ++-- 16 files changed, 108 insertions(+), 99 deletions(-) diff --git a/src/color/setting.js b/src/color/setting.js index 3c7a68c003..31f08db1f8 100644 --- a/src/color/setting.js +++ b/src/color/setting.js @@ -1269,7 +1269,7 @@ function setting(p5, fn){ * */ fn.noFill = function() { - this._renderer.states.doFill = false; + this._renderer.noFill(); return this; }; @@ -1325,7 +1325,7 @@ function setting(p5, fn){ * */ fn.noStroke = function() { - this._renderer.states.doStroke = false; + this._renderer.states.strokeColor = false; return this; }; diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index 09eebec1bd..d4d13bb75f 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -4,6 +4,7 @@ * @for p5 */ +import { Color } from '../color/p5.Color'; import * as constants from '../core/constants'; import { Image } from '../image/p5.Image'; @@ -25,9 +26,9 @@ class Renderer { // Renderer state machine this.states = { - doStroke: true, + strokeColor: new Color([0, 0, 0]), strokeSet: false, - doFill: true, + fillColor: new Color([255, 255, 255]), fillSet: false, tint: null, imageMode: constants.CORNER, @@ -153,14 +154,22 @@ class Renderer { } - fill() { + fill(...args) { this.states.fillSet = true; - this.states.doFill = true; + this.states.fillColor = this._pInst.color(...args); } - stroke() { + noFill() { + this.states.fillColor = null; + } + + stroke(...args) { this.states.strokeSet = true; - this.states.doStroke = true; + this.states.strokeColor = this._pInst.color(...args); + } + + noStroke() { + this.states.strokeColor = null; } textSize(s) { @@ -254,7 +263,7 @@ class Renderer { // fix for #5785 (top of bounding box) let finalMinHeight = y; - if (!(this.states.doFill || this.states.doStroke)) { + if (!(this.states.fillColor || this.states.strokeColor)) { return; } diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index 75cf1b953d..2cd1e27eb5 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -201,7 +201,7 @@ class Renderer2D extends Renderer { fill(...args) { super.fill(...args); - const color = this._pInst.color(...args); + const color = this.states.fillColor; this._setFill(color.toString()); //accessible Outputs @@ -212,7 +212,7 @@ class Renderer2D extends Renderer { stroke(...args) { super.stroke(...args); - const color = this._pInst.color(...args); + const color = this.states.strokeColor; this._setStroke(color.toString()); //accessible Outputs @@ -672,7 +672,7 @@ class Renderer2D extends Renderer { } // Fill curves - if (this.states.doFill) { + if (this.states.fillColor) { if (!this._clipping) ctx.beginPath(); curves.forEach((curve, index) => { if (index === 0) { @@ -692,7 +692,7 @@ class Renderer2D extends Renderer { } // Stroke curves - if (this.states.doStroke) { + if (this.states.strokeColor) { if (!this._clipping) ctx.beginPath(); curves.forEach((curve, index) => { if (index === 0) { @@ -717,8 +717,8 @@ class Renderer2D extends Renderer { ellipse(args) { const ctx = this.drawingContext; - const doFill = this.states.doFill, - doStroke = this.states.doStroke; + const doFill = !!this.states.fillColor, + doStroke = this.states.strokeColor; const x = parseFloat(args[0]), y = parseFloat(args[1]), w = parseFloat(args[2]), @@ -750,7 +750,7 @@ class Renderer2D extends Renderer { line(x1, y1, x2, y2) { const ctx = this.drawingContext; - if (!this.states.doStroke) { + if (!this.states.strokeColor) { return this; } else if (this._getStroke() === styleEmpty) { return this; @@ -764,7 +764,7 @@ class Renderer2D extends Renderer { point(x, y) { const ctx = this.drawingContext; - if (!this.states.doStroke) { + if (!this.states.strokeColor) { return this; } else if (this._getStroke() === styleEmpty) { return this; @@ -785,8 +785,8 @@ class Renderer2D extends Renderer { quad(x1, y1, x2, y2, x3, y3, x4, y4) { const ctx = this.drawingContext; - const doFill = this.states.doFill, - doStroke = this.states.doStroke; + const doFill = !!this.states.fillColor, + doStroke = this.states.strokeColor; if (doFill && !doStroke) { if (this._getFill() === styleEmpty) { return this; @@ -821,8 +821,8 @@ class Renderer2D extends Renderer { let br = args[6]; let bl = args[7]; const ctx = this.drawingContext; - const doFill = this.states.doFill, - doStroke = this.states.doStroke; + const doFill = !!this.states.fillColor, + doStroke = this.states.strokeColor; if (doFill && !doStroke) { if (this._getFill() === styleEmpty) { return this; @@ -891,10 +891,10 @@ class Renderer2D extends Renderer { ctx.arcTo(x, y, x + w, y, tl); ctx.closePath(); } - if (!this._clipping && this.states.doFill) { + if (!this._clipping && this.states.fillColor) { ctx.fill(); } - if (!this._clipping && this.states.doStroke) { + if (!this._clipping && this.states.strokeColor) { ctx.stroke(); } return this; @@ -903,8 +903,8 @@ class Renderer2D extends Renderer { triangle(args) { const ctx = this.drawingContext; - const doFill = this.states.doFill, - doStroke = this.states.doStroke; + const doFill = !!this.states.fillColor, + doStroke = this.states.strokeColor; const x1 = args[0], y1 = args[1]; const x2 = args[2], @@ -945,7 +945,7 @@ class Renderer2D extends Renderer { if (vertices.length === 0) { return this; } - if (!this.states.doStroke && !this.states.doFill) { + if (!this.states.strokeColor && !this.states.fillColor) { return this; } const closeShape = mode === constants.CLOSE; @@ -1039,7 +1039,7 @@ class Renderer2D extends Renderer { if (shapeKind === constants.POINTS) { for (i = 0; i < numVerts; i++) { v = vertices[i]; - if (this.states.doStroke) { + if (this.states.strokeColor) { this._pInst.stroke(v[6]); } this._pInst.point(v[0], v[1]); @@ -1047,7 +1047,7 @@ class Renderer2D extends Renderer { } else if (shapeKind === constants.LINES) { for (i = 0; i + 1 < numVerts; i += 2) { v = vertices[i]; - if (this.states.doStroke) { + if (this.states.strokeColor) { this._pInst.stroke(vertices[i + 1][6]); } this._pInst.line(v[0], v[1], vertices[i + 1][0], vertices[i + 1][1]); @@ -1060,11 +1060,11 @@ class Renderer2D extends Renderer { this.drawingContext.lineTo(vertices[i + 1][0], vertices[i + 1][1]); this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]); this.drawingContext.closePath(); - if (!this._clipping && this.states.doFill) { + if (!this._clipping && this.states.fillColor) { this._pInst.fill(vertices[i + 2][5]); this.drawingContext.fill(); } - if (!this._clipping && this.states.doStroke) { + if (!this._clipping && this.states.strokeColor) { this._pInst.stroke(vertices[i + 2][6]); this.drawingContext.stroke(); } @@ -1075,18 +1075,18 @@ class Renderer2D extends Renderer { if (!this._clipping) this.drawingContext.beginPath(); this.drawingContext.moveTo(vertices[i + 1][0], vertices[i + 1][1]); this.drawingContext.lineTo(v[0], v[1]); - if (!this._clipping && this.states.doStroke) { + if (!this._clipping && this.states.strokeColor) { this._pInst.stroke(vertices[i + 1][6]); } - if (!this._clipping && this.states.doFill) { + if (!this._clipping && this.states.fillColor) { this._pInst.fill(vertices[i + 1][5]); } if (i + 2 < numVerts) { this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]); - if (!this._clipping && this.states.doStroke) { + if (!this._clipping && this.states.strokeColor) { this._pInst.stroke(vertices[i + 2][6]); } - if (!this._clipping && this.states.doFill) { + if (!this._clipping && this.states.fillColor) { this._pInst.fill(vertices[i + 2][5]); } } @@ -1106,15 +1106,15 @@ class Renderer2D extends Renderer { // If the next colour is going to be different, stroke / fill now if (i < numVerts - 1) { if ( - (this.states.doFill && v[5] !== vertices[i + 1][5]) || - (this.states.doStroke && v[6] !== vertices[i + 1][6]) + (this.states.fillColor && v[5] !== vertices[i + 1][5]) || + (this.states.strokeColor && v[6] !== vertices[i + 1][6]) ) { - if (!this._clipping && this.states.doFill) { + if (!this._clipping && this.states.fillColor) { this._pInst.fill(v[5]); this.drawingContext.fill(); this._pInst.fill(vertices[i + 1][5]); } - if (!this._clipping && this.states.doStroke) { + if (!this._clipping && this.states.strokeColor) { this._pInst.stroke(v[6]); this.drawingContext.stroke(); this._pInst.stroke(vertices[i + 1][6]); @@ -1135,10 +1135,10 @@ class Renderer2D extends Renderer { this.drawingContext.lineTo(vertices[i + j][0], vertices[i + j][1]); } this.drawingContext.lineTo(v[0], v[1]); - if (!this._clipping && this.states.doFill) { + if (!this._clipping && this.states.fillColor) { this._pInst.fill(vertices[i + 3][5]); } - if (!this._clipping && this.states.doStroke) { + if (!this._clipping && this.states.strokeColor) { this._pInst.stroke(vertices[i + 3][6]); } this._doFillStrokeClose(closeShape); @@ -1156,10 +1156,10 @@ class Renderer2D extends Renderer { vertices[i + 1][0], vertices[i + 1][1]); this.drawingContext.lineTo( vertices[i + 3][0], vertices[i + 3][1]); - if (!this._clipping && this.states.doFill) { + if (!this._clipping && this.states.fillColor) { this._pInst.fill(vertices[i + 3][5]); } - if (!this._clipping && this.states.doStroke) { + if (!this._clipping && this.states.strokeColor) { this._pInst.stroke(vertices[i + 3][6]); } } else { @@ -1290,10 +1290,10 @@ class Renderer2D extends Renderer { if (closeShape) { this.drawingContext.closePath(); } - if (!this._clipping && this.states.doFill) { + if (!this._clipping && this.states.fillColor) { this.drawingContext.fill(); } - if (!this._clipping && this.states.doStroke) { + if (!this._clipping && this.states.strokeColor) { this.drawingContext.stroke(); } } @@ -1352,11 +1352,11 @@ class Renderer2D extends Renderer { // a system/browser font // no stroke unless specified by user - if (this.states.doStroke && this.states.strokeSet) { + if (this.states.strokeColor && this.states.strokeSet) { this.drawingContext.strokeText(line, x, y); } - if (!this._clipping && this.states.doFill) { + if (!this._clipping && this.states.fillColor) { // if fill hasn't been set by user, use default text fill if (!this.states.fillSet) { this._setFill(constants._DEFAULT_TEXT_FILL); diff --git a/src/shape/2d_primitives.js b/src/shape/2d_primitives.js index 6e4f551322..3f1e0a3506 100644 --- a/src/shape/2d_primitives.js +++ b/src/shape/2d_primitives.js @@ -313,7 +313,7 @@ function primitives(p5, fn){ // if the current stroke and fill settings wouldn't result in something // visible, exit immediately - if (!this._renderer.states.doStroke && !this._renderer.states.doFill) { + if (!this._renderer.states.strokeColor && !this._renderer.states.fillColor) { return this; } @@ -540,7 +540,7 @@ function primitives(p5, fn){ fn._renderEllipse = function(x, y, w, h, detailX) { // if the current stroke and fill settings wouldn't result in something // visible, exit immediately - if (!this._renderer.states.doStroke && !this._renderer.states.doFill) { + if (!this._renderer.states.strokeColor && !this._renderer.states.fillColor) { return this; } @@ -712,7 +712,7 @@ function primitives(p5, fn){ fn.line = function(...args) { p5._validateParameters('line', args); - if (this._renderer.states.doStroke) { + if (this._renderer.states.strokeColor) { this._renderer.line(...args); } @@ -896,7 +896,7 @@ function primitives(p5, fn){ fn.point = function(...args) { p5._validateParameters('point', args); - if (this._renderer.states.doStroke) { + if (this._renderer.states.strokeColor) { if (args.length === 1 && args[0] instanceof p5.Vector) { this._renderer.point.call( this._renderer, @@ -1057,7 +1057,7 @@ function primitives(p5, fn){ fn.quad = function(...args) { p5._validateParameters('quad', args); - if (this._renderer.states.doStroke || this._renderer.states.doFill) { + if (this._renderer.states.strokeColor || this._renderer.states.fillColor) { if (this._renderer.isP3D && args.length < 12) { // if 3D and we weren't passed 12 args, assume Z is 0 this._renderer.quad.call( @@ -1334,7 +1334,7 @@ function primitives(p5, fn){ // internal method to have renderer draw a rectangle fn._renderRect = function() { - if (this._renderer.states.doStroke || this._renderer.states.doFill) { + if (this._renderer.states.strokeColor || this._renderer.states.fillColor) { // duplicate width for height in case only 3 arguments is provided if (arguments.length === 3) { arguments[3] = arguments[2]; @@ -1433,7 +1433,7 @@ function primitives(p5, fn){ fn.triangle = function(...args) { p5._validateParameters('triangle', args); - if (this._renderer.states.doStroke || this._renderer.states.doFill) { + if (this._renderer.states.strokeColor || this._renderer.states.fillColor) { this._renderer.triangle(args); } diff --git a/src/shape/curves.js b/src/shape/curves.js index 949f60e8ec..54874bafd9 100644 --- a/src/shape/curves.js +++ b/src/shape/curves.js @@ -205,7 +205,7 @@ function curves(p5, fn){ // if the current stroke and fill settings wouldn't result in something // visible, exit immediately - if (!this._renderer.states.doStroke && !this._renderer.states.doFill) { + if (!this._renderer.states.strokeColor && !this._renderer.states.fillColor) { return this; } @@ -758,7 +758,7 @@ function curves(p5, fn){ fn.curve = function(...args) { p5._validateParameters('curve', args); - if (this._renderer.states.doStroke) { + if (this._renderer.states.strokeColor) { this._renderer.curve(...args); } diff --git a/src/shape/vertex.js b/src/shape/vertex.js index 73afcc1653..bc22d7d886 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -1529,7 +1529,7 @@ function vertex(p5, fn){ if (vertices.length === 0) { return this; } - if (!this._renderer.states.doStroke && !this._renderer.states.doFill) { + if (!this._renderer.states.strokeColor && !this._renderer.states.fillColor) { return this; } diff --git a/src/typography/loading_displaying.js b/src/typography/loading_displaying.js index 47e7fc5812..b7b22ea43f 100644 --- a/src/typography/loading_displaying.js +++ b/src/typography/loading_displaying.js @@ -325,7 +325,7 @@ p5.prototype.loadFont = async function(path, onSuccess, onError) { */ p5.prototype.text = function(str, x, y, maxWidth, maxHeight) { p5._validateParameters('text', arguments); - return !(this._renderer.states.doFill || this._renderer.states.doStroke) + return !(this._renderer.states.fillColor || this._renderer.states.strokeColor) ? this : this._renderer.text(...arguments); }; diff --git a/src/typography/p5.Font.js b/src/typography/p5.Font.js index a91712e2ed..43b98796ba 100644 --- a/src/typography/p5.Font.js +++ b/src/typography/p5.Font.js @@ -538,11 +538,11 @@ p5.Font = class Font { } // only draw stroke if manually set by user - if (pg.states.doStroke && pg.states.strokeSet && !pg._clipping) { + if (pg.states.strokeColor && pg.states.strokeSet && !pg._clipping) { ctx.stroke(); } - if (pg.states.doFill && !pg._clipping) { + if (pg.states.fillColor && !pg._clipping) { // if fill hasn't been set by user, use default-text-fill if (!pg.states.fillSet) { pg._setFill(constants._DEFAULT_TEXT_FILL); diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 869b7ab240..c9106c2e69 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -2250,7 +2250,7 @@ function primitives3D(p5, fn){ if (detail <= 50) { arcGeom._edgesToVertices(arcGeom); - } else if (this.states.doStroke) { + } else if (this.states.strokeColor) { console.log( `Cannot apply a stroke to an ${shape} with more than 50 detail` ); @@ -3179,7 +3179,7 @@ function primitives3D(p5, fn){ this.push(); this.noLights(); - this.states.doStroke = false;; + this.states.strokeColor = false;; this.texture(img); this.states.textureMode = constants.NORMAL; @@ -3366,7 +3366,7 @@ function primitives3D(p5, fn){ planeGeom.computeFaces().computeNormals(); if (detailX <= 1 && detailY <= 1) { planeGeom._makeTriangleEdges()._edgesToVertices(); - } else if (this.states.doStroke) { + } else if (this.states.strokeColor) { console.log( 'Cannot draw stroke on plane objects with more' + ' than 1 detailX or 1 detailY' @@ -3446,7 +3446,7 @@ function primitives3D(p5, fn){ boxGeom.computeNormals(); if (detailX <= 4 && detailY <= 4) { boxGeom._edgesToVertices(); - } else if (this.states.doStroke) { + } else if (this.states.strokeColor) { console.log( 'Cannot draw stroke on box objects with more' + ' than 4 detailX or 4 detailY' @@ -3502,7 +3502,7 @@ function primitives3D(p5, fn){ ellipsoidGeom.computeFaces(); if (detailX <= 24 && detailY <= 24) { ellipsoidGeom._makeTriangleEdges()._edgesToVertices(); - } else if (this.states.doStroke) { + } else if (this.states.strokeColor) { console.log( 'Cannot draw stroke on ellipsoids with more' + ' than 24 detailX or 24 detailY' @@ -3539,7 +3539,7 @@ function primitives3D(p5, fn){ // normals are computed in call to _truncatedCone if (detailX <= 24 && detailY <= 16) { cylinderGeom._makeTriangleEdges()._edgesToVertices(); - } else if (this.states.doStroke) { + } else if (this.states.strokeColor) { console.log( 'Cannot draw stroke on cylinder objects with more' + ' than 24 detailX or 16 detailY' @@ -3565,7 +3565,7 @@ function primitives3D(p5, fn){ _truncatedCone.call(coneGeom, 1, 0, 1, detailX, detailY, cap, false); if (detailX <= 24 && detailY <= 16) { coneGeom._makeTriangleEdges()._edgesToVertices(); - } else if (this.states.doStroke) { + } else if (this.states.strokeColor) { console.log( 'Cannot draw stroke on cone objects with more' + ' than 24 detailX or 16 detailY' @@ -3628,7 +3628,7 @@ function primitives3D(p5, fn){ torusGeom.computeFaces(); if (detailX <= 24 && detailY <= 16) { torusGeom._makeTriangleEdges()._edgesToVertices(); - } else if (this.states.doStroke) { + } else if (this.states.strokeColor) { console.log( 'Cannot draw strokes on torus object with more' + ' than 24 detailX or 16 detailY' diff --git a/src/webgl/GeometryBuilder.js b/src/webgl/GeometryBuilder.js index a5691db31e..8bb3c272c5 100644 --- a/src/webgl/GeometryBuilder.js +++ b/src/webgl/GeometryBuilder.js @@ -87,12 +87,12 @@ class GeometryBuilder { this.geometry.vertexProperty(propName, data, size); } - if (this.renderer.states.doFill) { + if (this.renderer.states.fillColor) { this.geometry.faces.push( ...input.faces.map(f => f.map(idx => idx + startIdx)) ); } - if (this.renderer.states.doStroke) { + if (this.renderer.states.strokeColor) { this.geometry.edges.push( ...input.edges.map(edge => edge.map(idx => idx + startIdx)) ); @@ -111,7 +111,7 @@ class GeometryBuilder { addImmediate(geometry, shapeMode) { const faces = []; - if (this.renderer.states.doFill) { + if (this.renderer.states.fillColor) { if ( shapeMode === constants.TRIANGLE_STRIP || shapeMode === constants.QUAD_STRIP diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index 258f8c07e8..02e5318ff1 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -260,7 +260,7 @@ export class ShapeBuilder { _processVertices(mode) { if (this.geometry.vertices.length === 0) return; - const calculateStroke = this.renderer.states.doStroke; + const calculateStroke = this.renderer.states.strokeColor; const shouldClose = mode === constants.CLOSE; if (calculateStroke) { this.geometry.edges = this._calculateEdges( @@ -280,7 +280,7 @@ export class ShapeBuilder { const hasContour = this.contourIndices.length > 0; // We tesselate when drawing curves or convex shapes const shouldTess = - this.renderer.states.doFill && + this.renderer.states.fillColor && ( this.isBezier || this.isQuadratic || diff --git a/src/webgl/material.js b/src/webgl/material.js index 288d83f822..a535d5392c 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -2928,7 +2928,7 @@ function material(p5, fn){ this._renderer.states.curAmbientColor = color._array; this._renderer.states._useNormalMaterial = false; this._renderer.states.enableLighting = true; - this._renderer.states.doFill = true; + this._renderer.states.fillColor = true; return this; }; @@ -3625,7 +3625,7 @@ function material(p5, fn){ this.states.drawMode = constants.TEXTURE; this.states._useNormalMaterial = false; this.states._tex = tex; - this.states.doFill = true; + this.states.fillColor = true; }; RendererGL.prototype.normalMaterial = function(...args) { @@ -3634,8 +3634,8 @@ function material(p5, fn){ this.states._useEmissiveMaterial = false; this.states._useNormalMaterial = true; this.states.curFillColor = [1, 1, 1, 1]; - this.states.doFill = true; - this.states.doStroke = false; + this.states.fillColor = true; + this.states.strokeColor = false; } // RendererGL.prototype.ambientMaterial = function(v1, v2, v3) { diff --git a/src/webgl/p5.Framebuffer.js b/src/webgl/p5.Framebuffer.js index 9d78813469..45da02c255 100644 --- a/src/webgl/p5.Framebuffer.js +++ b/src/webgl/p5.Framebuffer.js @@ -1559,7 +1559,7 @@ class Framebuffer { this.renderer.states.imageMode = constants.CORNER; this.renderer.setCamera(this.filterCamera); this.renderer.resetMatrix(); - this.renderer.states.doStroke = false; + this.renderer.states.strokeColor = false; this.renderer.clear(); this.renderer._drawingFilter = true; this.renderer.image( diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 93f9f14f53..cd195dcda1 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -468,7 +468,7 @@ class RendererGL extends Renderer { this.shapeBuilder.geometry, this.shapeBuilder.shapeMode ); - } else if (this.states.doFill || this.states.doStroke) { + } else if (this.states.fillColor || this.states.strokeColor) { this._drawGeometry( this.shapeBuilder.geometry, { mode: this.shapeBuilder.shapeMode, count } @@ -509,14 +509,14 @@ class RendererGL extends Renderer { } if ( - this.states.doFill && + this.states.fillColor && geometry.vertices.length >= 3 && ![constants.LINES, constants.POINTS].includes(mode) ) { this._drawFills(geometry, { mode, count }); } - if (this.states.doStroke && geometry.lineVertices.length >= 1) { + if (this.states.strokeColor && geometry.lineVertices.length >= 1) { this._drawStrokes(geometry, { count }); } @@ -967,7 +967,7 @@ class RendererGL extends Renderer { super.fill(...args); //see material.js for more info on color blending in webgl // const color = fn.color.apply(this._pInst, arguments); - const color = this._pInst.color(...args); + const color = this.states.fillColor; this.states.curFillColor = color._array; this.states.drawMode = constants.FILL; this.states._useNormalMaterial = false; @@ -1006,8 +1006,7 @@ class RendererGL extends Renderer { stroke(...args) { super.stroke(...args); // const color = fn.color.apply(this._pInst, arguments); - const color = this._pInst.color(...args); - this.states.curStrokeColor = color._array; + this.states.curStrokeColor = this.states.strokeColor._array; } strokeCap(cap) { @@ -1097,7 +1096,7 @@ class RendererGL extends Renderer { this.matchSize(tmp, target); // setup this.push(); - this.states.doStroke = false; + this.states.strokeColor = false; this.blendMode(constants.BLEND); // draw main to temp buffer @@ -1131,7 +1130,7 @@ class RendererGL extends Renderer { // every other non-blur shader uses single pass else { fbo.draw(() => { - this.states.doStroke = false; + this.states.strokeColor = false; this.blendMode(constants.BLEND); this.shader(this.states.filterShader); this.states.filterShader.setUniform('tex0', target); @@ -1147,7 +1146,7 @@ class RendererGL extends Renderer { } // draw fbo contents onto main renderer. this.push(); - this.states.doStroke = false; + this.states.strokeColor = false; this.clear(); this.push(); this.states.imageMode = constants.CORNER; @@ -1260,8 +1259,8 @@ class RendererGL extends Renderer { this.push(); this.resetShader(); - if (this.states.doFill) this.fill(0, 0); - if (this.states.doStroke) this.stroke(0, 0); + if (this.states.fillColor) this.fill(0, 0); + if (this.states.strokeColor) this.stroke(0, 0); } endClip() { @@ -2038,7 +2037,8 @@ class RendererGL extends Renderer { newFramebuffer.draw(() => { this.shader(this.states.diffusedShader); this.states.diffusedShader.setUniform('environmentMap', input); - this.states.doStroke = false; + this.states.strokeColor = false; + this.rectMode(constants.CENTER); this.noLights(); this.plane(width, height); }); @@ -2089,7 +2089,7 @@ class RendererGL extends Renderer { this.clear(); this.states.specularShader.setUniform('environmentMap', input); this.states.specularShader.setUniform('roughness', roughness); - this.states.doStroke = false; + this.states.strokeColor = false; this.noLights(); this.plane(w, w); }); diff --git a/src/webgl/text.js b/src/webgl/text.js index b8355f0c9a..d3d69e8b23 100644 --- a/src/webgl/text.js +++ b/src/webgl/text.js @@ -642,7 +642,7 @@ function text(p5, fn){ ); return; } - if (y >= maxY || !this.states.doFill) { + if (y >= maxY || !this.states.fillColor) { return; // don't render lines beyond our maxY position } @@ -656,10 +656,10 @@ function text(p5, fn){ p.push(); // fix to #803 // remember this state, so it can be restored later - const doStroke = this.states.doStroke; + const doStroke = this.states.strokeColor; const drawMode = this.states.drawMode; - this.states.doStroke = false; + this.states.strokeColor = false; this.states.drawMode = constants.TEXTURE; // get the cached FontInfo object @@ -752,7 +752,7 @@ function text(p5, fn){ // clean up sh.unbindShader(); - this.states.doStroke = doStroke; + this.states.strokeColor = doStroke; this.states.drawMode = drawMode; gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 931aa0ef24..0eb5891170 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -141,21 +141,21 @@ suite('p5.RendererGL', function() { test('check activate and deactivating fill and stroke', function() { myp5.noStroke(); assert( - !myp5._renderer.states.doStroke, + !myp5._renderer.states.strokeColor, 'stroke shader still active after noStroke()' ); - assert.isTrue( - myp5._renderer.states.doFill, + assert( + !myp5._renderer.states.doFill, 'fill shader deactivated by noStroke()' ); myp5.stroke(0); myp5.noFill(); assert( - myp5._renderer.states.doStroke, + !!myp5._renderer.states.strokeColor, 'stroke shader not active after stroke()' ); assert.isTrue( - !myp5._renderer.states.doFill, + !myp5._renderer.states.fillColor, 'fill shader still active after noFill()' ); }); From c7561727c4a5f6b6d81d3ed0fcf032180953f1e0 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Mon, 18 Nov 2024 16:55:18 -0500 Subject: [PATCH 022/111] Add vertex state getter --- src/core/p5.Renderer.js | 7 +++++++ src/webgl/p5.RendererGL.js | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index d4d13bb75f..08f825a4f0 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -172,6 +172,13 @@ class Renderer { this.states.strokeColor = null; } + vertexProperties() { + return { + stroke: this.states.strokeColor, + fill: this.states.fillColor, + } + } + textSize(s) { if (typeof s === 'number') { this.states.textSize = s; diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index cd195dcda1..efc815d184 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1009,6 +1009,13 @@ class RendererGL extends Renderer { this.states.curStrokeColor = this.states.strokeColor._array; } + vertexProperties() { + return { + ...super.vertexProperties(), + normal: this.states._currentNormal, + } + } + strokeCap(cap) { this.curStrokeCap = cap; } From b4e43e513dc53e8e2acae7645f49ad54d26cefba Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Fri, 22 Nov 2024 10:33:31 -0500 Subject: [PATCH 023/111] Fix usage of false instead of null --- src/color/setting.js | 2 +- src/webgl/3d_primitives.js | 2 +- src/webgl/material.js | 2 +- src/webgl/p5.Framebuffer.js | 2 +- src/webgl/p5.RendererGL.js | 11 +++++------ src/webgl/text.js | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/color/setting.js b/src/color/setting.js index 31f08db1f8..8103aae975 100644 --- a/src/color/setting.js +++ b/src/color/setting.js @@ -1325,7 +1325,7 @@ function setting(p5, fn){ * */ fn.noStroke = function() { - this._renderer.states.strokeColor = false; + this._renderer.states.strokeColor = null; return this; }; diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index c9106c2e69..9b71bc569a 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -3179,7 +3179,7 @@ function primitives3D(p5, fn){ this.push(); this.noLights(); - this.states.strokeColor = false;; + this.states.strokeColor = null;; this.texture(img); this.states.textureMode = constants.NORMAL; diff --git a/src/webgl/material.js b/src/webgl/material.js index a535d5392c..fa70667de6 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -3635,7 +3635,7 @@ function material(p5, fn){ this.states._useNormalMaterial = true; this.states.curFillColor = [1, 1, 1, 1]; this.states.fillColor = true; - this.states.strokeColor = false; + this.states.strokeColor = null; } // RendererGL.prototype.ambientMaterial = function(v1, v2, v3) { diff --git a/src/webgl/p5.Framebuffer.js b/src/webgl/p5.Framebuffer.js index 45da02c255..2a5ced5f7a 100644 --- a/src/webgl/p5.Framebuffer.js +++ b/src/webgl/p5.Framebuffer.js @@ -1559,7 +1559,7 @@ class Framebuffer { this.renderer.states.imageMode = constants.CORNER; this.renderer.setCamera(this.filterCamera); this.renderer.resetMatrix(); - this.renderer.states.strokeColor = false; + this.renderer.states.strokeColor = null; this.renderer.clear(); this.renderer._drawingFilter = true; this.renderer.image( diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index efc815d184..7b8e3cb6cd 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1103,7 +1103,7 @@ class RendererGL extends Renderer { this.matchSize(tmp, target); // setup this.push(); - this.states.strokeColor = false; + this.states.strokeColor = null; this.blendMode(constants.BLEND); // draw main to temp buffer @@ -1137,7 +1137,7 @@ class RendererGL extends Renderer { // every other non-blur shader uses single pass else { fbo.draw(() => { - this.states.strokeColor = false; + this.states.strokeColor = null; this.blendMode(constants.BLEND); this.shader(this.states.filterShader); this.states.filterShader.setUniform('tex0', target); @@ -1153,7 +1153,7 @@ class RendererGL extends Renderer { } // draw fbo contents onto main renderer. this.push(); - this.states.strokeColor = false; + this.states.strokeColor = null; this.clear(); this.push(); this.states.imageMode = constants.CORNER; @@ -2044,8 +2044,7 @@ class RendererGL extends Renderer { newFramebuffer.draw(() => { this.shader(this.states.diffusedShader); this.states.diffusedShader.setUniform('environmentMap', input); - this.states.strokeColor = false; - this.rectMode(constants.CENTER); + this.states.strokeColor = null; this.noLights(); this.plane(width, height); }); @@ -2096,7 +2095,7 @@ class RendererGL extends Renderer { this.clear(); this.states.specularShader.setUniform('environmentMap', input); this.states.specularShader.setUniform('roughness', roughness); - this.states.strokeColor = false; + this.states.strokeColor = null; this.noLights(); this.plane(w, w); }); diff --git a/src/webgl/text.js b/src/webgl/text.js index d3d69e8b23..1438b24cce 100644 --- a/src/webgl/text.js +++ b/src/webgl/text.js @@ -659,7 +659,7 @@ function text(p5, fn){ const doStroke = this.states.strokeColor; const drawMode = this.states.drawMode; - this.states.strokeColor = false; + this.states.strokeColor = null; this.states.drawMode = constants.TEXTURE; // get the cached FontInfo object From 64e319bd601de0d02424dc26d483f79b59d653e1 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Fri, 22 Nov 2024 11:16:25 -0500 Subject: [PATCH 024/111] Add properties to current shape --- src/core/p5.Renderer.js | 10 ++ src/shape/custom_shapes.js | 182 +++++++++++++++++++------------------ src/webgl/p5.RendererGL.js | 2 + 3 files changed, 106 insertions(+), 88 deletions(-) diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index 08f825a4f0..797b85bb22 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -7,6 +7,7 @@ import { Color } from '../color/p5.Color'; import * as constants from '../core/constants'; import { Image } from '../image/p5.Image'; +import { Shape } from '../shape/custom_shapes'; class Renderer { constructor(pInst, w, h, isMainCanvas) { @@ -50,6 +51,8 @@ class Renderer { this._clipping = false; this._clipInvert = false; this._curveTightness = 0; + + this.currentShape = new Shape(); } remove() { @@ -92,6 +95,7 @@ class Renderer { pop() { this._pushPopDepth--; Object.assign(this.states, this._pushPopStack.pop()); + this.updateShapeVertexProperties(); } beginClip(options = {}) { @@ -157,6 +161,7 @@ class Renderer { fill(...args) { this.states.fillSet = true; this.states.fillColor = this._pInst.color(...args); + this.updateShapeVertexProperties(); } noFill() { @@ -166,6 +171,7 @@ class Renderer { stroke(...args) { this.states.strokeSet = true; this.states.strokeColor = this._pInst.color(...args); + this.updateShapeVertexProperties(); } noStroke() { @@ -179,6 +185,10 @@ class Renderer { } } + updateShapeVertexProperties() { + this.currentShape.vertexProperties = this.vertexProperties(); + } + textSize(s) { if (typeof s === 'number') { this.states.textSize = s; diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 18348c8114..7bba519fc8 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -11,28 +11,34 @@ // ---- GENERAL CLASSES ---- -class Shape { +class Shape { + vertexProperties = {}; + constructor() { - + + } + + reset() { + // TODO: remove existing vertices } } class Contour { constructor() { - + } } // abstract class class ShapePrimitive { constructor() { - + } } class Vertex { constructor() { - + } // TODO: make sure name of array conversion method is @@ -44,26 +50,26 @@ class Vertex { class Anchor { constructor() { - + } } // abstract class class Segment extends ShapePrimitive { constructor() { - + } } class LineSegment extends Segment { constructor() { - + } } class BezierSegment extends Segment { constructor() { - + } } @@ -71,7 +77,7 @@ class BezierSegment extends Segment { // may want to use separate classes, but maybe not class SplineSegment extends Segment { constructor() { - + } } @@ -79,25 +85,25 @@ class SplineSegment extends Segment { class Point extends ShapePrimitive { constructor() { - + } } class Line extends ShapePrimitive { constructor() { - + } } class Triangle extends ShapePrimitive { constructor() { - + } } class Quad extends ShapePrimitive { constructor() { - + } } @@ -105,19 +111,19 @@ class Quad extends ShapePrimitive { class TriangleFan extends ShapePrimitive { constructor() { - + } } class TriangleStrip extends ShapePrimitive { constructor() { - + } } class QuadStrip extends ShapePrimitive { constructor() { - + } } @@ -126,26 +132,26 @@ class QuadStrip extends ShapePrimitive { // abstract class class PrimitiveVisitor { constructor() { - + } } // using this instead of PrimitiveToContext2DConverter for now class PrimitiveToPath2DConverter extends PrimitiveVisitor { constructor() { - + } } class PrimitiveToVerticesConverter extends PrimitiveVisitor { constructor() { - + } } class PointAtLengthGetter extends PrimitiveVisitor { constructor() { - + } } @@ -155,10 +161,10 @@ function customShapes(p5, fn) { /** * @private * A class to describe a custom shape made with `beginShape()`/`endShape()`. - * + * * Every `Shape` has a `kind`. The kind takes any value that * can be passed to beginShape(): - * + * * - `PATH` * - `POINTS` * - `LINES` @@ -167,32 +173,32 @@ function customShapes(p5, fn) { * - `TRIANGLE_FAN` * - `TRIANGLE_STRIP` * - `QUAD_STRIP` - * + * * A `Shape` of any kind consists of `contours`, which can be thought of as * subshapes (shapes inside another shape). Each `contour` is built from * basic shapes called primitives, and each primitive consists of one or more vertices. - * - * For example, a square can be made from a single path contour with four line-segment - * primitives. Each line segment contains a vertex that indicates its endpoint. A square + * + * For example, a square can be made from a single path contour with four line-segment + * primitives. Each line segment contains a vertex that indicates its endpoint. A square * with a circular hole in it contains the circle in a separate contour. - * - * By default, each vertex only has a position, but a shape's vertices may have other - * properties such as texture coordinates, a normal vector, a fill color, and a stroke color. - * The properties every vertex should have may be customized by passing `vertexProperties` to + * + * By default, each vertex only has a position, but a shape's vertices may have other + * properties such as texture coordinates, a normal vector, a fill color, and a stroke color. + * The properties every vertex should have may be customized by passing `vertexProperties` to * `createShape()`. - * + * * Once a shape is created and given a name like `myShape`, it can be built up with * methods such as `myShape.beginShape()`, `myShape.vertex()`, and `myShape.endShape()`. - * + * * Vertex functions such as `vertex()` or `bezierVertex()` are used to set the `position` * property of vertices, as well as the `textureCoordinates` property if applicable. Those * properties only apply to a single vertex. - * - * If `vertexProperties` includes other properties, they are each set by a method of the - * same name. For example, if vertices in `myShape` have a `fill`, then that is set with + * + * If `vertexProperties` includes other properties, they are each set by a method of the + * same name. For example, if vertices in `myShape` have a `fill`, then that is set with * `myShape.fill()`. In the same way that a fill() may be applied * to one or more shapes, `myShape.fill()` may be applied to one or more vertices. - * + * * @class p5.Shape * @param {Object} [vertexProperties={position: createVector(0, 0)}] vertex properties and their initial values. */ @@ -202,14 +208,14 @@ function customShapes(p5, fn) { /** * @private * A class to describe a contour made with `beginContour()`/`endContour()`. - * + * * Contours may be thought of as shapes inside of other shapes. * For example, a contour may be used to create a hole in a shape that is created - * with beginShape()/endShape(). + * with beginShape()/endShape(). * Multiple contours may be included inside a single shape. - * + * * Contours can have any `kind` that a shape can have: - * + * * - `PATH` * - `POINTS` * - `LINES` @@ -218,14 +224,14 @@ function customShapes(p5, fn) { * - `TRIANGLE_FAN` * - `TRIANGLE_STRIP` * - `QUAD_STRIP` - * - * By default, a contour has the same kind as the shape that contains it, but this + * + * By default, a contour has the same kind as the shape that contains it, but this * may be changed by passing a different `kind` to beginContour(). - * + * * A `Contour` of any kind consists of `primitives`, which are the most basic * shapes that can be drawn. For example, if a contour is a hexagon, then * it's made from six line-segment primitives. - * + * * @class p5.Contour */ @@ -233,32 +239,32 @@ function customShapes(p5, fn) { /** * @private - * A base class to describe a shape primitive (a basic shape drawn with + * A base class to describe a shape primitive (a basic shape drawn with * `beginShape()`/`endShape()`). - * - * Shape primitives are the most basic shapes that can be drawn with + * + * Shape primitives are the most basic shapes that can be drawn with * beginShape()/endShape(): - * - * - segment primitives: line segments, bezier segments, spline segments, and arc segments + * + * - segment primitives: line segments, bezier segments, spline segments, and arc segments * - isolated primitives: points, lines, triangles, and quads * - tessellation primitives: triangle fans, triangle strips, and quad strips - * + * * More complex shapes may be created by combining many primitives, possibly of different kinds, * into a single shape. - * - * In a similar way, every shape primitive is built from one or more vertices. + * + * In a similar way, every shape primitive is built from one or more vertices. * For example, a point consists of a single vertex, while a triangle consists of three vertices. - * Each type of shape primitive has a `vertexCapacity`, which may be `Infinity` (for example, a - * spline may consist of any number of vertices). A primitive's `vertexCount` is the number of + * Each type of shape primitive has a `vertexCapacity`, which may be `Infinity` (for example, a + * spline may consist of any number of vertices). A primitive's `vertexCount` is the number of * vertices it currently contains. - * + * * Each primitive can add itself to a shape with an `addToShape()` method. - * - * It can also accept visitor objects with an `accept()` method. When a primitive accepts a visitor, - * it gives the visitor access to its vertex data. For example, one visitor to a segment might turn - * the data into 2D drawing instructions. Another might find a point at a given distance + * + * It can also accept visitor objects with an `accept()` method. When a primitive accepts a visitor, + * it gives the visitor access to its vertex data. For example, one visitor to a segment might turn + * the data into 2D drawing instructions. Another might find a point at a given distance * along the segment. - * + * * @class p5.ShapePrimitive * @param {...p5.Vertex} vertices the vertices to include in the primitive. */ @@ -268,34 +274,34 @@ function customShapes(p5, fn) { /** * @private * A class to describe a vertex (a point on a shape), in 2D or 3D. - * + * * Vertices are the basic building blocks of all `p5.Shape` objects, including * shapes made with vertex(), arcVertex(), * bezierVertex(), and splineVertex(). - * - * Like a point on an object in the real world, a vertex may have different properties. - * These may include coordinate properties such as `position`, `textureCoordinates`, and `normal`, + * + * Like a point on an object in the real world, a vertex may have different properties. + * These may include coordinate properties such as `position`, `textureCoordinates`, and `normal`, * color properties such as `fill` and `stroke`, and more. - * - * A vertex called `myVertex` with position coordinates `(2, 3, 5)` and a green stroke may be created + * + * A vertex called `myVertex` with position coordinates `(2, 3, 5)` and a green stroke may be created * like this: - * + * * ```js * let myVertex = new p5.Vertex({ * position: createVector(2, 3, 5), * stroke: color('green') * }); * ``` - * - * Any property names may be used. Property values may be any - * JavaScript primitive, any - * object literal, - * or any object with an `array` property. - * + * + * Any property names may be used. Property values may be any + * JavaScript primitive, any + * object literal, + * or any object with an `array` property. + * * For example, if a position is stored as a `p5.Vector` object and a stroke is stored as a `p5.Color` object, * then the `array` properties of those objects will be used by the vertex's own `array` property, which provides * all the vertex data in a single array. - * + * * @class p5.Vertex * @param {Object} [properties={position: createVector(0, 0)}] vertex properties. */ @@ -306,7 +312,7 @@ function customShapes(p5, fn) { /** * @private - * A class responsible for... + * A class responsible for... */ p5.Anchor = Anchor; @@ -320,7 +326,7 @@ function customShapes(p5, fn) { /** * @private - * A class responsible for... + * A class responsible for... */ p5.LineSegment = LineSegment; @@ -334,7 +340,7 @@ function customShapes(p5, fn) { /** * @private - * A class responsible for... + * A class responsible for... */ p5.SplineSegment = SplineSegment; @@ -343,14 +349,14 @@ function customShapes(p5, fn) { /** * @private - * A class responsible for... + * A class responsible for... */ p5.Point = Point; /** * @private - * A class responsible for... + * A class responsible for... */ p5.Line = Line; @@ -364,7 +370,7 @@ function customShapes(p5, fn) { /** * @private - * A class responsible for... + * A class responsible for... */ p5.Quad = Quad; @@ -373,14 +379,14 @@ function customShapes(p5, fn) { /** * @private - * A class responsible for... + * A class responsible for... */ p5.TriangleFan = TriangleFan; /** * @private - * A class responsible for... + * A class responsible for... */ p5.TriangleStrip = TriangleStrip; @@ -396,7 +402,7 @@ function customShapes(p5, fn) { /** * @private - * A class responsible for... + * A class responsible for... */ p5.PrimitiveVisitor = PrimitiveVisitor; @@ -421,11 +427,11 @@ function customShapes(p5, fn) { */ p5.PointAtLengthGetter = PointAtLengthGetter; - + // ---- FUNCTIONS ---- // Note: Code is commented out for now, to avoid conflicts with the existing implementation. - + /** * Top-line description * @@ -436,7 +442,7 @@ function customShapes(p5, fn) { // // example of how to call an existing p5 function: // // this.background('yellow'); // }; - + /** * Top-line description * @@ -544,4 +550,4 @@ export { if (typeof p5 !== 'undefined') { customShapes(p5, p5.prototype); -} \ No newline at end of file +} diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 7b8e3cb6cd..5bce7d3720 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -442,6 +442,7 @@ class RendererGL extends Renderer { ////////////////////////////////////////////// beginShape(...args) { + this.currentShape.reset(); this.shapeBuilder.beginShape(...args); } @@ -494,6 +495,7 @@ class RendererGL extends Renderer { } else { this.states._currentNormal = new Vector(xorv, y, z); } + this.updateShapeVertexProperties(); } ////////////////////////////////////////////// From 290f5c7b5f6bc1cb0d244969985c919af5eba187 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Fri, 22 Nov 2024 19:42:50 -0500 Subject: [PATCH 025/111] Update vertexProperties to make setters --- src/core/p5.Renderer.js | 7 +++++-- src/shape/custom_shapes.js | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index 797b85bb22..166e661906 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -52,7 +52,7 @@ class Renderer { this._clipInvert = false; this._curveTightness = 0; - this.currentShape = new Shape(); + this.currentShape = new Shape(this.vertexProperties()); } remove() { @@ -186,7 +186,10 @@ class Renderer { } updateShapeVertexProperties() { - this.currentShape.vertexProperties = this.vertexProperties(); + const props = this.vertexProperties(); + for (const key in props) { + this.currentShape[key](props[key]); + } } textSize(s) { diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 7bba519fc8..c27d8ff6d9 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -12,10 +12,18 @@ // ---- GENERAL CLASSES ---- class Shape { - vertexProperties = {}; - - constructor() { - + vertexProperties; + + constructor(vertexProperties) { + this.vertexProperties = vertexProperties; + + for (const key in this.vertexProperties) { + if (key !== 'position' && key !== 'textureCoordinates') { + this[key] = function(value) { + this.vertexProperties[key] = value; + } + } + } } reset() { From cd156bf1bba32c99b4c2610cead893a9d136f104 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Fri, 22 Nov 2024 20:34:44 -0500 Subject: [PATCH 026/111] Refactor old vertex() into legacyVertex() and start making new vertex() --- src/core/p5.Renderer.js | 39 +++++ src/core/p5.Renderer2D.js | 6 +- src/shape/custom_shapes.js | 24 ++- src/shape/vertex.js | 95 ++++++----- src/webgl/3d_primitives.js | 65 +++---- src/webgl/p5.RendererGL.js | 11 +- test/unit/webgl/p5.RendererGL.js | 284 +++++++++++++++---------------- 7 files changed, 303 insertions(+), 221 deletions(-) diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index 166e661906..0139e0272a 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -7,6 +7,7 @@ import { Color } from '../color/p5.Color'; import * as constants from '../core/constants'; import { Image } from '../image/p5.Image'; +import { Vector } from '../math/p5.Vector'; import { Shape } from '../shape/custom_shapes'; class Renderer { @@ -98,6 +99,44 @@ class Renderer { this.updateShapeVertexProperties(); } + beginShape(...args) { + this.currentShape.reset(); + this.currentShape.beginShape(...args); + } + + endShape(...args) { + this.currentShape.endShape(...args); + this.drawShape(this.currentShape); + } + + drawShape(shape) { + throw new Error('Unimplemented') + } + + vertex(x, y) { + let z, u, v; + + // default to (x, y) mode: all other arguments assumed to be 0. + z = u = v = 0; + + if (arguments.length === 3) { + // (x, y, z) mode: (u, v) assumed to be 0. + z = arguments[2]; + } else if (arguments.length === 4) { + // (x, y, u, v) mode: z assumed to be 0. + u = arguments[2]; + v = arguments[3]; + } else if (arguments.length === 5) { + // (x, y, z, u, v) mode + z = arguments[2]; + u = arguments[3]; + v = arguments[4]; + } + const position = new Vector(x, y, z); + const textureCoordinates = new Vector(u, v); + this.currentShape.vertex(position, textureCoordinates); + } + beginClip(options = {}) { if (this._clipping) { throw new Error("It looks like you're trying to clip while already in the middle of clipping. Did you forget to endClip()?"); diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index 2cd1e27eb5..3e820ec12e 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -252,6 +252,10 @@ class Renderer2D extends Renderer { } } + drawShape(shape) { + // TODO + } + beginClip(options = {}) { super.beginClip(options); @@ -933,7 +937,7 @@ class Renderer2D extends Renderer { } } - endShape( + legacyEndShape( mode, vertices, isCurve, diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index c27d8ff6d9..e63ecb664d 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -7,7 +7,7 @@ */ // uncomment the following once you need it (otherwise VS Code complains): -// import * as constants from '../core/constants'; +import * as constants from '../core/constants'; // ---- GENERAL CLASSES ---- @@ -29,6 +29,28 @@ class Shape { reset() { // TODO: remove existing vertices } + + vertex(position, textureCoordinates) { + // Add the current position and texture coordiantes to the existing state + let vertex = this.createVertex({ ...this.vertexProperties, position, textureCoordinates }); + // TODO + // primitiveShapeCreator = primitiveShapeCreators.get(['vertex', this.kind]); + // primitiveShape = primitiveShapeCreator(vertex); + // primitiveShape.addToShape(this); + } + + createVertex(properties) { + // TODO + // return vertex; + } + + beginShape(shapeKind) { + // TODO + } + + endShape(closeMode = constants.OPEN) { + // TODO + } } class Contour { diff --git a/src/shape/vertex.js b/src/shape/vertex.js index bc22d7d886..0067e760b9 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -518,9 +518,10 @@ function vertex(p5, fn){ */ fn.beginShape = function(kind) { p5._validateParameters('beginShape', arguments); - if (this._renderer.isP3D) { - this._renderer.beginShape(...arguments); - } else { + this._renderer.beginShape(...arguments); + + // TODO remove this once shape implementation is complete + if (!this._renderer.isP3D) { if ( kind === constants.POINTS || kind === constants.LINES || @@ -1512,8 +1513,11 @@ function vertex(p5, fn){ count = 1; } + this._renderer.endShape(mode, count); + + // TODO remove once shape refactor is complete if (this._renderer.isP3D) { - this._renderer.endShape( + this._renderer.legacyEndShape( mode, isCurve, isBezier, @@ -1540,7 +1544,7 @@ function vertex(p5, fn){ vertices.push(vertices[0]); } - this._renderer.endShape( + this._renderer.legacyEndShape( mode, vertices, isCurve, @@ -2036,8 +2040,11 @@ function vertex(p5, fn){ * @chainable */ fn.vertex = function(x, y, moveTo, u, v) { + this._renderer.vertex(...arguments); + + // TODO remove after shape refactor if (this._renderer.isP3D) { - this._renderer.vertex(...arguments); + this._renderer.legacyVertex(...arguments); } else { const vert = []; vert.isVert = true; @@ -2255,27 +2262,27 @@ function vertex(p5, fn){ }; /** Sets the shader's vertex property or attribute variables. - * + * * An vertex property or vertex attribute is a variable belonging to a vertex in a shader. p5.js provides some * default properties, such as `aPosition`, `aNormal`, `aVertexColor`, etc. These are - * set using vertex(), normal() + * set using vertex(), normal() * and fill() respectively. Custom properties can also - * be defined within beginShape() and + * be defined within beginShape() and * endShape(). - * + * * The first parameter, `propertyName`, is a string with the property's name. * This is the same variable name which should be declared in the shader, such as * `in vec3 aProperty`, similar to .`setUniform()`. - * - * The second parameter, `data`, is the value assigned to the shader variable. This - * value will be applied to subsequent vertices created with + * + * The second parameter, `data`, is the value assigned to the shader variable. This + * value will be applied to subsequent vertices created with * vertex(). It can be a Number or an array of numbers, * and in the shader program the type can be declared according to the WebGL * specification. Common types include `float`, `vec2`, `vec3`, `vec4` or matrices. - * - * See also the vertexProperty() method on + * + * See also the vertexProperty() method on * Geometry objects. - * + * * @example *
* @@ -2283,40 +2290,40 @@ function vertex(p5, fn){ * precision mediump float; * uniform mat4 uModelViewMatrix; * uniform mat4 uProjectionMatrix; - * + * * in vec3 aPosition; * in vec2 aOffset; - * + * * void main(){ * vec4 positionVec4 = vec4(aPosition.xyz, 1.0); - * positionVec4.xy += aOffset; + * positionVec4.xy += aOffset; * gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4; * } * `; - * + * * const fragSrc = `#version 300 es * precision mediump float; - * out vec4 outColor; + * out vec4 outColor; * void main(){ - * outColor = vec4(0.0, 1.0, 1.0, 1.0); + * outColor = vec4(0.0, 1.0, 1.0, 1.0); * } * `; - * + * * function setup(){ * createCanvas(100, 100, WEBGL); * * // Create and use the custom shader. * const myShader = createShader(vertSrc, fragSrc); * shader(myShader); - * + * * describe('A wobbly, cyan circle on a gray background.'); * } - * + * * function draw(){ * // Set the styles * background(125); * noStroke(); - * + * * // Draw the circle. * beginShape(); * for (let i = 0; i < 30; i++){ @@ -2326,7 +2333,7 @@ function vertex(p5, fn){ * // Apply some noise to the coordinates. * const xOff = 10 * noise(x + millis()/1000) - 5; * const yOff = 10 * noise(y + millis()/1000) - 5; - * + * * // Apply these noise values to the following vertex. * vertexProperty('aOffset', [xOff, yOff]); * vertex(x, y); @@ -2335,26 +2342,26 @@ function vertex(p5, fn){ * } * *
- * + * *
* * let myShader; * const cols = 10; * const rows = 10; * const cellSize = 6; - * + * * const vertSrc = `#version 300 es * precision mediump float; * uniform mat4 uProjectionMatrix; * uniform mat4 uModelViewMatrix; - * + * * in vec3 aPosition; * in vec3 aNormal; * in vec3 aVertexColor; * in float aDistance; - * + * * out vec3 vVertexColor; - * + * * void main(){ * vec4 positionVec4 = vec4(aPosition, 1.0); * positionVec4.xyz += aDistance * aNormal * 2.0;; @@ -2362,49 +2369,49 @@ function vertex(p5, fn){ * gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4; * } * `; - * + * * const fragSrc = `#version 300 es * precision mediump float; - * + * * in vec3 vVertexColor; * out vec4 outColor; - * + * * void main(){ * outColor = vec4(vVertexColor, 1.0); * } * `; - * + * * function setup(){ * createCanvas(100, 100, WEBGL); - * + * * // Create and apply the custom shader. * myShader = createShader(vertSrc, fragSrc); * shader(myShader); * noStroke(); * describe('A blue grid, which moves away from the mouse position, on a gray background.'); * } - * + * * function draw(){ * background(200); - * + * * // Draw the grid in the middle of the screen. * translate(-cols*cellSize/2, -rows*cellSize/2); * beginShape(QUADS); * for (let i = 0; i < cols; i++) { * for (let j = 0; j < rows; j++) { - * + * * // Calculate the cell position. * let x = i * cellSize; * let y = j * cellSize; - * + * * fill(j/rows*255, j/cols*255, 255); - * + * * // Calculate the distance from the corner of each cell to the mouse. * let distance = dist(x1,y1, mouseX, mouseY); - * + * * // Send the distance to the shader. * vertexProperty('aDistance', min(distance, 100)); - * + * * vertex(x, y); * vertex(x + cellSize, y); * vertex(x + cellSize, y + cellSize); diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 9b71bc569a..5f9f56a2a9 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -2369,30 +2369,31 @@ function primitives3D(p5, fn){ let x2 = c; let y2 = d; + // TODO shapes refactor this.beginShape(); if (tr !== 0) { - this.vertex(x2 - tr, y1); + this.legacyVertex(x2 - tr, y1); this.quadraticVertex(x2, y1, x2, y1 + tr); } else { - this.vertex(x2, y1); + this.legacyVertex(x2, y1); } if (br !== 0) { - this.vertex(x2, y2 - br); + this.legacyVertex(x2, y2 - br); this.quadraticVertex(x2, y2, x2 - br, y2); } else { - this.vertex(x2, y2); + this.legacyVertex(x2, y2); } if (bl !== 0) { - this.vertex(x1 + bl, y2); + this.legacyVertex(x1 + bl, y2); this.quadraticVertex(x1, y2, x1, y2 - bl); } else { - this.vertex(x1, y2); + this.legacyVertex(x1, y2); } if (tl !== 0) { - this.vertex(x1, y1 + tl); + this.legacyVertex(x1, y1 + tl); this.quadraticVertex(x1, y1, x1 + tl, y1); } else { - this.vertex(x1, y1); + this.legacyVertex(x1, y1); } this.shapeBuilder.geometry.uvs.length = 0; @@ -2402,7 +2403,7 @@ function primitives3D(p5, fn){ this.shapeBuilder.geometry.uvs.push(u, v); } - this.endShape(constants.CLOSE); + this.legacyEndShape(constants.CLOSE); } return this; }; @@ -2496,19 +2497,20 @@ function primitives3D(p5, fn){ z1 = z2 = z3 = z4 = 0; } const bezierDetail = this._pInst._bezierDetail || 20; //value of Bezier detail + // TODO shapes refactor this.beginShape(); for (let i = 0; i <= bezierDetail; i++) { const c1 = Math.pow(1 - i / bezierDetail, 3); const c2 = 3 * (i / bezierDetail) * Math.pow(1 - i / bezierDetail, 2); const c3 = 3 * Math.pow(i / bezierDetail, 2) * (1 - i / bezierDetail); const c4 = Math.pow(i / bezierDetail, 3); - this.vertex( + this.legacyVertex( x1 * c1 + x2 * c2 + x3 * c3 + x4 * c4, y1 * c1 + y2 * c2 + y3 * c3 + y4 * c4, z1 * c1 + z2 * c2 + z3 * c3 + z4 * c4 ); } - this.endShape(); + this.legacyEndShape(); return this; }; @@ -2537,6 +2539,7 @@ function primitives3D(p5, fn){ z1 = z2 = z3 = z4 = 0; } const curveDetail = this._pInst._curveDetail; + // TODO shapes refactor this.beginShape(); for (let i = 0; i <= curveDetail; i++) { const c1 = Math.pow(i / curveDetail, 3) * 0.5; @@ -2558,9 +2561,9 @@ function primitives3D(p5, fn){ c2 * (2 * z1 - 5 * z2 + 4 * z3 - z4) + c3 * (-z1 + z3) + c4 * (2 * z2); - this.vertex(vx, vy, vz); + this.legacyVertex(vx, vy, vz); } - this.endShape(); + this.legacyEndShape(); return this; }; @@ -2595,15 +2598,16 @@ function primitives3D(p5, fn){ */ RendererGL.prototype.line = function(...args) { if (args.length === 6) { + // TODO shapes refactor this.beginShape(constants.LINES); - this.vertex(args[0], args[1], args[2]); - this.vertex(args[3], args[4], args[5]); - this.endShape(); + this.legacyVertex(args[0], args[1], args[2]); + this.legacyVertex(args[3], args[4], args[5]); + this.legacyEndShape(); } else if (args.length === 4) { this.beginShape(constants.LINES); - this.vertex(args[0], args[1], 0); - this.vertex(args[2], args[3], 0); - this.endShape(); + this.legacyVertex(args[0], args[1], 0); + this.legacyVertex(args[2], args[3], 0); + this.legacyEndShape(); } return this; }; @@ -2739,7 +2743,7 @@ function primitives3D(p5, fn){ } prop.setCurrentData(newValues); } - this.vertex(_x, _y); + this.legacyVertex(_x, _y); } // so that we leave currentColor with the last value the user set it to this.states.curFillColor = fillColors[3]; @@ -2816,7 +2820,7 @@ function primitives3D(p5, fn){ } prop.setCurrentData(newValues); } - this.vertex(_x, _y, _z); + this.legacyVertex(_x, _y, _z); } // so that we leave currentColor with the last value the user set it to this.states.curFillColor = fillColors[3]; @@ -2955,7 +2959,7 @@ function primitives3D(p5, fn){ } prop.setCurrentData(newValues); } - this.vertex(_x, _y); + this.legacyVertex(_x, _y); } // so that we leave currentColor with the last value the user set it to @@ -3026,7 +3030,7 @@ function primitives3D(p5, fn){ } prop.setCurrentData(newValues); } - this.vertex(_x, _y, _z); + this.legacyVertex(_x, _y, _z); } // so that we leave currentColor with the last value the user set it to @@ -3106,7 +3110,7 @@ function primitives3D(p5, fn){ w_y[1] * this._lookUpTableBezier[i][1] + w_y[2] * this._lookUpTableBezier[i][2] + w_y[3] * this._lookUpTableBezier[i][3]; - this.vertex(_x, _y); + this.legacyVertex(_x, _y); } for (i = 0; i < argLength; i++) { this.shapeBuilder._curveVertex.shift(); @@ -3152,7 +3156,7 @@ function primitives3D(p5, fn){ w_z[1] * this._lookUpTableBezier[i][1] + w_z[2] * this._lookUpTableBezier[i][2] + w_z[3] * this._lookUpTableBezier[i][3]; - this.vertex(_x, _y, _z); + this.legacyVertex(_x, _y, _z); } for (i = 0; i < argLength; i++) { this.shapeBuilder._curveVertex.shift(); @@ -3205,12 +3209,13 @@ function primitives3D(p5, fn){ } this._drawingImage = true; + // TODO shape refactor this.beginShape(); - this.vertex(dx, dy, 0, u0, v0); - this.vertex(dx + dWidth, dy, 0, u1, v0); - this.vertex(dx + dWidth, dy + dHeight, 0, u1, v1); - this.vertex(dx, dy + dHeight, 0, u0, v1); - this.endShape(constants.CLOSE); + this.legacyVertex(dx, dy, 0, u0, v0); + this.legacyVertex(dx + dWidth, dy, 0, u1, v0); + this.legacyVertex(dx + dWidth, dy + dHeight, 0, u1, v1); + this.legacyVertex(dx, dy + dHeight, 0, u0, v1); + this.legacyEndShape(constants.CLOSE); this._drawingImage = false; this.pop(); diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 5bce7d3720..2cdaf0a181 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -442,11 +442,16 @@ class RendererGL extends Renderer { ////////////////////////////////////////////// beginShape(...args) { - this.currentShape.reset(); + super.beginShape(...args); + // TODO remove when shape refactor is complete this.shapeBuilder.beginShape(...args); } - endShape( + drawShape(shape) { + // TODO + } + + legacyEndShape( mode, isCurve, isBezier, @@ -481,7 +486,7 @@ class RendererGL extends Renderer { this.shapeBuilder.beginContour(...args); } - vertex(...args) { + legacyVertex(...args) { this.shapeBuilder.vertex(...args); } diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 0eb5891170..ab2136f6de 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -1465,33 +1465,33 @@ suite('p5.RendererGL', function() { test('QUADS mode converts into triangles', function() { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.textureMode(myp5.NORMAL); - renderer.beginShape(myp5.QUADS); - renderer.fill(255, 0, 0); - renderer.normal(0, 1, 2); - renderer.vertex(0, 0, 0, 0, 0); - renderer.fill(0, 255, 0); - renderer.normal(3, 4, 5); - renderer.vertex(0, 1, 1, 0, 1); - renderer.fill(0, 0, 255); - renderer.normal(6, 7, 8); - renderer.vertex(1, 0, 2, 1, 0); - renderer.fill(255, 0, 255); - renderer.normal(9, 10, 11); - renderer.vertex(1, 1, 3, 1, 1); - - renderer.fill(255, 0, 0); - renderer.normal(12, 13, 14); - renderer.vertex(2, 0, 4, 0, 0); - renderer.fill(0, 255, 0); - renderer.normal(15, 16, 17); - renderer.vertex(2, 1, 5, 0, 1); - renderer.fill(0, 0, 255); - renderer.normal(18, 19, 20); - renderer.vertex(3, 0, 6, 1, 0); - renderer.fill(255, 0, 255); - renderer.normal(21, 22, 23); - renderer.vertex(3, 1, 7, 1, 1); - renderer.endShape(); + myp5.beginShape(myp5.QUADS); + myp5.fill(255, 0, 0); + myp5.normal(0, 1, 2); + myp5.vertex(0, 0, 0, 0, 0); + myp5.fill(0, 255, 0); + myp5.normal(3, 4, 5); + myp5.vertex(0, 1, 1, 0, 1); + myp5.fill(0, 0, 255); + myp5.normal(6, 7, 8); + myp5.vertex(1, 0, 2, 1, 0); + myp5.fill(255, 0, 255); + myp5.normal(9, 10, 11); + myp5.vertex(1, 1, 3, 1, 1); + + myp5.fill(255, 0, 0); + myp5.normal(12, 13, 14); + myp5.vertex(2, 0, 4, 0, 0); + myp5.fill(0, 255, 0); + myp5.normal(15, 16, 17); + myp5.vertex(2, 1, 5, 0, 1); + myp5.fill(0, 0, 255); + myp5.normal(18, 19, 20); + myp5.vertex(3, 0, 6, 1, 0); + myp5.fill(255, 0, 255); + myp5.normal(21, 22, 23); + myp5.vertex(3, 1, 7, 1, 1); + myp5.endShape(); const expectedVerts = [ [0, 0, 0], @@ -1591,33 +1591,33 @@ suite('p5.RendererGL', function() { test('QUADS mode makes edges for quad outlines', function() { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); - renderer.beginShape(myp5.QUADS); - renderer.vertex(0, 0); - renderer.vertex(0, 1); - renderer.vertex(1, 0); - renderer.vertex(1, 1); - - renderer.vertex(2, 0); - renderer.vertex(2, 1); - renderer.vertex(3, 0); - renderer.vertex(3, 1); - renderer.endShape(); + myp5.beginShape(myp5.QUADS); + myp5.vertex(0, 0); + myp5.vertex(0, 1); + myp5.vertex(1, 0); + myp5.vertex(1, 1); + + myp5.vertex(2, 0); + myp5.vertex(2, 1); + myp5.vertex(3, 0); + myp5.vertex(3, 1); + myp5.endShape(); assert.equal(renderer.shapeBuilder.geometry.edges.length, 8); }); test('QUAD_STRIP mode makes edges for strip outlines', function() { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); - renderer.beginShape(myp5.QUAD_STRIP); - renderer.vertex(0, 0); - renderer.vertex(0, 1); - renderer.vertex(1, 0); - renderer.vertex(1, 1); - renderer.vertex(2, 0); - renderer.vertex(2, 1); - renderer.vertex(3, 0); - renderer.vertex(3, 1); - renderer.endShape(); + myp5.beginShape(myp5.QUAD_STRIP); + myp5.vertex(0, 0); + myp5.vertex(0, 1); + myp5.vertex(1, 0); + myp5.vertex(1, 1); + myp5.vertex(2, 0); + myp5.vertex(2, 1); + myp5.vertex(3, 0); + myp5.vertex(3, 1); + myp5.endShape(); // Two full quads (2 * 4) plus two edges connecting them assert.equal(renderer.shapeBuilder.geometry.edges.length, 10); @@ -1630,13 +1630,13 @@ suite('p5.RendererGL', function() { // x--x--x // \ | / // x - renderer.beginShape(myp5.TRIANGLE_FAN); - renderer.vertex(0, 0); - renderer.vertex(0, -5); - renderer.vertex(5, 0); - renderer.vertex(0, 5); - renderer.vertex(-5, 0); - renderer.endShape(); + myp5.beginShape(myp5.TRIANGLE_FAN); + myp5.vertex(0, 0); + myp5.vertex(0, -5); + myp5.vertex(5, 0); + myp5.vertex(0, 5); + myp5.vertex(-5, 0); + myp5.endShape(); assert.equal(renderer.shapeBuilder.geometry.edges.length, 7); }); @@ -1645,24 +1645,24 @@ suite('p5.RendererGL', function() { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.textureMode(myp5.NORMAL); - renderer.beginShape(myp5.TESS); - renderer.fill(255, 255, 255); - renderer.normal(-1, -1, 1); - renderer.vertexProperty('aCustom', [1, 1, 1]) - renderer.vertex(-10, -10, 0, 0); - renderer.fill(255, 0, 0); - renderer.normal(1, -1, 1); - renderer.vertexProperty('aCustom', [1, 0, 0]) - renderer.vertex(10, -10, 1, 0); - renderer.fill(0, 255, 0); - renderer.normal(1, 1, 1); - renderer.vertexProperty('aCustom', [0, 1, 0]) - renderer.vertex(10, 10, 1, 1); - renderer.fill(0, 0, 255); - renderer.normal(-1, 1, 1); - renderer.vertexProperty('aCustom', [0, 0, 1]) - renderer.vertex(-10, 10, 0, 1); - renderer.endShape(myp5.CLOSE); + myp5.beginShape(myp5.TESS); + myp5.fill(255, 255, 255); + myp5.normal(-1, -1, 1); + myp5.vertexProperty('aCustom', [1, 1, 1]) + myp5.vertex(-10, -10, 0, 0); + myp5.fill(255, 0, 0); + myp5.normal(1, -1, 1); + myp5.vertexProperty('aCustom', [1, 0, 0]) + myp5.vertex(10, -10, 1, 0); + myp5.fill(0, 255, 0); + myp5.normal(1, 1, 1); + myp5.vertexProperty('aCustom', [0, 1, 0]) + myp5.vertex(10, 10, 1, 1); + myp5.fill(0, 0, 255); + myp5.normal(-1, 1, 1); + myp5.vertexProperty('aCustom', [0, 0, 1]) + myp5.vertex(-10, 10, 0, 1); + myp5.endShape(myp5.CLOSE); assert.equal(renderer.shapeBuilder.geometry.vertices.length, 6); assert.deepEqual( @@ -1748,17 +1748,17 @@ suite('p5.RendererGL', function() { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.textureMode(myp5.NORMAL); - renderer.beginShape(myp5.TESS); + myp5.beginShape(myp5.TESS); myp5.noFill(); - renderer.stroke(255, 255, 255); - renderer.vertex(-10, -10, 0, 0); - renderer.stroke(255, 0, 0); - renderer.vertex(10, -10, 1, 0); - renderer.stroke(0, 255, 0); - renderer.vertex(10, 10, 1, 1); - renderer.stroke(0, 0, 255); - renderer.vertex(-10, 10, 0, 1); - renderer.endShape(myp5.CLOSE); + myp5.stroke(255, 255, 255); + myp5.vertex(-10, -10, 0, 0); + myp5.stroke(255, 0, 0); + myp5.vertex(10, -10, 1, 0); + myp5.stroke(0, 255, 0); + myp5.vertex(10, 10, 1, 1); + myp5.stroke(0, 0, 255); + myp5.vertex(-10, 10, 0, 1); + myp5.endShape(myp5.CLOSE); // Vertex colors are not run through tessy assert.deepEqual(renderer.shapeBuilder.geometry.vertexStrokeColors, [ @@ -1775,13 +1775,13 @@ suite('p5.RendererGL', function() { myp5.textureMode(myp5.IMAGE); myp5.texture(texture); - renderer.beginShape(myp5.TESS); + myp5.beginShape(myp5.TESS); myp5.noFill(); - renderer.vertex(-10, -10, 0, 0); - renderer.vertex(10, -10, 25, 0); - renderer.vertex(10, 10, 25, 25); - renderer.vertex(-10, 10, 0, 25); - renderer.endShape(myp5.CLOSE); + myp5.vertex(-10, -10, 0, 0); + myp5.vertex(10, -10, 25, 0); + myp5.vertex(10, 10, 25, 25); + myp5.vertex(-10, 10, 0, 25); + myp5.endShape(myp5.CLOSE); // UVs are correctly translated through tessy assert.deepEqual(renderer.shapeBuilder.geometry.uvs, [ @@ -1806,20 +1806,20 @@ suite('p5.RendererGL', function() { // // Tessy will add a vertex in the middle myp5.textureMode(myp5.NORMAL); - renderer.beginShape(myp5.TESS); - renderer.fill(255, 255, 255); - renderer.normal(-1, -1, 1); - renderer.vertex(-10, -10, 0, 0); - renderer.fill(0, 255, 0); - renderer.normal(1, 1, 1); - renderer.vertex(10, 10, 1, 1); - renderer.fill(255, 0, 0); - renderer.normal(1, -1, 1); - renderer.vertex(10, -10, 1, 0); - renderer.fill(0, 0, 255); - renderer.normal(-1, 1, 1); - renderer.vertex(-10, 10, 0, 1); - renderer.endShape(myp5.CLOSE); + myp5.beginShape(myp5.TESS); + myp5.fill(255, 255, 255); + myp5.normal(-1, -1, 1); + myp5.vertex(-10, -10, 0, 0); + myp5.fill(0, 255, 0); + myp5.normal(1, 1, 1); + myp5.vertex(10, 10, 1, 1); + myp5.fill(255, 0, 0); + myp5.normal(1, -1, 1); + myp5.vertex(10, -10, 1, 0); + myp5.fill(0, 0, 255); + myp5.normal(-1, 1, 1); + myp5.vertex(-10, 10, 0, 1); + myp5.endShape(myp5.CLOSE); assert.equal(renderer.shapeBuilder.geometry.vertices.length, 6); assert.deepEqual( @@ -1896,12 +1896,12 @@ suite('p5.RendererGL', function() { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.textureMode(myp5.NORMAL); - renderer.beginShape(myp5.TESS); - renderer.vertex(-10, 0, -10); - renderer.vertex(10, 0, -10); - renderer.vertex(10, 0, 10); - renderer.vertex(-10, 0, 10); - renderer.endShape(myp5.CLOSE); + myp5.beginShape(myp5.TESS); + myp5.vertex(-10, 0, -10); + myp5.vertex(10, 0, -10); + myp5.vertex(10, 0, 10); + myp5.vertex(-10, 0, 10); + myp5.endShape(myp5.CLOSE); assert.equal(renderer.shapeBuilder.geometry.vertices.length, 6); assert.deepEqual( @@ -1939,13 +1939,13 @@ suite('p5.RendererGL', function() { // far right color: (42, 36, 240) // expected middle color: (142, 136, 140) - renderer.strokeWeight(4); - renderer.beginShape(); - renderer.stroke(242, 236, 40); - renderer.vertex(-256, 0); - renderer.stroke(42, 36, 240); - renderer.vertex(256, 0); - renderer.endShape(); + myp5.strokeWeight(4); + myp5.beginShape(); + myp5.stroke(242, 236, 40); + myp5.vertex(-256, 0); + myp5.stroke(42, 36, 240); + myp5.vertex(256, 0); + myp5.endShape(); assert.deepEqual(myp5.get(0, 2), [242, 236, 40, 255]); assert.deepEqual(myp5.get(256, 2), [142, 136, 140, 255]); @@ -1959,12 +1959,12 @@ suite('p5.RendererGL', function() { // end color: (255, 0, 0) // Intermediate values are expected to be approximately half the value. - renderer.beginShape(); - renderer.fill(255); - renderer.vertex(-128, -128); - renderer.fill(255, 0, 0); - renderer.bezierVertex(128, -128, 128, 128, -128, 128); - renderer.endShape(); + myp5.beginShape(); + myp5.fill(255); + myp5.vertex(-128, -128); + myp5.fill(255, 0, 0); + myp5.bezierVertex(128, -128, 128, 128, -128, 128); + myp5.endShape(); assert.deepEqual(myp5.get(128, 127), [255, 129, 129, 255]); }); @@ -1976,14 +1976,14 @@ suite('p5.RendererGL', function() { // end color: (255, 0, 0) // Intermediate values are expected to be approximately half the value. - renderer.strokeWeight(5); - renderer.beginShape(); + myp5.strokeWeight(5); + myp5.beginShape(); myp5.noFill(); - renderer.stroke(255); - renderer.vertex(-128, -128); - renderer.stroke(255, 0, 0); - renderer.bezierVertex(128, -128, 128, 128, -128, 128); - renderer.endShape(); + myp5.stroke(255); + myp5.vertex(-128, -128); + myp5.stroke(255, 0, 0); + myp5.bezierVertex(128, -128, 128, 128, -128, 128); + myp5.endShape(); assert.arrayApproximately(myp5.get(190, 127), [255, 128, 128, 255], 10); }); @@ -1995,12 +1995,12 @@ suite('p5.RendererGL', function() { // end color: (255, 0, 0) // Intermediate values are expected to be approximately half the value. - renderer.beginShape(); - renderer.fill(255); - renderer.vertex(-128, -128); - renderer.fill(255, 0, 0); - renderer.quadraticVertex(256, 0, -128, 128); - renderer.endShape(); + myp5.beginShape(); + myp5.fill(255); + myp5.vertex(-128, -128); + myp5.fill(255, 0, 0); + myp5.quadraticVertex(256, 0, -128, 128); + myp5.endShape(); assert.arrayApproximately(myp5.get(128, 127), [255, 128, 128, 255], 10); }); @@ -2012,14 +2012,14 @@ suite('p5.RendererGL', function() { // end color: (255, 0, 0) // Intermediate values are expected to be approximately half the value. - renderer.strokeWeight(5); - renderer.beginShape(); + myp5.strokeWeight(5); + myp5.beginShape(); myp5.noFill(); - renderer.stroke(255); - renderer.vertex(-128, -128); - renderer.stroke(255, 0, 0); - renderer.quadraticVertex(256, 0, -128, 128); - renderer.endShape(); + myp5.stroke(255); + myp5.vertex(-128, -128); + myp5.stroke(255, 0, 0); + myp5.quadraticVertex(256, 0, -128, 128); + myp5.endShape(); assert.deepEqual(myp5.get(190, 127), [255, 128, 128, 255]); }); From bc252e372ed2b7b5171d01b2409fc8e90bdfc932 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sat, 23 Nov 2024 01:43:20 -0700 Subject: [PATCH 027/111] Implement Vertex constructor --- src/shape/custom_shapes.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index e63ecb664d..de0106ab56 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -67,8 +67,18 @@ class ShapePrimitive { } class Vertex { - constructor() { - + constructor(properties) { + for (const [key, value] of Object.entries(properties)) { + this[key] = value; + } + } + + get array() { + // convert to 1D array + // call `toArray()` if value is an object with a toArray() method + // handle primitive values separately + // maybe handle object literals too, with Object.values()? + // probably don’t need anything else for now? } // TODO: make sure name of array conversion method is From 0c61aaf9c2ad366a789b30f659443ecbd87305b7 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sat, 23 Nov 2024 02:32:15 -0700 Subject: [PATCH 028/111] Implement ShapePrimitive, add some doc tags --- src/shape/custom_shapes.js | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index de0106ab56..3548d37a56 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -59,10 +59,27 @@ class Contour { } } -// abstract class class ShapePrimitive { constructor() { + if (this.constructor === ShapePrimitive) { + throw new Error("ShapePrimitive is an abstract class: it cannot be instantiated."); + } + } + + get vertexCount() { + throw new Error("Getter vertexCount must be implemented."); + } + + get vertexCapacity() { + throw new Error("Getter vertexCapacity must be implemented."); + } + + accept(visitor) { + throw new Error("Method accept() must be implemented."); + } + addToShape(shape) { + throw new Error("Method addToShape() must be implemented."); } } @@ -306,7 +323,7 @@ function customShapes(p5, fn) { * along the segment. * * @class p5.ShapePrimitive - * @param {...p5.Vertex} vertices the vertices to include in the primitive. + * @abstract */ p5.ShapePrimitive = ShapePrimitive; @@ -353,6 +370,9 @@ function customShapes(p5, fn) { /** * @private * A class responsible for... + * + * @class p5.Anchor + * @param {p5.Vertex} vertex the vertex to include in the anchor. */ p5.Anchor = Anchor; @@ -367,6 +387,9 @@ function customShapes(p5, fn) { /** * @private * A class responsible for... + * + * @class p5.LineSegment + * @param {p5.Vertex} vertex the vertex to include in the anchor. */ p5.LineSegment = LineSegment; @@ -397,6 +420,9 @@ function customShapes(p5, fn) { /** * @private * A class responsible for... + * + * @class p5.Line + * @param {...p5.Vertex} vertices the vertices to include in the line. */ p5.Line = Line; From 2326ff512a3fde1d5e46b441afb66fae0ae579f6 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sat, 23 Nov 2024 02:58:31 -0700 Subject: [PATCH 029/111] Add PATH to constants.js --- src/core/constants.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/core/constants.js b/src/core/constants.js index bb120fa712..b37ecaf393 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -696,6 +696,12 @@ export const BOTTOM = 'bottom'; * @final */ export const BASELINE = 'alphabetic'; +/** + * @typedef {unique symbol} PATH + * @property {PATH} PATH + * @final + */ +export const PATH = Symbol('path'); /** * @typedef {0x0000} POINTS * @property {POINTS} POINTS From 75030a5b4c4eb1cbf7d354d8d1e254556b349465 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sat, 23 Nov 2024 03:24:36 -0700 Subject: [PATCH 030/111] Implement Contour --- src/shape/custom_shapes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 3548d37a56..407d65a381 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -54,8 +54,9 @@ class Shape { } class Contour { - constructor() { - + constructor(kind = constants.PATH) { + this.kind = kind; + this.primitives = []; } } From 70a6fa6076b556c3bcf9969ad0b352299ff9d4e3 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sat, 23 Nov 2024 03:53:30 -0700 Subject: [PATCH 031/111] Add EMPTY_PATH to constants.js --- src/core/constants.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/core/constants.js b/src/core/constants.js index b37ecaf393..962375e63c 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -702,6 +702,12 @@ export const BASELINE = 'alphabetic'; * @final */ export const PATH = Symbol('path'); +/** + * @typedef {unique symbol} EMPTY_PATH + * @property {EMPTY_PATH} EMPTY_PATH + * @final + */ +export const EMPTY_PATH = Symbol('empty_path'); /** * @typedef {0x0000} POINTS * @property {POINTS} POINTS From d7aec76268fb207796bbc72c77edb9c37f4b189d Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sat, 23 Nov 2024 04:19:43 -0700 Subject: [PATCH 032/111] Add kind getter to Contour to handle EMPTY_PATH --- src/shape/custom_shapes.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 407d65a381..66d3e6ae95 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -54,10 +54,19 @@ class Shape { } class Contour { + #kind; + primitives; + constructor(kind = constants.PATH) { - this.kind = kind; + this.#kind = kind; this.primitives = []; } + + get kind() { + const isEmpty = this.primitives.length === 0; + const isPath = this.#kind === constants.PATH; + return isEmpty && isPath ? constants.EMPTY_PATH : this.#kind; + } } class ShapePrimitive { From 27f0b2642fe9e5dc71fb7b35c0e08b74c751d6f0 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sat, 23 Nov 2024 04:30:20 -0700 Subject: [PATCH 033/111] Add tag to Anchor docs --- src/shape/custom_shapes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 66d3e6ae95..ff4ec4b9cd 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -382,6 +382,7 @@ function customShapes(p5, fn) { * A class responsible for... * * @class p5.Anchor + * @extends p5.ShapePrimitive * @param {p5.Vertex} vertex the vertex to include in the anchor. */ From 4eaa56fbc1d890bbc5c4fd1c62370c30267e2505 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sat, 23 Nov 2024 05:42:37 -0700 Subject: [PATCH 034/111] Implement Anchor, add vertices to ShapePrimitive --- src/shape/custom_shapes.js | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index ff4ec4b9cd..5e12098eb9 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -70,10 +70,13 @@ class Contour { } class ShapePrimitive { - constructor() { + vertices; + + constructor(...vertices) { if (this.constructor === ShapePrimitive) { throw new Error("ShapePrimitive is an abstract class: it cannot be instantiated."); } + this.vertices = vertices; } get vertexCount() { @@ -115,9 +118,34 @@ class Vertex { // ---- PATH PRIMITIVES ---- -class Anchor { - constructor() { +class Anchor extends ShapePrimitive { + #vertexCapacity; + + constructor(...vertices) { + super(...vertices); + this.#vertexCapacity = 1; + } + get vertexCount() { + return this.vertices.length; + } + + get vertexCapacity() { + return this.#vertexCapacity; + } + + accept(visitor) { + visitor.visitAnchor(this); + } + + addToShape(shape) { + let lastContour = shape.contours.at(-1); + if (lastContour.kind === constants.EMPTY_PATH) { + lastContour.primitives.push(this); + } + else { + throw new Error("Anchor can only be added to an empty path contour."); + } } } From 13f21d9e1ff9059210c07223fcb579a6f34bb1b6 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sun, 24 Nov 2024 00:21:38 -0700 Subject: [PATCH 035/111] Implement Segment, add tags to docs --- src/shape/custom_shapes.js | 490 +++++++++++++++++++------------------ 1 file changed, 253 insertions(+), 237 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 5e12098eb9..2ce04ccd1c 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -6,254 +6,266 @@ * @requires constants */ -// uncomment the following once you need it (otherwise VS Code complains): -import * as constants from '../core/constants'; +// REMINDER: remove .js extension (currently using it to run file locally) +import * as constants from '../core/constants.js'; // ---- GENERAL CLASSES ---- class Shape { - vertexProperties; + vertexProperties; - constructor(vertexProperties) { - this.vertexProperties = vertexProperties; + constructor(vertexProperties) { + this.vertexProperties = vertexProperties; - for (const key in this.vertexProperties) { - if (key !== 'position' && key !== 'textureCoordinates') { - this[key] = function(value) { - this.vertexProperties[key] = value; - } - } + for (const key in this.vertexProperties) { + if (key !== 'position' && key !== 'textureCoordinates') { + this[key] = function(value) { + this.vertexProperties[key] = value; + }; } } - - reset() { - // TODO: remove existing vertices - } - - vertex(position, textureCoordinates) { - // Add the current position and texture coordiantes to the existing state - let vertex = this.createVertex({ ...this.vertexProperties, position, textureCoordinates }); - // TODO - // primitiveShapeCreator = primitiveShapeCreators.get(['vertex', this.kind]); - // primitiveShape = primitiveShapeCreator(vertex); - // primitiveShape.addToShape(this); - } - - createVertex(properties) { - // TODO - // return vertex; - } - - beginShape(shapeKind) { - // TODO - } - - endShape(closeMode = constants.OPEN) { - // TODO - } + } + + reset() { + // TODO: remove existing vertices + } + + vertex(position, textureCoordinates) { + // Add the current position and texture coordiantes to the existing state + let vertex = this.createVertex({ ...this.vertexProperties, position, textureCoordinates }); + // TODO + // primitiveShapeCreator = primitiveShapeCreators.get(['vertex', this.kind]); + // primitiveShape = primitiveShapeCreator(vertex); + // primitiveShape.addToShape(this); + } + + createVertex(properties) { + // TODO + // return vertex; + } + + beginShape(shapeKind) { + // TODO + } + + endShape(closeMode = constants.OPEN) { + // TODO + } } class Contour { - #kind; - primitives; - - constructor(kind = constants.PATH) { - this.#kind = kind; - this.primitives = []; - } - - get kind() { - const isEmpty = this.primitives.length === 0; - const isPath = this.#kind === constants.PATH; - return isEmpty && isPath ? constants.EMPTY_PATH : this.#kind; - } + #kind; + primitives; + + constructor(kind = constants.PATH) { + this.#kind = kind; + this.primitives = []; + } + + get kind() { + const isEmpty = this.primitives.length === 0; + const isPath = this.#kind === constants.PATH; + return isEmpty && isPath ? constants.EMPTY_PATH : this.#kind; + } } class ShapePrimitive { - vertices; + vertices; - constructor(...vertices) { - if (this.constructor === ShapePrimitive) { - throw new Error("ShapePrimitive is an abstract class: it cannot be instantiated."); - } - this.vertices = vertices; + constructor(...vertices) { + if (this.constructor === ShapePrimitive) { + throw new Error('ShapePrimitive is an abstract class: it cannot be instantiated.'); } + this.vertices = vertices; + } - get vertexCount() { - throw new Error("Getter vertexCount must be implemented."); - } + get vertexCount() { + throw new Error('Getter vertexCount must be implemented.'); + } - get vertexCapacity() { - throw new Error("Getter vertexCapacity must be implemented."); - } + get vertexCapacity() { + throw new Error('Getter vertexCapacity must be implemented.'); + } - accept(visitor) { - throw new Error("Method accept() must be implemented."); - } + accept(visitor) { + throw new Error('Method accept() must be implemented.'); + } - addToShape(shape) { - throw new Error("Method addToShape() must be implemented."); - } + addToShape(shape) { + throw new Error('Method addToShape() must be implemented.'); + } } class Vertex { - constructor(properties) { - for (const [key, value] of Object.entries(properties)) { - this[key] = value; - } - } - - get array() { - // convert to 1D array - // call `toArray()` if value is an object with a toArray() method - // handle primitive values separately - // maybe handle object literals too, with Object.values()? - // probably don’t need anything else for now? - } - - // TODO: make sure name of array conversion method is - // consistent with any modifications to the names of corresponding - // properties of p5.Vector and p5.Color + constructor(properties) { + for (const [key, value] of Object.entries(properties)) { + this[key] = value; + } + } + /* + get array() { + // convert to 1D array + // call `toArray()` if value is an object with a toArray() method + // handle primitive values separately + // maybe handle object literals too, with Object.values()? + // probably don’t need anything else for now? + } + */ + // TODO: make sure name of array conversion method is + // consistent with any modifications to the names of corresponding + // properties of p5.Vector and p5.Color } // ---- PATH PRIMITIVES ---- class Anchor extends ShapePrimitive { - #vertexCapacity; + #vertexCapacity; - constructor(...vertices) { - super(...vertices); - this.#vertexCapacity = 1; - } + constructor(...vertices) { + super(...vertices); + this.#vertexCapacity = 1; + } - get vertexCount() { - return this.vertices.length; - } + get vertexCount() { + return this.vertices.length; + } - get vertexCapacity() { - return this.#vertexCapacity; - } + get vertexCapacity() { + return this.#vertexCapacity; + } - accept(visitor) { - visitor.visitAnchor(this); - } + accept(visitor) { + visitor.visitAnchor(this); + } - addToShape(shape) { - let lastContour = shape.contours.at(-1); - if (lastContour.kind === constants.EMPTY_PATH) { - lastContour.primitives.push(this); - } - else { - throw new Error("Anchor can only be added to an empty path contour."); - } + addToShape(shape) { + let lastContour = shape.contours.at(-1); + if (lastContour.kind === constants.EMPTY_PATH) { + lastContour.primitives.push(this); + } + else { + throw new Error('Anchor can only be added to an empty path contour.'); } + } } // abstract class class Segment extends ShapePrimitive { - constructor() { + index = 0; + constructor(...vertices) { + super(...vertices); + if (this.constructor === Segment) { + throw new Error('Segment is an abstract class: it cannot be instantiated.'); } + } + + getStartVertex() { + throw new Error('Method getStartVertex() must be implemented.'); + } + + getEndVertex() { + throw new Error('Method getEndVertex() must be implemented.'); + } } class LineSegment extends Segment { - constructor() { - - } + constructor(...vertices) { + } } class BezierSegment extends Segment { - constructor() { + constructor() { - } + } } // consider type and end modes -- see #6766) // may want to use separate classes, but maybe not class SplineSegment extends Segment { - constructor() { + constructor() { - } + } } // ---- ISOLATED PRIMITIVES ---- class Point extends ShapePrimitive { - constructor() { + constructor() { - } + } } class Line extends ShapePrimitive { - constructor() { + constructor() { - } + } } class Triangle extends ShapePrimitive { - constructor() { + constructor() { - } + } } class Quad extends ShapePrimitive { - constructor() { + constructor() { - } + } } // ---- TESSELLATION PRIMITIVES ---- class TriangleFan extends ShapePrimitive { - constructor() { + constructor() { - } + } } class TriangleStrip extends ShapePrimitive { - constructor() { + constructor() { - } + } } class QuadStrip extends ShapePrimitive { - constructor() { + constructor() { - } + } } // ---- PRIMITIVE VISITORS ---- // abstract class class PrimitiveVisitor { - constructor() { + constructor() { - } + } } // using this instead of PrimitiveToContext2DConverter for now class PrimitiveToPath2DConverter extends PrimitiveVisitor { - constructor() { + constructor() { - } + } } class PrimitiveToVerticesConverter extends PrimitiveVisitor { - constructor() { + constructor() { - } + } } class PointAtLengthGetter extends PrimitiveVisitor { - constructor() { + constructor() { - } + } } function customShapes(p5, fn) { - // ---- GENERAL CLASSES ---- + // ---- GENERAL CLASSES ---- - /** + /** * @private * A class to describe a custom shape made with `beginShape()`/`endShape()`. * @@ -298,9 +310,9 @@ function customShapes(p5, fn) { * @param {Object} [vertexProperties={position: createVector(0, 0)}] vertex properties and their initial values. */ - p5.Shape = Shape; + p5.Shape = Shape; - /** + /** * @private * A class to describe a contour made with `beginContour()`/`endContour()`. * @@ -330,9 +342,9 @@ function customShapes(p5, fn) { * @class p5.Contour */ - p5.Contour = Contour; + p5.Contour = Contour; - /** + /** * @private * A base class to describe a shape primitive (a basic shape drawn with * `beginShape()`/`endShape()`). @@ -364,9 +376,9 @@ function customShapes(p5, fn) { * @abstract */ - p5.ShapePrimitive = ShapePrimitive; + p5.ShapePrimitive = ShapePrimitive; - /** + /** * @private * A class to describe a vertex (a point on a shape), in 2D or 3D. * @@ -401,258 +413,262 @@ function customShapes(p5, fn) { * @param {Object} [properties={position: createVector(0, 0)}] vertex properties. */ - p5.Vertex = Vertex; + p5.Vertex = Vertex; - // ---- PATH PRIMITIVES ---- + // ---- PATH PRIMITIVES ---- - /** + /** * @private * A class responsible for... - * + * * @class p5.Anchor * @extends p5.ShapePrimitive * @param {p5.Vertex} vertex the vertex to include in the anchor. */ - p5.Anchor = Anchor; + p5.Anchor = Anchor; - /** + /** * @private * A class responsible for... + * + * @class p5.Segment + * @extends p5.ShapePrimitive + * @param {...p5.Vertex} vertices the vertices to include in the segment. */ - p5.Segment = Segment; + p5.Segment = Segment; - /** + /** * @private * A class responsible for... - * + * * @class p5.LineSegment * @param {p5.Vertex} vertex the vertex to include in the anchor. */ - p5.LineSegment = LineSegment; + p5.LineSegment = LineSegment; - /** + /** * @private * A class responsible for... */ - p5.BezierSegment = BezierSegment; + p5.BezierSegment = BezierSegment; - /** + /** * @private * A class responsible for... */ - p5.SplineSegment = SplineSegment; + p5.SplineSegment = SplineSegment; - // ---- ISOLATED PRIMITIVES ---- + // ---- ISOLATED PRIMITIVES ---- - /** + /** * @private * A class responsible for... */ - p5.Point = Point; + p5.Point = Point; - /** + /** * @private * A class responsible for... - * + * * @class p5.Line * @param {...p5.Vertex} vertices the vertices to include in the line. */ - p5.Line = Line; + p5.Line = Line; - /** + /** * @private * A class responsible for... */ - p5.Triangle = Triangle; + p5.Triangle = Triangle; - /** + /** * @private * A class responsible for... */ - p5.Quad = Quad; + p5.Quad = Quad; - // ---- TESSELLATION PRIMITIVES ---- + // ---- TESSELLATION PRIMITIVES ---- - /** + /** * @private * A class responsible for... */ - p5.TriangleFan = TriangleFan; + p5.TriangleFan = TriangleFan; - /** + /** * @private * A class responsible for... */ - p5.TriangleStrip = TriangleStrip; + p5.TriangleStrip = TriangleStrip; - /** + /** * @private * A class responsible for... */ - p5.QuadStrip = QuadStrip; + p5.QuadStrip = QuadStrip; - // ---- PRIMITIVE VISITORS ---- + // ---- PRIMITIVE VISITORS ---- - /** + /** * @private * A class responsible for... */ - p5.PrimitiveVisitor = PrimitiveVisitor; + p5.PrimitiveVisitor = PrimitiveVisitor; - /** + /** * @private * A class responsible for... */ - p5.PrimitiveToPath2DConverter = PrimitiveToPath2DConverter; + p5.PrimitiveToPath2DConverter = PrimitiveToPath2DConverter; - /** + /** * @private * A class responsible for... */ - p5.PrimitiveToVerticesConverter = PrimitiveToVerticesConverter; + p5.PrimitiveToVerticesConverter = PrimitiveToVerticesConverter; - /** + /** * @private * A class responsible for... */ - p5.PointAtLengthGetter = PointAtLengthGetter; + p5.PointAtLengthGetter = PointAtLengthGetter; - // ---- FUNCTIONS ---- + // ---- FUNCTIONS ---- - // Note: Code is commented out for now, to avoid conflicts with the existing implementation. + // Note: Code is commented out for now, to avoid conflicts with the existing implementation. - /** + /** * Top-line description * * More details... */ - // fn.beginContour = function() { - // // example of how to call an existing p5 function: - // // this.background('yellow'); - // }; + // fn.beginContour = function() { + // // example of how to call an existing p5 function: + // // this.background('yellow'); + // }; - /** + /** * Top-line description * * More details... */ - // fn.beginShape = function() { + // fn.beginShape = function() { - // }; + // }; - /** + /** * Top-line description * * More details... */ - // fn.bezierVertex = function() { + // fn.bezierVertex = function() { - // }; + // }; - /** + /** * Top-line description * * More details... */ - // fn.curveVertex = function() { + // fn.curveVertex = function() { - // }; + // }; - /** + /** * Top-line description * * More details... */ - // fn.endContour = function() { + // fn.endContour = function() { - // }; + // }; - /** + /** * Top-line description * * More details... */ - // fn.endShape = function() { + // fn.endShape = function() { - // }; + // }; - /** + /** * Top-line description * * More details... */ - // fn.vertex = function() { + // fn.vertex = function() { - // }; + // }; - /** + /** * Top-line description * * More details... */ - // fn.normal = function() { + // fn.normal = function() { - // }; + // }; - /** + /** * Top-line description * * More details... */ - // fn.vertexProperty = function() { + // fn.vertexProperty = function() { - // }; + // }; } export default customShapes; export { - Shape, - Contour, - ShapePrimitive, - Vertex, - Anchor, - Segment, - LineSegment, - BezierSegment, - SplineSegment, - Point, - Line, - Triangle, - Quad, - TriangleFan, - TriangleStrip, - QuadStrip, - PrimitiveVisitor, - PrimitiveToPath2DConverter, - PrimitiveToVerticesConverter, - PointAtLengthGetter + Shape, + Contour, + ShapePrimitive, + Vertex, + Anchor, + Segment, + LineSegment, + BezierSegment, + SplineSegment, + Point, + Line, + Triangle, + Quad, + TriangleFan, + TriangleStrip, + QuadStrip, + PrimitiveVisitor, + PrimitiveToPath2DConverter, + PrimitiveToVerticesConverter, + PointAtLengthGetter }; if (typeof p5 !== 'undefined') { - customShapes(p5, p5.prototype); + customShapes(p5, p5.prototype); } From 787ca3c61269751a3373bd821e3a904837cb8df2 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sun, 24 Nov 2024 02:30:29 -0700 Subject: [PATCH 036/111] Implement addToShape on ShapePrimitive (base class) --- src/shape/custom_shapes.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 2ce04ccd1c..199b7cb5e2 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -92,7 +92,35 @@ class ShapePrimitive { } addToShape(shape) { - throw new Error('Method addToShape() must be implemented.'); + /* + TODO: Test this method once more primitives are implemented. + */ + let lastContour = shape.contours.at(-1); + + if (lastContour.length === 0) { + lastContour.push(this); + return; + } + + // last primitive in shape + let lastPrimitive = lastContour.primitives.at(-1); + let hasSameType = lastPrimitive instanceof this.constructor; + let spareCapacity = lastPrimitive.vertexCapacity - + lastPrimitive.vertexCount; + + // this primitive + let pushableVertices = this.vertices.splice(0, spareCapacity); + let remainingVertices = this.vertices; + + if (hasSameType && spareCapacity > 0) { + lastPrimitive.vertices.push(...pushableVertices); + if (remainingVertices.length > 0) { + lastContour.push(this); + } + } + else { + lastContour.push(this); + } } } From dbf64f6d256c475eb1b0dae4eead985ff3dec634 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sun, 24 Nov 2024 02:51:03 -0700 Subject: [PATCH 037/111] Changed initial value of index in Segment --- src/shape/custom_shapes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 199b7cb5e2..38b14568b3 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -179,7 +179,7 @@ class Anchor extends ShapePrimitive { // abstract class class Segment extends ShapePrimitive { - index = 0; + index = null; constructor(...vertices) { super(...vertices); @@ -198,7 +198,8 @@ class Segment extends ShapePrimitive { } class LineSegment extends Segment { - constructor(...vertices) { + constructor() { + } } From 65c2d32d6db8ad6afdd783ee84feffc886c9c0a4 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sun, 24 Nov 2024 03:39:49 -0700 Subject: [PATCH 038/111] Add note in Segment docs --- src/shape/custom_shapes.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 38b14568b3..b4ce235868 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -461,6 +461,9 @@ function customShapes(p5, fn) { * @private * A class responsible for... * + * Note: When a segment is added to a shape, it's attached to an anchor or another segment. + * Adding it to another shape may result in unexpected behavior. + * * @class p5.Segment * @extends p5.ShapePrimitive * @param {...p5.Vertex} vertices the vertices to include in the segment. From 20922c7bf2ef6503c08f5ebfa653123ad1116ddc Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sun, 24 Nov 2024 03:51:06 -0700 Subject: [PATCH 039/111] Move index out of base Segment class --- src/shape/custom_shapes.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index b4ce235868..390569fa5a 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -179,8 +179,6 @@ class Anchor extends ShapePrimitive { // abstract class class Segment extends ShapePrimitive { - index = null; - constructor(...vertices) { super(...vertices); if (this.constructor === Segment) { @@ -198,8 +196,11 @@ class Segment extends ShapePrimitive { } class LineSegment extends Segment { - constructor() { + #index = null; + #shape = null; + // TODO: finish implementation + constructor() { } } From 9468d2e9a34e2ba687aaaeae3a9e12c5c1931d79 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Sun, 24 Nov 2024 08:32:16 -0700 Subject: [PATCH 040/111] Add at() method on Shape, revise Segment & Anchor, implement LineSegment, plus small changes --- src/shape/custom_shapes.js | 96 ++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 390569fa5a..c0461516b2 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -13,6 +13,7 @@ import * as constants from '../core/constants.js'; class Shape { vertexProperties; + contours = []; constructor(vertexProperties) { this.vertexProperties = vertexProperties; @@ -26,6 +27,24 @@ class Shape { } } + // TODO: TEST, MAYBE REFACTOR + at(contoursIndex, primitivesIndex, verticesIndex) { + let contour; + let primitive; + + contour = this.contours.at(contoursIndex); + + switch(arguments.length) { + case 1: + return contour; + case 2: + return contour.primitives.at(primitivesIndex); + case 3: + primitive = contour.primitives.at(primitivesIndex); + return primitive.vertices.at(verticesIndex); + } + } + reset() { // TODO: remove existing vertices } @@ -76,11 +95,16 @@ class ShapePrimitive { if (this.constructor === ShapePrimitive) { throw new Error('ShapePrimitive is an abstract class: it cannot be instantiated.'); } - this.vertices = vertices; + if (vertices.length > 0) { + this.vertices = vertices; + } + else { + throw new Error('At least one vertex must be passed to the constructor.'); + } } get vertexCount() { - throw new Error('Getter vertexCount must be implemented.'); + return this.vertices.length; } get vertexCapacity() { @@ -93,9 +117,11 @@ class ShapePrimitive { addToShape(shape) { /* - TODO: Test this method once more primitives are implemented. + TODO: + Test this method once more primitives are implemented. + Test segments separately (Segment adds an extra step to this method). */ - let lastContour = shape.contours.at(-1); + let lastContour = shape.at(-1); if (lastContour.length === 0) { lastContour.push(this); @@ -103,7 +129,7 @@ class ShapePrimitive { } // last primitive in shape - let lastPrimitive = lastContour.primitives.at(-1); + let lastPrimitive = shape.at(-1, -1); let hasSameType = lastPrimitive instanceof this.constructor; let spareCapacity = lastPrimitive.vertexCapacity - lastPrimitive.vertexCount; @@ -147,15 +173,10 @@ class Vertex { // ---- PATH PRIMITIVES ---- class Anchor extends ShapePrimitive { - #vertexCapacity; + #vertexCapacity = 1; constructor(...vertices) { super(...vertices); - this.#vertexCapacity = 1; - } - - get vertexCount() { - return this.vertices.length; } get vertexCapacity() { @@ -166,19 +187,17 @@ class Anchor extends ShapePrimitive { visitor.visitAnchor(this); } - addToShape(shape) { - let lastContour = shape.contours.at(-1); - if (lastContour.kind === constants.EMPTY_PATH) { - lastContour.primitives.push(this); - } - else { - throw new Error('Anchor can only be added to an empty path contour.'); - } + getEndVertex() { + return this.vertices[0]; } } // abstract class class Segment extends ShapePrimitive { + _primitivesIndex = null; + _contoursIndex = null; + _shape = null; + constructor(...vertices) { super(...vertices); if (this.constructor === Segment) { @@ -186,8 +205,26 @@ class Segment extends ShapePrimitive { } } + addToShape(shape) { + super.addToShape(shape); + + // if primitive itself was added + // (i.e. its individual vertices weren't all added to an existing primitive) + // give it a reference to the shape and store its location within the shape + if (this.vertices > 0) { + let lastContour = shape.at(-1); + this._primitivesIndex = lastContour.primitives.length - 1; + this._contoursIndex = shape.contours.length - 1; + this._shape = shape; + } + } + getStartVertex() { - throw new Error('Method getStartVertex() must be implemented.'); + let previousPrimitive = this._shape.at( + this._contoursIndex, + this._primitivesIndex - 1 + ); + return previousPrimitive.getEndVertex(); } getEndVertex() { @@ -196,11 +233,22 @@ class Segment extends ShapePrimitive { } class LineSegment extends Segment { - #index = null; - #shape = null; + #vertexCapacity = 1; - // TODO: finish implementation - constructor() { + constructor(...vertices) { + super(...vertices); + } + + get vertexCapacity() { + return this.#vertexCapacity; + } + + accept(visitor) { + visitor.visitLineSegment(this); + } + + getEndVertex() { + return this.vertices[0]; } } From 6adb65a68955e491ca1de4dea11e322a04d21f0f Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 26 Nov 2024 03:22:17 -0700 Subject: [PATCH 041/111] Implement all custom shape constants as Symbols --- src/core/constants.js | 60 +++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/core/constants.js b/src/core/constants.js index 962375e63c..7664b796dd 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -696,12 +696,6 @@ export const BOTTOM = 'bottom'; * @final */ export const BASELINE = 'alphabetic'; -/** - * @typedef {unique symbol} PATH - * @property {PATH} PATH - * @final - */ -export const PATH = Symbol('path'); /** * @typedef {unique symbol} EMPTY_PATH * @property {EMPTY_PATH} EMPTY_PATH @@ -709,77 +703,83 @@ export const PATH = Symbol('path'); */ export const EMPTY_PATH = Symbol('empty_path'); /** - * @typedef {0x0000} POINTS + * @typedef {unique symbol} PATH + * @property {PATH} PATH + * @final + */ +export const PATH = Symbol('path'); +/** + * @typedef {unique symbol} POINTS * @property {POINTS} POINTS * @final */ -export const POINTS = 0x0000; +export const POINTS = Symbol('points'); /** - * @typedef {0x0001} LINES + * @typedef {unique symbol} LINES * @property {LINES} LINES * @final */ -export const LINES = 0x0001; +export const LINES = Symbol('lines'); /** - * @property {0x0003} LINE_STRIP + * @typedef {unique symbol} LINE_STRIP * @property {LINE_STRIP} LINE_STRIP * @final */ -export const LINE_STRIP = 0x0003; +export const LINE_STRIP = Symbol('line_strip'); /** - * @typedef {0x0002} LINE_LOOP + * @typedef {unique symbol} LINE_LOOP * @property {LINE_LOOP} LINE_LOOP * @final */ -export const LINE_LOOP = 0x0002; +export const LINE_LOOP = Symbol('line_loop'); /** - * @typedef {0x0004} TRIANGLES + * @typedef {unique symbol} TRIANGLES * @property {TRIANGLES} TRIANGLES * @final */ -export const TRIANGLES = 0x0004; +export const TRIANGLES = Symbol('triangles'); /** - * @typedef {0x0006} TRIANGLE_FAN + * @typedef {unique symbol} TRIANGLE_FAN * @property {TRIANGLE_FAN} TRIANGLE_FAN * @final */ -export const TRIANGLE_FAN = 0x0006; +export const TRIANGLE_FAN = Symbol('triangle_fan'); /** - * @typedef {0x0005} TRIANGLE_STRIP + * @typedef {unique symbol} TRIANGLE_STRIP * @property {TRIANGLE_STRIP} TRIANGLE_STRIP * @final */ -export const TRIANGLE_STRIP = 0x0005; +export const TRIANGLE_STRIP = Symbol('triangle_strip'); /** - * @typedef {'quads'} QUADS + * @typedef {unique symbol} QUADS * @property {QUADS} QUADS * @final */ -export const QUADS = 'quads'; +export const QUADS = Symbol('quads'); /** - * @typedef {'quad_strip'} QUAD_STRIP + * @typedef {unique symbol} QUAD_STRIP * @property {QUAD_STRIP} QUAD_STRIP * @final */ -export const QUAD_STRIP = 'quad_strip'; +export const QUAD_STRIP = Symbol('quad_strip'); /** - * @typedef {'tess'} TESS + * @typedef {unique symbol} TESS * @property {TESS} TESS * @final */ -export const TESS = 'tess'; +export const TESS = Symbol('tess'); /** - * @typedef {'close'} CLOSE + * @typedef {unique symbol} CLOSE * @property {CLOSE} CLOSE * @final */ -export const CLOSE = 'close'; +export const CLOSE = Symbol('close'); /** - * @typedef {'open'} OPEN + * @typedef {unique symbol} OPEN * @property {OPEN} OPEN * @final */ -export const OPEN = 'open'; +export const OPEN = Symbol('open'); /** * @typedef {'chord'} CHORD * @property {CHORD} CHORD From ee77b1cc47e344cfc3ca8c9fe9c47e5677c559b5 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 26 Nov 2024 03:56:12 -0700 Subject: [PATCH 042/111] Implement PrimitiveShapeCreators --- src/shape/custom_shapes.js | 101 ++++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 17 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index c0461516b2..9492180b21 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -27,7 +27,17 @@ class Shape { } } - // TODO: TEST, MAYBE REFACTOR + // TODO for at() method: + + // RENAME? + // -at() indicates it works like Array.prototype.at(), e.g. with negative indices + // -get() may work better if we want to add a corresponding set() method + // -a set() method could maybe check for problematic usage (e.g. inserting a Triangle into a PATH) + // -renaming or removing would necessitate changes at call sites (it's already in use) + + // REFACTOR? + + // TEST at(contoursIndex, primitivesIndex, verticesIndex) { let contour; let primitive; @@ -45,6 +55,7 @@ class Shape { } } + // maybe call this clear() for consistency with PrimitiveShapeCreators.clear()? reset() { // TODO: remove existing vertices } @@ -252,63 +263,119 @@ class LineSegment extends Segment { } } -class BezierSegment extends Segment { - constructor() { +// TOOO: Finish implementing remaining primitive classes +class BezierSegment extends Segment { + constructor(...vertices) { + super(...vertices); } } // consider type and end modes -- see #6766) // may want to use separate classes, but maybe not class SplineSegment extends Segment { - constructor() { - + constructor(...vertices) { + super(...vertices); } } // ---- ISOLATED PRIMITIVES ---- class Point extends ShapePrimitive { - constructor() { - + constructor(...vertices) { + super(...vertices); } } class Line extends ShapePrimitive { - constructor() { - + constructor(...vertices) { + super(...vertices); } } class Triangle extends ShapePrimitive { - constructor() { - + constructor(...vertices) { + super(...vertices); } } class Quad extends ShapePrimitive { - constructor() { - + constructor(...vertices) { + super(...vertices); } } // ---- TESSELLATION PRIMITIVES ---- class TriangleFan extends ShapePrimitive { - constructor() { - + constructor(...vertices) { + super(...vertices); } } class TriangleStrip extends ShapePrimitive { - constructor() { - + constructor(...vertices) { + super(...vertices); } } class QuadStrip extends ShapePrimitive { + constructor(...vertices) { + super(...vertices); + } +} + +// ---- PRIMITIVE SHAPE CREATORS ---- + +class PrimitiveShapeCreators { constructor() { + let creators = new Map(); + + // Store Symbols as strings for use in Map keys + const EMPTY_PATH = constants.EMPTY_PATH.description; + const PATH = constants.PATH.description; + const POINTS = constants.POINTS.description; + const LINES = constants.LINES.description; + const TRIANGLES = constants.TRIANGLES.description; + const QUADS = constants.QUADS.description; + const TRIANGLE_FAN = constants.TRIANGLE_FAN.description; + const TRIANGLE_STRIP = constants.TRIANGLE_STRIP.description; + const QUAD_STRIP = constants.QUAD_STRIP.description; + + // vertex + creators.set(`vertex-${EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); + creators.set(`vertex-${PATH}`, (...vertices) => new LineSegment(...vertices)); + creators.set(`vertex-${POINTS}`, (...vertices) => new Point(...vertices)); + creators.set(`vertex-${LINES}`, (...vertices) => new Line(...vertices)); + creators.set(`vertex-${TRIANGLES}`, (...vertices) => new Triangle(...vertices)); + creators.set(`vertex-${QUADS}`, (...vertices) => new Quad(...vertices)); + creators.set(`vertex-${TRIANGLE_FAN}`, (...vertices) => new TriangleFan(...vertices)); + creators.set(`vertex-${TRIANGLE_STRIP}`, (...vertices) => new TriangleStrip(...vertices)); + creators.set(`vertex-${QUAD_STRIP}`, (...vertices) => new QuadStrip(...vertices)); + + // bezierVertex + creators.set(`bezierVertex-${EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); + creators.set(`bezierVertex-${PATH}`, (...vertices) => new BezierSegment(...vertices)); + + // splineVertex + creators.set(`splineVertex-${EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); + creators.set(`splineVertex-${PATH}`, (...vertices) => new SplineSegment(...vertices)); + + this.creators = creators; + } + + get(vertexKind, shapeKind) { + const key = `${vertexKind}-${shapeKind.description}`; + return this.creators.get(key); + } + + set(vertexKind, shapeKind, creator) { + const key = `${vertexKind}-${shapeKind.description}`; + this.creators.set(key, creator); + } + clear() { + this.creators.clear(); } } From b6aec1968fef11c46a3a8d9810be3d3946e23c96 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 26 Nov 2024 20:57:52 -0700 Subject: [PATCH 043/111] Set all custom shape constants to numbers (for now) --- src/core/constants.js | 72 +++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/core/constants.js b/src/core/constants.js index 7664b796dd..dff724af6d 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -697,89 +697,89 @@ export const BOTTOM = 'bottom'; */ export const BASELINE = 'alphabetic'; /** - * @typedef {unique symbol} EMPTY_PATH - * @property {EMPTY_PATH} EMPTY_PATH - * @final - */ -export const EMPTY_PATH = Symbol('empty_path'); -/** - * @typedef {unique symbol} PATH - * @property {PATH} PATH - * @final - */ -export const PATH = Symbol('path'); -/** - * @typedef {unique symbol} POINTS + * @typedef {0x0000} POINTS * @property {POINTS} POINTS * @final */ -export const POINTS = Symbol('points'); +export const POINTS = 0x0000; /** - * @typedef {unique symbol} LINES + * @typedef {0x0001} LINES * @property {LINES} LINES * @final */ -export const LINES = Symbol('lines'); +export const LINES = 0x0001; /** - * @typedef {unique symbol} LINE_STRIP + * @property {0x0003} LINE_STRIP * @property {LINE_STRIP} LINE_STRIP * @final */ -export const LINE_STRIP = Symbol('line_strip'); +export const LINE_STRIP = 0x0003; /** - * @typedef {unique symbol} LINE_LOOP + * @typedef {0x0002} LINE_LOOP * @property {LINE_LOOP} LINE_LOOP * @final */ -export const LINE_LOOP = Symbol('line_loop'); +export const LINE_LOOP = 0x0002; /** - * @typedef {unique symbol} TRIANGLES + * @typedef {0x0004} TRIANGLES * @property {TRIANGLES} TRIANGLES * @final */ -export const TRIANGLES = Symbol('triangles'); +export const TRIANGLES = 0x0004; /** - * @typedef {unique symbol} TRIANGLE_FAN + * @typedef {0x0006} TRIANGLE_FAN * @property {TRIANGLE_FAN} TRIANGLE_FAN * @final */ -export const TRIANGLE_FAN = Symbol('triangle_fan'); +export const TRIANGLE_FAN = 0x0006; /** - * @typedef {unique symbol} TRIANGLE_STRIP + * @typedef {0x0005} TRIANGLE_STRIP * @property {TRIANGLE_STRIP} TRIANGLE_STRIP * @final */ -export const TRIANGLE_STRIP = Symbol('triangle_strip'); +export const TRIANGLE_STRIP = 0x0005; /** - * @typedef {unique symbol} QUADS + * @typedef {'quads'} QUADS * @property {QUADS} QUADS * @final */ -export const QUADS = Symbol('quads'); +export const QUADS = 'quads'; /** - * @typedef {unique symbol} QUAD_STRIP + * @typedef {'quad_strip'} QUAD_STRIP * @property {QUAD_STRIP} QUAD_STRIP * @final */ -export const QUAD_STRIP = Symbol('quad_strip'); +export const QUAD_STRIP = 'quad_strip'; /** - * @typedef {unique symbol} TESS + * @typedef {'tess'} TESS * @property {TESS} TESS * @final */ -export const TESS = Symbol('tess'); +export const TESS = 'tess'; +/** + * @typedef {0x0007} EMPTY_PATH + * @property {EMPTY_PATH} EMPTY_PATH + * @final + */ +export const EMPTY_PATH = 0x0007; +/** + * @typedef {0x0008} PATH + * @property {PATH} PATH + * @final + */ +export const PATH = 0x0008; /** - * @typedef {unique symbol} CLOSE + * @typedef {'close'} CLOSE * @property {CLOSE} CLOSE * @final */ -export const CLOSE = Symbol('close'); +export const CLOSE = 'close'; /** - * @typedef {unique symbol} OPEN + * @typedef {'open'} OPEN * @property {OPEN} OPEN * @final */ -export const OPEN = Symbol('open'); +export const OPEN = 'open'; /** * @typedef {'chord'} CHORD * @property {CHORD} CHORD From 69ed95309d7f3d3524a7b3bdd4c4fc14431339d1 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 26 Nov 2024 21:42:05 -0700 Subject: [PATCH 044/111] Make PrimitiveShapeCreators work with number constants instead of Symbols (for now) --- src/shape/custom_shapes.js | 58 +++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 9492180b21..f75dbc5705 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -69,6 +69,7 @@ class Shape { // primitiveShape.addToShape(this); } + // implement this as a separate function in this file, and attach it to p5? createVertex(properties) { // TODO // return vertex; @@ -328,49 +329,62 @@ class QuadStrip extends ShapePrimitive { // ---- PRIMITIVE SHAPE CREATORS ---- class PrimitiveShapeCreators { + // TODO: make creators private? + // That'd probably be better, but for now, it may be convenient to use + // native Map properties like size, e.g. for testing, and it's simpler to + // not have to wrap all the properties that might be useful + creators; + constructor() { let creators = new Map(); + /* TODO: REFACTOR BASED ON THE CODE BELOW, + ONCE CONSTANTS ARE IMPLEMENTED AS SYMBOLS + // Store Symbols as strings for use in Map keys const EMPTY_PATH = constants.EMPTY_PATH.description; const PATH = constants.PATH.description; - const POINTS = constants.POINTS.description; - const LINES = constants.LINES.description; - const TRIANGLES = constants.TRIANGLES.description; - const QUADS = constants.QUADS.description; - const TRIANGLE_FAN = constants.TRIANGLE_FAN.description; - const TRIANGLE_STRIP = constants.TRIANGLE_STRIP.description; - const QUAD_STRIP = constants.QUAD_STRIP.description; + //etc. - // vertex creators.set(`vertex-${EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); - creators.set(`vertex-${PATH}`, (...vertices) => new LineSegment(...vertices)); - creators.set(`vertex-${POINTS}`, (...vertices) => new Point(...vertices)); - creators.set(`vertex-${LINES}`, (...vertices) => new Line(...vertices)); - creators.set(`vertex-${TRIANGLES}`, (...vertices) => new Triangle(...vertices)); - creators.set(`vertex-${QUADS}`, (...vertices) => new Quad(...vertices)); - creators.set(`vertex-${TRIANGLE_FAN}`, (...vertices) => new TriangleFan(...vertices)); - creators.set(`vertex-${TRIANGLE_STRIP}`, (...vertices) => new TriangleStrip(...vertices)); - creators.set(`vertex-${QUAD_STRIP}`, (...vertices) => new QuadStrip(...vertices)); + // etc. + + get(vertexKind, shapeKind) { + const key = `${vertexKind}-${shapeKind.description}`; + return this.creators.get(key); + } + // etc. + */ + + // vertex + creators.set(`vertex-${constants.EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); + creators.set(`vertex-${constants.PATH}`, (...vertices) => new LineSegment(...vertices)); + creators.set(`vertex-${constants.POINTS}`, (...vertices) => new Point(...vertices)); + creators.set(`vertex-${constants.LINES}`, (...vertices) => new Line(...vertices)); + creators.set(`vertex-${constants.TRIANGLES}`, (...vertices) => new Triangle(...vertices)); + creators.set(`vertex-${constants.QUADS}`, (...vertices) => new Quad(...vertices)); + creators.set(`vertex-${constants.TRIANGLE_FAN}`, (...vertices) => new TriangleFan(...vertices)); + creators.set(`vertex-${constants.TRIANGLE_STRIP}`, (...vertices) => new TriangleStrip(...vertices)); + creators.set(`vertex-${constants.QUAD_STRIP}`, (...vertices) => new QuadStrip(...vertices)); // bezierVertex - creators.set(`bezierVertex-${EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); - creators.set(`bezierVertex-${PATH}`, (...vertices) => new BezierSegment(...vertices)); + creators.set(`bezierVertex-${constants.EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); + creators.set(`bezierVertex-${constants.PATH}`, (...vertices) => new BezierSegment(...vertices)); // splineVertex - creators.set(`splineVertex-${EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); - creators.set(`splineVertex-${PATH}`, (...vertices) => new SplineSegment(...vertices)); + creators.set(`splineVertex-${constants.EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); + creators.set(`splineVertex-${constants.PATH}`, (...vertices) => new SplineSegment(...vertices)); this.creators = creators; } get(vertexKind, shapeKind) { - const key = `${vertexKind}-${shapeKind.description}`; + const key = `${vertexKind}-${shapeKind}`; return this.creators.get(key); } set(vertexKind, shapeKind, creator) { - const key = `${vertexKind}-${shapeKind.description}`; + const key = `${vertexKind}-${shapeKind}`; this.creators.set(key, creator); } From 60739f00dce3c315c87680ad9aee709a8b268874 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Wed, 27 Nov 2024 02:36:34 -0700 Subject: [PATCH 045/111] Implement beginShape(), endShape() on Shape and update docs --- src/shape/custom_shapes.js | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index f75dbc5705..9ca3b0e06c 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -14,6 +14,7 @@ import * as constants from '../core/constants.js'; class Shape { vertexProperties; contours = []; + kind = null; constructor(vertexProperties) { this.vertexProperties = vertexProperties; @@ -76,11 +77,30 @@ class Shape { } beginShape(shapeKind) { - // TODO + this.kind = shapeKind; + this.contours.push(new Contour(shapeKind)); } endShape(closeMode = constants.OPEN) { - // TODO + // shape characteristics + const shapeIsPath = this.kind === constants.PATH; + const shapeIsClosed = closeMode === constants.CLOSE; + const shapeHasOneContour = this.contours.length === 1; + + // anchor characteristics + const anchorVertex = this.at(0, 0, 0); + const anchorHasPos = Object.hasOwn(anchorVertex, 'position'); + const anchorHasTex = Object.hasOwn(anchorVertex, 'textureCoordinates'); + + // close path + if (shapeIsPath && shapeIsClosed && shapeHasOneContour && anchorHasPos) { + if (anchorHasTex) { + this.vertex(anchorVertex.position, anchorVertex.textureCoordinates); + } + else { + this.vertex(anchorVertex.position); + } + } } } @@ -559,7 +579,11 @@ function customShapes(p5, fn) { * }); * ``` * - * Any property names may be used. Property values may be any + * Any property names may be used. The `p5.Shape` class assumes that if a vertex has a + * position or texture coordinates, they are stored in `position` and `textureCoordinates` + * properties. + * + * Property values may be any * JavaScript primitive, any * object literal, * or any object with an `array` property. From 15001d91a4a6b54fe235d162ad239a294874a187 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Wed, 27 Nov 2024 06:12:22 -0700 Subject: [PATCH 046/111] Implement vertex() on Shape, refactor, fix bugs --- src/shape/custom_shapes.js | 274 +++++++++++++++++++------------------ 1 file changed, 142 insertions(+), 132 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 9ca3b0e06c..f815740f9c 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -9,115 +9,25 @@ // REMINDER: remove .js extension (currently using it to run file locally) import * as constants from '../core/constants.js'; -// ---- GENERAL CLASSES ---- - -class Shape { - vertexProperties; - contours = []; - kind = null; - - constructor(vertexProperties) { - this.vertexProperties = vertexProperties; - - for (const key in this.vertexProperties) { - if (key !== 'position' && key !== 'textureCoordinates') { - this[key] = function(value) { - this.vertexProperties[key] = value; - }; - } - } - } - - // TODO for at() method: - - // RENAME? - // -at() indicates it works like Array.prototype.at(), e.g. with negative indices - // -get() may work better if we want to add a corresponding set() method - // -a set() method could maybe check for problematic usage (e.g. inserting a Triangle into a PATH) - // -renaming or removing would necessitate changes at call sites (it's already in use) - - // REFACTOR? - - // TEST - at(contoursIndex, primitivesIndex, verticesIndex) { - let contour; - let primitive; - - contour = this.contours.at(contoursIndex); - - switch(arguments.length) { - case 1: - return contour; - case 2: - return contour.primitives.at(primitivesIndex); - case 3: - primitive = contour.primitives.at(primitivesIndex); - return primitive.vertices.at(verticesIndex); - } - } - - // maybe call this clear() for consistency with PrimitiveShapeCreators.clear()? - reset() { - // TODO: remove existing vertices - } - - vertex(position, textureCoordinates) { - // Add the current position and texture coordiantes to the existing state - let vertex = this.createVertex({ ...this.vertexProperties, position, textureCoordinates }); - // TODO - // primitiveShapeCreator = primitiveShapeCreators.get(['vertex', this.kind]); - // primitiveShape = primitiveShapeCreator(vertex); - // primitiveShape.addToShape(this); - } - - // implement this as a separate function in this file, and attach it to p5? - createVertex(properties) { - // TODO - // return vertex; - } - - beginShape(shapeKind) { - this.kind = shapeKind; - this.contours.push(new Contour(shapeKind)); - } - - endShape(closeMode = constants.OPEN) { - // shape characteristics - const shapeIsPath = this.kind === constants.PATH; - const shapeIsClosed = closeMode === constants.CLOSE; - const shapeHasOneContour = this.contours.length === 1; - - // anchor characteristics - const anchorVertex = this.at(0, 0, 0); - const anchorHasPos = Object.hasOwn(anchorVertex, 'position'); - const anchorHasTex = Object.hasOwn(anchorVertex, 'textureCoordinates'); - - // close path - if (shapeIsPath && shapeIsClosed && shapeHasOneContour && anchorHasPos) { - if (anchorHasTex) { - this.vertex(anchorVertex.position, anchorVertex.textureCoordinates); - } - else { - this.vertex(anchorVertex.position); - } +// ---- GENERAL BUILDING BLOCKS ---- +class Vertex { + constructor(properties) { + for (const [key, value] of Object.entries(properties)) { + this[key] = value; } } -} - -class Contour { - #kind; - primitives; - - constructor(kind = constants.PATH) { - this.#kind = kind; - this.primitives = []; - } - - get kind() { - const isEmpty = this.primitives.length === 0; - const isPath = this.#kind === constants.PATH; - return isEmpty && isPath ? constants.EMPTY_PATH : this.#kind; + /* + get array() { + // convert to 1D array + // call `toArray()` if value is an object with a toArray() method + // handle primitive values separately + // maybe handle object literals too, with Object.values()? + // probably don’t need anything else for now? } + */ + // TODO: make sure name of array conversion method is + // consistent with any modifications to the names of corresponding + // properties of p5.Vector and p5.Color } class ShapePrimitive { @@ -150,13 +60,14 @@ class ShapePrimitive { addToShape(shape) { /* TODO: + Refactor? Test this method once more primitives are implemented. Test segments separately (Segment adds an extra step to this method). */ let lastContour = shape.at(-1); - if (lastContour.length === 0) { - lastContour.push(this); + if (lastContour.primitives.length === 0) { + lastContour.primitives.push(this); return; } @@ -167,39 +78,39 @@ class ShapePrimitive { lastPrimitive.vertexCount; // this primitive - let pushableVertices = this.vertices.splice(0, spareCapacity); - let remainingVertices = this.vertices; + let pushableVertices; + let remainingVertices; if (hasSameType && spareCapacity > 0) { + + pushableVertices = this.vertices.splice(0, spareCapacity); + remainingVertices = this.vertices; lastPrimitive.vertices.push(...pushableVertices); + if (remainingVertices.length > 0) { - lastContour.push(this); + lastContour.primitives.push(this); } } else { - lastContour.push(this); + lastContour.primitives.push(this); } } } -class Vertex { - constructor(properties) { - for (const [key, value] of Object.entries(properties)) { - this[key] = value; - } +class Contour { + #kind; + primitives; + + constructor(kind = constants.PATH) { + this.#kind = kind; + this.primitives = []; } - /* - get array() { - // convert to 1D array - // call `toArray()` if value is an object with a toArray() method - // handle primitive values separately - // maybe handle object literals too, with Object.values()? - // probably don’t need anything else for now? + + get kind() { + const isEmpty = this.primitives.length === 0; + const isPath = this.#kind === constants.PATH; + return isEmpty && isPath ? constants.EMPTY_PATH : this.#kind; } - */ - // TODO: make sure name of array conversion method is - // consistent with any modifications to the names of corresponding - // properties of p5.Vector and p5.Color } // ---- PATH PRIMITIVES ---- @@ -243,7 +154,7 @@ class Segment extends ShapePrimitive { // if primitive itself was added // (i.e. its individual vertices weren't all added to an existing primitive) // give it a reference to the shape and store its location within the shape - if (this.vertices > 0) { + if (this.vertices.length > 0) { let lastContour = shape.at(-1); this._primitivesIndex = lastContour.primitives.length - 1; this._contoursIndex = shape.contours.length - 1; @@ -413,6 +324,105 @@ class PrimitiveShapeCreators { } } +// ---- SHAPE ---- +class Shape { + #vertexProperties; + #primitiveShapeCreators; + kind = null; + contours = []; + + constructor( + vertexProperties, + primitiveShapeCreators = new PrimitiveShapeCreators() + ) { + this.#vertexProperties = vertexProperties; + this.#primitiveShapeCreators = primitiveShapeCreators; + + for (const key in this.#vertexProperties) { + if (key !== 'position' && key !== 'textureCoordinates') { + this[key] = function(value) { + this.#vertexProperties[key] = value; + }; + } + } + } + + // TODO for at() method: + + // RENAME? + // -at() indicates it works like Array.prototype.at(), e.g. with negative indices + // -get() may work better if we want to add a corresponding set() method + // -a set() method could maybe check for problematic usage (e.g. inserting a Triangle into a PATH) + // -renaming or removing would necessitate changes at call sites (it's already in use) + + // REFACTOR? + + // TEST + at(contoursIndex, primitivesIndex, verticesIndex) { + let contour; + let primitive; + + contour = this.contours.at(contoursIndex); + + switch(arguments.length) { + case 1: + return contour; + case 2: + return contour.primitives.at(primitivesIndex); + case 3: + primitive = contour.primitives.at(primitivesIndex); + return primitive.vertices.at(verticesIndex); + } + } + + // maybe call this clear() for consistency with PrimitiveShapeCreators.clear()? + reset() { + // TODO: remove existing vertices + } + + vertex(position, textureCoordinates) { + this.#vertexProperties.position = position; + + if (textureCoordinates !== undefined) { + this.#vertexProperties.textureCoordinates = textureCoordinates; + } + + let vertex = new Vertex(this.#vertexProperties); + let lastContour = this.at(-1); + let primitiveShapeCreator = this.#primitiveShapeCreators.get('vertex', lastContour.kind); + let primitiveShape = primitiveShapeCreator(vertex); + primitiveShape.addToShape(this); + } + + beginShape(shapeKind = constants.PATH) { + this.kind = shapeKind; + this.contours.push(new Contour(shapeKind)); + } + + endShape(closeMode = constants.OPEN) { + if (closeMode === constants.CLOSE) { + // shape characteristics + const shapeIsPath = this.kind === constants.PATH; + const shapeHasOneContour = this.contours.length === 1; + + // anchor characteristics + const anchorVertex = this.at(0, 0, 0); + const anchorHasPosition = Object.hasOwn(anchorVertex, 'position'); + const anchorHasTextureCoordinates = Object.hasOwn(anchorVertex, 'textureCoordinates'); + + // close path + if (shapeIsPath && shapeHasOneContour && anchorHasPosition) { + if (anchorHasTextureCoordinates) { + this.vertex(anchorVertex.position, anchorVertex.textureCoordinates); + } + else { + this.vertex(anchorVertex.position); + } + } + } + } +} + // ---- PRIMITIVE VISITORS ---- // abstract class @@ -425,19 +435,19 @@ class PrimitiveVisitor { // using this instead of PrimitiveToContext2DConverter for now class PrimitiveToPath2DConverter extends PrimitiveVisitor { constructor() { - + super(); } } class PrimitiveToVerticesConverter extends PrimitiveVisitor { constructor() { - + super(); } } class PointAtLengthGetter extends PrimitiveVisitor { constructor() { - + super(); } } From e86209239298176197e1e8845a9c32cf0b7a43ea Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Wed, 27 Nov 2024 06:16:23 -0700 Subject: [PATCH 047/111] Fix spacing --- src/shape/custom_shapes.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index f815740f9c..681ef70a76 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -10,6 +10,7 @@ import * as constants from '../core/constants.js'; // ---- GENERAL BUILDING BLOCKS ---- + class Vertex { constructor(properties) { for (const [key, value] of Object.entries(properties)) { @@ -325,6 +326,7 @@ class PrimitiveShapeCreators { } // ---- SHAPE ---- + class Shape { #vertexProperties; #primitiveShapeCreators; From 722125b5e98eeaad096a5807018ebf33a5083898 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Thu, 28 Nov 2024 12:49:00 -0700 Subject: [PATCH 048/111] Implement abstract base class PrimitiveVisitor --- src/shape/custom_shapes.js | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 681ef70a76..0a700dfc9d 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -430,7 +430,47 @@ class Shape { // abstract class class PrimitiveVisitor { constructor() { + if (this.constructor === PrimitiveVisitor) { + throw new Error('PrimitiveVisitor is an abstract class: it cannot be instantiated.'); + } + } + // segment primitives + visitLineSegment(lineSegment) { + throw new Error('Method visitLineSegment() has not been implemented.'); + } + visitBezierSegment(bezierSegment) { + throw new Error('Method visitBezierSegment() has not been implemented.'); + } + visitSplineSegment(curveSegment) { + throw new Error('Method visitSplineSegment() has not been implemented.'); + } + visitArcSegment(arcSegment) { + throw new Error('Method visitArcSegment() has not been implemented.'); + } + // isolated primitives + visitPoint(point) { + throw new Error('Method visitPoint() has not been implemented.'); + } + visitLine(line) { + throw new Error('Method visitLine() has not been implemented.'); + } + visitTriangle(triangle) { + throw new Error('Method visitTriangle() has not been implemented.'); + } + visitQuad(quad) { + throw new Error('Method visitQuad() has not been implemented.'); + } + + // tessellation primitives + visitTriangleFan(triangleFan) { + throw new Error('Method visitTriangleFan() has not been implemented.'); + } + visitTriangleStrip(triangleStrip) { + throw new Error('Method visitTriangleStrip() has not been implemented.'); + } + visitQuadStrip(quadStrip) { + throw new Error('Method visitQuadStrip() has not been implemented.'); } } From 4fd04587fd3c2a936fc504977d814f5f88851d22 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Thu, 28 Nov 2024 12:51:30 -0700 Subject: [PATCH 049/111] Add anchor visitor method to base class --- src/shape/custom_shapes.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 0a700dfc9d..78ac2ac12c 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -434,7 +434,10 @@ class PrimitiveVisitor { throw new Error('PrimitiveVisitor is an abstract class: it cannot be instantiated.'); } } - // segment primitives + // path primitives + visitAnchor(anchor) { + throw new Error('Method visitAnchor() has not been implemented.'); + } visitLineSegment(lineSegment) { throw new Error('Method visitLineSegment() has not been implemented.'); } @@ -476,9 +479,7 @@ class PrimitiveVisitor { // using this instead of PrimitiveToContext2DConverter for now class PrimitiveToPath2DConverter extends PrimitiveVisitor { - constructor() { - super(); - } + } class PrimitiveToVerticesConverter extends PrimitiveVisitor { From 18a2e6caec02eb530e3f436495c76e961bed4a74 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Thu, 28 Nov 2024 13:32:55 -0700 Subject: [PATCH 050/111] Implement PrimitiveToPath2DConverter with anchor and line segment visitor methods only --- src/shape/custom_shapes.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 78ac2ac12c..979a9ae986 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -478,8 +478,24 @@ class PrimitiveVisitor { } // using this instead of PrimitiveToContext2DConverter for now +// requires testing class PrimitiveToPath2DConverter extends PrimitiveVisitor { + #drawingContext; + constructor(renderer) { + super(); + this.#drawingContext = renderer.drawingContext; + } + + // path primitives + visitAnchor(anchor) { + let vertex = anchor.getEndVertex(); + this.#drawingContext.moveTo(vertex.x, vertex.y); + } + visitLineSegment(lineSegment) { + let vertex = lineSegment.getEndVertex(); + this.#drawingContext.lineTo(vertex.x, vertex.y); + } } class PrimitiveToVerticesConverter extends PrimitiveVisitor { @@ -770,6 +786,10 @@ function customShapes(p5, fn) { /** * @private * A class responsible for... + * + * Notes: + * 1. Assumes vertex positions are stored as p5.Vector instances. + * 2. Currently only supports position properties of vectors. */ p5.PrimitiveToPath2DConverter = PrimitiveToPath2DConverter; From a42fb19ea4a286545c60789b716de68cfcd2ec39 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Thu, 28 Nov 2024 18:02:35 -0500 Subject: [PATCH 051/111] Use only new rendering code and start connecting visitors --- preview/index.html | 19 ++++++------------- src/core/p5.Renderer2D.js | 10 +++++++++- src/shape/custom_shapes.js | 27 ++++++++++++++++++++++----- src/shape/vertex.js | 3 +++ 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/preview/index.html b/preview/index.html index ac5bedefcc..e03cc0f959 100644 --- a/preview/index.html +++ b/preview/index.html @@ -20,25 +20,18 @@ import p5 from '../src/app.js'; const sketch = function (p) { - let g, f; - p.setup = function () { p.createCanvas(200, 200); - g = p.createGraphics(200, 200); - f = p.createGraphics(200, 200, p.WEBGL); }; p.draw = function () { p.background(0, 50, 50); - p.circle(100, 100, 50); - - p.fill('white'); - p.textSize(30); - p.text('hello', 10, 30); - - // f.fill('red'); - f.sphere(); - p.image(f, 0, 0); + p.beginShape(); + p.vertex(50, 50); + p.vertex(100, 50); + p.vertex(100, 100); + p.vertex(50, 50); + p.endShape(); }; }; diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index 3e820ec12e..8866f5e35a 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -5,6 +5,7 @@ import { Graphics } from './p5.Graphics'; import { Image } from '../image/p5.Image'; import { Element } from '../dom/p5.Element'; import { MediaElement } from '../dom/p5.MediaElement'; +import { PrimitiveToPath2DConverter } from '../shape/custom_shapes'; const styleEmpty = 'rgba(0,0,0,0)'; // const alphaThreshold = 0.00125; // minimum visible @@ -253,7 +254,14 @@ class Renderer2D extends Renderer { } drawShape(shape) { - // TODO + const visitor = new PrimitiveToPath2DConverter(); + shape.accept(visitor); + if (this.states.fillColor) { + this.drawingContext.fill(visitor.path); + } + if (this.states.strokeColor) { + this.drawingContext.stroke(visitor.path); + } } beginClip(options = {}) { diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 979a9ae986..5bd81fbf72 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -112,6 +112,12 @@ class Contour { const isPath = this.#kind === constants.PATH; return isEmpty && isPath ? constants.EMPTY_PATH : this.#kind; } + + accept(visitor) { + for (const primitive of this.primitives) { + primitive.accept(visitor); + } + } } // ---- PATH PRIMITIVES ---- @@ -329,6 +335,7 @@ class PrimitiveShapeCreators { class Shape { #vertexProperties; + #initialVertexProperties; #primitiveShapeCreators; kind = null; contours = []; @@ -337,6 +344,7 @@ class Shape { vertexProperties, primitiveShapeCreators = new PrimitiveShapeCreators() ) { + this.#initialVertexProperties = vertexProperties; this.#vertexProperties = vertexProperties; this.#primitiveShapeCreators = primitiveShapeCreators; @@ -379,7 +387,10 @@ class Shape { // maybe call this clear() for consistency with PrimitiveShapeCreators.clear()? reset() { - // TODO: remove existing vertices + this.kind = null; + this.#vertexProperties = { ...this.#initialVertexProperties }; + this.kind = null; + this.contours = []; } vertex(position, textureCoordinates) { @@ -423,6 +434,12 @@ class Shape { } } } + + accept(visitor) { + for (const contour of this.contours) { + contour.accept(visitor); + } + } } // ---- PRIMITIVE VISITORS ---- @@ -481,20 +498,20 @@ class PrimitiveVisitor { // requires testing class PrimitiveToPath2DConverter extends PrimitiveVisitor { #drawingContext; + path = new Path2D(); - constructor(renderer) { + constructor() { super(); - this.#drawingContext = renderer.drawingContext; } // path primitives visitAnchor(anchor) { let vertex = anchor.getEndVertex(); - this.#drawingContext.moveTo(vertex.x, vertex.y); + this.path.moveTo(vertex.position.x, vertex.position.y); } visitLineSegment(lineSegment) { let vertex = lineSegment.getEndVertex(); - this.#drawingContext.lineTo(vertex.x, vertex.y); + this.path.lineTo(vertex.position.x, vertex.position.y); } } diff --git a/src/shape/vertex.js b/src/shape/vertex.js index 0067e760b9..50993098a1 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -519,6 +519,7 @@ function vertex(p5, fn){ fn.beginShape = function(kind) { p5._validateParameters('beginShape', arguments); this._renderer.beginShape(...arguments); + return; // TODO remove this once shape implementation is complete if (!this._renderer.isP3D) { @@ -1514,6 +1515,7 @@ function vertex(p5, fn){ } this._renderer.endShape(mode, count); + return; // TODO remove once shape refactor is complete if (this._renderer.isP3D) { @@ -2041,6 +2043,7 @@ function vertex(p5, fn){ */ fn.vertex = function(x, y, moveTo, u, v) { this._renderer.vertex(...arguments); + return; // TODO remove after shape refactor if (this._renderer.isP3D) { From 49be1690744101e701673a80baba45a9d887f322 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Thu, 28 Nov 2024 19:18:44 -0500 Subject: [PATCH 052/111] Initial WebGL tesselation support --- preview/index.html | 13 +++++++++--- src/color/p5.Color.js | 4 ++++ src/core/p5.Renderer.js | 2 +- src/shape/custom_shapes.js | 19 ++++++++++------- src/webgl/ShapeBuilder.js | 43 +++++++++++++++++++++++++++++++++++++- src/webgl/p5.RendererGL.js | 28 ++++++++++++++++++++++--- 6 files changed, 94 insertions(+), 15 deletions(-) diff --git a/preview/index.html b/preview/index.html index e03cc0f959..1258bdc4b4 100644 --- a/preview/index.html +++ b/preview/index.html @@ -21,17 +21,24 @@ const sketch = function (p) { p.setup = function () { - p.createCanvas(200, 200); + p.createCanvas(200, 200, p.WEBGL); }; p.draw = function () { p.background(0, 50, 50); + p.translate(-p.width/2, -p.height/2); + p.fill('red'); + p.stroke('white'); + p.strokeWeight(10); p.beginShape(); p.vertex(50, 50); p.vertex(100, 50); + p.stroke('blue'); p.vertex(100, 100); - p.vertex(50, 50); - p.endShape(); + p.vertex(50, 100); + p.vertex(70, 70); + p.stroke('white'); + p.endShape(p.CLOSE); }; }; diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js index ab4b3f13e6..213821bf14 100644 --- a/src/color/p5.Color.js +++ b/src/color/p5.Color.js @@ -466,6 +466,10 @@ class Color { } get _array() { + return this.array(); + } + + array() { return [...this.color.coords, this.color.alpha]; } diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index 0139e0272a..248de3356c 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -109,7 +109,7 @@ class Renderer { this.drawShape(this.currentShape); } - drawShape(shape) { + drawShape(shape, count) { throw new Error('Unimplemented') } diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 5bd81fbf72..43eebe36a4 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -497,13 +497,8 @@ class PrimitiveVisitor { // using this instead of PrimitiveToContext2DConverter for now // requires testing class PrimitiveToPath2DConverter extends PrimitiveVisitor { - #drawingContext; path = new Path2D(); - constructor() { - super(); - } - // path primitives visitAnchor(anchor) { let vertex = anchor.getEndVertex(); @@ -516,8 +511,18 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { } class PrimitiveToVerticesConverter extends PrimitiveVisitor { - constructor() { - super(); + contours = []; + + lastContour() { + return this.contours[this.contours.length - 1]; + } + + visitAnchor(anchor) { + this.contours.push([]); + this.lastContour().push(anchor.getEndVertex()); + } + visitLineSegment(lineSegment) { + this.lastContour().push(lineSegment.getEndVertex()); } } diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index 02bc79de40..c113f3de83 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -49,6 +49,46 @@ export class ShapeBuilder { this.contourIndices = []; } + constructFromContours(shape, contours) { + if (this._useUserVertexProperties === true){ + this._resetUserVertexProperties(); + } + this.geometry.reset(); + this.contourIndices = []; + this.shapeMode = constants.TESS; + const shouldProcessEdges = !!this.renderer.states.strokeColor; + + let idx = -1; + for (const contour of contours) { + this.contourIndices.push(this.geometry.vertices.length); + let prevIdx = -1; + for (const vertex of contour) { + idx++ + this.geometry.vertices.push(vertex.position); + this.geometry.vertexNormals.push(vertex.normal); + this.geometry.uvs.push(vertex.textureCoordinates.x, vertex.textureCoordinates.y); + this.geometry.vertexColors.push(...vertex.fill.array()); + this.geometry.vertexStrokeColors.push(...vertex.stroke.array()); + if (shouldProcessEdges && prevIdx >= 0) { + // TODO: handle other shape modes + this.geometry.edges.push([prevIdx, idx]); + } + + prevIdx++ + } + } + + if (shouldProcessEdges && !this.renderer.geometryBuilder) { + this.geometry._edgesToVertices(); + } + + this.isProcessingVertices = true; + this._tesselateShape(); + this.isProcessingVertices = false; + } + + // TODO: remove all below + endShape = function( mode, isCurve, @@ -390,7 +430,8 @@ export class ShapeBuilder { _tesselateShape() { // TODO: handle non-TESS shape modes that have contours this.shapeMode = constants.TRIANGLES; - const contours = [[]]; + // const contours = [[]]; + const contours = []; for (let i = 0; i < this.geometry.vertices.length; i++) { if ( this.contourIndices.length > 0 && diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 5bcee1a246..f405d80e78 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -44,6 +44,7 @@ import filterOpaqueFrag from './shaders/filters/opaque.frag'; import filterInvertFrag from './shaders/filters/invert.frag'; import filterThresholdFrag from './shaders/filters/threshold.frag'; import filterShaderVert from './shaders/filters/default.vert'; +import { PrimitiveToVerticesConverter } from '../shape/custom_shapes'; const STROKE_CAP_ENUM = {}; const STROKE_JOIN_ENUM = {}; @@ -243,7 +244,7 @@ class RendererGL extends Renderer { this._isErasing = false; // simple lines - this._simpleLines = false; + this._simpleLines = false; // clipping this._clipDepths = []; @@ -373,6 +374,7 @@ class RendererGL extends Renderer { this.fontInfos = {}; this._curShader = undefined; + this.drawShapeCount = 1; } ////////////////////////////////////////////// @@ -447,11 +449,31 @@ class RendererGL extends Renderer { beginShape(...args) { super.beginShape(...args); // TODO remove when shape refactor is complete - this.shapeBuilder.beginShape(...args); + // this.shapeBuilder.beginShape(...args); } drawShape(shape) { - // TODO + const visitor = new PrimitiveToVerticesConverter(); + shape.accept(visitor); + this.shapeBuilder.constructFromContours(shape, visitor.contours); + + if (this.geometryBuilder) { + this.geometryBuilder.addImmediate( + this.shapeBuilder.geometry, + this.shapeBuilder.shapeMode + ); + } else if (this.states.fillColor || this.states.strokeColor) { + this._drawGeometry( + this.shapeBuilder.geometry, + { mode: this.shapeBuilder.shapeMode, count: this.drawShapeCount } + ); + } + this.drawShapeCount = 1; + } + + endShape(mode, count) { + super.endShape(mode, count); + this.drawShapeCount = count; } legacyEndShape( From d7f722ac892c3e4278bc928f2e1a85c0fdb8a29d Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Mon, 2 Dec 2024 16:58:45 -0700 Subject: [PATCH 053/111] Remove duplicate line of code --- src/shape/custom_shapes.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 43eebe36a4..89d2680955 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -387,7 +387,6 @@ class Shape { // maybe call this clear() for consistency with PrimitiveShapeCreators.clear()? reset() { - this.kind = null; this.#vertexProperties = { ...this.#initialVertexProperties }; this.kind = null; this.contours = []; From 18179eb462978288cdfaa2bafd313844dde10c36 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Mon, 2 Dec 2024 19:18:28 -0700 Subject: [PATCH 054/111] Add comment about naming --- src/shape/custom_shapes.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 89d2680955..392058a4da 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -386,6 +386,10 @@ class Shape { } // maybe call this clear() for consistency with PrimitiveShapeCreators.clear()? + // note: p5.Geometry has a reset() method, but also clearColors() + // looks like reset() isn't in the public reference, so maybe we can switch + // everything to clear()? Not sure if reset/clear is used in other classes, + // but it'd be good if geometries and shapes are consistent reset() { this.#vertexProperties = { ...this.#initialVertexProperties }; this.kind = null; From 2eb615af8035251008cf01ad4e966c1ec75f525a Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Mon, 2 Dec 2024 19:23:36 -0700 Subject: [PATCH 055/111] Remove trailing spaces per linter --- src/shape/custom_shapes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 392058a4da..41cca20a55 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -388,7 +388,7 @@ class Shape { // maybe call this clear() for consistency with PrimitiveShapeCreators.clear()? // note: p5.Geometry has a reset() method, but also clearColors() // looks like reset() isn't in the public reference, so maybe we can switch - // everything to clear()? Not sure if reset/clear is used in other classes, + // everything to clear()? Not sure if reset/clear is used in other classes, // but it'd be good if geometries and shapes are consistent reset() { this.#vertexProperties = { ...this.#initialVertexProperties }; From ba4b42f9a0a94c7a56dfcf07772bc73acdb2df71 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 3 Dec 2024 01:49:31 -0700 Subject: [PATCH 056/111] Implement getEndVertex() on base class (derived classes may override it) --- src/shape/custom_shapes.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 41cca20a55..fac68e7db4 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -178,7 +178,7 @@ class Segment extends ShapePrimitive { } getEndVertex() { - throw new Error('Method getEndVertex() must be implemented.'); + return this.vertices.at(-1); } } @@ -196,14 +196,8 @@ class LineSegment extends Segment { accept(visitor) { visitor.visitLineSegment(this); } - - getEndVertex() { - return this.vertices[0]; - } } -// TOOO: Finish implementing remaining primitive classes - class BezierSegment extends Segment { constructor(...vertices) { super(...vertices); From e86d2e028117ab3e8162c0921c25f42208de9dad Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 3 Dec 2024 03:19:41 -0700 Subject: [PATCH 057/111] Implement BezierSegment class and modify PrimitiveShapeCreators to reflect new design --- src/shape/custom_shapes.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index fac68e7db4..608309fc1e 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -199,13 +199,31 @@ class LineSegment extends Segment { } class BezierSegment extends Segment { - constructor(...vertices) { + #order; + #vertexCapacity; + + constructor(order, ...vertices) { super(...vertices); + this.#order = order; + this.#vertexCapacity = order; + } + + get order() { + return this.#order; + } + + get vertexCapacity() { + return this.#vertexCapacity; + } + + accept(visitor) { + visitor.visitBezierSegment(this); } } // consider type and end modes -- see #6766) // may want to use separate classes, but maybe not +// may need to implement getEndVertex() to override super.getEndVertex() class SplineSegment extends Segment { constructor(...vertices) { super(...vertices); @@ -301,7 +319,7 @@ class PrimitiveShapeCreators { // bezierVertex creators.set(`bezierVertex-${constants.EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); - creators.set(`bezierVertex-${constants.PATH}`, (...vertices) => new BezierSegment(...vertices)); + creators.set(`bezierVertex-${constants.PATH}`, (order, ...vertices) => new BezierSegment(order, ...vertices)); // splineVertex creators.set(`splineVertex-${constants.EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); From a25cbafb28d291234ce9e19bc5906e2c68456195 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 3 Dec 2024 03:54:22 -0700 Subject: [PATCH 058/111] Add comment about Bezier order --- src/shape/custom_shapes.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 608309fc1e..6a857ee978 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -198,6 +198,19 @@ class LineSegment extends Segment { } } +/* +Note: +Bezier-based primitives (such as segments, triangle patches, and quad patches) +could be accommodated by letting order be an array. Then a segment could +have order [m], and a quad patch could have order [m, n], for example. + +This would allow Shape.prototype.bezierVertex() to pass in the same data to +`primitiveShapeCreator()`, which it gets from #primitiveShapeCreators, +regardless of the type of primitive. + +However, while Bezier segments are the only Bezier-based primitives supported, +order is implemented as a number, rather than an array. +*/ class BezierSegment extends Segment { #order; #vertexCapacity; From 94ce58ea13ebe425080c32f1fed91ddefdeb9247 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 3 Dec 2024 04:08:47 -0700 Subject: [PATCH 059/111] Revise comment about Bezier order --- src/shape/custom_shapes.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 6a857ee978..9d4711df2a 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -204,9 +204,8 @@ Bezier-based primitives (such as segments, triangle patches, and quad patches) could be accommodated by letting order be an array. Then a segment could have order [m], and a quad patch could have order [m, n], for example. -This would allow Shape.prototype.bezierVertex() to pass in the same data to -`primitiveShapeCreator()`, which it gets from #primitiveShapeCreators, -regardless of the type of primitive. +This would allow Shape.prototype.bezierVertex() to pass in the same type of +data to `primitiveShapeCreator()`, regardless of the type of primitive. However, while Bezier segments are the only Bezier-based primitives supported, order is implemented as a number, rather than an array. From 3b5a0fc933e2dac0b0e9348264899e261098e371 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 3 Dec 2024 04:51:38 -0700 Subject: [PATCH 060/111] Implement bezierOrder(), revise BezierSegment --- src/shape/custom_shapes.js | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 9d4711df2a..0ea2dadae6 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -198,26 +198,20 @@ class LineSegment extends Segment { } } -/* -Note: -Bezier-based primitives (such as segments, triangle patches, and quad patches) -could be accommodated by letting order be an array. Then a segment could -have order [m], and a quad patch could have order [m, n], for example. - -This would allow Shape.prototype.bezierVertex() to pass in the same type of -data to `primitiveShapeCreator()`, regardless of the type of primitive. - -However, while Bezier segments are the only Bezier-based primitives supported, -order is implemented as a number, rather than an array. -*/ class BezierSegment extends Segment { #order; #vertexCapacity; constructor(order, ...vertices) { super(...vertices); - this.#order = order; - this.#vertexCapacity = order; + + // Order m may sometimes be passed as an array [m], since arrays + // may be used elsewhere to store order of + // Bezier curves and surfaces in a common format + + let numericalOrder = Array.isArray(order) ? order[0] : order; + this.#order = numericalOrder; + this.#vertexCapacity = numericalOrder; } get order() { @@ -361,6 +355,7 @@ class Shape { #vertexProperties; #initialVertexProperties; #primitiveShapeCreators; + #bezierOrder; kind = null; contours = []; @@ -420,6 +415,16 @@ class Shape { this.contours = []; } + /* + Note: Internally, #bezierOrder is stored as an array, in order to accommodate + primitives including Bezier segments, Bezier triangles, and Bezier quads. Whereas + a segment may have #bezierOrder [m], a quad may have #bezierOrder [m, n], for example. + */ + + bezierOrder(...order) { + this.#bezierOrder = order; + } + vertex(position, textureCoordinates) { this.#vertexProperties.position = position; From 03686092e2257e124dd5941fd5aaa5ee13879857 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 3 Dec 2024 07:09:37 -0700 Subject: [PATCH 061/111] Refactor, implement all vertex functions on Shape --- src/shape/custom_shapes.js | 59 ++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 0ea2dadae6..2c4596a4f6 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -324,7 +324,7 @@ class PrimitiveShapeCreators { creators.set(`vertex-${constants.QUAD_STRIP}`, (...vertices) => new QuadStrip(...vertices)); // bezierVertex - creators.set(`bezierVertex-${constants.EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); + creators.set(`bezierVertex-${constants.EMPTY_PATH}`, (order, ...vertices) => new Anchor(...vertices)); creators.set(`bezierVertex-${constants.PATH}`, (order, ...vertices) => new BezierSegment(order, ...vertices)); // splineVertex @@ -417,28 +417,71 @@ class Shape { /* Note: Internally, #bezierOrder is stored as an array, in order to accommodate - primitives including Bezier segments, Bezier triangles, and Bezier quads. Whereas - a segment may have #bezierOrder [m], a quad may have #bezierOrder [m, n], for example. + primitives including Bezier segments, Bezier triangles, and Bezier quads. For example, + a segment may have #bezierOrder [m], whereas a quad may have #bezierOrder [m, n]. */ bezierOrder(...order) { this.#bezierOrder = order; } - vertex(position, textureCoordinates) { + #createVertex(position, textureCoordinates) { this.#vertexProperties.position = position; if (textureCoordinates !== undefined) { this.#vertexProperties.textureCoordinates = textureCoordinates; } - let vertex = new Vertex(this.#vertexProperties); - let lastContour = this.at(-1); - let primitiveShapeCreator = this.#primitiveShapeCreators.get('vertex', lastContour.kind); - let primitiveShape = primitiveShapeCreator(vertex); + return new Vertex(this.#vertexProperties); + } + + #createPrimitiveShape(vertexKind, shapeKind, ...vertices) { + let primitiveShapeCreator = this.#primitiveShapeCreators.get( + vertexKind, shapeKind + ); + + return vertexKind === 'bezierVertex' ? + primitiveShapeCreator(this.#bezierOrder, ...vertices) : + primitiveShapeCreator(...vertices); + } + + /* + #generalVertex() is reused by the special vertex functions, + including vertex(), bezierVertex(), splineVertex(), and arcVertex(): + + It creates a vertex, builds a primitive including that + vertex, and has the primitive add itself to the shape. + */ + #generalVertex(kind, position, textureCoordinates) { + let vertexKind = kind; + let lastContourKind = this.at(-1).kind; + let vertex = this.#createVertex(position, textureCoordinates); + + let primitiveShape = this.#createPrimitiveShape( + vertexKind, + lastContourKind, + vertex + ); + primitiveShape.addToShape(this); } + vertex(position, textureCoordinates) { + this.#generalVertex('vertex', position, textureCoordinates); + } + + bezierVertex(position, textureCoordinates) { + this.#generalVertex('bezierVertex', position, textureCoordinates); + } + + splineVertex() { + this.#generalVertex('splineVertex', position, textureCoordinates); + } + + arcVertex() { + this.#generalVertex('arcVertex', position, textureCoordinates); + } + beginShape(shapeKind = constants.PATH) { this.kind = shapeKind; this.contours.push(new Contour(shapeKind)); From 9e63827cc3dcdea2f81f8bbf8cca011a7ea30242 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 3 Dec 2024 08:26:48 -0700 Subject: [PATCH 062/111] Implement Path2D visitor method for Bezier segments --- src/shape/custom_shapes.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 2c4596a4f6..0a876b0906 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -583,6 +583,30 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { let vertex = lineSegment.getEndVertex(); this.path.lineTo(vertex.position.x, vertex.position.y); } + visitBezierSegment(bezierSegment) { + let [v1, v2, v3] = bezierSegment.vertices; + + switch (bezierSegment.order) { + case 2: + this.path.quadraticCurveTo( + v1.position.x, + v1.position.y, + v2.position.x, + v2.position.y + ); + break; + case 3: + this.path.bezierCurveTo( + v1.position.x, + v1.position.y, + v2.position.x, + v2.position.y, + v3.position.x, + v3.position.y + ); + break; + } + } } class PrimitiveToVerticesConverter extends PrimitiveVisitor { From 3ca8c6f1295f7fbccad9d484f0237f513046accc Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Tue, 3 Dec 2024 20:35:48 -0700 Subject: [PATCH 063/111] Add explanatory and to-do comments --- src/shape/custom_shapes.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 0a876b0906..602d5a12c4 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -323,7 +323,7 @@ class PrimitiveShapeCreators { creators.set(`vertex-${constants.TRIANGLE_STRIP}`, (...vertices) => new TriangleStrip(...vertices)); creators.set(`vertex-${constants.QUAD_STRIP}`, (...vertices) => new QuadStrip(...vertices)); - // bezierVertex + // bezierVertex (constructors all take order and vertices so they can be called in a uniform way) creators.set(`bezierVertex-${constants.EMPTY_PATH}`, (order, ...vertices) => new Anchor(...vertices)); creators.set(`bezierVertex-${constants.PATH}`, (order, ...vertices) => new BezierSegment(order, ...vertices)); @@ -486,7 +486,12 @@ class Shape { this.kind = shapeKind; this.contours.push(new Contour(shapeKind)); } - + /* TO-DO: + Refactor? + - Might not need anchorHasPosition. + - Might combine conditions at top, and rely on shortcircuiting. + Does nothing if shape is not a path or has multiple contours. Might discuss this. + */ endShape(closeMode = constants.OPEN) { if (closeMode === constants.CLOSE) { // shape characteristics From 34e629bfa2016df4b2fcfb5bc0029c198999f0da Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Wed, 4 Dec 2024 03:33:18 -0700 Subject: [PATCH 064/111] Implement SplineSegment and refactor --- src/shape/custom_shapes.js | 99 +++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 8 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 602d5a12c4..ac2b01b4d0 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -169,12 +169,20 @@ class Segment extends ShapePrimitive { } } + get _belongsToShape() { + return this._shape !== null; + } + + // segments in a shape always have a predecessor + // (either an anchor or another segment) + get _previousPrimitive() { + return this._belongsToShape ? + this._shape.at(this._contoursIndex, this._primitivesIndex - 1) : + null; + } + getStartVertex() { - let previousPrimitive = this._shape.at( - this._contoursIndex, - this._primitivesIndex - 1 - ); - return previousPrimitive.getEndVertex(); + return this._previousPrimitive.getEndVertex(); } getEndVertex() { @@ -227,13 +235,83 @@ class BezierSegment extends Segment { } } -// consider type and end modes -- see #6766) -// may want to use separate classes, but maybe not -// may need to implement getEndVertex() to override super.getEndVertex() +/* +To-do: Consider type and end modes -- see #6766 +may want to use separate classes, but maybe not + +For now, the implementation overrides +super.getEndVertex() in order to preserve current p5 +endpoint behavior, but we're considering defaulting +to interpolated endpoints (a breaking change) +*/ class SplineSegment extends Segment { + #vertexCapacity = Infinity; + constructor(...vertices) { super(...vertices); } + + get vertexCapacity() { + return this.#vertexCapacity; + } + + accept(visitor) { + visitor.visitSplineSegment(this); + } + + get _comesAfterSegment() { + return this._previousPrimitive instanceof Segment; + } + + get _interpolatedStartPosition() { + return this.vertices[1].position; + } + + get _chainedToSegment() { + if (this._belongsToShape && this._comesAfterSegment) { + let predecessorEnd = this.getStartVertex().position; + return predecessorEnd.equals(this._interpolatedStartPosition); + } + else { + return false; + } + } + + // extend addToShape() with a warning in case second vertex + // doesn't line up with end of last segment + addToShape(shape) { + super.addToShape(shape); + + let verticesPushed = !this._belongsToShape; + let lastPrimitive = shape.at(-1, -1); + + let message = (array1, array2) => + `A spline does not start where previous path segment ends: + second spline vertex at (${array1}) + expected to be at (${array2}).`; + + if ( + verticesPushed && + !lastPrimitive._chainedToSegment + ) { + let interpolatedStart = lastPrimitive._interpolatedStartPosition; + let predecessorEnd = lastPrimitive.getStartVertex().position; + console.warn(message(interpolatedStart.array(), predecessorEnd.array())); + } + + // Note: Could add a warning in an else-if case for when this spline segment + // is added directly to the shape instead of pushing its vertices to + // an existing spline segment. However, if we assume addToShape() is called by + // splineVertex(), it'd add a new spline segment with only one vertex in that case, + // and the check wouldn't be needed yet. + + // TODO: Consider case where positions match but other vertex properties don't. + } + + // override method on base class + getEndVertex() { + return this.vertices.at(-2); + } } // ---- ISOLATED PRIMITIVES ---- @@ -351,6 +429,11 @@ class PrimitiveShapeCreators { // ---- SHAPE ---- +/* Note: It's assumed that Shape instances are always built through + * their beginShape()/endShape() methods. For example, this ensures + * that a segment is never the first primitive in a contour (paths + * always start with an anchor), which simplifies code elsewhere. + */ class Shape { #vertexProperties; #initialVertexProperties; From 8b45067b9b9bd6a9f59403164ed81ceccc9f8d42 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Wed, 4 Dec 2024 03:50:44 -0700 Subject: [PATCH 065/111] Fix naming --- src/shape/custom_shapes.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index ac2b01b4d0..5c48b11ae1 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -269,8 +269,8 @@ class SplineSegment extends Segment { get _chainedToSegment() { if (this._belongsToShape && this._comesAfterSegment) { - let predecessorEnd = this.getStartVertex().position; - return predecessorEnd.equals(this._interpolatedStartPosition); + let predecessorEndPosition = this.getStartVertex().position; + return predecessorEndPosition.equals(this._interpolatedStartPosition); } else { return false; @@ -294,9 +294,9 @@ class SplineSegment extends Segment { verticesPushed && !lastPrimitive._chainedToSegment ) { - let interpolatedStart = lastPrimitive._interpolatedStartPosition; - let predecessorEnd = lastPrimitive.getStartVertex().position; - console.warn(message(interpolatedStart.array(), predecessorEnd.array())); + let interpolatedStartPosition = lastPrimitive._interpolatedStartPosition; + let predecessorEndPosition = lastPrimitive.getStartVertex().position; + console.warn(message(interpolatedStartPosition.array(), predecessorEndPosition.array())); } // Note: Could add a warning in an else-if case for when this spline segment From 38e3906f907d78562f996c484a3f004d5fea7399 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Wed, 4 Dec 2024 04:01:43 -0700 Subject: [PATCH 066/111] Fix warning message and spacing --- src/shape/custom_shapes.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 5c48b11ae1..56e736cf74 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -286,17 +286,20 @@ class SplineSegment extends Segment { let lastPrimitive = shape.at(-1, -1); let message = (array1, array2) => - `A spline does not start where previous path segment ends: + `Spline does not start where previous path segment ends: second spline vertex at (${array1}) expected to be at (${array2}).`; - if ( - verticesPushed && - !lastPrimitive._chainedToSegment - ) { + if (verticesPushed && !lastPrimitive._chainedToSegment) { let interpolatedStartPosition = lastPrimitive._interpolatedStartPosition; let predecessorEndPosition = lastPrimitive.getStartVertex().position; - console.warn(message(interpolatedStartPosition.array(), predecessorEndPosition.array())); + + console.warn( + message( + interpolatedStartPosition.array(), + predecessorEndPosition.array() + ) + ); } // Note: Could add a warning in an else-if case for when this spline segment From e4a174998a7b91a5716ac33e70b8fdf52c66d797 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Wed, 4 Dec 2024 04:14:26 -0700 Subject: [PATCH 067/111] Fix logic for warning message in SplineSegment --- src/shape/custom_shapes.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 56e736cf74..c28121da5d 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -290,7 +290,10 @@ class SplineSegment extends Segment { second spline vertex at (${array1}) expected to be at (${array2}).`; - if (verticesPushed && !lastPrimitive._chainedToSegment) { + if (verticesPushed && + lastPrimitive._comesAfterSegment && + !lastPrimitive._chainedToSegment + ) { let interpolatedStartPosition = lastPrimitive._interpolatedStartPosition; let predecessorEndPosition = lastPrimitive.getStartVertex().position; From 0e6debe077ff4e332fc9dc40930abdf47d89e072 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Wed, 4 Dec 2024 05:37:27 -0700 Subject: [PATCH 068/111] Implement catmullRomToBezier() --- src/shape/custom_shapes.js | 57 +++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index c28121da5d..b3c4ebb4d5 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -9,6 +9,61 @@ // REMINDER: remove .js extension (currently using it to run file locally) import * as constants from '../core/constants.js'; +// ---- UTILITY FUNCTIONS ---- + +/* +catmullRomToBezier(vertices, tightness) + +Abbreviated description: +Converts a Catmull-Rom spline to a sequence of Bezier curves. + +Parameters: +vertices -> Array [x1, y1, x2, y2, ...] of at least four vertices +tightness -> Number affecting shape of curve + +Returns: +array of Bezier curves, each represented as [x1, y1, x2, y2, x3, y3] +*/ + +/* +TODO: +1. It seems p5 contains code for converting from Catmull-Rom to Bezier in at least two places: + +catmullRomToBezier() is based on code in the legacy endShape() function: +https://github.com/processing/p5.js/blob/1b66f097761d3c2057c0cec4349247d6125f93ca/src/core/p5.Renderer2D.js#L859C1-L886C1 + +A different conversion can be found elsewhere in p5: +https://github.com/processing/p5.js/blob/17304ce9e9ef3f967bd828102a51b62a2d39d4f4/src/typography/p5.Font.js#L1179 + +A more careful review and comparison of both implementations would be helpful. They're different. I put +catmullRomToBezier() together quickly without checking the math/algorithm, when I made the proof of concept +for the refactor. + +2. It may be possible to replace the code in p5.Font.js with the code here, to reduce duplication. +*/ +function catmullRomToBezier(vertices, tightness) { + let X0, Y0, X1, Y1, X2, Y2, X3, Y3; + let s = 1 - tightness; + let bezX1, bezY1, bezX2, bezY2, bezX3, bezY3; + let bezArrays = []; + + for (let i = 0; i + 6 < vertices.length; i += 2) { + [X0, Y0, X1, Y1, X2, Y2, X3, Y3] = vertices.slice(i, i + 8); + + bezX1 = X1 + s * (X2 - X0) / 6; + bezY1 = Y1 + s * (Y2 - Y0) / 6; + + bezX2 = X2 + s * (X1 - X3) / 6; + bezY2 = Y2 + s * (Y1 - Y3) / 6; + + bezX3 = X2; + bezY3 = Y2; + + bezArrays.push([bezX1, bezY1, bezX2, bezY2, bezX3, bezY3]); + } + return bezArrays; +} + // ---- GENERAL BUILDING BLOCKS ---- class Vertex { @@ -663,7 +718,6 @@ class PrimitiveVisitor { } } -// using this instead of PrimitiveToContext2DConverter for now // requires testing class PrimitiveToPath2DConverter extends PrimitiveVisitor { path = new Path2D(); @@ -1121,6 +1175,7 @@ function customShapes(p5, fn) { export default customShapes; export { + catmullRomToBezier, Shape, Contour, ShapePrimitive, From a33fde0c50df78d259499f500bdda94b04a3e9f7 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Wed, 4 Dec 2024 06:59:53 -0700 Subject: [PATCH 069/111] Refactor and implement spline segment visitor --- src/shape/custom_shapes.js | 47 ++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index b3c4ebb4d5..60bb3ec92f 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -318,14 +318,21 @@ class SplineSegment extends Segment { return this._previousPrimitive instanceof Segment; } - get _interpolatedStartPosition() { - return this.vertices[1].position; + // assuming for now that the first interpolated vertex is always + // the second vertex passed to splineVertex() + // if this spline segment doesn't follow another segment, + // the first vertex is in an anchor + get _firstInterpolatedVertex() { + return this._comesAfterSegment ? + this.vertices[1] : + this.vertices[0]; } get _chainedToSegment() { if (this._belongsToShape && this._comesAfterSegment) { + let interpolatedStartPosition = this._firstInterpolatedVertex.position; let predecessorEndPosition = this.getStartVertex().position; - return predecessorEndPosition.equals(this._interpolatedStartPosition); + return predecessorEndPosition.equals(interpolatedStartPosition); } else { return false; @@ -349,14 +356,11 @@ class SplineSegment extends Segment { lastPrimitive._comesAfterSegment && !lastPrimitive._chainedToSegment ) { - let interpolatedStartPosition = lastPrimitive._interpolatedStartPosition; - let predecessorEndPosition = lastPrimitive.getStartVertex().position; + let interpolatedStart = lastPrimitive._firstInterpolatedVertex.position; + let predecessorEnd = lastPrimitive.getStartVertex().position; console.warn( - message( - interpolatedStartPosition.array(), - predecessorEndPosition.array() - ) + message(interpolatedStart.array(), predecessorEnd.array()) ); } @@ -499,7 +503,8 @@ class Shape { #vertexProperties; #initialVertexProperties; #primitiveShapeCreators; - #bezierOrder; + #bezierOrder = 3; + _splineTightness = 0; kind = null; contours = []; @@ -569,6 +574,10 @@ class Shape { this.#bezierOrder = order; } + splineTightness(tightness) { + this._splineTightness = tightness; + } + #createVertex(position, textureCoordinates) { this.#vertexProperties.position = position; @@ -755,6 +764,24 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { break; } } + visitSplineSegment(splineSegment) { + let shape = splineSegment._shape; + let flatVertices = []; + + for (const vertex of splineSegment.vertices) { + flatVertices.push(vertex.position.x, vertex.position.y); + } + + if (!splineSegment._comesAfterSegment) { + let startVertex = splineSegment._firstInterpolatedVertex; + this.path.moveTo(startVertex.position.x, startVertex.position.y); + } + + let bezierArrays = catmullRomToBezier(flatVertices, shape._splineTightness); + for (const array of bezierArrays) { + this.path.bezierCurveTo(...array); + } + } } class PrimitiveToVerticesConverter extends PrimitiveVisitor { From 9a27eb4c83c5598dbc6190d781c7317ca23df370 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Wed, 4 Dec 2024 17:58:53 -0500 Subject: [PATCH 070/111] Connect bezier methods --- preview/index.html | 14 ++-- src/core/main.js | 3 - src/core/p5.Renderer.js | 31 ++++++++- src/shape/curves.js | 103 ---------------------------- src/shape/custom_shapes.js | 17 ++++- src/shape/vertex.js | 35 +++------- src/webgl/3d_primitives.js | 136 ++++++++++++++++++++++++++++++++----- src/webgl/p5.RendererGL.js | 24 ++++--- 8 files changed, 196 insertions(+), 167 deletions(-) diff --git a/preview/index.html b/preview/index.html index 1258bdc4b4..08f73ba32c 100644 --- a/preview/index.html +++ b/preview/index.html @@ -21,23 +21,23 @@ const sketch = function (p) { p.setup = function () { - p.createCanvas(200, 200, p.WEBGL); + p.createCanvas(200, 200); // , p.WEBGL }; p.draw = function () { p.background(0, 50, 50); - p.translate(-p.width/2, -p.height/2); + //p.translate(-p.width/2, -p.height/2); p.fill('red'); p.stroke('white'); p.strokeWeight(10); p.beginShape(); p.vertex(50, 50); p.vertex(100, 50); - p.stroke('blue'); - p.vertex(100, 100); - p.vertex(50, 100); - p.vertex(70, 70); - p.stroke('white'); + p.bezierOrder(3); + // p.bezierVertex(100, 100, 50, 100, 70, 70); + p.bezierVertex(100, 100); + p.bezierVertex(50, 100); + p.bezierVertex(70, 70); p.endShape(p.CLOSE); }; }; diff --git a/src/core/main.js b/src/core/main.js index 37aae7c13f..b7b935dbb0 100644 --- a/src/core/main.js +++ b/src/core/main.js @@ -414,9 +414,6 @@ class p5 { this._styles = []; - this._bezierDetail = 20; - this._curveDetail = 20; - this._colorMode = constants.RGB; this._colorMaxes = { rgb: [255, 255, 255, 255], diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index 248de3356c..92811504de 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -43,7 +43,8 @@ class Renderer { textAlign: constants.LEFT, textBaseline: constants.BASELINE, textStyle: constants.NORMAL, - textWrap: constants.WORD + textWrap: constants.WORD, + bezierOrder: 3 }; this._pushPopStack = []; // NOTE: can use the length of the push pop stack instead @@ -97,6 +98,30 @@ class Renderer { this._pushPopDepth--; Object.assign(this.states, this._pushPopStack.pop()); this.updateShapeVertexProperties(); + this.updateShapeProperties(); + } + + bezierOrder(order) { + if (order === undefined) { + return this.states.bezierOrder; + } else { + this.states.bezierOrder = order; + this.updateShapeProperties(); + } + } + + bezierVertex(x, y, z = 0, u = 0, v = 0) { + const position = new Vector(x, y, z); + const textureCoordinates = new Vector(u, v); + this.currentShape.bezierVertex(position, textureCoordinates); + } + + curveDetail(d) { + if (d === undefined) { + return this.states.curveDetail; + } else { + this.states.curveDetail = d; + } } beginShape(...args) { @@ -224,6 +249,10 @@ class Renderer { } } + updateShapeProperties() { + this.currentShape.bezierOrder(this.states.bezierOrder) + } + updateShapeVertexProperties() { const props = this.vertexProperties(); for (const key in props) { diff --git a/src/shape/curves.js b/src/shape/curves.js index 54874bafd9..fc033b476b 100644 --- a/src/shape/curves.js +++ b/src/shape/curves.js @@ -765,109 +765,6 @@ function curves(p5, fn){ return this; }; - /** - * Sets the number of segments used to draw spline curves in WebGL mode. - * - * In WebGL mode, smooth shapes are drawn using many flat segments. Adding - * more flat segments makes shapes appear smoother. - * - * The parameter, `detail`, is the number of segments to use while drawing a - * spline curve. For example, calling `curveDetail(5)` will use 5 segments to - * draw curves with the curve() function. By - * default,`detail` is 20. - * - * Note: `curveDetail()` has no effect in 2D mode. - * - * @method curveDetail - * @param {Number} resolution number of segments to use. Defaults to 20. - * @chainable - * - * @example - *
- * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Draw a black spline curve. - * noFill(); - * strokeWeight(1); - * stroke(0); - * curve(5, 26, 73, 24, 73, 61, 15, 65); - * - * // Draw red spline curves from the anchor points to the control points. - * stroke(255, 0, 0); - * curve(5, 26, 5, 26, 73, 24, 73, 61); - * curve(73, 24, 73, 61, 15, 65, 15, 65); - * - * // Draw the anchor points in black. - * strokeWeight(5); - * stroke(0); - * point(73, 24); - * point(73, 61); - * - * // Draw the control points in red. - * stroke(255, 0, 0); - * point(5, 26); - * point(15, 65); - * - * describe( - * 'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.' - * ); - * } - * - *
- * - *
- * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * background(200); - * - * // Set the curveDetail() to 3. - * curveDetail(3); - * - * // Draw a black spline curve. - * noFill(); - * strokeWeight(1); - * stroke(0); - * curve(-45, -24, 0, 23, -26, 0, 23, 11, 0, -35, 15, 0); - * - * // Draw red spline curves from the anchor points to the control points. - * stroke(255, 0, 0); - * curve(-45, -24, 0, -45, -24, 0, 23, -26, 0, 23, 11, 0); - * curve(23, -26, 0, 23, 11, 0, -35, 15, 0, -35, 15, 0); - * - * // Draw the anchor points in black. - * strokeWeight(5); - * stroke(0); - * point(23, -26); - * point(23, 11); - * - * // Draw the control points in red. - * stroke(255, 0, 0); - * point(-45, -24); - * point(-35, 15); - * - * describe( - * 'A gray square with a jagged curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.' - * ); - * } - * - *
- */ - fn.curveDetail = function(d) { - p5._validateParameters('curveDetail', arguments); - if (d < 3) { - this._curveDetail = 3; - } else { - this._curveDetail = d; - } - return this; - }; - /** * Adjusts the way curve() and * curveVertex() draw. diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 60bb3ec92f..99cde9731b 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -348,7 +348,7 @@ class SplineSegment extends Segment { let lastPrimitive = shape.at(-1, -1); let message = (array1, array2) => - `Spline does not start where previous path segment ends: + `Spline does not start where previous path segment ends: second spline vertex at (${array1}) expected to be at (${array2}).`; @@ -786,6 +786,12 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { class PrimitiveToVerticesConverter extends PrimitiveVisitor { contours = []; + curveDetail; + + constructor({ curveDetail = 1 } = {}) { + super(); + this.curveDetail = curveDetail; + } lastContour() { return this.contours[this.contours.length - 1]; @@ -798,6 +804,11 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { visitLineSegment(lineSegment) { this.lastContour().push(lineSegment.getEndVertex()); } + visitBezierSegment(bezierSegment) { + // TODO: use this.curveDetail and actually evaluate the curve. + // Currently just returning the control points for testing. + this.lastContour().push(...bezierSegment.vertices.slice(0, bezierSegment.order)); + } } class PointAtLengthGetter extends PrimitiveVisitor { @@ -1106,6 +1117,10 @@ function customShapes(p5, fn) { // ---- FUNCTIONS ---- + fn.bezierOrder = function(order) { + return this._renderer.bezierOrder(order); + } + // Note: Code is commented out for now, to avoid conflicts with the existing implementation. /** diff --git a/src/shape/vertex.js b/src/shape/vertex.js index 50993098a1..b7bb2e441a 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -576,7 +576,6 @@ function vertex(p5, fn){ * @param {Number} y3 y-coordinate of the second control point. * @param {Number} x4 x-coordinate of the anchor point. * @param {Number} y4 y-coordinate of the anchor point. - * @chainable * * @example *
@@ -803,33 +802,21 @@ function vertex(p5, fn){ * @param {Number} x4 * @param {Number} y4 * @param {Number} z4 z-coordinate of the anchor point. - * @chainable */ fn.bezierVertex = function(...args) { - p5._validateParameters('bezierVertex', args); - if (this._renderer.isP3D) { - this._renderer.bezierVertex(...args); - } else { - if (vertices.length === 0) { - p5._friendlyError( - 'vertex() must be used once before calling bezierVertex()', - 'bezierVertex' - ); - } else { - isBezier = true; - const vert = []; - for (let i = 0; i < args.length; i++) { - vert[i] = args[i]; - } - vert.isVert = false; - if (isContour) { - contourVertices.push(vert); - } else { - vertices.push(vert); - } + if (args.length === 2 * 3 || args.length === 3 * 3) { + // Handle the legacy case where all bezier control points are provided + // at once. We'll translate them into 3 individual calls. + const stride = args.length / 3; + + const prevOrder = this._renderer.bezierOrder(); + for (let i = 0; i < args.length; i += stride) { + this._renderer.bezierVertex(...args.slice(i, i + stride)); } + this._renderer.bezierOrder(prevOrder); + } else { + this._renderer.bezierVertex(...args); } - return this; }; /** diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 034c3904f5..81c8891b34 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -543,10 +543,10 @@ function primitives3D(p5, fn){ * * function setup() { * createCanvas(300, 300, WEBGL); - * - * describe('A sphere with red stroke and a red, wavy line on a gray background.'); + * + * describe('A sphere with red stroke and a red, wavy line on a gray background.'); * } - * + * * function draw() { * background(128); * strokeMode(FULL); // Enables detailed rendering with caps, joins, and stroke color. @@ -554,8 +554,8 @@ function primitives3D(p5, fn){ * strokeWeight(1); * translate(0, -50, 0); * sphere(50); - * pop(); - * + * pop(); + * * noFill(); * strokeWeight(15); * beginShape(); @@ -566,15 +566,15 @@ function primitives3D(p5, fn){ * } * *
- * + * *
* * function setup() { * createCanvas(300, 300, WEBGL); - * - * describe('A sphere with red stroke and a wavy line without full curve decorations without caps and color on a gray background.'); + * + * describe('A sphere with red stroke and a wavy line without full curve decorations without caps and color on a gray background.'); * } - * + * * function draw() { * background(128); * strokeMode(SIMPLE); // Enables simple rendering without caps, joins, and stroke color. @@ -582,8 +582,8 @@ function primitives3D(p5, fn){ * strokeWeight(1); * translate(0, -50, 0); * sphere(50); - * pop(); - * + * pop(); + * * noFill(); * strokeWeight(15); * beginShape(); @@ -595,7 +595,7 @@ function primitives3D(p5, fn){ * *
*/ - + fn.strokeMode = function (mode) { if (mode === undefined) { return this._renderer._simpleLines ? constants.SIMPLE : constants.FULL; @@ -2699,7 +2699,7 @@ function primitives3D(p5, fn){ return this; }; - RendererGL.prototype.bezierVertex = function(...args) { + /*RendererGL.prototype.bezierVertex = function(...args) { if (this.shapeBuilder._bezierVertex.length === 0) { throw Error('vertex() must be used once before calling bezierVertex()'); } else { @@ -3250,7 +3250,7 @@ function primitives3D(p5, fn){ } } } - }; + };*/ RendererGL.prototype.image = function( img, @@ -3659,11 +3659,11 @@ function primitives3D(p5, fn){ this, 1, 0, - 1, + 1, detailX, detailY, - cap, - false + cap, + false ); }, this); if (detailX <= 24 && detailY <= 16) { @@ -3742,6 +3742,108 @@ function primitives3D(p5, fn){ } this._drawGeometryScaled(this.geometryBufferCache.getGeometryByID(gid), radius, radius, radius); } + + /** + * Sets the number of segments used to draw spline curves in WebGL mode. + * + * In WebGL mode, smooth shapes are drawn using many flat segments. Adding + * more flat segments makes shapes appear smoother. + * + * The parameter, `detail`, is the number of segments to use while drawing a + * spline curve. For example, calling `curveDetail(5)` will use 5 segments to + * draw curves with the curve() function. By + * default,`detail` is 20. + * + * Note: `curveDetail()` has no effect in 2D mode. + * + * @method curveDetail + * @param {Number} resolution number of segments to use. Defaults to 20. + * @chainable + * + * @example + *
+ * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Draw a black spline curve. + * noFill(); + * strokeWeight(1); + * stroke(0); + * curve(5, 26, 73, 24, 73, 61, 15, 65); + * + * // Draw red spline curves from the anchor points to the control points. + * stroke(255, 0, 0); + * curve(5, 26, 5, 26, 73, 24, 73, 61); + * curve(73, 24, 73, 61, 15, 65, 15, 65); + * + * // Draw the anchor points in black. + * strokeWeight(5); + * stroke(0); + * point(73, 24); + * point(73, 61); + * + * // Draw the control points in red. + * stroke(255, 0, 0); + * point(5, 26); + * point(15, 65); + * + * describe( + * 'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.' + * ); + * } + * + *
+ * + *
+ * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * background(200); + * + * // Set the curveDetail() to 3. + * curveDetail(3); + * + * // Draw a black spline curve. + * noFill(); + * strokeWeight(1); + * stroke(0); + * curve(-45, -24, 0, 23, -26, 0, 23, 11, 0, -35, 15, 0); + * + * // Draw red spline curves from the anchor points to the control points. + * stroke(255, 0, 0); + * curve(-45, -24, 0, -45, -24, 0, 23, -26, 0, 23, 11, 0); + * curve(23, -26, 0, 23, 11, 0, -35, 15, 0, -35, 15, 0); + * + * // Draw the anchor points in black. + * strokeWeight(5); + * stroke(0); + * point(23, -26); + * point(23, 11); + * + * // Draw the control points in red. + * stroke(255, 0, 0); + * point(-45, -24); + * point(-35, 15); + * + * describe( + * 'A gray square with a jagged curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.' + * ); + * } + * + *
+ */ + fn.curveDetail = function(d) { + if (!(this._renderer instanceof RendererGL)) { + throw new Error( + 'curveDetail() only works in WebGL mode. Did you mean to call createCanvas(width, height, WEBGL)?' + ); + } + return this._renderer.curveDetail(d); + }; } export default primitives3D; diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index f405d80e78..8ecf6bf87f 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -311,6 +311,8 @@ class RendererGL extends Renderer { this.states.userPointShader = undefined; this.states.userImageShader = undefined; + this.states.curveDetail = 0.5; + // Used by beginShape/endShape functions to construct a p5.Geometry this.shapeBuilder = new ShapeBuilder(this); @@ -360,16 +362,6 @@ class RendererGL extends Renderer { this._curveTightness = 6; - // lookUpTable for coefficients needed to be calculated for bezierVertex, same are used for curveVertex - this._lookUpTableBezier = []; - // lookUpTable for coefficients needed to be calculated for quadraticVertex - this._lookUpTableQuadratic = []; - - // current curveDetail in the Bezier lookUpTable - this._lutBezierDetail = 0; - // current curveDetail in the Quadratic lookUpTable - this._lutQuadraticDetail = 0; - this.fontInfos = {}; @@ -452,8 +444,18 @@ class RendererGL extends Renderer { // this.shapeBuilder.beginShape(...args); } + curveDetail(d) { + if (d === undefined) { + return this.states.curveDetail; + } else { + this.states.curveDetail = d; + } + } + drawShape(shape) { - const visitor = new PrimitiveToVerticesConverter(); + const visitor = new PrimitiveToVerticesConverter({ + curveDetail: this.states.curveDetail, + }); shape.accept(visitor); this.shapeBuilder.constructFromContours(shape, visitor.contours); From 22e4450de3a22f341574cb6d81409541abbb7a83 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Wed, 4 Dec 2024 19:44:57 -0500 Subject: [PATCH 071/111] Add spline end modes --- preview/index.html | 15 ++- src/core/constants.js | 28 ++++- src/core/p5.Renderer.js | 21 +++- src/shape/custom_shapes.js | 208 ++++++++++++++++++++++++++++--------- 4 files changed, 210 insertions(+), 62 deletions(-) diff --git a/preview/index.html b/preview/index.html index 08f73ba32c..b7353b54bb 100644 --- a/preview/index.html +++ b/preview/index.html @@ -31,14 +31,13 @@ p.stroke('white'); p.strokeWeight(10); p.beginShape(); - p.vertex(50, 50); - p.vertex(100, 50); - p.bezierOrder(3); - // p.bezierVertex(100, 100, 50, 100, 70, 70); - p.bezierVertex(100, 100); - p.bezierVertex(50, 100); - p.bezierVertex(70, 70); - p.endShape(p.CLOSE); + p.splineEnds(p.JOIN); + p.splineVertex(50, 50); + p.splineVertex(100, 50); + p.splineVertex(100, 100); + p.splineVertex(50, 100); + p.splineVertex(70, 70); + p.endShape(); // p.CLOSE }; }; diff --git a/src/core/constants.js b/src/core/constants.js index 1836bb5213..e847744702 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -1342,4 +1342,30 @@ export const HALF_FLOAT = 'half-float'; * @property {RGBA} RGBA * @final */ -export const RGBA = 'rgba'; \ No newline at end of file +export const RGBA = 'rgba'; + +/** + * The `splineEnds` mode where splines curve through + * their first and last points. + * @typedef {unique symbol} P2D + * @property {SHOW} SHOW + * @final + */ +export const SHOW = Symbol('show'); + +/** + * The `splineEnds` mode where the first and last points in a spline + * affect the direction of the curve, but are not rendered. + * @typedef {unique symbol} P2D + * @property {HIDE} HIDE + * @final + */ +export const HIDE = Symbol('hide'); + +/** + * The `splineEnds` mode where the spline loops back to its first point. + * @typedef {unique symbol} P2D + * @property {JOIN} JOIN + * @final + */ +export const JOIN = Symbol('join'); diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index 92811504de..d7e213219f 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -44,7 +44,8 @@ class Renderer { textBaseline: constants.BASELINE, textStyle: constants.NORMAL, textWrap: constants.WORD, - bezierOrder: 3 + bezierOrder: 3, + splineEnds: constants.SHOW }; this._pushPopStack = []; // NOTE: can use the length of the push pop stack instead @@ -116,6 +117,21 @@ class Renderer { this.currentShape.bezierVertex(position, textureCoordinates); } + splineEnds(mode) { + if (mode === undefined) { + return this.states.splineEnds; + } else { + this.states.splineEnds = mode; + } + this.updateShapeProperties(); + } + + splineVertex(x, y, z = 0, u = 0, v = 0) { + const position = new Vector(x, y, z); + const textureCoordinates = new Vector(u, v); + this.currentShape.splineVertex(position, textureCoordinates); + } + curveDetail(d) { if (d === undefined) { return this.states.curveDetail; @@ -250,7 +266,8 @@ class Renderer { } updateShapeProperties() { - this.currentShape.bezierOrder(this.states.bezierOrder) + this.currentShape.bezierOrder(this.states.bezierOrder); + this.currentShape.splineEnds(this.states.splineEnds); } updateShapeVertexProperties() { diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 99cde9731b..7fe426f28e 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -88,6 +88,9 @@ class Vertex { class ShapePrimitive { vertices; + _shape = null; + _primitivesIndex = null; + _contoursIndex = null; constructor(...vertices) { if (this.constructor === ShapePrimitive) { @@ -109,6 +112,14 @@ class ShapePrimitive { throw new Error('Getter vertexCapacity must be implemented.'); } + get _firstInterpolatedVertex() { + return this.startVertex(); + } + + get canOverrideAnchor() { + return false; + } + accept(visitor) { throw new Error('Method accept() must be implemented.'); } @@ -124,33 +135,56 @@ class ShapePrimitive { if (lastContour.primitives.length === 0) { lastContour.primitives.push(this); - return; - } - - // last primitive in shape - let lastPrimitive = shape.at(-1, -1); - let hasSameType = lastPrimitive instanceof this.constructor; - let spareCapacity = lastPrimitive.vertexCapacity - - lastPrimitive.vertexCount; + } else { + // last primitive in shape + let lastPrimitive = shape.at(-1, -1); + let hasSameType = lastPrimitive instanceof this.constructor; + let spareCapacity = lastPrimitive.vertexCapacity - + lastPrimitive.vertexCount; - // this primitive - let pushableVertices; - let remainingVertices; + // this primitive + let pushableVertices; + let remainingVertices; - if (hasSameType && spareCapacity > 0) { + if (hasSameType && spareCapacity > 0) { - pushableVertices = this.vertices.splice(0, spareCapacity); - remainingVertices = this.vertices; - lastPrimitive.vertices.push(...pushableVertices); + pushableVertices = this.vertices.splice(0, spareCapacity); + remainingVertices = this.vertices; + lastPrimitive.vertices.push(...pushableVertices); - if (remainingVertices.length > 0) { + if (remainingVertices.length > 0) { + lastContour.primitives.push(this); + } + } + else { lastContour.primitives.push(this); } } - else { - lastContour.primitives.push(this); + + // if primitive itself was added + // (i.e. its individual vertices weren't all added to an existing primitive) + // give it a reference to the shape and store its location within the shape + if (this.shouldAddToShape) { + let lastContour = shape.at(-1); + this._primitivesIndex = lastContour.primitives.length - 1; + this._contoursIndex = shape.contours.length - 1; + this._shape = shape; } } + + get shouldAddToShape() { + return false; + } + + get _nextPrimitive() { + return this._belongsToShape ? + this._shape.at(this._contoursIndex, this._primitivesIndex + 1) : + null; + } + + get _belongsToShape() { + return this._shape !== null; + } } class Contour { @@ -188,6 +222,10 @@ class Anchor extends ShapePrimitive { return this.#vertexCapacity; } + get shouldAddToShape() { + return true; + } + accept(visitor) { visitor.visitAnchor(this); } @@ -199,10 +237,6 @@ class Anchor extends ShapePrimitive { // abstract class class Segment extends ShapePrimitive { - _primitivesIndex = null; - _contoursIndex = null; - _shape = null; - constructor(...vertices) { super(...vertices); if (this.constructor === Segment) { @@ -210,22 +244,8 @@ class Segment extends ShapePrimitive { } } - addToShape(shape) { - super.addToShape(shape); - - // if primitive itself was added - // (i.e. its individual vertices weren't all added to an existing primitive) - // give it a reference to the shape and store its location within the shape - if (this.vertices.length > 0) { - let lastContour = shape.at(-1); - this._primitivesIndex = lastContour.primitives.length - 1; - this._contoursIndex = shape.contours.length - 1; - this._shape = shape; - } - } - - get _belongsToShape() { - return this._shape !== null; + get shouldAddToShape() { + return this.vertices.length > 0; } // segments in a shape always have a predecessor @@ -301,6 +321,7 @@ to interpolated endpoints (a breaking change) */ class SplineSegment extends Segment { #vertexCapacity = Infinity; + _splineEnds = constants.SHOW; constructor(...vertices) { super(...vertices); @@ -318,14 +339,22 @@ class SplineSegment extends Segment { return this._previousPrimitive instanceof Segment; } + get canOverrideAnchor() { + return this._splineEnds === constants.HIDE; + } + // assuming for now that the first interpolated vertex is always // the second vertex passed to splineVertex() // if this spline segment doesn't follow another segment, // the first vertex is in an anchor get _firstInterpolatedVertex() { - return this._comesAfterSegment ? - this.vertices[1] : - this.vertices[0]; + if (this._splineEnds === constants.HIDE) { + return this._comesAfterSegment ? + this.vertices[1] : + this.vertices[0]; + } else { + return this.vertices[0]; + } } get _chainedToSegment() { @@ -343,6 +372,9 @@ class SplineSegment extends Segment { // doesn't line up with end of last segment addToShape(shape) { super.addToShape(shape); + this._splineEnds = shape._splineEnds; + + if (this.splineEnds !== constants.HIDE) return; let verticesPushed = !this._belongsToShape; let lastPrimitive = shape.at(-1, -1); @@ -375,7 +407,36 @@ class SplineSegment extends Segment { // override method on base class getEndVertex() { - return this.vertices.at(-2); + if (this._splineEnds === constants.SHOW) { + return super.getEndVertex() + } else if (this._splineEnds === constants.HIDE) { + return this.vertices.at(-2); + } else { + return this.getStartVertex(); + } + } + + getControlPoints() { + let points = []; + + if (this._comesAfterSegment) { + points.push(this.getStartVertex()); + } + + for (const vertex of this.vertices) { + points.push(vertex); + } + + const prevVertex = this.getStartVertex() + if (this._splineEnds === constants.SHOW) { + points.unshift(prevVertex); + points.push(this.vertices.at(-1)); + } else if (this._splineEnds === constants.JOIN) { + points.unshift(this.vertices.at(-1), prevVertex); + points.push(prevVertex, this.vertices.at(0)); + } + + return points; } } @@ -507,6 +568,7 @@ class Shape { _splineTightness = 0; kind = null; contours = []; + _splineEnds = constants.SHOW; constructor( vertexProperties, @@ -574,6 +636,10 @@ class Shape { this.#bezierOrder = order; } + splineEnds(mode) { + this._splineEnds = mode; + } + splineTightness(tightness) { this._splineTightness = tightness; } @@ -627,7 +693,7 @@ class Shape { this.#generalVertex('bezierVertex', position, textureCoordinates); } - splineVertex() { + splineVertex(position, textureCoordinates) { this.#generalVertex('splineVertex', position, textureCoordinates); } @@ -766,13 +832,14 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { } visitSplineSegment(splineSegment) { let shape = splineSegment._shape; - let flatVertices = []; + let flatVertices = splineSegment + .getControlPoints() + .flatMap((v) => [v.position.x, v.position.y]); - for (const vertex of splineSegment.vertices) { - flatVertices.push(vertex.position.x, vertex.position.y); - } - - if (!splineSegment._comesAfterSegment) { + if ( + splineSegment._splineEnds === constants.HIDE && + !splineSegment._comesAfterSegment + ) { let startVertex = splineSegment._firstInterpolatedVertex; this.path.moveTo(startVertex.position.x, startVertex.position.y); } @@ -799,7 +866,14 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { visitAnchor(anchor) { this.contours.push([]); - this.lastContour().push(anchor.getEndVertex()); + // Weird edge case: if the next segment is a spline, we might + // need to jump to a different vertex. + const next = anchor._nextPrimitive; + if (next?.canOverrideAnchor) { + this.lastContour().push(next._firstInterpolatedVertex); + } else { + this.lastContour().push(anchor.getEndVertex()); + } } visitLineSegment(lineSegment) { this.lastContour().push(lineSegment.getEndVertex()); @@ -809,6 +883,11 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { // Currently just returning the control points for testing. this.lastContour().push(...bezierSegment.vertices.slice(0, bezierSegment.order)); } + visitSplineSegment(splineSegment) { + // TODO: convert these to bezier and then do whatever we will also + // be doing in visitBezierSegment + this.lastContour().push(...splineSegment.getControlPoints().slice(1, -1)); + } } class PointAtLengthGetter extends PrimitiveVisitor { @@ -1117,9 +1196,36 @@ function customShapes(p5, fn) { // ---- FUNCTIONS ---- + /** + * TODO: documentation + */ fn.bezierOrder = function(order) { return this._renderer.bezierOrder(order); - } + }; + + /** + * TODO: documentation + */ + fn.splineVertex = function(...args) { + let x = 0, y = 0, z = 0, u = 0, v = 0; + if (args.length === 2) { + [x, y] = args; + } else if (args.length === 4) { + [x, y, u, v] = args; + } else if (args.length === 3) { + [x, y, z] = args; + } else if (args.length === 5) { + [x, y, z, u, v] = args; + } + this._renderer.splineVertex(x, y, z, u, v); + }; + + /** + * TODO: documentation + */ + fn.splineEnds = function(mode) { + return this._renderer.splineEnds(mode); + }; // Note: Code is commented out for now, to avoid conflicts with the existing implementation. From dba73b3f5f1aeeab49d86b1ef20b097fb1ff9e35 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Wed, 4 Dec 2024 19:55:31 -0500 Subject: [PATCH 072/111] Update warning check --- preview/index.html | 5 +++-- src/shape/custom_shapes.js | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/preview/index.html b/preview/index.html index b7353b54bb..df30d6fbee 100644 --- a/preview/index.html +++ b/preview/index.html @@ -31,12 +31,13 @@ p.stroke('white'); p.strokeWeight(10); p.beginShape(); - p.splineEnds(p.JOIN); - p.splineVertex(50, 50); + p.splineEnds(p.HIDE); + p.vertex(50, 50); p.splineVertex(100, 50); p.splineVertex(100, 100); p.splineVertex(50, 100); p.splineVertex(70, 70); + p.vertex(50, 50); p.endShape(); // p.CLOSE }; }; diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 7fe426f28e..c2e924a87b 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -374,7 +374,7 @@ class SplineSegment extends Segment { super.addToShape(shape); this._splineEnds = shape._splineEnds; - if (this.splineEnds !== constants.HIDE) return; + if (this._splineEnds !== constants.HIDE) return; let verticesPushed = !this._belongsToShape; let lastPrimitive = shape.at(-1, -1); @@ -385,6 +385,8 @@ class SplineSegment extends Segment { expected to be at (${array2}).`; if (verticesPushed && + // Only check once the first interpolated vertex has been added + lastPrimitive.vertices.length === 2 && lastPrimitive._comesAfterSegment && !lastPrimitive._chainedToSegment ) { From d6080169638a4ccce289232031f9d602a828d60c Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Thu, 5 Dec 2024 08:15:56 -0500 Subject: [PATCH 073/111] Fix missing bits --- src/core/constants.js | 6 +++--- src/shape/vertex.js | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/constants.js b/src/core/constants.js index e847744702..a2619d7b2c 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -1347,7 +1347,7 @@ export const RGBA = 'rgba'; /** * The `splineEnds` mode where splines curve through * their first and last points. - * @typedef {unique symbol} P2D + * @typedef {unique symbol} SHOW * @property {SHOW} SHOW * @final */ @@ -1356,7 +1356,7 @@ export const SHOW = Symbol('show'); /** * The `splineEnds` mode where the first and last points in a spline * affect the direction of the curve, but are not rendered. - * @typedef {unique symbol} P2D + * @typedef {unique symbol} HIDE * @property {HIDE} HIDE * @final */ @@ -1364,7 +1364,7 @@ export const HIDE = Symbol('hide'); /** * The `splineEnds` mode where the spline loops back to its first point. - * @typedef {unique symbol} P2D + * @typedef {unique symbol} JOIN * @property {JOIN} JOIN * @final */ diff --git a/src/shape/vertex.js b/src/shape/vertex.js index b7bb2e441a..accb641166 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -810,6 +810,7 @@ function vertex(p5, fn){ const stride = args.length / 3; const prevOrder = this._renderer.bezierOrder(); + this._renderer.bezierOrder(3); for (let i = 0; i < args.length; i += stride) { this._renderer.bezierVertex(...args.slice(i, i + stride)); } From 7fd2eb23892acb266881f3a658c60686dc5af124 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Thu, 5 Dec 2024 18:10:32 -0500 Subject: [PATCH 074/111] Support WebGL custom vertex attributes --- preview/global/sketch.js | 57 +++++++++++++++++++++++++++++++----- src/shape/custom_shapes.js | 17 +++++++++++ src/shape/vertex.js | 2 +- src/webgl/ShapeBuilder.js | 60 +++++++++++++++++++++++--------------- src/webgl/material.js | 5 ++-- src/webgl/p5.Geometry.js | 3 ++ src/webgl/p5.RendererGL.js | 2 +- 7 files changed, 110 insertions(+), 36 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 00719764a7..4789f83f36 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,14 +1,55 @@ -console.log(p5); +const vertSrc = `#version 300 es + precision mediump float; + uniform mat4 uModelViewMatrix; + uniform mat4 uProjectionMatrix; + in vec3 aPosition; + in vec2 aOffset; + + void main(){ + vec4 positionVec4 = vec4(aPosition.xyz, 1.0); + positionVec4.xy += aOffset; + gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4; + } +`; + +const fragSrc = `#version 300 es + precision mediump float; + out vec4 outColor; + void main(){ + outColor = vec4(0.0, 1.0, 1.0, 1.0); + } +`; + +let myShader; function setup(){ - createCanvas(200, 200); + createCanvas(100, 100, WEBGL); + + // Create and use the custom shader. + myShader = createShader(vertSrc, fragSrc); + + describe('A wobbly, cyan circle on a gray background.'); } -async function draw(){ - background(0, 50, 50); - circle(100, 100, 50); +function draw(){ + // Set the styles + background(125); + noStroke(); + shader(myShader); + + // Draw the circle. + beginShape(); + for (let i = 0; i < 30; i++){ + const x = 40 * cos(i/30 * TWO_PI); + const y = 40 * sin(i/30 * TWO_PI); + + // Apply some noise to the coordinates. + const xOff = 10 * noise(x + millis()/1000) - 5; + const yOff = 10 * noise(y + millis()/1000) - 5; - fill('white'); - textSize(30); - text('hello', 10, 30); + // Apply these noise values to the following vertex. + vertexProperty('aOffset', [xOff, yOff]); + vertex(x, y); + } + endShape(CLOSE); } diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index c2e924a87b..b13cd885a2 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -571,6 +571,7 @@ class Shape { kind = null; contours = []; _splineEnds = constants.SHOW; + userVertexProperties = null; constructor( vertexProperties, @@ -626,6 +627,22 @@ class Shape { this.#vertexProperties = { ...this.#initialVertexProperties }; this.kind = null; this.contours = []; + this.userVertexProperties = null; + } + + vertexProperty(name, data) { + this.userVertexProperties = this.userVertexProperties || {}; + const key = this.vertexPropertyKey(name); + if (!this.userVertexProperties[key]) { + this.userVertexProperties[key] = data.length ? data.length : 1; + } + this.#vertexProperties[key] = data; + } + vertexPropertyName(key) { + return key.replace(/Src$/, ''); + } + vertexPropertyKey(name) { + return name + 'Src'; } /* diff --git a/src/shape/vertex.js b/src/shape/vertex.js index accb641166..eece9ad0c5 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -2398,7 +2398,7 @@ function vertex(p5, fn){ * fill(j/rows*255, j/cols*255, 255); * * // Calculate the distance from the corner of each cell to the mouse. - * let distance = dist(x1,y1, mouseX, mouseY); + * let distance = dist(x, y, mouseX, mouseY); * * // Send the distance to the shader. * vertexProperty('aDistance', min(distance, 100)); diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index c113f3de83..301dd19672 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -50,7 +50,7 @@ export class ShapeBuilder { } constructFromContours(shape, contours) { - if (this._useUserVertexProperties === true){ + if (this._useUserVertexProperties){ this._resetUserVertexProperties(); } this.geometry.reset(); @@ -58,6 +58,23 @@ export class ShapeBuilder { this.shapeMode = constants.TESS; const shouldProcessEdges = !!this.renderer.states.strokeColor; + const userVertexPropertyHelpers = {}; + if (shape.userVertexProperties) { + this._useUserVertexProperties = true; + for (const key in shape.userVertexProperties) { + const name = shape.vertexPropertyName(key); + const prop = this.geometry._userVertexPropertyHelper(name, [], shape.userVertexProperties[key]); + userVertexPropertyHelpers[key] = prop; + this.tessyVertexSize += prop.getDataSize(); + this.bufferStrides[prop.getSrcName()] = prop.getDataSize(); + this.renderer.buffers.user.push( + new RenderBuffer(prop.getDataSize(), prop.getSrcName(), prop.getDstName(), name, this.renderer) + ); + } + } else { + this._useUserVertexProperties = false; + } + let idx = -1; for (const contour of contours) { this.contourIndices.push(this.geometry.vertices.length); @@ -65,10 +82,25 @@ export class ShapeBuilder { for (const vertex of contour) { idx++ this.geometry.vertices.push(vertex.position); - this.geometry.vertexNormals.push(vertex.normal); + this.geometry.vertexNormals.push(vertex.normal || new Vector(0, 0, 0)); this.geometry.uvs.push(vertex.textureCoordinates.x, vertex.textureCoordinates.y); - this.geometry.vertexColors.push(...vertex.fill.array()); - this.geometry.vertexStrokeColors.push(...vertex.stroke.array()); + if (this.renderer.states.fillColor) { + this.geometry.vertexColors.push(...vertex.fill.array()); + } else { + this.geometry.vertexColors.push(0, 0, 0, 0); + } + if (this.renderer.states.strokeColor) { + this.geometry.vertexStrokeColors.push(...vertex.stroke.array()); + } else { + this.geometry.vertexColors.push(0, 0, 0, 0); + } + for (const key in userVertexPropertyHelpers) { + const prop = userVertexPropertyHelpers[key]; + if (key in vertex) { + prop.setCurrentData(vertex[key]); + } + prop.pushCurrentData(); + } if (shouldProcessEdges && prevIdx >= 0) { // TODO: handle other shape modes this.geometry.edges.push([prevIdx, idx]); @@ -259,26 +291,6 @@ export class ShapeBuilder { return this; } - vertexProperty(propertyName, data) { - if (!this._useUserVertexProperties) { - this._useUserVertexProperties = true; - this.geometry.userVertexProperties = {}; - } - const propertyExists = this.geometry.userVertexProperties[propertyName]; - let prop; - if (propertyExists){ - prop = this.geometry.userVertexProperties[propertyName]; - } else { - prop = this.geometry._userVertexPropertyHelper(propertyName, data); - this.tessyVertexSize += prop.getDataSize(); - this.bufferStrides[prop.getSrcName()] = prop.getDataSize(); - this.renderer.buffers.user.push( - new RenderBuffer(prop.getDataSize(), prop.getSrcName(), prop.getDstName(), propertyName, this.renderer) - ); - } - prop.setCurrentData(data); - } - _resetUserVertexProperties() { const properties = this.geometry.userVertexProperties; for (const propName in properties){ diff --git a/src/webgl/material.js b/src/webgl/material.js index fa70667de6..4a42d117fe 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -9,6 +9,7 @@ import * as constants from '../core/constants'; import { RendererGL } from './p5.RendererGL'; import { Shader } from './p5.Shader'; import { request } from '../io/files'; +import { Color } from '../color/p5.Color'; function material(p5, fn){ /** @@ -3625,7 +3626,7 @@ function material(p5, fn){ this.states.drawMode = constants.TEXTURE; this.states._useNormalMaterial = false; this.states._tex = tex; - this.states.fillColor = true; + this.states.fillColor = new Color(255); }; RendererGL.prototype.normalMaterial = function(...args) { @@ -3634,7 +3635,7 @@ function material(p5, fn){ this.states._useEmissiveMaterial = false; this.states._useNormalMaterial = true; this.states.curFillColor = [1, 1, 1, 1]; - this.states.fillColor = true; + this.states.fillColor = new Color(255); this.states.strokeColor = null; } diff --git a/src/webgl/p5.Geometry.js b/src/webgl/p5.Geometry.js index afaf4c3b7c..89701ced9c 100644 --- a/src/webgl/p5.Geometry.js +++ b/src/webgl/p5.Geometry.js @@ -1818,6 +1818,9 @@ class Geometry { return this.name; }, getCurrentData(){ + if (this.currentData === undefined) { + this.currentData = new Array(this.getDataSize()).fill(0); + } return this.currentData; }, getDataSize() { diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 8ecf6bf87f..a804242c72 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -518,7 +518,7 @@ class RendererGL extends Renderer { } vertexProperty(...args) { - this.shapeBuilder.vertexProperty(...args); + this.currentShape.vertexProperty(...args); } normal(xorv, y, z) { From b26ca0ff7e20c8860693f7b0361fa26f092a5ac1 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Thu, 5 Dec 2024 19:16:16 -0500 Subject: [PATCH 075/111] Implement WebGL bezier vertex subdivision --- preview/index.html | 20 +++--- src/shape/custom_shapes.js | 132 +++++++++++++++++++++++++++++++++++-- 2 files changed, 139 insertions(+), 13 deletions(-) diff --git a/preview/index.html b/preview/index.html index df30d6fbee..7025e9268d 100644 --- a/preview/index.html +++ b/preview/index.html @@ -21,24 +21,26 @@ const sketch = function (p) { p.setup = function () { - p.createCanvas(200, 200); // , p.WEBGL + p.createCanvas(200, 200, p.WEBGL); }; p.draw = function () { p.background(0, 50, 50); - //p.translate(-p.width/2, -p.height/2); + p.translate(-p.width/2, -p.height/2); p.fill('red'); p.stroke('white'); p.strokeWeight(10); p.beginShape(); - p.splineEnds(p.HIDE); p.vertex(50, 50); - p.splineVertex(100, 50); - p.splineVertex(100, 100); - p.splineVertex(50, 100); - p.splineVertex(70, 70); - p.vertex(50, 50); - p.endShape(); // p.CLOSE + p.vertex(100, 50); + p.bezierOrder(3); + // p.bezierVertex(100, 100, 50, 100, 70, 70); + p.bezierVertex(100, 100); + p.stroke('blue'); + p.bezierVertex(50, 100); + p.bezierVertex(70, 70); + p.stroke('white'); + p.endShape(p.CLOSE); }; }; diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index b13cd885a2..49a3072f7d 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -7,7 +7,9 @@ */ // REMINDER: remove .js extension (currently using it to run file locally) -import * as constants from '../core/constants.js'; +import { Color } from '../color/p5.Color'; +import { Vector } from '../math/p5.Vector'; +import * as constants from '../core/constants'; // ---- UTILITY FUNCTIONS ---- @@ -305,6 +307,18 @@ class BezierSegment extends Segment { return this.#vertexCapacity; } + #_hullLength; + hullLength() { + if (this.#_hullLength === undefined) { + let length = 0 + for (let i = 1; i < this.order; i++) { + length += this.vertices[i-1].position.dist(this.vertices[i].position); + } + this.#_hullLength = length; + } + return this.#_hullLength; + } + accept(visitor) { visitor.visitBezierSegment(this); } @@ -590,6 +604,103 @@ class Shape { } } + serializeToArray(val) { + if (val instanceof Number) { + return [val]; + } else if (val instanceof Array) { + return val; + } else if (val.array instanceof Function) { + return val.array(); + } else { + throw new Error(`Can't convert ${val} to array!`); + } + } + + vertexToArray(vertex) { + const array = []; + for (const key in this.#vertexProperties) { + if (this.userVertexProperties && key in this.userVertexProperties) continue; + const val = vertex[key]; + array.push(...this.serializeToArray(val)); + } + for (const key in this.userVertexProperties) { + if (key in vertex) { + array.push(...this.serializeToArray(vertex[key])); + } else { + array.push(...new Array(this.userVertexProperties[key]).fill(0)); + } + } + return array; + } + + hydrateValue(queue, original) { + if (original instanceof Number) { + return queue.shift(); + } else if (original instanceof Array) { + const array = []; + for (let i = 0; i < original.length; i++) { + array.push(queue.shift()); + } + // TODO: handle typed array here? Generally doesn't matter though + return array; + } else if (original instanceof Vector) { + return new Vector(queue.shift(), queue.shift(), queue.shift()); + } else if (original instanceof Color) { + const array = [queue.shift(), queue.shift(), queue.shift(), queue.shift()]; + return new Color( + array.map((v, i) => v * original.maxes[original.mode][i]), + original.mode, + original.maxes + ); + } + } + + arrayToVertex(array) { + const vertex = {}; + + for (const key in this.#vertexProperties) { + if (this.userVertexProperties && key in this.userVertexProperties) continue; + const original = this.#vertexProperties[key]; + vertex[key] = this.hydrateValue(array, original); + } + for (const key in this.userVertexProperties) { + const original = this.#vertexProperties[key]; + vertex[key] = this.hydrateValue(array, original); + } + return vertex; + } + + arrayScale(array, scale) { + return array.map((v) => v * scale); + } + + arraySum(first, ...rest) { + return first.map((v, i) => { + let result = v; + for (let j = 0; j < rest.length; j++) { + result += rest[j][i]; + } + return result; + }); + } + + evaluateCubicBezier([a, b, c, d], t) { + return this.arraySum( + this.arrayScale(a, Math.pow(1 - t, 3)), + this.arrayScale(b, 3 * Math.pow(1 - t, 2) * t), + this.arrayScale(c, 3 * (1 - t) * Math.pow(t, 2)), + this.arrayScale(d, Math.pow(t, 3)), + ); + } + + evaluateQuadraticBezier([a, b, c], t) { + return this.arraySum( + this.arrayScale(a, Math.pow(1 - t, 2)), + this.arrayScale(b, 2 * (1 - t) * t), + this.arrayScale(c, t * t), + ); + } + // TODO for at() method: // RENAME? @@ -898,9 +1009,22 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { this.lastContour().push(lineSegment.getEndVertex()); } visitBezierSegment(bezierSegment) { - // TODO: use this.curveDetail and actually evaluate the curve. - // Currently just returning the control points for testing. - this.lastContour().push(...bezierSegment.vertices.slice(0, bezierSegment.order)); + const contour = this.lastContour(); + const numPoints = Math.max(1, bezierSegment.hullLength() * this.curveDetail); + const vertexArrays = [ + bezierSegment.getStartVertex(), + ...bezierSegment.vertices + ].map((v) => bezierSegment._shape.vertexToArray(v)); + for (let i = 0; i < numPoints; i++) { + const t = (i + 1) / numPoints; + contour.push( + bezierSegment._shape.arrayToVertex( + bezierSegment.order === 3 + ? bezierSegment._shape.evaluateCubicBezier(vertexArrays, t) + : bezierSegment._shape.evaluateQuadraticBezier(vertexArrays, t) + ) + ) + } } visitSplineSegment(splineSegment) { // TODO: convert these to bezier and then do whatever we will also From 9cb0dda2c39074654cd3b96fd58fb733c58c4ffd Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Thu, 5 Dec 2024 20:06:17 -0500 Subject: [PATCH 076/111] Implement spline vertices for WebGL --- preview/index.html | 16 ++--- src/shape/custom_shapes.js | 136 ++++++++++++++++++++++++------------- src/webgl/p5.RendererGL.js | 2 +- 3 files changed, 96 insertions(+), 58 deletions(-) diff --git a/preview/index.html b/preview/index.html index 7025e9268d..728cb64d8c 100644 --- a/preview/index.html +++ b/preview/index.html @@ -31,16 +31,14 @@ p.stroke('white'); p.strokeWeight(10); p.beginShape(); + //p.splineEnds(p.HIDE); p.vertex(50, 50); - p.vertex(100, 50); - p.bezierOrder(3); - // p.bezierVertex(100, 100, 50, 100, 70, 70); - p.bezierVertex(100, 100); - p.stroke('blue'); - p.bezierVertex(50, 100); - p.bezierVertex(70, 70); - p.stroke('white'); - p.endShape(p.CLOSE); + p.splineVertex(100, 50); + p.splineVertex(100, 100); + p.splineVertex(50, 100); + p.splineVertex(70, 70); + p.vertex(50, 50); + p.endShape(); // p.CLOSE }; }; diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 49a3072f7d..12303ae077 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -12,37 +12,14 @@ import { Vector } from '../math/p5.Vector'; import * as constants from '../core/constants'; // ---- UTILITY FUNCTIONS ---- +function polylineLength(vertices) { + let length = 0 + for (let i = 1; i < vertices.length; i++) { + length += vertices[i-1].position.dist(vertices[i].position); + } + return length; +} -/* -catmullRomToBezier(vertices, tightness) - -Abbreviated description: -Converts a Catmull-Rom spline to a sequence of Bezier curves. - -Parameters: -vertices -> Array [x1, y1, x2, y2, ...] of at least four vertices -tightness -> Number affecting shape of curve - -Returns: -array of Bezier curves, each represented as [x1, y1, x2, y2, x3, y3] -*/ - -/* -TODO: -1. It seems p5 contains code for converting from Catmull-Rom to Bezier in at least two places: - -catmullRomToBezier() is based on code in the legacy endShape() function: -https://github.com/processing/p5.js/blob/1b66f097761d3c2057c0cec4349247d6125f93ca/src/core/p5.Renderer2D.js#L859C1-L886C1 - -A different conversion can be found elsewhere in p5: -https://github.com/processing/p5.js/blob/17304ce9e9ef3f967bd828102a51b62a2d39d4f4/src/typography/p5.Font.js#L1179 - -A more careful review and comparison of both implementations would be helpful. They're different. I put -catmullRomToBezier() together quickly without checking the math/algorithm, when I made the proof of concept -for the refactor. - -2. It may be possible to replace the code in p5.Font.js with the code here, to reduce duplication. -*/ function catmullRomToBezier(vertices, tightness) { let X0, Y0, X1, Y1, X2, Y2, X3, Y3; let s = 1 - tightness; @@ -310,11 +287,7 @@ class BezierSegment extends Segment { #_hullLength; hullLength() { if (this.#_hullLength === undefined) { - let length = 0 - for (let i = 1; i < this.order; i++) { - length += this.vertices[i-1].position.dist(this.vertices[i].position); - } - this.#_hullLength = length; + this.#_hullLength = polylineLength([this.getStartVertex(), ...this.vertices]); } return this.#_hullLength; } @@ -336,6 +309,7 @@ to interpolated endpoints (a breaking change) class SplineSegment extends Segment { #vertexCapacity = Infinity; _splineEnds = constants.SHOW; + _splineTightness = 0; constructor(...vertices) { super(...vertices); @@ -387,6 +361,7 @@ class SplineSegment extends Segment { addToShape(shape) { super.addToShape(shape); this._splineEnds = shape._splineEnds; + this._splineTightness = shape._splineTightness; if (this._splineEnds !== constants.HIDE) return; @@ -641,7 +616,6 @@ class Shape { for (let i = 0; i < original.length; i++) { array.push(queue.shift()); } - // TODO: handle typed array here? Generally doesn't matter though return array; } else if (original instanceof Vector) { return new Vector(queue.shift(), queue.shift(), queue.shift()); @@ -657,15 +631,16 @@ class Shape { arrayToVertex(array) { const vertex = {}; + const queue = [...array]; for (const key in this.#vertexProperties) { if (this.userVertexProperties && key in this.userVertexProperties) continue; const original = this.#vertexProperties[key]; - vertex[key] = this.hydrateValue(array, original); + vertex[key] = this.hydrateValue(queue, original); } for (const key in this.userVertexProperties) { const original = this.#vertexProperties[key]; - vertex[key] = this.hydrateValue(array, original); + vertex[key] = this.hydrateValue(queue, original); } return vertex; } @@ -684,6 +659,10 @@ class Shape { }); } + arrayMinus(a, b) { + return a.map((v, i) => v - b[i]); + } + evaluateCubicBezier([a, b, c, d], t) { return this.arraySum( this.arrayScale(a, Math.pow(1 - t, 3)), @@ -701,6 +680,55 @@ class Shape { ); } + /* + catmullRomToBezier(vertices, tightness) + + Abbreviated description: + Converts a Catmull-Rom spline to a sequence of Bezier curveTo points. + + Parameters: + vertices -> Array [v0, v1, v2, v3, ...] of at least four vertices + tightness -> Number affecting shape of curve + + Returns: + array of Bezier curveTo control points, each represented as [c1, c2, c3][] + + TODO: + 1. It seems p5 contains code for converting from Catmull-Rom to Bezier in at least two places: + + catmullRomToBezier() is based on code in the legacy endShape() function: + https://github.com/processing/p5.js/blob/1b66f097761d3c2057c0cec4349247d6125f93ca/src/core/p5.Renderer2D.js#L859C1-L886C1 + + A different conversion can be found elsewhere in p5: + https://github.com/processing/p5.js/blob/17304ce9e9ef3f967bd828102a51b62a2d39d4f4/src/typography/p5.Font.js#L1179 + + A more careful review and comparison of both implementations would be helpful. They're different. I put + catmullRomToBezier() together quickly without checking the math/algorithm, when I made the proof of concept + for the refactor. + + 2. It may be possible to replace the code in p5.Font.js with the code here, to reduce duplication. + */ + catmullRomToBezier(vertices, tightness) { + let s = 1 - tightness; + let bezArrays = []; + + for (let i = 0; i + 3 < vertices.length; i++) { + const [a, b, c, d] = vertices.slice(i, i + 4); + const bezB = this.arraySum( + b, + this.arrayScale(this.arrayMinus(c, a), s / 6) + ); + const bezC = this.arraySum( + c, + this.arrayScale(this.arrayMinus(b, d), s / 6) + ); + const bezD = c; + + bezArrays.push([bezB, bezC, bezD]); + } + return bezArrays; + } + // TODO for at() method: // RENAME? @@ -961,10 +989,7 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { } } visitSplineSegment(splineSegment) { - let shape = splineSegment._shape; - let flatVertices = splineSegment - .getControlPoints() - .flatMap((v) => [v.position.x, v.position.y]); + const shape = splineSegment._shape; if ( splineSegment._splineEnds === constants.HIDE && @@ -974,9 +999,12 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { this.path.moveTo(startVertex.position.x, startVertex.position.y); } - let bezierArrays = catmullRomToBezier(flatVertices, shape._splineTightness); + const arrayVertices = splineSegment.getControlPoints().map((v) => shape.vertexToArray(v)); + let bezierArrays = shape.catmullRomToBezier(arrayVertices, splineSegment._splineTightness) + .map((arr) => arr.map((vertArr) => shape.arrayToVertex(vertArr))); for (const array of bezierArrays) { - this.path.bezierCurveTo(...array); + const points = array.flatMap((vert) => [vert.position.x, vert.position.y]); + this.path.bezierCurveTo(...points); } } } @@ -1010,7 +1038,7 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { } visitBezierSegment(bezierSegment) { const contour = this.lastContour(); - const numPoints = Math.max(1, bezierSegment.hullLength() * this.curveDetail); + const numPoints = Math.max(1, Math.ceil(bezierSegment.hullLength() * this.curveDetail)); const vertexArrays = [ bezierSegment.getStartVertex(), ...bezierSegment.vertices @@ -1027,9 +1055,21 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { } } visitSplineSegment(splineSegment) { - // TODO: convert these to bezier and then do whatever we will also - // be doing in visitBezierSegment - this.lastContour().push(...splineSegment.getControlPoints().slice(1, -1)); + const shape = splineSegment._shape; + const contour = this.lastContour(); + + const arrayVertices = splineSegment.getControlPoints().map((v) => shape.vertexToArray(v)); + let bezierArrays = shape.catmullRomToBezier(arrayVertices, splineSegment._splineTightness) + let startVertex = shape.vertexToArray(splineSegment.getStartVertex()); + for (const array of bezierArrays) { + const bezierControls = [startVertex, ...array]; + const numPoints = Math.max(1, Math.ceil(polylineLength(bezierControls.map(v => shape.arrayToVertex(v))) * this.curveDetail)); + for (let i = 0; i < numPoints; i++) { + const t = (i + 1) / numPoints; + contour.push(shape.arrayToVertex(shape.evaluateCubicBezier(bezierControls, t))); + } + startVertex = array[2]; + } } } diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index a804242c72..7ff314bd2c 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -311,7 +311,7 @@ class RendererGL extends Renderer { this.states.userPointShader = undefined; this.states.userImageShader = undefined; - this.states.curveDetail = 0.5; + this.states.curveDetail = 1 / 4; // Used by beginShape/endShape functions to construct a p5.Geometry this.shapeBuilder = new ShapeBuilder(this); From 9b6745a21dbc9b0d21c7560c354b03b92c8f5ac1 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 7 Dec 2024 10:57:31 -0500 Subject: [PATCH 077/111] Only pass fill, stroke, and texture coordinates if they are supported --- src/core/p5.Renderer.js | 45 ++++---- src/shape/custom_shapes.js | 210 +++++++++++++++++++++++++++++++++++++ src/webgl/p5.RendererGL.js | 12 ++- 3 files changed, 238 insertions(+), 29 deletions(-) diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index b3e7c19aec..90e9cc5af2 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -64,7 +64,7 @@ class Renderer { this._clipInvert = false; this._curveTightness = 0; - this.currentShape = new Shape(this.vertexProperties()); + this.currentShape = new Shape(this.getCommonVertexProperties()); } remove() { @@ -122,7 +122,9 @@ class Renderer { bezierVertex(x, y, z = 0, u = 0, v = 0) { const position = new Vector(x, y, z); - const textureCoordinates = new Vector(u, v); + const textureCoordinates = this.getSupportedIndividualVertexProperties().textureCoordinates + ? new Vector(u, v) + : undefined; this.currentShape.bezierVertex(position, textureCoordinates); } @@ -137,7 +139,9 @@ class Renderer { splineVertex(x, y, z = 0, u = 0, v = 0) { const position = new Vector(x, y, z); - const textureCoordinates = new Vector(u, v); + const textureCoordinates = this.getSupportedIndividualVertexProperties().textureCoordinates + ? new Vector(u, v) + : undefined; this.currentShape.splineVertex(position, textureCoordinates); } @@ -163,27 +167,11 @@ class Renderer { throw new Error('Unimplemented') } - vertex(x, y) { - let z, u, v; - - // default to (x, y) mode: all other arguments assumed to be 0. - z = u = v = 0; - - if (arguments.length === 3) { - // (x, y, z) mode: (u, v) assumed to be 0. - z = arguments[2]; - } else if (arguments.length === 4) { - // (x, y, u, v) mode: z assumed to be 0. - u = arguments[2]; - v = arguments[3]; - } else if (arguments.length === 5) { - // (x, y, z, u, v) mode - z = arguments[2]; - u = arguments[3]; - v = arguments[4]; - } + vertex(x, y, z = 0, u = 0, v = 0) { const position = new Vector(x, y, z); - const textureCoordinates = new Vector(u, v); + const textureCoordinates = this.getSupportedIndividualVertexProperties().textureCoordinates + ? new Vector(u, v) + : undefined; this.currentShape.vertex(position, textureCoordinates); } @@ -267,10 +255,13 @@ class Renderer { this.states.strokeColor = null; } - vertexProperties() { + getCommonVertexProperties() { + return {} + } + + getSupportedIndividualVertexProperties() { return { - stroke: this.states.strokeColor, - fill: this.states.fillColor, + textureCoordinates: false, } } @@ -280,7 +271,7 @@ class Renderer { } updateShapeVertexProperties() { - const props = this.vertexProperties(); + const props = this.getCommonVertexProperties(); for (const key in props) { this.currentShape[key](props[key]); } diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 12303ae077..91638c1eb9 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -1410,6 +1410,216 @@ function customShapes(p5, fn) { return this._renderer.splineEnds(mode); }; + /** + * Adds a vertex to a custom shape. + * + * `vertex()` sets the coordinates of vertices drawn between the + * beginShape() and + * endShape() functions. + * + * The first two parameters, `x` and `y`, set the x- and y-coordinates of the + * vertex. + * + * The third parameter, `z`, is optional. It sets the z-coordinate of the + * vertex in WebGL mode. By default, `z` is 0. + * + * The fourth and fifth parameters, `u` and `v`, are also optional. They set + * the u- and v-coordinates for the vertex’s texture when used with + * endShape(). By default, `u` and `v` are both 0. + * + * @method vertex + * @param {Number} x x-coordinate of the vertex. + * @param {Number} y y-coordinate of the vertex. + * @chainable + * + * @example + *
+ * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the shape. + * strokeWeight(3); + * + * // Start drawing the shape. + * // Only draw the vertices. + * beginShape(POINTS); + * + * // Add the vertices. + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * + * // Stop drawing the shape. + * endShape(); + * + * describe('Four black dots that form a square are drawn on a gray background.'); + * } + * + *
+ * + *
+ * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Start drawing the shape. + * beginShape(); + * + * // Add vertices. + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * + * // Stop drawing the shape. + * endShape(CLOSE); + * + * describe('A white square on a gray background.'); + * } + * + *
+ * + *
+ * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * background(200); + * + * // Start drawing the shape. + * beginShape(); + * + * // Add vertices. + * vertex(-20, -30, 0); + * vertex(35, -30, 0); + * vertex(35, 25, 0); + * vertex(-20, 25, 0); + * + * // Stop drawing the shape. + * endShape(CLOSE); + * + * describe('A white square on a gray background.'); + * } + * + *
+ * + *
+ * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A white square spins around slowly on a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Rotate around the y-axis. + * rotateY(frameCount * 0.01); + * + * // Start drawing the shape. + * beginShape(); + * + * // Add vertices. + * vertex(-20, -30, 0); + * vertex(35, -30, 0); + * vertex(35, 25, 0); + * vertex(-20, 25, 0); + * + * // Stop drawing the shape. + * endShape(CLOSE); + * } + * + *
+ * + *
+ * + * let img; + * + * // Load an image to apply as a texture. + * function preload() { + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A photograph of a ceiling rotates slowly against a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Rotate around the y-axis. + * rotateY(frameCount * 0.01); + * + * // Style the shape. + * noStroke(); + * + * // Apply the texture. + * texture(img); + * textureMode(NORMAL); + * + * // Start drawing the shape + * beginShape(); + * + * // Add vertices. + * vertex(-20, -30, 0, 0, 0); + * vertex(35, -30, 0, 1, 0); + * vertex(35, 25, 0, 1, 1); + * vertex(-20, 25, 0, 0, 1); + * + * // Stop drawing the shape. + * endShape(); + * } + * + *
+ */ + /** + * @method vertex + * @param {Number} x + * @param {Number} y + * @param {Number} [z] z-coordinate of the vertex. Defaults to 0. + * @chainable + */ + /** + * @method vertex + * @param {Number} x + * @param {Number} y + * @param {Number} [z] + * @param {Number} [u] u-coordinate of the vertex's texture. Defaults to 0. + * @param {Number} [v] v-coordinate of the vertex's texture. Defaults to 0. + * @chainable + */ + fn.vertex = function(x, y) { + let z, u, v; + + // default to (x, y) mode: all other arguments assumed to be 0. + z = u = v = 0; + + if (arguments.length === 3) { + // (x, y, z) mode: (u, v) assumed to be 0. + z = arguments[2]; + } else if (arguments.length === 4) { + // (x, y, u, v) mode: z assumed to be 0. + u = arguments[2]; + v = arguments[3]; + } else if (arguments.length === 5) { + // (x, y, z, u, v) mode + z = arguments[2]; + u = arguments[3]; + v = arguments[4]; + } + this._renderer.vertex(x, y, z, u, v); + return; + } + // Note: Code is commented out for now, to avoid conflicts with the existing implementation. /** diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 7ff314bd2c..ea784f946c 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1043,13 +1043,21 @@ class RendererGL extends Renderer { this.states.curStrokeColor = this.states.strokeColor._array; } - vertexProperties() { + getCommonVertexProperties() { return { - ...super.vertexProperties(), + ...super.getCommonVertexProperties(), + stroke: this.states.strokeColor, + fill: this.states.fillColor, normal: this.states._currentNormal, } } + getSupportedIndividualVertexProperties() { + return { + textureCoordinates: true, + } + } + strokeCap(cap) { this.curStrokeCap = cap; } From 2ea2f77440679e6e3bf6364925734fce9ec3ae16 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 7 Dec 2024 11:16:26 -0500 Subject: [PATCH 078/111] Handle endShape(CLOSE) with a stroke join --- preview/index.html | 8 +- src/shape/custom_shapes.js | 43 +++++--- src/shape/vertex.js | 220 ------------------------------------- 3 files changed, 34 insertions(+), 237 deletions(-) diff --git a/preview/index.html b/preview/index.html index 728cb64d8c..28de16adb0 100644 --- a/preview/index.html +++ b/preview/index.html @@ -21,12 +21,13 @@ const sketch = function (p) { p.setup = function () { - p.createCanvas(200, 200, p.WEBGL); + p.createCanvas(200, 200); + p.strokeJoin(p.MITER); }; p.draw = function () { p.background(0, 50, 50); - p.translate(-p.width/2, -p.height/2); + //p.translate(-p.width/2, -p.height/2); p.fill('red'); p.stroke('white'); p.strokeWeight(10); @@ -37,8 +38,7 @@ p.splineVertex(100, 100); p.splineVertex(50, 100); p.splineVertex(70, 70); - p.vertex(50, 50); - p.endShape(); // p.CLOSE + p.endShape(p.CLOSE); }; }; diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 91638c1eb9..8f00c15e62 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -70,6 +70,7 @@ class ShapePrimitive { _shape = null; _primitivesIndex = null; _contoursIndex = null; + isClosing = false; constructor(...vertices) { if (this.constructor === ShapePrimitive) { @@ -149,6 +150,8 @@ class ShapePrimitive { this._contoursIndex = shape.contours.length - 1; this._shape = shape; } + + return shape.at(-1, -1); } get shouldAddToShape() { @@ -164,6 +167,14 @@ class ShapePrimitive { get _belongsToShape() { return this._shape !== null; } + + handlesClose() { + return false; + } + + close(vertex) { + throw new Error('Unimplemented!'); + } } class Contour { @@ -359,11 +370,11 @@ class SplineSegment extends Segment { // extend addToShape() with a warning in case second vertex // doesn't line up with end of last segment addToShape(shape) { - super.addToShape(shape); + const added = super.addToShape(shape); this._splineEnds = shape._splineEnds; this._splineTightness = shape._splineTightness; - if (this._splineEnds !== constants.HIDE) return; + if (this._splineEnds !== constants.HIDE) return added; let verticesPushed = !this._belongsToShape; let lastPrimitive = shape.at(-1, -1); @@ -840,11 +851,12 @@ class Shape { vertex ); - primitiveShape.addToShape(this); + return primitiveShape.addToShape(this); } - vertex(position, textureCoordinates) { - this.#generalVertex('vertex', position, textureCoordinates); + vertex(position, textureCoordinates, { isClosing = false } = {}) { + const added = this.#generalVertex('vertex', position, textureCoordinates); + added.isClosing = isClosing; } bezierVertex(position, textureCoordinates) { @@ -878,15 +890,14 @@ class Shape { // anchor characteristics const anchorVertex = this.at(0, 0, 0); const anchorHasPosition = Object.hasOwn(anchorVertex, 'position'); - const anchorHasTextureCoordinates = Object.hasOwn(anchorVertex, 'textureCoordinates'); + const lastSegment = this.at(0, -1); // close path if (shapeIsPath && shapeHasOneContour && anchorHasPosition) { - if (anchorHasTextureCoordinates) { - this.vertex(anchorVertex.position, anchorVertex.textureCoordinates); - } - else { - this.vertex(anchorVertex.position); + if (lastSegment.handlesClose()) { + lastSegment.close(anchorVertex); + } else { + this.vertex(anchorVertex.position, anchorVertex.textureCoordinates, { isClosing: true }); } } } @@ -961,8 +972,14 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { this.path.moveTo(vertex.position.x, vertex.position.y); } visitLineSegment(lineSegment) { - let vertex = lineSegment.getEndVertex(); - this.path.lineTo(vertex.position.x, vertex.position.y); + if (lineSegment.isClosing) { + // The same as lineTo, but it adds a stroke join between this + // and the starting vertex rather than having two caps + this.path.closePath(); + } else { + let vertex = lineSegment.getEndVertex(); + this.path.lineTo(vertex.position.x, vertex.position.y); + } } visitBezierSegment(bezierSegment) { let [v1, v2, v3] = bezierSegment.vertices; diff --git a/src/shape/vertex.js b/src/shape/vertex.js index eece9ad0c5..599bf4e317 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -1842,226 +1842,6 @@ function vertex(p5, fn){ return this; }; - /** - * Adds a vertex to a custom shape. - * - * `vertex()` sets the coordinates of vertices drawn between the - * beginShape() and - * endShape() functions. - * - * The first two parameters, `x` and `y`, set the x- and y-coordinates of the - * vertex. - * - * The third parameter, `z`, is optional. It sets the z-coordinate of the - * vertex in WebGL mode. By default, `z` is 0. - * - * The fourth and fifth parameters, `u` and `v`, are also optional. They set - * the u- and v-coordinates for the vertex’s texture when used with - * endShape(). By default, `u` and `v` are both 0. - * - * @method vertex - * @param {Number} x x-coordinate of the vertex. - * @param {Number} y y-coordinate of the vertex. - * @chainable - * - * @example - *
- * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the shape. - * strokeWeight(3); - * - * // Start drawing the shape. - * // Only draw the vertices. - * beginShape(POINTS); - * - * // Add the vertices. - * vertex(30, 20); - * vertex(85, 20); - * vertex(85, 75); - * vertex(30, 75); - * - * // Stop drawing the shape. - * endShape(); - * - * describe('Four black dots that form a square are drawn on a gray background.'); - * } - * - *
- * - *
- * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Start drawing the shape. - * beginShape(); - * - * // Add vertices. - * vertex(30, 20); - * vertex(85, 20); - * vertex(85, 75); - * vertex(30, 75); - * - * // Stop drawing the shape. - * endShape(CLOSE); - * - * describe('A white square on a gray background.'); - * } - * - *
- * - *
- * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * background(200); - * - * // Start drawing the shape. - * beginShape(); - * - * // Add vertices. - * vertex(-20, -30, 0); - * vertex(35, -30, 0); - * vertex(35, 25, 0); - * vertex(-20, 25, 0); - * - * // Stop drawing the shape. - * endShape(CLOSE); - * - * describe('A white square on a gray background.'); - * } - * - *
- * - *
- * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A white square spins around slowly on a gray background.'); - * } - * - * function draw() { - * background(200); - * - * // Rotate around the y-axis. - * rotateY(frameCount * 0.01); - * - * // Start drawing the shape. - * beginShape(); - * - * // Add vertices. - * vertex(-20, -30, 0); - * vertex(35, -30, 0); - * vertex(35, 25, 0); - * vertex(-20, 25, 0); - * - * // Stop drawing the shape. - * endShape(CLOSE); - * } - * - *
- * - *
- * - * let img; - * - * // Load an image to apply as a texture. - * function preload() { - * img = loadImage('assets/laDefense.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A photograph of a ceiling rotates slowly against a gray background.'); - * } - * - * function draw() { - * background(200); - * - * // Rotate around the y-axis. - * rotateY(frameCount * 0.01); - * - * // Style the shape. - * noStroke(); - * - * // Apply the texture. - * texture(img); - * textureMode(NORMAL); - * - * // Start drawing the shape - * beginShape(); - * - * // Add vertices. - * vertex(-20, -30, 0, 0, 0); - * vertex(35, -30, 0, 1, 0); - * vertex(35, 25, 0, 1, 1); - * vertex(-20, 25, 0, 0, 1); - * - * // Stop drawing the shape. - * endShape(); - * } - * - *
- */ - /** - * @method vertex - * @param {Number} x - * @param {Number} y - * @param {Number} [z] z-coordinate of the vertex. Defaults to 0. - * @chainable - */ - /** - * @method vertex - * @param {Number} x - * @param {Number} y - * @param {Number} [z] - * @param {Number} [u] u-coordinate of the vertex's texture. Defaults to 0. - * @param {Number} [v] v-coordinate of the vertex's texture. Defaults to 0. - * @chainable - */ - fn.vertex = function(x, y, moveTo, u, v) { - this._renderer.vertex(...arguments); - return; - - // TODO remove after shape refactor - if (this._renderer.isP3D) { - this._renderer.legacyVertex(...arguments); - } else { - const vert = []; - vert.isVert = true; - vert[0] = x; - vert[1] = y; - vert[2] = 0; - vert[3] = 0; - vert[4] = 0; - vert[5] = this._renderer._getFill(); - vert[6] = this._renderer._getStroke(); - - if (moveTo) { - vert.moveTo = moveTo; - } - if (isContour) { - if (contourVertices.length === 0) { - vert.moveTo = true; - } - contourVertices.push(vert); - } else { - vertices.push(vert); - } - } - return this; - }; - /** * Sets the normal vector for vertices in a custom 3D shape. * From 76dab142c623d2fba43caa18d806eb8347e083f1 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 7 Dec 2024 11:25:45 -0500 Subject: [PATCH 079/111] Only use JOIN internally when closing a shape --- preview/index.html | 2 +- src/core/constants.js | 2 ++ src/shape/custom_shapes.js | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/preview/index.html b/preview/index.html index 28de16adb0..177eafa3da 100644 --- a/preview/index.html +++ b/preview/index.html @@ -27,7 +27,7 @@ p.draw = function () { p.background(0, 50, 50); - //p.translate(-p.width/2, -p.height/2); + // p.translate(-p.width/2, -p.height/2); p.fill('red'); p.stroke('white'); p.strokeWeight(10); diff --git a/src/core/constants.js b/src/core/constants.js index a2619d7b2c..b452e10997 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -1364,8 +1364,10 @@ export const HIDE = Symbol('hide'); /** * The `splineEnds` mode where the spline loops back to its first point. + * Only used internally. * @typedef {unique symbol} JOIN * @property {JOIN} JOIN * @final + * @private */ export const JOIN = Symbol('join'); diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 8f00c15e62..b2011e64e0 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -405,6 +405,7 @@ class SplineSegment extends Segment { // and the check wouldn't be needed yet. // TODO: Consider case where positions match but other vertex properties don't. + return added; } // override method on base class @@ -440,6 +441,19 @@ class SplineSegment extends Segment { return points; } + + handlesClose() { + if (!this._belongsToShape) return false; + + // Only handle closing if the spline is the only thing in its contour after + // the anchor + const contour = this._shape.at(this._contoursIndex); + return contour.primitives.length === 2 && this._primitivesIndex === 1; + } + + close() { + this._splineEnds = constants.JOIN; + } } // ---- ISOLATED PRIMITIVES ---- @@ -1422,6 +1436,7 @@ function customShapes(p5, fn) { /** * TODO: documentation + * @param {SHOW|HIDE} mode */ fn.splineEnds = function(mode) { return this._renderer.splineEnds(mode); From 746bc1ffdfbe64d05eac697b8ffed21649c3259f Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 7 Dec 2024 11:56:56 -0500 Subject: [PATCH 080/111] Handle closing of contours --- preview/index.html | 13 +- src/core/p5.Renderer.js | 8 ++ src/shape/custom_shapes.js | 269 ++++++++++++++++++++++++++++++++----- src/shape/vertex.js | 229 ------------------------------- src/webgl/ShapeBuilder.js | 4 +- src/webgl/p5.RendererGL.js | 4 - 6 files changed, 254 insertions(+), 273 deletions(-) diff --git a/preview/index.html b/preview/index.html index 177eafa3da..84c74f6327 100644 --- a/preview/index.html +++ b/preview/index.html @@ -21,23 +21,30 @@ const sketch = function (p) { p.setup = function () { - p.createCanvas(200, 200); + p.createCanvas(200, 200, p.WEBGL); p.strokeJoin(p.MITER); }; p.draw = function () { p.background(0, 50, 50); - // p.translate(-p.width/2, -p.height/2); + p.translate(-p.width/2, -p.height/2); p.fill('red'); p.stroke('white'); p.strokeWeight(10); p.beginShape(); - //p.splineEnds(p.HIDE); + + p.vertex(10, 10); + p.vertex(10, 150); + p.vertex(150, 150); + p.vertex(150, 10); + + p.beginContour(); p.vertex(50, 50); p.splineVertex(100, 50); p.splineVertex(100, 100); p.splineVertex(50, 100); p.splineVertex(70, 70); + p.endContour(p.CLOSE); p.endShape(p.CLOSE); }; }; diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index 90e9cc5af2..64c3bb70ee 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -163,6 +163,14 @@ class Renderer { this.drawShape(this.currentShape); } + beginContour(shapeKind) { + this.currentShape.beginContour(shapeKind); + } + + endContour(mode) { + this.currentShape.endContour(mode); + } + drawShape(shape, count) { throw new Error('Unimplemented') } diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index b2011e64e0..df25ada684 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -885,38 +885,58 @@ class Shape { this.#generalVertex('arcVertex', position, textureCoordinates); } - beginShape(shapeKind = constants.PATH) { - this.kind = shapeKind; + beginContour(shapeKind = constants.PATH) { + if (this.at(-1)?.kind === constants.EMPTY_PATH) { + this.contours.pop(); + } this.contours.push(new Contour(shapeKind)); } - /* TO-DO: - Refactor? - - Might not need anchorHasPosition. - - Might combine conditions at top, and rely on shortcircuiting. - Does nothing if shape is not a path or has multiple contours. Might discuss this. - */ - endShape(closeMode = constants.OPEN) { + + endContour(closeMode = constants.OPEN, _index = -1) { + const contour = this.at(_index); if (closeMode === constants.CLOSE) { // shape characteristics - const shapeIsPath = this.kind === constants.PATH; - const shapeHasOneContour = this.contours.length === 1; + const isPath = contour.kind === constants.PATH; // anchor characteristics - const anchorVertex = this.at(0, 0, 0); + const anchorVertex = this.at(_index, 0, 0); const anchorHasPosition = Object.hasOwn(anchorVertex, 'position'); - const lastSegment = this.at(0, -1); + const lastSegment = this.at(_index, -1); // close path - if (shapeIsPath && shapeHasOneContour && anchorHasPosition) { + if (isPath && anchorHasPosition) { if (lastSegment.handlesClose()) { lastSegment.close(anchorVertex); } else { + // Temporarily remove contours after the current one so that we add to the original + // contour again + const rest = this.contours.splice(_index + 1, this.contours.length - _index - 1); this.vertex(anchorVertex.position, anchorVertex.textureCoordinates, { isClosing: true }); + this.contours.push(...rest); } } } } + beginShape(shapeKind = constants.PATH) { + this.kind = shapeKind; + // Implicitly start a contour + this.beginContour(shapeKind); + } + /* TO-DO: + Refactor? + - Might not need anchorHasPosition. + - Might combine conditions at top, and rely on shortcircuiting. + Does nothing if shape is not a path or has multiple contours. Might discuss this. + */ + endShape(closeMode = constants.OPEN) { + if (closeMode === constants.CLOSE) { + // Close the first contour, the one implicitly used for shape data + // added without an explicit contour + this.endContour(closeMode, 0); + } + } + accept(visitor) { for (const contour of this.contours) { contour.accept(visitor); @@ -1462,7 +1482,6 @@ function customShapes(p5, fn) { * @method vertex * @param {Number} x x-coordinate of the vertex. * @param {Number} y y-coordinate of the vertex. - * @chainable * * @example *
@@ -1618,7 +1637,6 @@ function customShapes(p5, fn) { * @param {Number} x * @param {Number} y * @param {Number} [z] z-coordinate of the vertex. Defaults to 0. - * @chainable */ /** * @method vertex @@ -1627,7 +1645,6 @@ function customShapes(p5, fn) { * @param {Number} [z] * @param {Number} [u] u-coordinate of the vertex's texture. Defaults to 0. * @param {Number} [v] v-coordinate of the vertex's texture. Defaults to 0. - * @chainable */ fn.vertex = function(x, y) { let z, u, v; @@ -1654,17 +1671,6 @@ function customShapes(p5, fn) { // Note: Code is commented out for now, to avoid conflicts with the existing implementation. - /** - * Top-line description - * - * More details... - */ - - // fn.beginContour = function() { - // // example of how to call an existing p5 function: - // // this.background('yellow'); - // }; - /** * Top-line description * @@ -1696,14 +1702,207 @@ function customShapes(p5, fn) { // }; /** - * Top-line description - * - * More details... - */ - - // fn.endContour = function() { + * Begins creating a hole within a flat shape. + * + * The `beginContour()` and endContour() + * functions allow for creating negative space within custom shapes that are + * flat. `beginContour()` begins adding vertices to a negative space and + * endContour() stops adding them. + * `beginContour()` and endContour() must be + * called between beginShape() and + * endShape(). + * + * Transformations such as translate(), + * rotate(), and scale() + * don't work between `beginContour()` and + * endContour(). It's also not possible to use + * other shapes, such as ellipse() or + * rect(), between `beginContour()` and + * endContour(). + * + * Note: The vertices that define a negative space must "wind" in the opposite + * direction from the outer shape. First, draw vertices for the outer shape + * clockwise order. Then, draw vertices for the negative space in + * counter-clockwise order. + * + * @method beginContour + * + * @example + *
+ * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Start drawing the shape. + * beginShape(); + * + * // Exterior vertices, clockwise winding. + * vertex(10, 10); + * vertex(90, 10); + * vertex(90, 90); + * vertex(10, 90); + * + * // Interior vertices, counter-clockwise winding. + * beginContour(); + * vertex(30, 30); + * vertex(30, 70); + * vertex(70, 70); + * vertex(70, 30); + * endContour(); + * + * // Stop drawing the shape. + * endShape(CLOSE); + * + * describe('A white square with a square hole in its center drawn on a gray background.'); + * } + * + *
+ * + *
+ * + * // Click and drag the mouse to view the scene from different angles. + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A white square with a square hole in its center drawn on a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Enable orbiting with the mouse. + * orbitControl(); + * + * // Start drawing the shape. + * beginShape(); + * + * // Exterior vertices, clockwise winding. + * vertex(-40, -40); + * vertex(40, -40); + * vertex(40, 40); + * vertex(-40, 40); + * + * // Interior vertices, counter-clockwise winding. + * beginContour(); + * vertex(-20, -20); + * vertex(-20, 20); + * vertex(20, 20); + * vertex(20, -20); + * endContour(); + * + * // Stop drawing the shape. + * endShape(CLOSE); + * } + * + *
+ */ + fn.beginContour = function(kind) { + this._renderer.beginContour(kind); + }; - // }; + /** + * Stops creating a hole within a flat shape. + * + * The beginContour() and `endContour()` + * functions allow for creating negative space within custom shapes that are + * flat. beginContour() begins adding vertices + * to a negative space and `endContour()` stops adding them. + * beginContour() and `endContour()` must be + * called between beginShape() and + * endShape(). + * + * Transformations such as translate(), + * rotate(), and scale() + * don't work between beginContour() and + * `endContour()`. It's also not possible to use other shapes, such as + * ellipse() or rect(), + * between beginContour() and `endContour()`. + * + * Note: The vertices that define a negative space must "wind" in the opposite + * direction from the outer shape. First, draw vertices for the outer shape + * clockwise order. Then, draw vertices for the negative space in + * counter-clockwise order. + * + * @method endContour + * + * @example + *
+ * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Start drawing the shape. + * beginShape(); + * + * // Exterior vertices, clockwise winding. + * vertex(10, 10); + * vertex(90, 10); + * vertex(90, 90); + * vertex(10, 90); + * + * // Interior vertices, counter-clockwise winding. + * beginContour(); + * vertex(30, 30); + * vertex(30, 70); + * vertex(70, 70); + * vertex(70, 30); + * endContour(); + * + * // Stop drawing the shape. + * endShape(CLOSE); + * + * describe('A white square with a square hole in its center drawn on a gray background.'); + * } + * + *
+ * + *
+ * + * // Click and drag the mouse to view the scene from different angles. + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A white square with a square hole in its center drawn on a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Enable orbiting with the mouse. + * orbitControl(); + * + * // Start drawing the shape. + * beginShape(); + * + * // Exterior vertices, clockwise winding. + * vertex(-40, -40); + * vertex(40, -40); + * vertex(40, 40); + * vertex(-40, 40); + * + * // Interior vertices, counter-clockwise winding. + * beginContour(); + * vertex(-20, -20); + * vertex(-20, 20); + * vertex(20, 20); + * vertex(20, -20); + * endContour(); + * + * // Stop drawing the shape. + * endShape(CLOSE); + * } + * + *
+ */ + fn.endContour = function(mode = constants.OPEN) { + this._renderer.endContour(mode); + }; /** * Top-line description diff --git a/src/shape/vertex.js b/src/shape/vertex.js index 599bf4e317..d9a7151f6b 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -18,115 +18,6 @@ function vertex(p5, fn){ let isContour = false; let isFirstContour = true; - /** - * Begins creating a hole within a flat shape. - * - * The `beginContour()` and endContour() - * functions allow for creating negative space within custom shapes that are - * flat. `beginContour()` begins adding vertices to a negative space and - * endContour() stops adding them. - * `beginContour()` and endContour() must be - * called between beginShape() and - * endShape(). - * - * Transformations such as translate(), - * rotate(), and scale() - * don't work between `beginContour()` and - * endContour(). It's also not possible to use - * other shapes, such as ellipse() or - * rect(), between `beginContour()` and - * endContour(). - * - * Note: The vertices that define a negative space must "wind" in the opposite - * direction from the outer shape. First, draw vertices for the outer shape - * clockwise order. Then, draw vertices for the negative space in - * counter-clockwise order. - * - * @method beginContour - * @chainable - * - * @example - *
- * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Start drawing the shape. - * beginShape(); - * - * // Exterior vertices, clockwise winding. - * vertex(10, 10); - * vertex(90, 10); - * vertex(90, 90); - * vertex(10, 90); - * - * // Interior vertices, counter-clockwise winding. - * beginContour(); - * vertex(30, 30); - * vertex(30, 70); - * vertex(70, 70); - * vertex(70, 30); - * endContour(); - * - * // Stop drawing the shape. - * endShape(CLOSE); - * - * describe('A white square with a square hole in its center drawn on a gray background.'); - * } - * - *
- * - *
- * - * // Click and drag the mouse to view the scene from different angles. - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A white square with a square hole in its center drawn on a gray background.'); - * } - * - * function draw() { - * background(200); - * - * // Enable orbiting with the mouse. - * orbitControl(); - * - * // Start drawing the shape. - * beginShape(); - * - * // Exterior vertices, clockwise winding. - * vertex(-40, -40); - * vertex(40, -40); - * vertex(40, 40); - * vertex(-40, 40); - * - * // Interior vertices, counter-clockwise winding. - * beginContour(); - * vertex(-20, -20); - * vertex(-20, 20); - * vertex(20, 20); - * vertex(20, -20); - * endContour(); - * - * // Stop drawing the shape. - * endShape(CLOSE); - * } - * - *
- */ - fn.beginContour = function() { - if (this._renderer.isP3D) { - this._renderer.beginContour(); - } else { - contourVertices = []; - isContour = true; - } - return this; - }; - /** * Begins adding vertices to a custom shape. * @@ -1212,126 +1103,6 @@ function vertex(p5, fn){ return this; }; - /** - * Stops creating a hole within a flat shape. - * - * The beginContour() and `endContour()` - * functions allow for creating negative space within custom shapes that are - * flat. beginContour() begins adding vertices - * to a negative space and `endContour()` stops adding them. - * beginContour() and `endContour()` must be - * called between beginShape() and - * endShape(). - * - * Transformations such as translate(), - * rotate(), and scale() - * don't work between beginContour() and - * `endContour()`. It's also not possible to use other shapes, such as - * ellipse() or rect(), - * between beginContour() and `endContour()`. - * - * Note: The vertices that define a negative space must "wind" in the opposite - * direction from the outer shape. First, draw vertices for the outer shape - * clockwise order. Then, draw vertices for the negative space in - * counter-clockwise order. - * - * @method endContour - * @chainable - * - * @example - *
- * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Start drawing the shape. - * beginShape(); - * - * // Exterior vertices, clockwise winding. - * vertex(10, 10); - * vertex(90, 10); - * vertex(90, 90); - * vertex(10, 90); - * - * // Interior vertices, counter-clockwise winding. - * beginContour(); - * vertex(30, 30); - * vertex(30, 70); - * vertex(70, 70); - * vertex(70, 30); - * endContour(); - * - * // Stop drawing the shape. - * endShape(CLOSE); - * - * describe('A white square with a square hole in its center drawn on a gray background.'); - * } - * - *
- * - *
- * - * // Click and drag the mouse to view the scene from different angles. - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A white square with a square hole in its center drawn on a gray background.'); - * } - * - * function draw() { - * background(200); - * - * // Enable orbiting with the mouse. - * orbitControl(); - * - * // Start drawing the shape. - * beginShape(); - * - * // Exterior vertices, clockwise winding. - * vertex(-40, -40); - * vertex(40, -40); - * vertex(40, 40); - * vertex(-40, 40); - * - * // Interior vertices, counter-clockwise winding. - * beginContour(); - * vertex(-20, -20); - * vertex(-20, 20); - * vertex(20, 20); - * vertex(20, -20); - * endContour(); - * - * // Stop drawing the shape. - * endShape(CLOSE); - * } - * - *
- */ - fn.endContour = function() { - if (this._renderer.isP3D) { - return this; - } - - const vert = contourVertices[0].slice(); // copy all data - vert.isVert = contourVertices[0].isVert; - vert.moveTo = false; - contourVertices.push(vert); - - // prevent stray lines with multiple contours - if (isFirstContour) { - vertices.push(vertices[0]); - isFirstContour = false; - } - - for (let i = 0; i < contourVertices.length; i++) { - vertices.push(contourVertices[i]); - } - return this; - }; - /** * Begins adding vertices to a custom shape. * diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index 301dd19672..4e941344db 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -101,12 +101,12 @@ export class ShapeBuilder { } prop.pushCurrentData(); } - if (shouldProcessEdges && prevIdx >= 0) { + if (shouldProcessEdges && prevIdx >= 0 && idx !== this.contourIndices.at(-1)) { // TODO: handle other shape modes this.geometry.edges.push([prevIdx, idx]); } - prevIdx++ + prevIdx = idx; } } diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index ea784f946c..a03d4fe0cc 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -509,10 +509,6 @@ class RendererGL extends Renderer { } } - beginContour(...args) { - this.shapeBuilder.beginContour(...args); - } - legacyVertex(...args) { this.shapeBuilder.vertex(...args); } From 7e33ec7900a06d4f29bec7ab12a13c69268214dd Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Wed, 11 Dec 2024 18:06:34 -0500 Subject: [PATCH 081/111] Remove begin/endGeometry in favor of buildGeometry --- src/webgl/3d_primitives.js | 508 ------------------------------------- src/webgl/p5.RendererGL.js | 20 +- 2 files changed, 11 insertions(+), 517 deletions(-) diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 81c8891b34..322a4afbc5 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -13,514 +13,6 @@ import { Geometry } from './p5.Geometry'; import { Matrix } from './p5.Matrix'; function primitives3D(p5, fn){ - /** - * Begins adding shapes to a new - * p5.Geometry object. - * - * The `beginGeometry()` and endGeometry() - * functions help with creating complex 3D shapes from simpler ones such as - * sphere(). `beginGeometry()` begins adding shapes - * to a custom p5.Geometry object and - * endGeometry() stops adding them. - * - * `beginGeometry()` and endGeometry() can help - * to make sketches more performant. For example, if a complex 3D shape - * doesn’t change while a sketch runs, then it can be created with - * `beginGeometry()` and endGeometry(). - * Creating a p5.Geometry object once and then - * drawing it will run faster than repeatedly drawing the individual pieces. - * - * See buildGeometry() for another way to - * build 3D shapes. - * - * Note: `beginGeometry()` can only be used in WebGL mode. - * - * @method beginGeometry - * - * @example - *
- * - * // Click and drag the mouse to view the scene from different angles. - * - * let shape; - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * // Start building the p5.Geometry object. - * beginGeometry(); - * - * // Add a cone. - * cone(); - * - * // Stop building the p5.Geometry object. - * shape = endGeometry(); - * - * describe('A white cone drawn on a gray background.'); - * } - * - * function draw() { - * background(50); - * - * // Enable orbiting with the mouse. - * orbitControl(); - * - * // Turn on the lights. - * lights(); - * - * // Style the p5.Geometry object. - * noStroke(); - * - * // Draw the p5.Geometry object. - * model(shape); - * } - * - *
- * - *
- * - * // Click and drag the mouse to view the scene from different angles. - * - * let shape; - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * // Create the p5.Geometry object. - * createArrow(); - * - * describe('A white arrow drawn on a gray background.'); - * } - * - * function draw() { - * background(50); - * - * // Enable orbiting with the mouse. - * orbitControl(); - * - * // Turn on the lights. - * lights(); - * - * // Style the p5.Geometry object. - * noStroke(); - * - * // Draw the p5.Geometry object. - * model(shape); - * } - * - * function createArrow() { - * // Start building the p5.Geometry object. - * beginGeometry(); - * - * // Add shapes. - * push(); - * rotateX(PI); - * cone(10); - * translate(0, -10, 0); - * cylinder(3, 20); - * pop(); - * - * // Stop building the p5.Geometry object. - * shape = endGeometry(); - * } - * - *
- * - *
- * - * // Click and drag the mouse to view the scene from different angles. - * - * let blueArrow; - * let redArrow; - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * // Create the arrows. - * redArrow = createArrow('red'); - * blueArrow = createArrow('blue'); - * - * describe('A red arrow and a blue arrow drawn on a gray background. The blue arrow rotates slowly.'); - * } - * - * function draw() { - * background(200); - * - * // Enable orbiting with the mouse. - * orbitControl(); - * - * // Turn on the lights. - * lights(); - * - * // Style the arrows. - * noStroke(); - * - * // Draw the red arrow. - * model(redArrow); - * - * // Translate and rotate the coordinate system. - * translate(30, 0, 0); - * rotateZ(frameCount * 0.01); - * - * // Draw the blue arrow. - * model(blueArrow); - * } - * - * function createArrow(fillColor) { - * // Start building the p5.Geometry object. - * beginGeometry(); - * - * fill(fillColor); - * - * // Add shapes to the p5.Geometry object. - * push(); - * rotateX(PI); - * cone(10); - * translate(0, -10, 0); - * cylinder(3, 20); - * pop(); - * - * // Stop building the p5.Geometry object. - * let shape = endGeometry(); - * - * return shape; - * } - * - *
- * - *
- * - * // Click and drag the mouse to view the scene from different angles. - * - * let button; - * let particles; - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * // Create a button to reset the particle system. - * button = createButton('Reset'); - * - * // Call resetModel() when the user presses the button. - * button.mousePressed(resetModel); - * - * // Add the original set of particles. - * resetModel(); - * } - * - * function draw() { - * background(50); - * - * // Enable orbiting with the mouse. - * orbitControl(); - * - * // Turn on the lights. - * lights(); - * - * // Style the particles. - * noStroke(); - * - * // Draw the particles. - * model(particles); - * } - * - * function resetModel() { - * // If the p5.Geometry object has already been created, - * // free those resources. - * if (particles) { - * freeGeometry(particles); - * } - * - * // Create a new p5.Geometry object with random spheres. - * particles = createParticles(); - * } - * - * function createParticles() { - * // Start building the p5.Geometry object. - * beginGeometry(); - * - * // Add shapes. - * for (let i = 0; i < 60; i += 1) { - * // Calculate random coordinates. - * let x = randomGaussian(0, 20); - * let y = randomGaussian(0, 20); - * let z = randomGaussian(0, 20); - * - * push(); - * // Translate to the particle's coordinates. - * translate(x, y, z); - * // Draw the particle. - * sphere(5); - * pop(); - * } - * - * // Stop building the p5.Geometry object. - * let shape = endGeometry(); - * - * return shape; - * } - * - *
- */ - fn.beginGeometry = function() { - return this._renderer.beginGeometry(); - }; - - /** - * Stops adding shapes to a new - * p5.Geometry object and returns the object. - * - * The `beginGeometry()` and endGeometry() - * functions help with creating complex 3D shapes from simpler ones such as - * sphere(). `beginGeometry()` begins adding shapes - * to a custom p5.Geometry object and - * endGeometry() stops adding them. - * - * `beginGeometry()` and endGeometry() can help - * to make sketches more performant. For example, if a complex 3D shape - * doesn’t change while a sketch runs, then it can be created with - * `beginGeometry()` and endGeometry(). - * Creating a p5.Geometry object once and then - * drawing it will run faster than repeatedly drawing the individual pieces. - * - * See buildGeometry() for another way to - * build 3D shapes. - * - * Note: `endGeometry()` can only be used in WebGL mode. - * - * @method endGeometry - * @returns {p5.Geometry} new 3D shape. - * - * @example - *
- * - * // Click and drag the mouse to view the scene from different angles. - * - * let shape; - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * // Start building the p5.Geometry object. - * beginGeometry(); - * - * // Add a cone. - * cone(); - * - * // Stop building the p5.Geometry object. - * shape = endGeometry(); - * - * describe('A white cone drawn on a gray background.'); - * } - * - * function draw() { - * background(50); - * - * // Enable orbiting with the mouse. - * orbitControl(); - * - * // Turn on the lights. - * lights(); - * - * // Style the p5.Geometry object. - * noStroke(); - * - * // Draw the p5.Geometry object. - * model(shape); - * } - * - *
- * - *
- * - * // Click and drag the mouse to view the scene from different angles. - * - * let shape; - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * // Create the p5.Geometry object. - * createArrow(); - * - * describe('A white arrow drawn on a gray background.'); - * } - * - * function draw() { - * background(50); - * - * // Enable orbiting with the mouse. - * orbitControl(); - * - * // Turn on the lights. - * lights(); - * - * // Style the p5.Geometry object. - * noStroke(); - * - * // Draw the p5.Geometry object. - * model(shape); - * } - * - * function createArrow() { - * // Start building the p5.Geometry object. - * beginGeometry(); - * - * // Add shapes. - * push(); - * rotateX(PI); - * cone(10); - * translate(0, -10, 0); - * cylinder(3, 20); - * pop(); - * - * // Stop building the p5.Geometry object. - * shape = endGeometry(); - * } - * - *
- * - *
- * - * // Click and drag the mouse to view the scene from different angles. - * - * let blueArrow; - * let redArrow; - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * // Create the arrows. - * redArrow = createArrow('red'); - * blueArrow = createArrow('blue'); - * - * describe('A red arrow and a blue arrow drawn on a gray background. The blue arrow rotates slowly.'); - * } - * - * function draw() { - * background(200); - * - * // Enable orbiting with the mouse. - * orbitControl(); - * - * // Turn on the lights. - * lights(); - * - * // Style the arrows. - * noStroke(); - * - * // Draw the red arrow. - * model(redArrow); - * - * // Translate and rotate the coordinate system. - * translate(30, 0, 0); - * rotateZ(frameCount * 0.01); - * - * // Draw the blue arrow. - * model(blueArrow); - * } - * - * function createArrow(fillColor) { - * // Start building the p5.Geometry object. - * beginGeometry(); - * - * fill(fillColor); - * - * // Add shapes to the p5.Geometry object. - * push(); - * rotateX(PI); - * cone(10); - * translate(0, -10, 0); - * cylinder(3, 20); - * pop(); - * - * // Stop building the p5.Geometry object. - * let shape = endGeometry(); - * - * return shape; - * } - * - *
- * - *
- * - * // Click and drag the mouse to view the scene from different angles. - * - * let button; - * let particles; - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * // Create a button to reset the particle system. - * button = createButton('Reset'); - * - * // Call resetModel() when the user presses the button. - * button.mousePressed(resetModel); - * - * // Add the original set of particles. - * resetModel(); - * } - * - * function draw() { - * background(50); - * - * // Enable orbiting with the mouse. - * orbitControl(); - * - * // Turn on the lights. - * lights(); - * - * // Style the particles. - * noStroke(); - * - * // Draw the particles. - * model(particles); - * } - * - * function resetModel() { - * // If the p5.Geometry object has already been created, - * // free those resources. - * if (particles) { - * freeGeometry(particles); - * } - * - * // Create a new p5.Geometry object with random spheres. - * particles = createParticles(); - * } - * - * function createParticles() { - * // Start building the p5.Geometry object. - * beginGeometry(); - * - * // Add shapes. - * for (let i = 0; i < 60; i += 1) { - * // Calculate random coordinates. - * let x = randomGaussian(0, 20); - * let y = randomGaussian(0, 20); - * let z = randomGaussian(0, 20); - * - * push(); - * // Translate to the particle's coordinates. - * translate(x, y, z); - * // Draw the particle. - * sphere(5); - * pop(); - * } - * - * // Stop building the p5.Geometry object. - * let shape = endGeometry(); - * - * return shape; - * } - * - *
- */ - fn.endGeometry = function() { - return this._renderer.endGeometry(); - }; - - /** * Sets the stroke rendering mode to balance performance and visual features when drawing lines. * diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index a03d4fe0cc..b8d31328c0 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -374,15 +374,16 @@ class RendererGL extends Renderer { ////////////////////////////////////////////// /** - * Starts creating a new p5.Geometry. Subsequent shapes drawn will be added - * to the geometry and then returned when - * endGeometry() is called. One can also use - * buildGeometry() to pass a function that - * draws shapes. - * - * If you need to draw complex shapes every frame which don't change over time, - * combining them upfront with `beginGeometry()` and `endGeometry()` and then - * drawing that will run faster than repeatedly drawing the individual pieces. + * Starts creating a new p5.Geometry. Subsequent shapes drawn will be added + * to the geometry and then returned when + * endGeometry() is called. One can also use + * buildGeometry() to pass a function that + * draws shapes. + * + * If you need to draw complex shapes every frame which don't change over time, + * combining them upfront with `beginGeometry()` and `endGeometry()` and then + * drawing that will run faster than repeatedly drawing the individual pieces. + * @private */ beginGeometry() { if (this.geometryBuilder) { @@ -398,6 +399,7 @@ class RendererGL extends Renderer { * started using beginGeometry(). One can also * use buildGeometry() to pass a function that * draws shapes. + * @private * * @returns {p5.Geometry} The model that was built. */ From 949ca22706bcd8926fa73adcb9517010c05a0513 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Wed, 11 Dec 2024 18:48:26 -0500 Subject: [PATCH 082/111] Add WebGL handling for other shape primitives --- src/shape/custom_shapes.js | 24 +++ src/webgl/ShapeBuilder.js | 316 +++++++------------------------------ src/webgl/p5.RendererGL.js | 12 +- 3 files changed, 92 insertions(+), 260 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index df25ada684..5f5df1f7d8 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -1122,6 +1122,30 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { startVertex = array[2]; } } + visitPoint(point) { + this.contours.push(point.vertices.slice()); + } + visitLine(line) { + this.contours.push(line.vertices.slice()); + } + visitTriangle(triangle) { + this.contours.push(triangle.vertices.slice()); + } + visitQuad(quad) { + this.contours.push(quad.vertices.slice()); + } + visitTriangleFan(triangleFan) { + // WebGL itself interprets the vertices as a fan, no reformatting needed + this.contours.push(triangleFan.vertices.slice()); + } + visitTriangleStrip(triangleStrip) { + // WebGL itself interprets the vertices as a strip, no reformatting needed + this.contours.push(triangleStrip.vertices.slice()); + } + visitQuadStrip(quadStrip) { + // WebGL itself interprets the vertices as a strip, no reformatting needed + this.contours.push(quadStrip.vertices.slice()); + } } class PointAtLengthGetter extends PrimitiveVisitor { diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index 4e941344db..5f076baab6 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -40,22 +40,14 @@ export class ShapeBuilder { this.bufferStrides = { ...INITIAL_BUFFER_STRIDES }; } - beginShape(mode = constants.TESS) { - this.shapeMode = mode; - if (this._useUserVertexProperties === true){ - this._resetUserVertexProperties(); - } - this.geometry.reset(); - this.contourIndices = []; - } - constructFromContours(shape, contours) { if (this._useUserVertexProperties){ this._resetUserVertexProperties(); } this.geometry.reset(); this.contourIndices = []; - this.shapeMode = constants.TESS; + // TODO: handle just some contours having non-PATH mode + this.shapeMode = shape.contours[0].kind; const shouldProcessEdges = !!this.renderer.states.strokeColor; const userVertexPropertyHelpers = {}; @@ -75,12 +67,35 @@ export class ShapeBuilder { this._useUserVertexProperties = false; } - let idx = -1; for (const contour of contours) { this.contourIndices.push(this.geometry.vertices.length); - let prevIdx = -1; for (const vertex of contour) { - idx++ + // WebGL doesn't support QUADS or QUAD_STRIP, so we duplicate data to turn + // QUADS into TRIANGLES and QUAD_STRIP into TRIANGLE_STRIP. (There is no extra + // work to convert QUAD_STRIP here, since the only difference is in how edges + // are rendered.) + if (this.shapeMode === constants.QUADS) { + // A finished quad turned into triangles should leave 6 vertices in the + // buffer: + // 0--3 0 3--5 + // | | --> | \ \ | + // 1--2 1--2 4 + // When vertex index 3 is being added, add the necessary duplicates. + if (this.geometry.vertices.length % 6 === 3) { + for (const key in this.bufferStrides) { + const stride = this.bufferStrides[key]; + const buffer = this.geometry[key]; + buffer.push( + ...buffer.slice( + buffer.length - 3 * stride, + buffer.length - 2 * stride + ), + ...buffer.slice(buffer.length - stride, buffer.length) + ); + } + } + } + this.geometry.vertices.push(vertex.position); this.geometry.vertexNormals.push(vertex.normal || new Vector(0, 0, 0)); this.geometry.uvs.push(vertex.textureCoordinates.x, vertex.textureCoordinates.y); @@ -101,194 +116,36 @@ export class ShapeBuilder { } prop.pushCurrentData(); } - if (shouldProcessEdges && prevIdx >= 0 && idx !== this.contourIndices.at(-1)) { - // TODO: handle other shape modes - this.geometry.edges.push([prevIdx, idx]); - } - - prevIdx = idx; } } + if (shouldProcessEdges) { + this.geometry.edges = this._calculateEdges(this.shapeMode, this.geometry.vertices); + } if (shouldProcessEdges && !this.renderer.geometryBuilder) { this.geometry._edgesToVertices(); } - this.isProcessingVertices = true; - this._tesselateShape(); - this.isProcessingVertices = false; - } - - // TODO: remove all below - - endShape = function( - mode, - isCurve, - isBezier, - isQuadratic, - isContour, - shapeKind, - count = 1 - ) { - if (this.shapeMode === constants.POINTS) { - // @TODO(dave) move to renderer directly - this.renderer._drawPoints( - this.geometry.vertices, - this.renderer.buffers.point - ); - return this; - } - // When we are drawing a shape then the shape mode is TESS, - // but in case of triangle we can skip the breaking into small triangle - // this can optimize performance by skipping the step of breaking it into triangles - if (this.geometry.vertices.length === 3 && - this.shapeMode === constants.TESS - ) { - this.shapeMode === constants.TRIANGLES; - } - - this.isProcessingVertices = true; - this._processVertices(...arguments); - this.isProcessingVertices = false; - - // WebGL doesn't support the QUADS and QUAD_STRIP modes, so we - // need to convert them to a supported format. In `vertex()`, we reformat - // the input data into the formats specified below. - if (this.shapeMode === constants.QUADS) { - this.shapeMode = constants.TRIANGLES; - } else if (this.shapeMode === constants.QUAD_STRIP) { - this.shapeMode = constants.TRIANGLE_STRIP; - } - - this.isBezier = false; - this.isQuadratic = false; - this.isCurve = false; - this._bezierVertex.length = 0; - this._quadraticVertex.length = 0; - this._curveVertex.length = 0; - } - - beginContour() { - if (this.shapeMode !== constants.TESS) { - throw new Error('WebGL mode can only use contours with beginShape(TESS).'); - } - this.contourIndices.push( - this.geometry.vertices.length - ); - } - - vertex(x, y) { - // WebGL doesn't support QUADS or QUAD_STRIP, so we duplicate data to turn - // QUADS into TRIANGLES and QUAD_STRIP into TRIANGLE_STRIP. (There is no extra - // work to convert QUAD_STRIP here, since the only difference is in how edges - // are rendered.) - if (this.shapeMode === constants.QUADS) { - // A finished quad turned into triangles should leave 6 vertices in the - // buffer: - // 0--3 0 3--5 - // | | --> | \ \ | - // 1--2 1--2 4 - // When vertex index 3 is being added, add the necessary duplicates. - if (this.geometry.vertices.length % 6 === 3) { - for (const key in this.bufferStrides) { - const stride = this.bufferStrides[key]; - const buffer = this.geometry[key]; - buffer.push( - ...buffer.slice( - buffer.length - 3 * stride, - buffer.length - 2 * stride - ), - ...buffer.slice(buffer.length - stride, buffer.length) - ); - } - } - } - - let z, u, v; - - // default to (x, y) mode: all other arguments assumed to be 0. - z = u = v = 0; - - if (arguments.length === 3) { - // (x, y, z) mode: (u, v) assumed to be 0. - z = arguments[2]; - } else if (arguments.length === 4) { - // (x, y, u, v) mode: z assumed to be 0. - u = arguments[2]; - v = arguments[3]; - } else if (arguments.length === 5) { - // (x, y, z, u, v) mode - z = arguments[2]; - u = arguments[3]; - v = arguments[4]; - } - const vert = new Vector(x, y, z); - this.geometry.vertices.push(vert); - this.geometry.vertexNormals.push(this.renderer.states._currentNormal); - - for (const propName in this.geometry.userVertexProperties){ - const geom = this.geometry; - const prop = geom.userVertexProperties[propName]; - const verts = geom.vertices; - if (prop.getSrcArray().length === 0 && verts.length > 1) { - const numMissingValues = prop.getDataSize() * (verts.length - 1); - const missingValues = Array(numMissingValues).fill(0); - prop.pushDirect(missingValues); - } - prop.pushCurrentData(); + if (this.shapeMode === constants.PATH) { + this.isProcessingVertices = true; + this._tesselateShape(); + this.isProcessingVertices = false; } - const vertexColor = this.renderer.states.curFillColor || [0.5, 0.5, 0.5, 1.0]; - this.geometry.vertexColors.push( - vertexColor[0], - vertexColor[1], - vertexColor[2], - vertexColor[3] - ); - const lineVertexColor = this.renderer.states.curStrokeColor || [0.5, 0.5, 0.5, 1]; - this.geometry.vertexStrokeColors.push( - lineVertexColor[0], - lineVertexColor[1], - lineVertexColor[2], - lineVertexColor[3] - ); - - if (this.renderer.states.textureMode === constants.IMAGE && !this.isProcessingVertices) { - if (this.renderer.states._tex !== null) { - if (this.renderer.states._tex.width > 0 && this.renderer.states._tex.height > 0) { - u /= this.renderer.states._tex.width; - v /= this.renderer.states._tex.height; + if ( + this.renderer.states.textureMode === constants.IMAGE && + this.renderer.states._tex !== null && + this.renderer.states._tex.width > 0 && + this.renderer.states._tex.height > 0 + ) { + this.geometry.uvs = this.geometry.uvs.map((val, i) => { + if (i % 2 === 0) { + return val / this.renderer.states._tex.width; + } else { + return val / this.renderer.states._tex.height; } - } else if ( - this.renderer.states.userFillShader !== undefined || - this.renderer.states.userStrokeShader !== undefined || - this.renderer.states.userPointShader !== undefined || - this.renderer.states.userImageShader !== undefined - ) { - // Do nothing if user-defined shaders are present - } else if ( - this.renderer.states._tex === null && - arguments.length >= 4 - ) { - // Only throw this warning if custom uv's have been provided - console.warn( - 'You must first call texture() before using' + - ' vertex() with image based u and v coordinates' - ); - } + }) } - - this.geometry.uvs.push(u, v); - - this._bezierVertex[0] = x; - this._bezierVertex[1] = y; - this._bezierVertex[2] = z; - - this._quadraticVertex[0] = x; - this._quadraticVertex[1] = y; - this._quadraticVertex[2] = z; - - return this; } _resetUserVertexProperties() { @@ -303,50 +160,6 @@ export class ShapeBuilder { this.geometry.userVertexProperties = {}; } - /** - * Interpret the vertices of the current geometry according to - * the current shape mode, and convert them to something renderable (either - * triangles or lines.) - * @private - */ - _processVertices(mode) { - if (this.geometry.vertices.length === 0) return; - - const calculateStroke = this.renderer.states.strokeColor; - const shouldClose = mode === constants.CLOSE; - if (calculateStroke) { - this.geometry.edges = this._calculateEdges( - this.shapeMode, - this.geometry.vertices, - shouldClose - ); - if (!this.renderer.geometryBuilder) { - this.geometry._edgesToVertices(); - } - } - - // For hollow shapes, user must set mode to TESS - const convexShape = this.shapeMode === constants.TESS; - // If the shape has a contour, we have to re-triangulate to cut out the - // contour region - const hasContour = this.contourIndices.length > 0; - // We tesselate when drawing curves or convex shapes - const shouldTess = - this.renderer.states.fillColor && - ( - this.isBezier || - this.isQuadratic || - this.isCurve || - convexShape || - hasContour - ) && - this.shapeMode !== constants.LINES; - - if (shouldTess) { - this._tesselateShape(); - } - } - /** * Called from _processVertices(). This function calculates the stroke vertices for custom shapes and * tesselates shapes when applicable. @@ -356,12 +169,10 @@ export class ShapeBuilder { _calculateEdges( shapeMode, verts, - shouldClose ) { const res = []; let i = 0; const contourIndices = this.contourIndices.slice(); - let contourStart = 0; switch (shapeMode) { case constants.TRIANGLE_STRIP: for (i = 0; i < verts.length - 2; i++) { @@ -415,23 +226,14 @@ export class ShapeBuilder { default: // TODO: handle contours in other modes too for (i = 0; i < verts.length; i++) { - // Handle breaks between contours - if (i + 1 < verts.length && i + 1 !== contourIndices[0]) { - res.push([i, i + 1]); + if (i !== contourIndices[0]) { + res.push([i - 1, i]); } else { - if (shouldClose || contourStart) { - res.push([i, contourStart]); - } - if (contourIndices.length > 0) { - contourStart = contourIndices.shift(); - } + contourIndices.shift(); } } break; } - if (shapeMode !== constants.TESS && shouldClose) { - res.push([verts.length - 1, 0]); - } return res; } @@ -491,19 +293,21 @@ export class ShapeBuilder { j = j + this.tessyVertexSize ) { colors.push(...polyTriangles.slice(j + 5, j + 9)); - this.renderer.normal(...polyTriangles.slice(j + 9, j + 12)); + this.geometry.vertexNormals.push(new Vector(...polyTriangles.slice(j + 9, j + 12))); { let offset = 12; for (const propName in this.geometry.userVertexProperties){ - const prop = this.geometry.userVertexProperties[propName]; - const size = prop.getDataSize(); - const start = j + offset; - const end = start + size; - prop.setCurrentData(polyTriangles.slice(start, end)); - offset += size; + const prop = this.geometry.userVertexProperties[propName]; + const size = prop.getDataSize(); + const start = j + offset; + const end = start + size; + prop.setCurrentData(polyTriangles.slice(start, end)); + prop.pushCurrentData(); + offset += size; } } - this.vertex(...polyTriangles.slice(j, j + 5)); + this.geometry.vertices.push(new Vector(...polyTriangles.slice(j, j + 3))); + this.geometry.uvs.push(...polyTriangles.slice(j + 3, j + 5)); } if (this.renderer.geometryBuilder) { // Tesselating the face causes the indices of edge vertices to stop being diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index b8d31328c0..e0c8c3416d 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -467,10 +467,14 @@ class RendererGL extends Renderer { this.shapeBuilder.shapeMode ); } else if (this.states.fillColor || this.states.strokeColor) { - this._drawGeometry( - this.shapeBuilder.geometry, - { mode: this.shapeBuilder.shapeMode, count: this.drawShapeCount } - ); + if (this.shapeBuilder.shapeMode === constants.POINTS) { + this._drawPoints(this.shapeBuilder.geometry.vertices, this.buffers.point); + } else { + this._drawGeometry( + this.shapeBuilder.geometry, + { mode: this.shapeBuilder.shapeMode, count: this.drawShapeCount } + ); + } } this.drawShapeCount = 1; } From 9765060d04427268efdf833a546ac4ba3724281a Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Wed, 11 Dec 2024 19:17:42 -0500 Subject: [PATCH 083/111] Add 2D path visitors --- src/core/p5.Renderer2D.js | 2 +- src/shape/custom_shapes.js | 67 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index 8866f5e35a..dc7eeda5d6 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -254,7 +254,7 @@ class Renderer2D extends Renderer { } drawShape(shape) { - const visitor = new PrimitiveToPath2DConverter(); + const visitor = new PrimitiveToPath2DConverter({ strokeWeight: this.states.strokeWeight }); shape.accept(visitor); if (this.states.fillColor) { this.drawingContext.fill(visitor.path); diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 5f5df1f7d8..0aba0a4073 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -999,6 +999,12 @@ class PrimitiveVisitor { // requires testing class PrimitiveToPath2DConverter extends PrimitiveVisitor { path = new Path2D(); + strokeWeight; + + constructor({ strokeWeight }) { + super() + this.strokeWeight = strokeWeight; + } // path primitives visitAnchor(anchor) { @@ -1058,6 +1064,67 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { this.path.bezierCurveTo(...points); } } + visitPoint(point) { + const { x, y } = point.getStartVertex().position; + this.path.arc(x, y, this.strokeWeight / 2, 0, constants.TWO_PI, false); + } + visitLine(line) { + const { x: x0, y: y0 } = line.getStartVertex().position; + const { x: x1, y: y1 } = line.getEndVertex().position; + this.path.moveTo(x0, y0); + this.path.lineTo(x1, y1); + } + visitTriangle(triangle) { + const [v0, v1, v2] = triangle.vertices; + this.path.moveTo(v0.position.x, v0.position.y); + this.path.lineTo(v1.position.x, v1.position.y); + this.path.lineTo(v2.position.x, v2.position.y); + this.path.close(); + } + visitQuad(quad) { + const [v0, v1, v2, v3] = quad.vertices; + this.path.moveTo(v0.position.x, v0.position.y); + this.path.lineTo(v1.position.x, v1.position.y); + this.path.lineTo(v2.position.x, v2.position.y); + this.path.lineTo(v3.position.x, v3.position.y); + this.path.close(); + } + visitTriangleFan(triangleFan) { + const [v0, ...rest] = triangleFan.vertices; + for (let i = 0; i < rest.length - 1; i++) { + const v1 = rest[i]; + const v2 = rest[i + 1]; + this.path.moveTo(v0.position.x, v0.position.y); + this.path.lineTo(v1.position.x, v1.position.y); + this.path.lineTo(v2.position.x, v2.position.y); + this.path.close(); + } + } + visitTriangleStrip(triangleStrip) { + for (let i = 0; i < triangleStrip.vertices.length - 2; i++) { + const v0 = triangleStrip.vertices[i]; + const v1 = triangleStrip.vertices[i + 1]; + const v2 = triangleStrip.vertices[i + 2]; + this.path.moveTo(v0.position.x, v0.position.y); + this.path.lineTo(v1.position.x, v1.position.y); + this.path.lineTo(v2.position.x, v2.position.y); + this.path.close(); + } + } + visitQuadStrip(quadStrip) { + for (let i = 0; i < quadStrip.vertices.length - 2; i++) { + const v0 = quadStrip.vertices[i]; + const v1 = quadStrip.vertices[i + 1]; + const v2 = quadStrip.vertices[i + 2]; + const v3 = quadStrip.vertices[i + 3]; + this.path.moveTo(v0.position.x, v0.position.y); + this.path.lineTo(v1.position.x, v1.position.y); + // These are intentionally out of order to go around the contour + this.path.lineTo(v3.position.x, v3.position.y); + this.path.lineTo(v2.position.x, v2.position.y); + this.path.close(); + } + } } class PrimitiveToVerticesConverter extends PrimitiveVisitor { From e16839c7d928ea426159ae445054432060009000 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Wed, 11 Dec 2024 19:57:55 -0500 Subject: [PATCH 084/111] Convert more legacy WebGL shape code --- src/shape/vertex.js | 138 +------- src/webgl/3d_primitives.js | 675 +++---------------------------------- 2 files changed, 59 insertions(+), 754 deletions(-) diff --git a/src/shape/vertex.js b/src/shape/vertex.js index d9a7151f6b..3dacd01491 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -9,15 +9,6 @@ import * as constants from '../core/constants'; function vertex(p5, fn){ - let shapeKind = null; - let vertices = []; - let contourVertices = []; - let isBezier = false; - let isCurve = false; - let isQuadratic = false; - let isContour = false; - let isFirstContour = true; - /** * Begins adding vertices to a custom shape. * @@ -410,28 +401,6 @@ function vertex(p5, fn){ fn.beginShape = function(kind) { p5._validateParameters('beginShape', arguments); this._renderer.beginShape(...arguments); - return; - - // TODO remove this once shape implementation is complete - if (!this._renderer.isP3D) { - if ( - kind === constants.POINTS || - kind === constants.LINES || - kind === constants.TRIANGLES || - kind === constants.TRIANGLE_FAN || - kind === constants.TRIANGLE_STRIP || - kind === constants.QUADS || - kind === constants.QUAD_STRIP - ) { - shapeKind = kind; - } else { - shapeKind = null; - } - - vertices = []; - contourVertices = []; - } - return this; }; /** @@ -1094,12 +1063,7 @@ function vertex(p5, fn){ */ fn.curveVertex = function(...args) { p5._validateParameters('curveVertex', args); - if (this._renderer.isP3D) { - this._renderer.curveVertex(...args); - } else { - isCurve = true; - this.vertex(args[0], args[1]); - } + this._renderer.curveVertex(...args); return this; }; @@ -1274,62 +1238,6 @@ function vertex(p5, fn){ } this._renderer.endShape(mode, count); - return; - - // TODO remove once shape refactor is complete - if (this._renderer.isP3D) { - this._renderer.legacyEndShape( - mode, - isCurve, - isBezier, - isQuadratic, - isContour, - shapeKind, - count - ); - } else { - if (count !== 1) { - console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode'); - } - if (vertices.length === 0) { - return this; - } - if (!this._renderer.states.strokeColor && !this._renderer.states.fillColor) { - return this; - } - - const closeShape = mode === constants.CLOSE; - - // if the shape is closed, the first element is also the last element - if (closeShape && !isContour) { - vertices.push(vertices[0]); - } - - this._renderer.legacyEndShape( - mode, - vertices, - isCurve, - isBezier, - isQuadratic, - isContour, - shapeKind - ); - - // Reset some settings - isCurve = false; - isBezier = false; - isQuadratic = false; - isContour = false; - isFirstContour = true; - - // If the shape is closed, the first element was added as last element. - // We must remove it again to prevent the list of vertices from growing - // over successive calls to endShape(CLOSE) - if (closeShape) { - vertices.pop(); - } - } - return this; }; /** @@ -1574,42 +1482,18 @@ function vertex(p5, fn){ * @param {Number} z3 z-coordinate of the anchor point. */ fn.quadraticVertex = function(...args) { - p5._validateParameters('quadraticVertex', args); - if (this._renderer.isP3D) { - this._renderer.quadraticVertex(...args); + let x1, y1, z1, x2, y2, z2 = 0; + if (args.length === 4) { + [x1, y1, x2, y2] = args; } else { - //if we're drawing a contour, put the points into an - // array for inside drawing - if (this._contourInited) { - const pt = {}; - pt.x = args[0]; - pt.y = args[1]; - pt.x3 = args[2]; - pt.y3 = args[3]; - pt.type = constants.QUADRATIC; - this._contourVertices.push(pt); - - return this; - } - if (vertices.length > 0) { - isQuadratic = true; - const vert = []; - for (let i = 0; i < args.length; i++) { - vert[i] = args[i]; - } - vert.isVert = false; - if (isContour) { - contourVertices.push(vert); - } else { - vertices.push(vert); - } - } else { - p5._friendlyError( - 'vertex() must be used once before calling quadraticVertex()', - 'quadraticVertex' - ); - } + [x1, y1, z1, x2, y2, z2] = args; } + p5._validateParameters('quadraticVertex', args); + const prevOrder = this.bezierOrder(); + this.bezierOrder(2); + this.bezierVertex(x1, y1, z1); + this.bezierVertex(x2, y2, z2); + this.bezierOrder(prevOrder); return this; }; diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 322a4afbc5..34d8ff04c1 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -1948,41 +1948,44 @@ function primitives3D(p5, fn){ let x2 = c; let y2 = d; - // TODO shapes refactor + const prevMode = this.states.textureMode; + this.states.textureMode = constants.NORMAL; + const prevOrder = this.bezierOrder(); + this.bezierOrder(2); this.beginShape(); + const addUVs = (x, y) => [x, y, (x - x1)/width, (y - y1)/height]; if (tr !== 0) { - this.legacyVertex(x2 - tr, y1); - this.quadraticVertex(x2, y1, x2, y1 + tr); + this.vertex(...addUVs(x2 - tr, y1)); + this.bezierVertex(...addUVs(x2, y1)) + this.bezierVertex(...addUVs(x2, y1 + tr)); } else { - this.legacyVertex(x2, y1); + this.vertex(...addUVs(x2, y1)); } if (br !== 0) { - this.legacyVertex(x2, y2 - br); - this.quadraticVertex(x2, y2, x2 - br, y2); + this.vertex(...addUVs(x2, y2 - br)); + this.bezierVertex(...addUVs(x2, y2)); + this.bezierVertex(...addUVs(x2 - br, y2)) } else { - this.legacyVertex(x2, y2); + this.vertex(...addUVs(x2, y2)); } if (bl !== 0) { - this.legacyVertex(x1 + bl, y2); - this.quadraticVertex(x1, y2, x1, y2 - bl); + this.vertex(...addUVs(x1 + bl, y2)); + this.bezierVertex(...addUVs(x1, y2)); + this.bezierVertex(...addUVs(x1, y2 - bl)); } else { - this.legacyVertex(x1, y2); + this.vertex(...addUVs(x1, y2)); } if (tl !== 0) { - this.legacyVertex(x1, y1 + tl); - this.quadraticVertex(x1, y1, x1 + tl, y1); + this.vertex(...addUVs(x1, y1 + tl)); + this.bezierVertex(...addUVs(x1, y1)); + this.bezierVertex(...addUVs(x1 + tl, y1)); } else { - this.legacyVertex(x1, y1); - } - - this.shapeBuilder.geometry.uvs.length = 0; - for (const vert of this.shapeBuilder.geometry.vertices) { - const u = (vert.x - x1) / width; - const v = (vert.y - y1) / height; - this.shapeBuilder.geometry.uvs.push(u, v); + this.vertex(...addUVs(x1, y1)); } - this.legacyEndShape(constants.CLOSE); + this.endShape(constants.CLOSE); + this.states.textureMode = prevMode; + this.bezierOrder(prevOrder); } return this; }; @@ -2075,22 +2078,15 @@ function primitives3D(p5, fn){ x2 = z1; z1 = z2 = z3 = z4 = 0; } - const bezierDetail = this._pInst._bezierDetail || 20; //value of Bezier detail - // TODO shapes refactor + // TODO: handle quadratic? + const prevOrder = this.bezierOrder(); + this.bezierOrder(3); this.beginShape(); - for (let i = 0; i <= bezierDetail; i++) { - const c1 = Math.pow(1 - i / bezierDetail, 3); - const c2 = 3 * (i / bezierDetail) * Math.pow(1 - i / bezierDetail, 2); - const c3 = 3 * Math.pow(i / bezierDetail, 2) * (1 - i / bezierDetail); - const c4 = Math.pow(i / bezierDetail, 3); - this.legacyVertex( - x1 * c1 + x2 * c2 + x3 * c3 + x4 * c4, - y1 * c1 + y2 * c2 + y3 * c3 + y4 * c4, - z1 * c1 + z2 * c2 + z3 * c3 + z4 * c4 - ); - } - this.legacyEndShape(); - return this; + this.vertex(x1, y1, z1); + this.bezierVertex(x2, y2, z2); + this.bezierVertex(x3, y3, z3); + this.bezierVertex(x4, y4, z4); + this.endShape(); }; // pretier-ignore @@ -2117,33 +2113,12 @@ function primitives3D(p5, fn){ y2 = x2; z1 = z2 = z3 = z4 = 0; } - const curveDetail = this._pInst._curveDetail; - // TODO shapes refactor this.beginShape(); - for (let i = 0; i <= curveDetail; i++) { - const c1 = Math.pow(i / curveDetail, 3) * 0.5; - const c2 = Math.pow(i / curveDetail, 2) * 0.5; - const c3 = i / curveDetail * 0.5; - const c4 = 0.5; - const vx = - c1 * (-x1 + 3 * x2 - 3 * x3 + x4) + - c2 * (2 * x1 - 5 * x2 + 4 * x3 - x4) + - c3 * (-x1 + x3) + - c4 * (2 * x2); - const vy = - c1 * (-y1 + 3 * y2 - 3 * y3 + y4) + - c2 * (2 * y1 - 5 * y2 + 4 * y3 - y4) + - c3 * (-y1 + y3) + - c4 * (2 * y2); - const vz = - c1 * (-z1 + 3 * z2 - 3 * z3 + z4) + - c2 * (2 * z1 - 5 * z2 + 4 * z3 - z4) + - c3 * (-z1 + z3) + - c4 * (2 * z2); - this.legacyVertex(vx, vy, vz); - } - this.legacyEndShape(); - return this; + this.curveVertex(x1, y1, z1); + this.curveVertex(x2, y2, z2); + this.curveVertex(x3, y3, z3); + this.curveVertex(x4, y4, z4); + this.endShape(); }; /** @@ -2179,571 +2154,18 @@ function primitives3D(p5, fn){ if (args.length === 6) { // TODO shapes refactor this.beginShape(constants.LINES); - this.legacyVertex(args[0], args[1], args[2]); - this.legacyVertex(args[3], args[4], args[5]); - this.legacyEndShape(); + this.vertex(args[0], args[1], args[2]); + this.vertex(args[3], args[4], args[5]); + this.endShape(); } else if (args.length === 4) { this.beginShape(constants.LINES); - this.legacyVertex(args[0], args[1], 0); - this.legacyVertex(args[2], args[3], 0); - this.legacyEndShape(); + this.vertex(args[0], args[1], 0); + this.vertex(args[2], args[3], 0); + this.endShape(); } return this; }; - /*RendererGL.prototype.bezierVertex = function(...args) { - if (this.shapeBuilder._bezierVertex.length === 0) { - throw Error('vertex() must be used once before calling bezierVertex()'); - } else { - let w_x = []; - let w_y = []; - let w_z = []; - let t, _x, _y, _z, i, k, m; - // variable i for bezierPoints, k for components, and m for anchor points. - const argLength = args.length; - - t = 0; - - if ( - this._lookUpTableBezier.length === 0 || - this._lutBezierDetail !== this._pInst._curveDetail - ) { - this._lookUpTableBezier = []; - this._lutBezierDetail = this._pInst._curveDetail; - const step = 1 / this._lutBezierDetail; - let start = 0; - let end = 1; - let j = 0; - while (start < 1) { - t = parseFloat(start.toFixed(6)); - this._lookUpTableBezier[j] = this._bezierCoefficients(t); - if (end.toFixed(6) === step.toFixed(6)) { - t = parseFloat(end.toFixed(6)) + parseFloat(start.toFixed(6)); - ++j; - this._lookUpTableBezier[j] = this._bezierCoefficients(t); - break; - } - start += step; - end -= step; - ++j; - } - } - - const LUTLength = this._lookUpTableBezier.length; - const immediateGeometry = this.shapeBuilder.geometry; - - // fillColors[0]: start point color - // fillColors[1],[2]: control point color - // fillColors[3]: end point color - const fillColors = []; - for (m = 0; m < 4; m++) fillColors.push([]); - fillColors[0] = immediateGeometry.vertexColors.slice(-4); - fillColors[3] = this.states.curFillColor.slice(); - - // Do the same for strokeColor. - const strokeColors = []; - for (m = 0; m < 4; m++) strokeColors.push([]); - strokeColors[0] = immediateGeometry.vertexStrokeColors.slice(-4); - strokeColors[3] = this.states.curStrokeColor.slice(); - - // Do the same for custom vertex properties - const userVertexProperties = {}; - for (const propName in immediateGeometry.userVertexProperties){ - const prop = immediateGeometry.userVertexProperties[propName]; - const size = prop.getDataSize(); - userVertexProperties[propName] = []; - for (m = 0; m < 4; m++) userVertexProperties[propName].push([]); - userVertexProperties[propName][0] = prop.getSrcArray().slice(-size); - userVertexProperties[propName][3] = prop.getCurrentData(); - } - - if (argLength === 6) { - this.isBezier = true; - - w_x = [this.shapeBuilder._bezierVertex[0], args[0], args[2], args[4]]; - w_y = [this.shapeBuilder._bezierVertex[1], args[1], args[3], args[5]]; - // The ratio of the distance between the start point, the two control- - // points, and the end point determines the intermediate color. - let d0 = Math.hypot(w_x[0]-w_x[1], w_y[0]-w_y[1]); - let d1 = Math.hypot(w_x[1]-w_x[2], w_y[1]-w_y[2]); - let d2 = Math.hypot(w_x[2]-w_x[3], w_y[2]-w_y[3]); - const totalLength = d0 + d1 + d2; - d0 /= totalLength; - d2 /= totalLength; - for (k = 0; k < 4; k++) { - fillColors[1].push( - fillColors[0][k] * (1-d0) + fillColors[3][k] * d0 - ); - fillColors[2].push( - fillColors[0][k] * d2 + fillColors[3][k] * (1-d2) - ); - strokeColors[1].push( - strokeColors[0][k] * (1-d0) + strokeColors[3][k] * d0 - ); - strokeColors[2].push( - strokeColors[0][k] * d2 + strokeColors[3][k] * (1-d2) - ); - } - for (const propName in immediateGeometry.userVertexProperties){ - const size = immediateGeometry.userVertexProperties[propName].getDataSize(); - for (k = 0; k < size; k++){ - userVertexProperties[propName][1].push( - userVertexProperties[propName][0][k] * (1-d0) + userVertexProperties[propName][3][k] * d0 - ); - userVertexProperties[propName][2].push( - userVertexProperties[propName][0][k] * (1-d2) + userVertexProperties[propName][3][k] * d2 - ); - } - } - - for (let i = 0; i < LUTLength; i++) { - // Interpolate colors using control points - this.states.curFillColor = [0, 0, 0, 0]; - this.states.curStrokeColor = [0, 0, 0, 0]; - _x = _y = 0; - for (let m = 0; m < 4; m++) { - for (let k = 0; k < 4; k++) { - this.states.curFillColor[k] += - this._lookUpTableBezier[i][m] * fillColors[m][k]; - this.states.curStrokeColor[k] += - this._lookUpTableBezier[i][m] * strokeColors[m][k]; - } - _x += w_x[m] * this._lookUpTableBezier[i][m]; - _y += w_y[m] * this._lookUpTableBezier[i][m]; - } - for (const propName in immediateGeometry.userVertexProperties){ - const prop = immediateGeometry.userVertexProperties[propName]; - const size = prop.getDataSize(); - let newValues = Array(size).fill(0); - for (let m = 0; m < 4; m++){ - for (let k = 0; k < size; k++){ - newValues[k] += this._lookUpTableBezier[i][m] * userVertexProperties[propName][m][k]; - } - } - prop.setCurrentData(newValues); - } - this.legacyVertex(_x, _y); - } - // so that we leave currentColor with the last value the user set it to - this.states.curFillColor = fillColors[3]; - this.states.curStrokeColor = strokeColors[3]; - for (const propName in immediateGeometry.userVertexProperties) { - const prop = immediateGeometry.userVertexProperties[propName]; - prop.setCurrentData(userVertexProperties[propName][2]); - } - this.shapeBuilder._bezierVertex[0] = args[4]; - this.shapeBuilder._bezierVertex[1] = args[5]; - } else if (argLength === 9) { - this.isBezier = true; - - w_x = [this.shapeBuilder._bezierVertex[0], args[0], args[3], args[6]]; - w_y = [this.shapeBuilder._bezierVertex[1], args[1], args[4], args[7]]; - w_z = [this.shapeBuilder._bezierVertex[2], args[2], args[5], args[8]]; - // The ratio of the distance between the start point, the two control- - // points, and the end point determines the intermediate color. - let d0 = Math.hypot(w_x[0]-w_x[1], w_y[0]-w_y[1], w_z[0]-w_z[1]); - let d1 = Math.hypot(w_x[1]-w_x[2], w_y[1]-w_y[2], w_z[1]-w_z[2]); - let d2 = Math.hypot(w_x[2]-w_x[3], w_y[2]-w_y[3], w_z[2]-w_z[3]); - const totalLength = d0 + d1 + d2; - d0 /= totalLength; - d2 /= totalLength; - for (let k = 0; k < 4; k++) { - fillColors[1].push( - fillColors[0][k] * (1-d0) + fillColors[3][k] * d0 - ); - fillColors[2].push( - fillColors[0][k] * d2 + fillColors[3][k] * (1-d2) - ); - strokeColors[1].push( - strokeColors[0][k] * (1-d0) + strokeColors[3][k] * d0 - ); - strokeColors[2].push( - strokeColors[0][k] * d2 + strokeColors[3][k] * (1-d2) - ); - } - for (const propName in immediateGeometry.userVertexProperties){ - const size = immediateGeometry.userVertexProperties[propName].getDataSize(); - for (k = 0; k < size; k++){ - userVertexProperties[propName][1].push( - userVertexProperties[propName][0][k] * (1-d0) + userVertexProperties[propName][3][k] * d0 - ); - userVertexProperties[propName][2].push( - userVertexProperties[propName][0][k] * (1-d2) + userVertexProperties[propName][3][k] * d2 - ); - } - } - for (let i = 0; i < LUTLength; i++) { - // Interpolate colors using control points - this.states.curFillColor = [0, 0, 0, 0]; - this.states.curStrokeColor = [0, 0, 0, 0]; - _x = _y = _z = 0; - for (m = 0; m < 4; m++) { - for (k = 0; k < 4; k++) { - this.states.curFillColor[k] += - this._lookUpTableBezier[i][m] * fillColors[m][k]; - this.states.curStrokeColor[k] += - this._lookUpTableBezier[i][m] * strokeColors[m][k]; - } - _x += w_x[m] * this._lookUpTableBezier[i][m]; - _y += w_y[m] * this._lookUpTableBezier[i][m]; - _z += w_z[m] * this._lookUpTableBezier[i][m]; - } - for (const propName in immediateGeometry.userVertexProperties){ - const prop = immediateGeometry.userVertexProperties[propName]; - const size = prop.getDataSize(); - let newValues = Array(size).fill(0); - for (let m = 0; m < 4; m++){ - for (let k = 0; k < size; k++){ - newValues[k] += this._lookUpTableBezier[i][m] * userVertexProperties[propName][m][k]; - } - } - prop.setCurrentData(newValues); - } - this.legacyVertex(_x, _y, _z); - } - // so that we leave currentColor with the last value the user set it to - this.states.curFillColor = fillColors[3]; - this.states.curStrokeColor = strokeColors[3]; - for (const propName in immediateGeometry.userVertexProperties) { - const prop = immediateGeometry.userVertexProperties[propName]; - prop.setCurrentData(userVertexProperties[propName][2]); - } - this.shapeBuilder._bezierVertex[0] = args[6]; - this.shapeBuilder._bezierVertex[1] = args[7]; - this.shapeBuilder._bezierVertex[2] = args[8]; - } - } - }; - - RendererGL.prototype.quadraticVertex = function(...args) { - if (this.shapeBuilder._quadraticVertex.length === 0) { - throw Error('vertex() must be used once before calling quadraticVertex()'); - } else { - let w_x = []; - let w_y = []; - let w_z = []; - let t, _x, _y, _z, i, k, m; - // variable i for bezierPoints, k for components, and m for anchor points. - const argLength = args.length; - - t = 0; - - if ( - this._lookUpTableQuadratic.length === 0 || - this._lutQuadraticDetail !== this._pInst._curveDetail - ) { - this._lookUpTableQuadratic = []; - this._lutQuadraticDetail = this._pInst._curveDetail; - const step = 1 / this._lutQuadraticDetail; - let start = 0; - let end = 1; - let j = 0; - while (start < 1) { - t = parseFloat(start.toFixed(6)); - this._lookUpTableQuadratic[j] = this._quadraticCoefficients(t); - if (end.toFixed(6) === step.toFixed(6)) { - t = parseFloat(end.toFixed(6)) + parseFloat(start.toFixed(6)); - ++j; - this._lookUpTableQuadratic[j] = this._quadraticCoefficients(t); - break; - } - start += step; - end -= step; - ++j; - } - } - - const LUTLength = this._lookUpTableQuadratic.length; - const immediateGeometry = this.shapeBuilder.geometry; - - // fillColors[0]: start point color - // fillColors[1]: control point color - // fillColors[2]: end point color - const fillColors = []; - for (m = 0; m < 3; m++) fillColors.push([]); - fillColors[0] = immediateGeometry.vertexColors.slice(-4); - fillColors[2] = this.states.curFillColor.slice(); - - // Do the same for strokeColor. - const strokeColors = []; - for (m = 0; m < 3; m++) strokeColors.push([]); - strokeColors[0] = immediateGeometry.vertexStrokeColors.slice(-4); - strokeColors[2] = this.states.curStrokeColor.slice(); - - // Do the same for user defined vertex properties - const userVertexProperties = {}; - for (const propName in immediateGeometry.userVertexProperties){ - const prop = immediateGeometry.userVertexProperties[propName]; - const size = prop.getDataSize(); - userVertexProperties[propName] = []; - for (m = 0; m < 3; m++) userVertexProperties[propName].push([]); - userVertexProperties[propName][0] = prop.getSrcArray().slice(-size); - userVertexProperties[propName][2] = prop.getCurrentData(); - } - - if (argLength === 4) { - this.isQuadratic = true; - - w_x = [this.shapeBuilder._quadraticVertex[0], args[0], args[2]]; - w_y = [this.shapeBuilder._quadraticVertex[1], args[1], args[3]]; - - // The ratio of the distance between the start point, the control- - // point, and the end point determines the intermediate color. - let d0 = Math.hypot(w_x[0]-w_x[1], w_y[0]-w_y[1]); - let d1 = Math.hypot(w_x[1]-w_x[2], w_y[1]-w_y[2]); - const totalLength = d0 + d1; - d0 /= totalLength; - for (let k = 0; k < 4; k++) { - fillColors[1].push( - fillColors[0][k] * (1-d0) + fillColors[2][k] * d0 - ); - strokeColors[1].push( - strokeColors[0][k] * (1-d0) + strokeColors[2][k] * d0 - ); - } - for (const propName in immediateGeometry.userVertexProperties){ - const prop = immediateGeometry.userVertexProperties[propName]; - const size = prop.getDataSize(); - for (let k = 0; k < size; k++){ - userVertexProperties[propName][1].push( - userVertexProperties[propName][0][k] * (1-d0) + userVertexProperties[propName][2][k] * d0 - ); - } - } - - for (let i = 0; i < LUTLength; i++) { - // Interpolate colors using control points - this.states.curFillColor = [0, 0, 0, 0]; - this.states.curStrokeColor = [0, 0, 0, 0]; - _x = _y = 0; - for (let m = 0; m < 3; m++) { - for (let k = 0; k < 4; k++) { - this.states.curFillColor[k] += - this._lookUpTableQuadratic[i][m] * fillColors[m][k]; - this.states.curStrokeColor[k] += - this._lookUpTableQuadratic[i][m] * strokeColors[m][k]; - } - _x += w_x[m] * this._lookUpTableQuadratic[i][m]; - _y += w_y[m] * this._lookUpTableQuadratic[i][m]; - } - - for (const propName in immediateGeometry.userVertexProperties) { - const prop = immediateGeometry.userVertexProperties[propName]; - const size = prop.getDataSize(); - let newValues = Array(size).fill(0); - for (let m = 0; m < 3; m++){ - for (let k = 0; k < size; k++){ - newValues[k] += this._lookUpTableQuadratic[i][m] * userVertexProperties[propName][m][k]; - } - } - prop.setCurrentData(newValues); - } - this.legacyVertex(_x, _y); - } - - // so that we leave currentColor with the last value the user set it to - this.states.curFillColor = fillColors[2]; - this.states.curStrokeColor = strokeColors[2]; - for (const propName in immediateGeometry.userVertexProperties) { - const prop = immediateGeometry.userVertexProperties[propName]; - prop.setCurrentData(userVertexProperties[propName][2]); - } - this.shapeBuilder._quadraticVertex[0] = args[2]; - this.shapeBuilder._quadraticVertex[1] = args[3]; - } else if (argLength === 6) { - this.isQuadratic = true; - - w_x = [this.shapeBuilder._quadraticVertex[0], args[0], args[3]]; - w_y = [this.shapeBuilder._quadraticVertex[1], args[1], args[4]]; - w_z = [this.shapeBuilder._quadraticVertex[2], args[2], args[5]]; - - // The ratio of the distance between the start point, the control- - // point, and the end point determines the intermediate color. - let d0 = Math.hypot(w_x[0]-w_x[1], w_y[0]-w_y[1], w_z[0]-w_z[1]); - let d1 = Math.hypot(w_x[1]-w_x[2], w_y[1]-w_y[2], w_z[1]-w_z[2]); - const totalLength = d0 + d1; - d0 /= totalLength; - for (k = 0; k < 4; k++) { - fillColors[1].push( - fillColors[0][k] * (1-d0) + fillColors[2][k] * d0 - ); - strokeColors[1].push( - strokeColors[0][k] * (1-d0) + strokeColors[2][k] * d0 - ); - } - - for (const propName in immediateGeometry.userVertexProperties){ - const prop = immediateGeometry.userVertexProperties[propName]; - const size = prop.getDataSize(); - for (let k = 0; k < size; k++){ - userVertexProperties[propName][1].push( - userVertexProperties[propName][0][k] * (1-d0) + userVertexProperties[propName][2][k] * d0 - ); - } - } - - for (i = 0; i < LUTLength; i++) { - // Interpolate colors using control points - this.states.curFillColor = [0, 0, 0, 0]; - this.states.curStrokeColor = [0, 0, 0, 0]; - _x = _y = _z = 0; - for (m = 0; m < 3; m++) { - for (k = 0; k < 4; k++) { - this.states.curFillColor[k] += - this._lookUpTableQuadratic[i][m] * fillColors[m][k]; - this.states.curStrokeColor[k] += - this._lookUpTableQuadratic[i][m] * strokeColors[m][k]; - } - _x += w_x[m] * this._lookUpTableQuadratic[i][m]; - _y += w_y[m] * this._lookUpTableQuadratic[i][m]; - _z += w_z[m] * this._lookUpTableQuadratic[i][m]; - } - for (const propName in immediateGeometry.userVertexProperties) { - const prop = immediateGeometry.userVertexProperties[propName]; - const size = prop.getDataSize(); - let newValues = Array(size).fill(0); - for (let m = 0; m < 3; m++){ - for (let k = 0; k < size; k++){ - newValues[k] += this._lookUpTableQuadratic[i][m] * userVertexProperties[propName][m][k]; - } - } - prop.setCurrentData(newValues); - } - this.legacyVertex(_x, _y, _z); - } - - // so that we leave currentColor with the last value the user set it to - this.states.curFillColor = fillColors[2]; - this.states.curStrokeColor = strokeColors[2]; - for (const propName in immediateGeometry.userVertexProperties) { - const prop = immediateGeometry.userVertexProperties[propName]; - prop.setCurrentData(userVertexProperties[propName][2]); - } - this.shapeBuilder._quadraticVertex[0] = args[3]; - this.shapeBuilder._quadraticVertex[1] = args[4]; - this.shapeBuilder._quadraticVertex[2] = args[5]; - } - } - }; - - RendererGL.prototype.curveVertex = function(...args) { - let w_x = []; - let w_y = []; - let w_z = []; - let t, _x, _y, _z, i; - t = 0; - const argLength = args.length; - - if ( - this._lookUpTableBezier.length === 0 || - this._lutBezierDetail !== this._pInst._curveDetail - ) { - this._lookUpTableBezier = []; - this._lutBezierDetail = this._pInst._curveDetail; - const step = 1 / this._lutBezierDetail; - let start = 0; - let end = 1; - let j = 0; - while (start < 1) { - t = parseFloat(start.toFixed(6)); - this._lookUpTableBezier[j] = this._bezierCoefficients(t); - if (end.toFixed(6) === step.toFixed(6)) { - t = parseFloat(end.toFixed(6)) + parseFloat(start.toFixed(6)); - ++j; - this._lookUpTableBezier[j] = this._bezierCoefficients(t); - break; - } - start += step; - end -= step; - ++j; - } - } - - const LUTLength = this._lookUpTableBezier.length; - - if (argLength === 2) { - this.shapeBuilder._curveVertex.push(args[0]); - this.shapeBuilder._curveVertex.push(args[1]); - if (this.shapeBuilder._curveVertex.length === 8) { - this.isCurve = true; - w_x = this._bezierToCatmull([ - this.shapeBuilder._curveVertex[0], - this.shapeBuilder._curveVertex[2], - this.shapeBuilder._curveVertex[4], - this.shapeBuilder._curveVertex[6] - ]); - w_y = this._bezierToCatmull([ - this.shapeBuilder._curveVertex[1], - this.shapeBuilder._curveVertex[3], - this.shapeBuilder._curveVertex[5], - this.shapeBuilder._curveVertex[7] - ]); - for (i = 0; i < LUTLength; i++) { - _x = - w_x[0] * this._lookUpTableBezier[i][0] + - w_x[1] * this._lookUpTableBezier[i][1] + - w_x[2] * this._lookUpTableBezier[i][2] + - w_x[3] * this._lookUpTableBezier[i][3]; - _y = - w_y[0] * this._lookUpTableBezier[i][0] + - w_y[1] * this._lookUpTableBezier[i][1] + - w_y[2] * this._lookUpTableBezier[i][2] + - w_y[3] * this._lookUpTableBezier[i][3]; - this.legacyVertex(_x, _y); - } - for (i = 0; i < argLength; i++) { - this.shapeBuilder._curveVertex.shift(); - } - } - } else if (argLength === 3) { - this.shapeBuilder._curveVertex.push(args[0]); - this.shapeBuilder._curveVertex.push(args[1]); - this.shapeBuilder._curveVertex.push(args[2]); - if (this.shapeBuilder._curveVertex.length === 12) { - this.isCurve = true; - w_x = this._bezierToCatmull([ - this.shapeBuilder._curveVertex[0], - this.shapeBuilder._curveVertex[3], - this.shapeBuilder._curveVertex[6], - this.shapeBuilder._curveVertex[9] - ]); - w_y = this._bezierToCatmull([ - this.shapeBuilder._curveVertex[1], - this.shapeBuilder._curveVertex[4], - this.shapeBuilder._curveVertex[7], - this.shapeBuilder._curveVertex[10] - ]); - w_z = this._bezierToCatmull([ - this.shapeBuilder._curveVertex[2], - this.shapeBuilder._curveVertex[5], - this.shapeBuilder._curveVertex[8], - this.shapeBuilder._curveVertex[11] - ]); - for (i = 0; i < LUTLength; i++) { - _x = - w_x[0] * this._lookUpTableBezier[i][0] + - w_x[1] * this._lookUpTableBezier[i][1] + - w_x[2] * this._lookUpTableBezier[i][2] + - w_x[3] * this._lookUpTableBezier[i][3]; - _y = - w_y[0] * this._lookUpTableBezier[i][0] + - w_y[1] * this._lookUpTableBezier[i][1] + - w_y[2] * this._lookUpTableBezier[i][2] + - w_y[3] * this._lookUpTableBezier[i][3]; - _z = - w_z[0] * this._lookUpTableBezier[i][0] + - w_z[1] * this._lookUpTableBezier[i][1] + - w_z[2] * this._lookUpTableBezier[i][2] + - w_z[3] * this._lookUpTableBezier[i][3]; - this.legacyVertex(_x, _y, _z); - } - for (i = 0; i < argLength; i++) { - this.shapeBuilder._curveVertex.shift(); - } - } - } - };*/ - RendererGL.prototype.image = function( img, sx, @@ -2788,13 +2210,12 @@ function primitives3D(p5, fn){ } this._drawingImage = true; - // TODO shape refactor this.beginShape(); - this.legacyVertex(dx, dy, 0, u0, v0); - this.legacyVertex(dx + dWidth, dy, 0, u1, v0); - this.legacyVertex(dx + dWidth, dy + dHeight, 0, u1, v1); - this.legacyVertex(dx, dy + dHeight, 0, u0, v1); - this.legacyEndShape(constants.CLOSE); + this.vertex(dx, dy, 0, u0, v0); + this.vertex(dx + dWidth, dy, 0, u1, v0); + this.vertex(dx + dWidth, dy + dHeight, 0, u1, v1); + this.vertex(dx, dy + dHeight, 0, u0, v1); + this.endShape(constants.CLOSE); this._drawingImage = false; this.pop(); From 80e1c3e66f02b5ab6fdf9ee6fc604fbf31c89e42 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Wed, 11 Dec 2024 20:10:58 -0500 Subject: [PATCH 085/111] Get 2D clipping working again --- src/core/p5.Renderer2D.js | 327 ++++---------------------------------- 1 file changed, 27 insertions(+), 300 deletions(-) diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index dc7eeda5d6..57b3784bd3 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -69,6 +69,8 @@ class Renderer2D extends Renderer { // Set and return p5.Element this.wrappedElt = new Element(this.elt, this._pInst); + + this.clipPath = null; } remove(){ @@ -256,11 +258,15 @@ class Renderer2D extends Renderer { drawShape(shape) { const visitor = new PrimitiveToPath2DConverter({ strokeWeight: this.states.strokeWeight }); shape.accept(visitor); - if (this.states.fillColor) { - this.drawingContext.fill(visitor.path); - } - if (this.states.strokeColor) { - this.drawingContext.stroke(visitor.path); + if (this._clipping) { + this.clipPath.addPath(visitor.path); + } else { + if (this.states.fillColor) { + this.drawingContext.fill(visitor.path); + } + if (this.states.strokeColor) { + this.drawingContext.stroke(visitor.path); + } } } @@ -282,36 +288,37 @@ class Renderer2D extends Renderer { this.blendMode(constants.BLEND); this._cachedBlendMode = tempBlendMode; + // Since everything must be in one path, create a new single Path2D to chain all shapes onto. // Start a new path. Everything from here on out should become part of this // one path so that we can clip to the whole thing. - this.drawingContext.beginPath(); + this.clipPath = new Path2D(); if (this._clipInvert) { // Slight hack: draw a big rectangle over everything with reverse winding // order. This is hopefully large enough to cover most things. - this.drawingContext.moveTo( + this.clipPath.moveTo( -2 * this.width, -2 * this.height ); - this.drawingContext.lineTo( + this.clipPath.lineTo( -2 * this.width, 2 * this.height ); - this.drawingContext.lineTo( + this.clipPath.lineTo( 2 * this.width, 2 * this.height ); - this.drawingContext.lineTo( + this.clipPath.lineTo( 2 * this.width, -2 * this.height ); - this.drawingContext.closePath(); + this.clipPath.closePath(); } } endClip() { - this._doFillStrokeClose(); - this.drawingContext.clip(); + this.drawingContext.clip(this.clipPath); + this.clipPath = null; super.endClip(); @@ -666,7 +673,7 @@ class Renderer2D extends Renderer { * start <= stop < start + TWO_PI */ arc(x, y, w, h, start, stop, mode) { - const ctx = this.drawingContext; + const ctx = this.clipPa || this.drawingContext; const rx = w / 2.0; const ry = h / 2.0; const epsilon = 0.00001; // Smallest visible angle on displays up to 4K. @@ -728,7 +735,7 @@ class Renderer2D extends Renderer { } ellipse(args) { - const ctx = this.drawingContext; + const ctx = this.clipPath || this.drawingContext; const doFill = !!this.states.fillColor, doStroke = this.states.strokeColor; const x = parseFloat(args[0]), @@ -761,7 +768,7 @@ class Renderer2D extends Renderer { } line(x1, y1, x2, y2) { - const ctx = this.drawingContext; + const ctx = this.clipPath || this.drawingContext; if (!this.states.strokeColor) { return this; } else if (this._getStroke() === styleEmpty) { @@ -775,7 +782,7 @@ class Renderer2D extends Renderer { } point(x, y) { - const ctx = this.drawingContext; + const ctx = this.clipPath || this.drawingContext; if (!this.states.strokeColor) { return this; } else if (this._getStroke() === styleEmpty) { @@ -796,7 +803,7 @@ class Renderer2D extends Renderer { } quad(x1, y1, x2, y2, x3, y3, x4, y4) { - const ctx = this.drawingContext; + const ctx = this.clipPath || this.drawingContext; const doFill = !!this.states.fillColor, doStroke = this.states.strokeColor; if (doFill && !doStroke) { @@ -832,7 +839,7 @@ class Renderer2D extends Renderer { let tr = args[5]; let br = args[6]; let bl = args[7]; - const ctx = this.drawingContext; + const ctx = this.clipPath || this.drawingContext; const doFill = !!this.states.fillColor, doStroke = this.states.strokeColor; if (doFill && !doStroke) { @@ -914,7 +921,7 @@ class Renderer2D extends Renderer { triangle(args) { - const ctx = this.drawingContext; + const ctx = this.clipPath || this.drawingContext; const doFill = !!this.states.fillColor, doStroke = this.states.strokeColor; const x1 = args[0], @@ -945,270 +952,6 @@ class Renderer2D extends Renderer { } } - legacyEndShape( - mode, - vertices, - isCurve, - isBezier, - isQuadratic, - isContour, - shapeKind - ) { - if (vertices.length === 0) { - return this; - } - if (!this.states.strokeColor && !this.states.fillColor) { - return this; - } - const closeShape = mode === constants.CLOSE; - let v; - if (closeShape && !isContour) { - vertices.push(vertices[0]); - } - let i, j; - const numVerts = vertices.length; - if (isCurve && shapeKind === null) { - if (numVerts > 3) { - const b = [], - s = 1 - this._curveTightness; - if (!this._clipping) this.drawingContext.beginPath(); - this.drawingContext.moveTo(vertices[1][0], vertices[1][1]); - for (i = 1; i + 2 < numVerts; i++) { - v = vertices[i]; - b[0] = [v[0], v[1]]; - b[1] = [ - v[0] + (s * vertices[i + 1][0] - s * vertices[i - 1][0]) / 6, - v[1] + (s * vertices[i + 1][1] - s * vertices[i - 1][1]) / 6 - ]; - b[2] = [ - vertices[i + 1][0] + - (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]]; - this.drawingContext.bezierCurveTo( - b[1][0], - b[1][1], - b[2][0], - b[2][1], - b[3][0], - b[3][1] - ); - } - if (closeShape) { - this.drawingContext.lineTo(vertices[i + 1][0], vertices[i + 1][1]); - } - this._doFillStrokeClose(closeShape); - } - } else if ( - isBezier && - shapeKind === null - ) { - if (!this._clipping) this.drawingContext.beginPath(); - for (i = 0; i < numVerts; i++) { - if (vertices[i].isVert) { - if (vertices[i].moveTo) { - this.drawingContext.moveTo(vertices[i][0], vertices[i][1]); - } else { - this.drawingContext.lineTo(vertices[i][0], vertices[i][1]); - } - } else { - this.drawingContext.bezierCurveTo( - vertices[i][0], - vertices[i][1], - vertices[i][2], - vertices[i][3], - vertices[i][4], - vertices[i][5] - ); - } - } - this._doFillStrokeClose(closeShape); - } else if ( - isQuadratic && - shapeKind === null - ) { - if (!this._clipping) this.drawingContext.beginPath(); - for (i = 0; i < numVerts; i++) { - if (vertices[i].isVert) { - if (vertices[i].moveTo) { - this.drawingContext.moveTo(vertices[i][0], vertices[i][1]); - } else { - this.drawingContext.lineTo(vertices[i][0], vertices[i][1]); - } - } else { - this.drawingContext.quadraticCurveTo( - vertices[i][0], - vertices[i][1], - vertices[i][2], - vertices[i][3] - ); - } - } - this._doFillStrokeClose(closeShape); - } else { - if (shapeKind === constants.POINTS) { - for (i = 0; i < numVerts; i++) { - v = vertices[i]; - if (this.states.strokeColor) { - this._pInst.stroke(v[6]); - } - this._pInst.point(v[0], v[1]); - } - } else if (shapeKind === constants.LINES) { - for (i = 0; i + 1 < numVerts; i += 2) { - v = vertices[i]; - if (this.states.strokeColor) { - this._pInst.stroke(vertices[i + 1][6]); - } - this._pInst.line(v[0], v[1], vertices[i + 1][0], vertices[i + 1][1]); - } - } else if (shapeKind === constants.TRIANGLES) { - for (i = 0; i + 2 < numVerts; i += 3) { - v = vertices[i]; - if (!this._clipping) this.drawingContext.beginPath(); - this.drawingContext.moveTo(v[0], v[1]); - this.drawingContext.lineTo(vertices[i + 1][0], vertices[i + 1][1]); - this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]); - this.drawingContext.closePath(); - if (!this._clipping && this.states.fillColor) { - this._pInst.fill(vertices[i + 2][5]); - this.drawingContext.fill(); - } - if (!this._clipping && this.states.strokeColor) { - this._pInst.stroke(vertices[i + 2][6]); - this.drawingContext.stroke(); - } - } - } else if (shapeKind === constants.TRIANGLE_STRIP) { - for (i = 0; i + 1 < numVerts; i++) { - v = vertices[i]; - if (!this._clipping) this.drawingContext.beginPath(); - this.drawingContext.moveTo(vertices[i + 1][0], vertices[i + 1][1]); - this.drawingContext.lineTo(v[0], v[1]); - if (!this._clipping && this.states.strokeColor) { - this._pInst.stroke(vertices[i + 1][6]); - } - if (!this._clipping && this.states.fillColor) { - this._pInst.fill(vertices[i + 1][5]); - } - if (i + 2 < numVerts) { - this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]); - if (!this._clipping && this.states.strokeColor) { - this._pInst.stroke(vertices[i + 2][6]); - } - if (!this._clipping && this.states.fillColor) { - this._pInst.fill(vertices[i + 2][5]); - } - } - this._doFillStrokeClose(closeShape); - } - } else if (shapeKind === constants.TRIANGLE_FAN) { - if (numVerts > 2) { - // For performance reasons, try to batch as many of the - // fill and stroke calls as possible. - if (!this._clipping) this.drawingContext.beginPath(); - for (i = 2; i < numVerts; i++) { - v = vertices[i]; - this.drawingContext.moveTo(vertices[0][0], vertices[0][1]); - this.drawingContext.lineTo(vertices[i - 1][0], vertices[i - 1][1]); - this.drawingContext.lineTo(v[0], v[1]); - this.drawingContext.lineTo(vertices[0][0], vertices[0][1]); - // If the next colour is going to be different, stroke / fill now - if (i < numVerts - 1) { - if ( - (this.states.fillColor && v[5] !== vertices[i + 1][5]) || - (this.states.strokeColor && v[6] !== vertices[i + 1][6]) - ) { - if (!this._clipping && this.states.fillColor) { - this._pInst.fill(v[5]); - this.drawingContext.fill(); - this._pInst.fill(vertices[i + 1][5]); - } - if (!this._clipping && this.states.strokeColor) { - this._pInst.stroke(v[6]); - this.drawingContext.stroke(); - this._pInst.stroke(vertices[i + 1][6]); - } - this.drawingContext.closePath(); - if (!this._clipping) this.drawingContext.beginPath(); // Begin the next one - } - } - } - this._doFillStrokeClose(closeShape); - } - } else if (shapeKind === constants.QUADS) { - for (i = 0; i + 3 < numVerts; i += 4) { - v = vertices[i]; - if (!this._clipping) this.drawingContext.beginPath(); - this.drawingContext.moveTo(v[0], v[1]); - for (j = 1; j < 4; j++) { - this.drawingContext.lineTo(vertices[i + j][0], vertices[i + j][1]); - } - this.drawingContext.lineTo(v[0], v[1]); - if (!this._clipping && this.states.fillColor) { - this._pInst.fill(vertices[i + 3][5]); - } - if (!this._clipping && this.states.strokeColor) { - this._pInst.stroke(vertices[i + 3][6]); - } - this._doFillStrokeClose(closeShape); - } - } else if (shapeKind === constants.QUAD_STRIP) { - if (numVerts > 3) { - for (i = 0; i + 1 < numVerts; i += 2) { - v = vertices[i]; - if (!this._clipping) this.drawingContext.beginPath(); - if (i + 3 < numVerts) { - this.drawingContext.moveTo( - vertices[i + 2][0], vertices[i + 2][1]); - this.drawingContext.lineTo(v[0], v[1]); - this.drawingContext.lineTo( - vertices[i + 1][0], vertices[i + 1][1]); - this.drawingContext.lineTo( - vertices[i + 3][0], vertices[i + 3][1]); - if (!this._clipping && this.states.fillColor) { - this._pInst.fill(vertices[i + 3][5]); - } - if (!this._clipping && this.states.strokeColor) { - this._pInst.stroke(vertices[i + 3][6]); - } - } else { - this.drawingContext.moveTo(v[0], v[1]); - this.drawingContext.lineTo( - vertices[i + 1][0], vertices[i + 1][1]); - } - this._doFillStrokeClose(closeShape); - } - } - } else { - if (!this._clipping) this.drawingContext.beginPath(); - this.drawingContext.moveTo(vertices[0][0], vertices[0][1]); - for (i = 1; i < numVerts; i++) { - v = vertices[i]; - if (v.isVert) { - if (v.moveTo) { - if (closeShape) this.drawingContext.closePath(); - this.drawingContext.moveTo(v[0], v[1]); - } else { - this.drawingContext.lineTo(v[0], v[1]); - } - } - } - this._doFillStrokeClose(closeShape); - } - } - isCurve = false; - isBezier = false; - isQuadratic = false; - isContour = false; - if (closeShape) { - vertices.pop(); - } - - return this; - } ////////////////////////////////////////////// // SHAPE | Attributes ////////////////////////////////////////////// @@ -1294,22 +1037,6 @@ class Renderer2D extends Renderer { return this; } - ////////////////////////////////////////////// - // SHAPE | Vertex - ////////////////////////////////////////////// - - _doFillStrokeClose(closeShape) { - if (closeShape) { - this.drawingContext.closePath(); - } - if (!this._clipping && this.states.fillColor) { - this.drawingContext.fill(); - } - if (!this._clipping && this.states.strokeColor) { - this.drawingContext.stroke(); - } - } - ////////////////////////////////////////////// // TRANSFORM ////////////////////////////////////////////// From 7fbcaa6d9b3d3e6559ccc446d90169c0907cab6b Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 13 Dec 2024 13:22:21 -0700 Subject: [PATCH 086/111] Add to-do comment and fix mistake --- src/shape/custom_shapes.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 60bb3ec92f..75f7fe391e 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -578,6 +578,10 @@ class Shape { this._splineTightness = tightness; } + /* + To-do: Maybe refactor #createVertex() since this has side effects that aren't advertised + in the method name? + */ #createVertex(position, textureCoordinates) { this.#vertexProperties.position = position; @@ -627,11 +631,11 @@ class Shape { this.#generalVertex('bezierVertex', position, textureCoordinates); } - splineVertex() { + splineVertex(position, textureCoordinates) { this.#generalVertex('splineVertex', position, textureCoordinates); } - arcVertex() { + arcVertex(position, textureCoordinates) { this.#generalVertex('arcVertex', position, textureCoordinates); } From da3c1fc2a165efa4b3191a7424d872d9012f7c9e Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 13 Dec 2024 14:36:46 -0700 Subject: [PATCH 087/111] Address linter warnings --- src/shape/custom_shapes.js | 87 +++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 36541cc0cd..d6d58ae2f5 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -13,7 +13,7 @@ import * as constants from '../core/constants'; // ---- UTILITY FUNCTIONS ---- function polylineLength(vertices) { - let length = 0 + let length = 0; for (let i = 1; i < vertices.length; i++) { length += vertices[i-1].position.dist(vertices[i].position); } @@ -298,7 +298,10 @@ class BezierSegment extends Segment { #_hullLength; hullLength() { if (this.#_hullLength === undefined) { - this.#_hullLength = polylineLength([this.getStartVertex(), ...this.vertices]); + this.#_hullLength = polylineLength([ + this.getStartVertex(), + ...this.vertices + ]); } return this.#_hullLength; } @@ -411,7 +414,7 @@ class SplineSegment extends Segment { // override method on base class getEndVertex() { if (this._splineEnds === constants.SHOW) { - return super.getEndVertex() + return super.getEndVertex(); } else if (this._splineEnds === constants.HIDE) { return this.vertices.at(-2); } else { @@ -430,7 +433,7 @@ class SplineSegment extends Segment { points.push(vertex); } - const prevVertex = this.getStartVertex() + const prevVertex = this.getStartVertex(); if (this._splineEnds === constants.SHOW) { points.unshift(prevVertex); points.push(this.vertices.at(-1)); @@ -619,7 +622,8 @@ class Shape { vertexToArray(vertex) { const array = []; for (const key in this.#vertexProperties) { - if (this.userVertexProperties && key in this.userVertexProperties) continue; + if (this.userVertexProperties && key in this.userVertexProperties) + continue; const val = vertex[key]; array.push(...this.serializeToArray(val)); } @@ -645,7 +649,12 @@ class Shape { } else if (original instanceof Vector) { return new Vector(queue.shift(), queue.shift(), queue.shift()); } else if (original instanceof Color) { - const array = [queue.shift(), queue.shift(), queue.shift(), queue.shift()]; + const array = [ + queue.shift(), + queue.shift(), + queue.shift(), + queue.shift() + ]; return new Color( array.map((v, i) => v * original.maxes[original.mode][i]), original.mode, @@ -659,7 +668,8 @@ class Shape { const queue = [...array]; for (const key in this.#vertexProperties) { - if (this.userVertexProperties && key in this.userVertexProperties) continue; + if (this.userVertexProperties && key in this.userVertexProperties) + continue; const original = this.#vertexProperties[key]; vertex[key] = this.hydrateValue(queue, original); } @@ -671,7 +681,7 @@ class Shape { } arrayScale(array, scale) { - return array.map((v) => v * scale); + return array.map(v => v * scale); } arraySum(first, ...rest) { @@ -693,7 +703,7 @@ class Shape { this.arrayScale(a, Math.pow(1 - t, 3)), this.arrayScale(b, 3 * Math.pow(1 - t, 2) * t), this.arrayScale(c, 3 * (1 - t) * Math.pow(t, 2)), - this.arrayScale(d, Math.pow(t, 3)), + this.arrayScale(d, Math.pow(t, 3)) ); } @@ -701,7 +711,7 @@ class Shape { return this.arraySum( this.arrayScale(a, Math.pow(1 - t, 2)), this.arrayScale(b, 2 * (1 - t) * t), - this.arrayScale(c, t * t), + this.arrayScale(c, t * t) ); } @@ -914,8 +924,15 @@ class Shape { } else { // Temporarily remove contours after the current one so that we add to the original // contour again - const rest = this.contours.splice(_index + 1, this.contours.length - _index - 1); - this.vertex(anchorVertex.position, anchorVertex.textureCoordinates, { isClosing: true }); + const rest = this.contours.splice( + _index + 1, + this.contours.length - _index - 1 + ); + this.vertex( + anchorVertex.position, + anchorVertex.textureCoordinates, + { isClosing: true } + ); this.contours.push(...rest); } } @@ -1006,7 +1023,7 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { strokeWeight; constructor({ strokeWeight }) { - super() + super(); this.strokeWeight = strokeWeight; } @@ -1060,11 +1077,15 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { this.path.moveTo(startVertex.position.x, startVertex.position.y); } - const arrayVertices = splineSegment.getControlPoints().map((v) => shape.vertexToArray(v)); - let bezierArrays = shape.catmullRomToBezier(arrayVertices, splineSegment._splineTightness) - .map((arr) => arr.map((vertArr) => shape.arrayToVertex(vertArr))); + const arrayVertices = splineSegment.getControlPoints().map( + v => shape.vertexToArray(v) + ); + let bezierArrays = shape.catmullRomToBezier( + arrayVertices, + splineSegment._splineTightness + ).map(arr => arr.map(vertArr => shape.arrayToVertex(vertArr))); for (const array of bezierArrays) { - const points = array.flatMap((vert) => [vert.position.x, vert.position.y]); + const points = array.flatMap(vert => [vert.position.x, vert.position.y]); this.path.bezierCurveTo(...points); } } @@ -1160,11 +1181,14 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { } visitBezierSegment(bezierSegment) { const contour = this.lastContour(); - const numPoints = Math.max(1, Math.ceil(bezierSegment.hullLength() * this.curveDetail)); + const numPoints = Math.max( + 1, + Math.ceil(bezierSegment.hullLength() * this.curveDetail) + ); const vertexArrays = [ bezierSegment.getStartVertex(), ...bezierSegment.vertices - ].map((v) => bezierSegment._shape.vertexToArray(v)); + ].map(v => bezierSegment._shape.vertexToArray(v)); for (let i = 0; i < numPoints; i++) { const t = (i + 1) / numPoints; contour.push( @@ -1173,22 +1197,35 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { ? bezierSegment._shape.evaluateCubicBezier(vertexArrays, t) : bezierSegment._shape.evaluateQuadraticBezier(vertexArrays, t) ) - ) + ); } } visitSplineSegment(splineSegment) { const shape = splineSegment._shape; const contour = this.lastContour(); - const arrayVertices = splineSegment.getControlPoints().map((v) => shape.vertexToArray(v)); - let bezierArrays = shape.catmullRomToBezier(arrayVertices, splineSegment._splineTightness) + const arrayVertices = splineSegment.getControlPoints().map( + v => shape.vertexToArray(v) + ); + let bezierArrays = shape.catmullRomToBezier( + arrayVertices, + splineSegment._splineTightness + ); let startVertex = shape.vertexToArray(splineSegment.getStartVertex()); for (const array of bezierArrays) { const bezierControls = [startVertex, ...array]; - const numPoints = Math.max(1, Math.ceil(polylineLength(bezierControls.map(v => shape.arrayToVertex(v))) * this.curveDetail)); + const numPoints = Math.max( + 1, + Math.ceil( + polylineLength(bezierControls.map(v => shape.arrayToVertex(v))) * + this.curveDetail + ) + ); for (let i = 0; i < numPoints; i++) { const t = (i + 1) / numPoints; - contour.push(shape.arrayToVertex(shape.evaluateCubicBezier(bezierControls, t))); + contour.push( + shape.arrayToVertex(shape.evaluateCubicBezier(bezierControls, t)) + ); } startVertex = array[2]; } @@ -1762,7 +1799,7 @@ function customShapes(p5, fn) { } this._renderer.vertex(x, y, z, u, v); return; - } + }; // Note: Code is commented out for now, to avoid conflicts with the existing implementation. From 48055835f75925948c3744d7e4f44d7ca23f8b13 Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 13 Dec 2024 15:42:10 -0700 Subject: [PATCH 088/111] Replace shouldAddToShape() with addedToShape() and implement on base class only --- src/shape/custom_shapes.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index d6d58ae2f5..296cc16b5a 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -144,7 +144,7 @@ class ShapePrimitive { // if primitive itself was added // (i.e. its individual vertices weren't all added to an existing primitive) // give it a reference to the shape and store its location within the shape - if (this.shouldAddToShape) { + if (this.addedToShape) { let lastContour = shape.at(-1); this._primitivesIndex = lastContour.primitives.length - 1; this._contoursIndex = shape.contours.length - 1; @@ -154,8 +154,8 @@ class ShapePrimitive { return shape.at(-1, -1); } - get shouldAddToShape() { - return false; + get addedToShape() { + return this.vertices.length > 0; } get _nextPrimitive() { @@ -212,10 +212,6 @@ class Anchor extends ShapePrimitive { return this.#vertexCapacity; } - get shouldAddToShape() { - return true; - } - accept(visitor) { visitor.visitAnchor(this); } @@ -234,10 +230,6 @@ class Segment extends ShapePrimitive { } } - get shouldAddToShape() { - return this.vertices.length > 0; - } - // segments in a shape always have a predecessor // (either an anchor or another segment) get _previousPrimitive() { From d46c68a0231914152faeaad8626a9c09ea148b3f Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 13 Dec 2024 15:49:02 -0700 Subject: [PATCH 089/111] Replace addedToShape() with a local variable --- src/shape/custom_shapes.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 296cc16b5a..c4ab0ae994 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -144,7 +144,8 @@ class ShapePrimitive { // if primitive itself was added // (i.e. its individual vertices weren't all added to an existing primitive) // give it a reference to the shape and store its location within the shape - if (this.addedToShape) { + let addedToShape = this.vertices.length > 0; + if (addedToShape) { let lastContour = shape.at(-1); this._primitivesIndex = lastContour.primitives.length - 1; this._contoursIndex = shape.contours.length - 1; @@ -154,10 +155,6 @@ class ShapePrimitive { return shape.at(-1, -1); } - get addedToShape() { - return this.vertices.length > 0; - } - get _nextPrimitive() { return this._belongsToShape ? this._shape.at(this._contoursIndex, this._primitivesIndex + 1) : From bf66a982cb01a504acf88905bd8778c5db39703e Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 13 Dec 2024 16:07:39 -0700 Subject: [PATCH 090/111] Remove unnecessary constructors from derived classes --- src/shape/custom_shapes.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index c4ab0ae994..0676531d9b 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -201,10 +201,6 @@ class Contour { class Anchor extends ShapePrimitive { #vertexCapacity = 1; - constructor(...vertices) { - super(...vertices); - } - get vertexCapacity() { return this.#vertexCapacity; } @@ -247,10 +243,6 @@ class Segment extends ShapePrimitive { class LineSegment extends Segment { #vertexCapacity = 1; - constructor(...vertices) { - super(...vertices); - } - get vertexCapacity() { return this.#vertexCapacity; } @@ -314,10 +306,6 @@ class SplineSegment extends Segment { _splineEnds = constants.SHOW; _splineTightness = 0; - constructor(...vertices) { - super(...vertices); - } - get vertexCapacity() { return this.#vertexCapacity; } From 3d6afa4dd4382d78df1333e65e25d5b65aec8f8f Mon Sep 17 00:00:00 2001 From: Greg Stanton Date: Fri, 13 Dec 2024 18:24:29 -0700 Subject: [PATCH 091/111] Implement isolated and tessellation primitives, fix visitors --- preview/index.html | 33 ++++++--------- src/shape/custom_shapes.js | 82 ++++++++++++++++++++++++++++---------- 2 files changed, 74 insertions(+), 41 deletions(-) diff --git a/preview/index.html b/preview/index.html index 9203365dfe..acc9cee700 100644 --- a/preview/index.html +++ b/preview/index.html @@ -21,31 +21,22 @@ const sketch = function (p) { p.setup = function () { - p.createCanvas(200, 200, p.WEBGL); - p.strokeJoin(p.MITER); + p.createCanvas(100, 100, p.WEBGL); }; p.draw = function () { - p.background(0, 50, 50); + p.background(200); p.translate(-p.width/2, -p.height/2); - p.fill('red'); - p.stroke('white'); - p.strokeWeight(10); - p.beginShape(); - - p.vertex(10, 10); - p.vertex(10, 150); - p.vertex(150, 150); - p.vertex(150, 10); - - p.beginContour(); - p.vertex(50, 50); - p.splineVertex(100, 50); - p.splineVertex(100, 100); - p.splineVertex(50, 100); - p.splineVertex(70, 70); - p.endContour(p.CLOSE); - p.endShape(p.CLOSE); + p.beginShape(p.QUAD_STRIP); + p.vertex(30, 20); + p.vertex(30, 75); + p.vertex(50, 20); + p.vertex(50, 75); + p.vertex(65, 20); + p.vertex(65, 75); + p.vertex(85, 20); + p.vertex(85, 75); + p.endShape(); }; }; diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 0676531d9b..3a74b70482 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -439,46 +439,88 @@ class SplineSegment extends Segment { // ---- ISOLATED PRIMITIVES ---- class Point extends ShapePrimitive { - constructor(...vertices) { - super(...vertices); + #vertexCapacity = 1; + + get vertexCapacity() { + return this.#vertexCapacity; + } + + accept(visitor) { + visitor.visitPoint(this); } } class Line extends ShapePrimitive { - constructor(...vertices) { - super(...vertices); + #vertexCapacity = 2; + + get vertexCapacity() { + return this.#vertexCapacity; + } + + accept(visitor) { + visitor.visitLine(this); } } class Triangle extends ShapePrimitive { - constructor(...vertices) { - super(...vertices); + #vertexCapacity = 3; + + get vertexCapacity() { + return this.#vertexCapacity; + } + + accept(visitor) { + visitor.visitTriangle(this); } } class Quad extends ShapePrimitive { - constructor(...vertices) { - super(...vertices); + #vertexCapacity = 4; + + get vertexCapacity() { + return this.#vertexCapacity; + } + + accept(visitor) { + visitor.visitQuad(this); } } // ---- TESSELLATION PRIMITIVES ---- class TriangleFan extends ShapePrimitive { - constructor(...vertices) { - super(...vertices); + #vertexCapacity = Infinity; + + get vertexCapacity() { + return this.#vertexCapacity; + } + + accept(visitor) { + visitor.visitTriangleFan(this); } } class TriangleStrip extends ShapePrimitive { - constructor(...vertices) { - super(...vertices); + #vertexCapacity = Infinity; + + get vertexCapacity() { + return this.#vertexCapacity; + } + + accept(visitor) { + visitor.visitTriangleStrip(this); } } class QuadStrip extends ShapePrimitive { - constructor(...vertices) { - super(...vertices); + #vertexCapacity = Infinity; + + get vertexCapacity() { + return this.#vertexCapacity; + } + + accept(visitor) { + visitor.visitQuadStrip(this); } } @@ -1067,12 +1109,12 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { } } visitPoint(point) { - const { x, y } = point.getStartVertex().position; - this.path.arc(x, y, this.strokeWeight / 2, 0, constants.TWO_PI, false); + const { x, y } = point.vertices[0].position; + this.path.arc(x, y, this.strokeWeight / 2, 0, constants.TWO_PI); } visitLine(line) { - const { x: x0, y: y0 } = line.getStartVertex().position; - const { x: x1, y: y1 } = line.getEndVertex().position; + const { x: x0, y: y0 } = line.vertices[0].position; + const { x: x1, y: y1 } = line.vertices[1].position; this.path.moveTo(x0, y0); this.path.lineTo(x1, y1); } @@ -1114,14 +1156,14 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { } } visitQuadStrip(quadStrip) { - for (let i = 0; i < quadStrip.vertices.length - 2; i++) { + for (let i = 0; i < quadStrip.vertices.length - 3; i += 2) { const v0 = quadStrip.vertices[i]; const v1 = quadStrip.vertices[i + 1]; const v2 = quadStrip.vertices[i + 2]; const v3 = quadStrip.vertices[i + 3]; this.path.moveTo(v0.position.x, v0.position.y); this.path.lineTo(v1.position.x, v1.position.y); - // These are intentionally out of order to go around the contour + // These are intentionally out of order to go around the quad this.path.lineTo(v3.position.x, v3.position.y); this.path.lineTo(v2.position.x, v2.position.y); this.path.close(); From b6725bb1b21fdfb273b59f2d00bdfb2df90792b7 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 14 Dec 2024 08:46:37 -0500 Subject: [PATCH 092/111] Fix fills not working with QUAD_STRIP --- preview/index.html | 2 +- src/webgl/ShapeBuilder.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/preview/index.html b/preview/index.html index acc9cee700..99fac972e8 100644 --- a/preview/index.html +++ b/preview/index.html @@ -27,6 +27,7 @@ p.draw = function () { p.background(200); p.translate(-p.width/2, -p.height/2); + p.fill(255); p.beginShape(p.QUAD_STRIP); p.vertex(30, 20); p.vertex(30, 75); @@ -42,7 +43,6 @@ new p5(sketch); -

hello, world!

\ No newline at end of file diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index 5f076baab6..3a8c85ade6 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -130,6 +130,17 @@ export class ShapeBuilder { this.isProcessingVertices = true; this._tesselateShape(); this.isProcessingVertices = false; + } else if (this.shapeMode === constants.QUAD_STRIP) { + // The only difference between these two modes is which edges are + // displayed, so after we've updated the edges, we switch the mode + // to one that native WebGL knows how to render. + this.shapeMode = constants.TRIANGLE_STRIP; + } else if (this.shapeMode === constants.QUADS) { + // We translate QUADS to TRIANGLES when vertices are being added, + // since QUADS is just a p5 mode, whereas TRIANGLES is also a mode + // that native WebGL knows how to render. Once we've processed edges, + // everything should be set up for TRIANGLES mode. + this.shapeMode = constants.TRIANGLES; } if ( From 2e31059c2d1a91433b6b7ebdca0c8b9139061a32 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 14 Dec 2024 08:51:06 -0500 Subject: [PATCH 093/111] Fix QUAD_STRIP in 2d mode --- preview/index.html | 6 +++--- src/shape/custom_shapes.js | 10 +++++----- src/webgl/ShapeBuilder.js | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/preview/index.html b/preview/index.html index 99fac972e8..39e2a435eb 100644 --- a/preview/index.html +++ b/preview/index.html @@ -21,13 +21,13 @@ const sketch = function (p) { p.setup = function () { - p.createCanvas(100, 100, p.WEBGL); + p.createCanvas(100, 100); }; p.draw = function () { p.background(200); - p.translate(-p.width/2, -p.height/2); - p.fill(255); + //p.translate(-p.width/2, -p.height/2); + p.strokeWeight(3); p.beginShape(p.QUAD_STRIP); p.vertex(30, 20); p.vertex(30, 75); diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 3a74b70482..8ab24b9824 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -1123,7 +1123,7 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { this.path.moveTo(v0.position.x, v0.position.y); this.path.lineTo(v1.position.x, v1.position.y); this.path.lineTo(v2.position.x, v2.position.y); - this.path.close(); + this.path.closePath(); } visitQuad(quad) { const [v0, v1, v2, v3] = quad.vertices; @@ -1131,7 +1131,7 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { this.path.lineTo(v1.position.x, v1.position.y); this.path.lineTo(v2.position.x, v2.position.y); this.path.lineTo(v3.position.x, v3.position.y); - this.path.close(); + this.path.closePath(); } visitTriangleFan(triangleFan) { const [v0, ...rest] = triangleFan.vertices; @@ -1141,7 +1141,7 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { this.path.moveTo(v0.position.x, v0.position.y); this.path.lineTo(v1.position.x, v1.position.y); this.path.lineTo(v2.position.x, v2.position.y); - this.path.close(); + this.path.closePath(); } } visitTriangleStrip(triangleStrip) { @@ -1152,7 +1152,7 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { this.path.moveTo(v0.position.x, v0.position.y); this.path.lineTo(v1.position.x, v1.position.y); this.path.lineTo(v2.position.x, v2.position.y); - this.path.close(); + this.path.closePath(); } } visitQuadStrip(quadStrip) { @@ -1166,7 +1166,7 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { // These are intentionally out of order to go around the quad this.path.lineTo(v3.position.x, v3.position.y); this.path.lineTo(v2.position.x, v2.position.y); - this.path.close(); + this.path.closePath(); } } } diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index 3a8c85ade6..9ddb2d48e7 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -219,8 +219,8 @@ export class ShapeBuilder { for (i = 0; i < verts.length - 5; i += 6) { res.push([i, i + 1]); res.push([i + 1, i + 2]); - res.push([i + 3, i + 5]); - res.push([i + 4, i + 5]); + res.push([i + 2, i + 5]); + res.push([i + 5, i]); } break; case constants.QUAD_STRIP: @@ -229,8 +229,8 @@ export class ShapeBuilder { // 1---3---5 for (i = 0; i < verts.length - 2; i += 2) { res.push([i, i + 1]); - res.push([i, i + 2]); res.push([i + 1, i + 3]); + res.push([i, i + 2]); } res.push([i, i + 1]); break; From 7818a248ff446493cb10d28f464ea26f1bb223a9 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 14 Dec 2024 09:10:50 -0500 Subject: [PATCH 094/111] Fix more 2D bugs --- preview/index.html | 2 +- src/core/p5.Renderer2D.js | 1 + src/webgl/ShapeBuilder.js | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/preview/index.html b/preview/index.html index 39e2a435eb..d5e274ab92 100644 --- a/preview/index.html +++ b/preview/index.html @@ -28,7 +28,7 @@ p.background(200); //p.translate(-p.width/2, -p.height/2); p.strokeWeight(3); - p.beginShape(p.QUAD_STRIP); + p.beginShape(p.POINTS); p.vertex(30, 20); p.vertex(30, 75); p.vertex(50, 20); diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index bd4c511b57..5dcf530cc1 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -979,6 +979,7 @@ class Renderer2D extends Renderer { } strokeWeight(w) { + super.strokeWeight(w); if (typeof w === 'undefined' || w === 0) { // hack because lineWidth 0 doesn't work this.drawingContext.lineWidth = 0.0001; diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index 9ddb2d48e7..c36658752d 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -90,7 +90,7 @@ export class ShapeBuilder { buffer.length - 3 * stride, buffer.length - 2 * stride ), - ...buffer.slice(buffer.length - stride, buffer.length) + ...buffer.slice(buffer.length - stride, buffer.length), ); } } @@ -107,7 +107,7 @@ export class ShapeBuilder { if (this.renderer.states.strokeColor) { this.geometry.vertexStrokeColors.push(...vertex.stroke.array()); } else { - this.geometry.vertexColors.push(0, 0, 0, 0); + this.geometry.vertexStrokeColors.push(0, 0, 0, 0); } for (const key in userVertexPropertyHelpers) { const prop = userVertexPropertyHelpers[key]; From 315da930fb7d79629632d6f20bbec4acea8e88bc Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 14 Dec 2024 09:17:13 -0500 Subject: [PATCH 095/111] Fix POINTS mode in 2D --- src/core/p5.Renderer.js | 9 +++++++ src/shape/custom_shapes.js | 4 ++- src/webgl/p5.RendererGL.js | 51 ++------------------------------------ 3 files changed, 14 insertions(+), 50 deletions(-) diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index 8b1f1a715a..acb7557645 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -36,6 +36,7 @@ class Renderer { imageMode: constants.CORNER, rectMode: constants.CORNER, ellipseMode: constants.CENTER, + strokeWeight: 1, textFont: { family: 'sans-serif' }, textLeading: 15, @@ -253,6 +254,14 @@ class Renderer { this.states.fillColor = null; } + strokeWeight(w) { + if (w === undefined) { + return this.states.strokeWeight; + } else { + this.states.strokeWeight = w; + } + } + stroke(...args) { this.states.strokeSet = true; this.states.strokeColor = this._pInst.color(...args); diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 8ab24b9824..326f12f46e 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -1110,7 +1110,9 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { } visitPoint(point) { const { x, y } = point.vertices[0].position; - this.path.arc(x, y, this.strokeWeight / 2, 0, constants.TWO_PI); + this.path.moveTo(x, y); + // Hack: to draw just strokes and not fills, draw a very very tiny line + this.path.lineTo(x + 0.00001, y); } visitLine(line) { const { x: x0, y: y0 } = line.vertices[0].position; diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index e0c8c3416d..04256dc75b 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -341,8 +341,6 @@ class RendererGL extends Renderer { this.geometryBufferCache = new GeometryBufferCache(this); - this.pointSize = 5.0; //default point size - this.curStrokeWeight = 1; this.curStrokeCap = constants.ROUND; this.curStrokeJoin = constants.ROUND; @@ -1346,51 +1344,6 @@ class RendererGL extends Renderer { this.drawTarget()._isClipApplied = false; } - /** - * Change weight of stroke - * @param {Number} stroke weight to be used for drawing - * @example - *
- * - * function setup() { - * createCanvas(200, 400, WEBGL); - * setAttributes('antialias', true); - * } - * - * function draw() { - * background(0); - * noStroke(); - * translate(0, -100, 0); - * stroke(240, 150, 150); - * fill(100, 100, 240); - * push(); - * strokeWeight(8); - * rotateX(frameCount * 0.01); - * rotateY(frameCount * 0.01); - * sphere(75); - * pop(); - * push(); - * translate(0, 200, 0); - * strokeWeight(1); - * rotateX(frameCount * 0.01); - * rotateY(frameCount * 0.01); - * sphere(75); - * pop(); - * } - * - *
- * - * @alt - * black canvas with two purple rotating spheres with pink - * outlines the sphere on top has much heavier outlines, - */ - strokeWeight(w) { - if (this.curStrokeWeight !== w) { - this.pointSize = w; - this.curStrokeWeight = w; - } - } - // x,y are canvas-relative (pre-scaled by _pixelDensity) _getPixel(x, y) { const gl = this.GL; @@ -2203,7 +2156,7 @@ class RendererGL extends Renderer { strokeShader.setUniform('uSimpleLines', this._simpleLines); strokeShader.setUniform('uUseLineColor', this._useLineColor); strokeShader.setUniform('uMaterialColor', this.states.curStrokeColor); - strokeShader.setUniform('uStrokeWeight', this.curStrokeWeight); + strokeShader.setUniform('uStrokeWeight', this.states.strokeWeight); strokeShader.setUniform('uStrokeCap', STROKE_CAP_ENUM[this.curStrokeCap]); strokeShader.setUniform('uStrokeJoin', STROKE_JOIN_ENUM[this.curStrokeJoin]); } @@ -2318,7 +2271,7 @@ class RendererGL extends Renderer { // should be they be same var? pointShader.setUniform( 'uPointSize', - this.pointSize * this._pixelDensity + this.states.strokeWeight * this._pixelDensity ); } From 999339e8496a3f94b754f198f64f71800a975206 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 14 Dec 2024 10:24:56 -0500 Subject: [PATCH 096/111] Fix some spline test issues, rename curveVertex to splineVertex everywhere --- preview/index.html | 21 +- src/core/p5.Renderer.js | 9 +- src/core/p5.Renderer2D.js | 8 +- src/shape/curves.js | 14 +- src/shape/custom_shapes.js | 29 +- src/shape/vertex.js | 118 +- src/webgl/3d_primitives.js | 8 +- src/webgl/p5.RendererGL.js | 1022 +++++++++-------- .../addons/p5.sound/autoCorrelation/sketch.js | 2 +- .../webgl/curves/sketch.js | 20 +- test/unit/core/vertex.js | 8 - test/unit/visual/cases/shapes.js | 56 +- 12 files changed, 693 insertions(+), 622 deletions(-) diff --git a/preview/index.html b/preview/index.html index d5e274ab92..47feee9de2 100644 --- a/preview/index.html +++ b/preview/index.html @@ -27,16 +27,17 @@ p.draw = function () { p.background(200); //p.translate(-p.width/2, -p.height/2); - p.strokeWeight(3); - p.beginShape(p.POINTS); - p.vertex(30, 20); - p.vertex(30, 75); - p.vertex(50, 20); - p.vertex(50, 75); - p.vertex(65, 20); - p.vertex(65, 75); - p.vertex(85, 20); - p.vertex(85, 75); + p.scale(2) + p.beginShape(); + p.vertex(0, 0) + p.vertex(5, 5) + //p.splineEnds(p.HIDE); + p.splineVertex(10, 10); + p.splineVertex(15, 40); + p.splineVertex(40, 35); + p.splineVertex(25, 15); + p.splineVertex(15, 25); + p.splineVertex(0, 0); p.endShape(); }; }; diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index acb7557645..04cda0e527 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -65,7 +65,14 @@ class Renderer { this._clipInvert = false; this._curveTightness = 0; - this.currentShape = new Shape(this.getCommonVertexProperties()); + this._currentShape = undefined; // Lazily generate current shape + } + + get currentShape() { + if (!this._currentShape) { + this._currentShape = new Shape(this.getCommonVertexProperties()); + } + return this._currentShape; } remove() { diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index 5dcf530cc1..6213b08228 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -1030,10 +1030,10 @@ class Renderer2D extends Renderer { curve(x1, y1, x2, y2, x3, y3, x4, y4) { this._pInst.beginShape(); - this._pInst.curveVertex(x1, y1); - this._pInst.curveVertex(x2, y2); - this._pInst.curveVertex(x3, y3); - this._pInst.curveVertex(x4, y4); + this._pInst.splineVertex(x1, y1); + this._pInst.splineVertex(x2, y2); + this._pInst.splineVertex(x3, y3); + this._pInst.splineVertex(x4, y4); this._pInst.endShape(); return this; } diff --git a/src/shape/curves.js b/src/shape/curves.js index fc033b476b..30f7287d9d 100644 --- a/src/shape/curves.js +++ b/src/shape/curves.js @@ -767,7 +767,7 @@ function curves(p5, fn){ /** * Adjusts the way curve() and - * curveVertex() draw. + * splineVertex() draw. * * Spline curves are like cables that are attached to a set of points. * `curveTightness()` adjusts how tightly the cable is attached to the points. @@ -803,12 +803,12 @@ function curves(p5, fn){ * // Draw the curve. * noFill(); * beginShape(); - * curveVertex(10, 26); - * curveVertex(10, 26); - * curveVertex(83, 24); - * curveVertex(83, 61); - * curveVertex(25, 65); - * curveVertex(25, 65); + * splineVertex(10, 26); + * splineVertex(10, 26); + * splineVertex(83, 24); + * splineVertex(83, 61); + * splineVertex(25, 65); + * splineVertex(25, 65); * endShape(); * } *
diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 326f12f46e..1aeebae6aa 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -20,29 +20,6 @@ function polylineLength(vertices) { return length; } -function catmullRomToBezier(vertices, tightness) { - let X0, Y0, X1, Y1, X2, Y2, X3, Y3; - let s = 1 - tightness; - let bezX1, bezY1, bezX2, bezY2, bezX3, bezY3; - let bezArrays = []; - - for (let i = 0; i + 6 < vertices.length; i += 2) { - [X0, Y0, X1, Y1, X2, Y2, X3, Y3] = vertices.slice(i, i + 8); - - bezX1 = X1 + s * (X2 - X0) / 6; - bezY1 = Y1 + s * (Y2 - Y0) / 6; - - bezX2 = X2 + s * (X1 - X3) / 6; - bezY2 = Y2 + s * (Y1 - Y3) / 6; - - bezX3 = X2; - bezY3 = Y2; - - bezArrays.push([bezX1, bezY1, bezX2, bezY2, bezX3, bezY3]); - } - return bezArrays; -} - // ---- GENERAL BUILDING BLOCKS ---- class Vertex { @@ -332,7 +309,7 @@ class SplineSegment extends Segment { this.vertices[1] : this.vertices[0]; } else { - return this.vertices[0]; + return this.getStartVertex() } } @@ -405,6 +382,7 @@ class SplineSegment extends Segment { if (this._comesAfterSegment) { points.push(this.getStartVertex()); } + points.push(this.getStartVertex()); for (const vertex of this.vertices) { points.push(vertex); @@ -1232,7 +1210,7 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { arrayVertices, splineSegment._splineTightness ); - let startVertex = shape.vertexToArray(splineSegment.getStartVertex()); + let startVertex = shape.vertexToArray(splineSegment._firstInterpolatedVertex); for (const array of bezierArrays) { const bezierControls = [startVertex, ...array]; const numPoints = Math.max( @@ -2100,7 +2078,6 @@ function customShapes(p5, fn) { export default customShapes; export { - catmullRomToBezier, Shape, Contour, ShapePrimitive, diff --git a/src/shape/vertex.js b/src/shape/vertex.js index 3dacd01491..2d98f12cf2 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -33,7 +33,7 @@ function vertex(p5, fn){ * vertex(), * bezierVertex(), * quadraticVertex(), and/or - * curveVertex(). Calling + * splineVertex(). Calling * endShape() will stop adding vertices to the * shape. Each shape will be outlined with the current stroke color and filled * with the current fill color. @@ -683,15 +683,15 @@ function vertex(p5, fn){ /** * Adds a spline curve segment to a custom shape. * - * `curveVertex()` adds a curved segment to custom shapes. The spline curves + * `splineVertex()` adds a curved segment to custom shapes. The spline curves * it creates are defined like those made by the - * curve() function. `curveVertex()` must be called + * curve() function. `splineVertex()` must be called * between the beginShape() and * endShape() functions. * * Spline curves can form shapes and curves that slope gently. They’re like * cables that are attached to a set of points. Splines are defined by two - * anchor points and two control points. `curveVertex()` must be called at + * anchor points and two control points. `splineVertex()` must be called at * least four times between * beginShape() and * endShape() in order to draw a curve: @@ -700,14 +700,14 @@ function vertex(p5, fn){ * beginShape(); * * // Add the first control point. - * curveVertex(84, 91); + * splineVertex(84, 91); * * // Add the anchor points to draw between. - * curveVertex(68, 19); - * curveVertex(21, 17); + * splineVertex(68, 19); + * splineVertex(21, 17); * * // Add the second control point. - * curveVertex(32, 91); + * splineVertex(32, 91); * * endShape(); * @@ -715,37 +715,37 @@ function vertex(p5, fn){ * The code snippet above would only draw the curve between the anchor points, * similar to the curve() function. The segments * between the control and anchor points can be drawn by calling - * `curveVertex()` with the coordinates of the control points: + * `splineVertex()` with the coordinates of the control points: * * * beginShape(); * * // Add the first control point and draw a segment to it. - * curveVertex(84, 91); - * curveVertex(84, 91); + * splineVertex(84, 91); + * splineVertex(84, 91); * * // Add the anchor points to draw between. - * curveVertex(68, 19); - * curveVertex(21, 17); + * splineVertex(68, 19); + * splineVertex(21, 17); * * // Add the second control point. - * curveVertex(32, 91); + * splineVertex(32, 91); * * // Uncomment the next line to draw the segment to the second control point. - * // curveVertex(32, 91); + * // splineVertex(32, 91); * * endShape(); * * * The first two parameters, `x` and `y`, set the vertex’s location. For - * example, calling `curveVertex(10, 10)` adds a point to the curve at + * example, calling `splineVertex(10, 10)` adds a point to the curve at * `(10, 10)`. * * Spline curves can also be drawn in 3D using WebGL mode. The 3D version of - * `curveVertex()` has three arguments because each point has x-, y-, and + * `splineVertex()` has three arguments because each point has x-, y-, and * z-coordinates. By default, the vertex’s z-coordinate is set to 0. * - * Note: `curveVertex()` won’t work when an argument is passed to + * Note: `splineVertex()` won’t work when an argument is passed to * beginShape(). * * @method curveVertex @@ -769,14 +769,14 @@ function vertex(p5, fn){ * beginShape(); * * // Add the first control point. - * curveVertex(32, 91); + * splineVertex(32, 91); * * // Add the anchor points. - * curveVertex(21, 17); - * curveVertex(68, 19); + * splineVertex(21, 17); + * splineVertex(68, 19); * * // Add the second control point. - * curveVertex(84, 91); + * splineVertex(84, 91); * * // Stop drawing the shape. * endShape(); @@ -816,15 +816,15 @@ function vertex(p5, fn){ * beginShape(); * * // Add the first control point and draw a segment to it. - * curveVertex(32, 91); - * curveVertex(32, 91); + * splineVertex(32, 91); + * splineVertex(32, 91); * * // Add the anchor points. - * curveVertex(21, 17); - * curveVertex(68, 19); + * splineVertex(21, 17); + * splineVertex(68, 19); * * // Add the second control point. - * curveVertex(84, 91); + * splineVertex(84, 91); * * // Stop drawing the shape. * endShape(); @@ -864,16 +864,16 @@ function vertex(p5, fn){ * beginShape(); * * // Add the first control point and draw a segment to it. - * curveVertex(32, 91); - * curveVertex(32, 91); + * splineVertex(32, 91); + * splineVertex(32, 91); * * // Add the anchor points. - * curveVertex(21, 17); - * curveVertex(68, 19); + * splineVertex(21, 17); + * splineVertex(68, 19); * * // Add the second control point and draw a segment to it. - * curveVertex(84, 91); - * curveVertex(84, 91); + * splineVertex(84, 91); + * splineVertex(84, 91); * * // Stop drawing the shape. * endShape(); @@ -927,16 +927,16 @@ function vertex(p5, fn){ * beginShape(); * * // Add the first control point and draw a segment to it. - * curveVertex(x1, y1); - * curveVertex(x1, y1); + * splineVertex(x1, y1); + * splineVertex(x1, y1); * * // Add the anchor points. - * curveVertex(21, 17); - * curveVertex(68, 19); + * splineVertex(21, 17); + * splineVertex(68, 19); * * // Add the second control point and draw a segment to it. - * curveVertex(84, 91); - * curveVertex(84, 91); + * splineVertex(84, 91); + * splineVertex(84, 91); * * // Stop drawing the shape. * endShape(); @@ -988,16 +988,16 @@ function vertex(p5, fn){ * beginShape(); * * // Add the first control point and draw a segment to it. - * curveVertex(32, 91); - * curveVertex(32, 91); + * splineVertex(32, 91); + * splineVertex(32, 91); * * // Add the anchor points. - * curveVertex(21, 17); - * curveVertex(68, 19); + * splineVertex(21, 17); + * splineVertex(68, 19); * * // Add the second control point. - * curveVertex(84, 91); - * curveVertex(84, 91); + * splineVertex(84, 91); + * splineVertex(84, 91); * * // Stop drawing the shape. * endShape(); @@ -1037,12 +1037,12 @@ function vertex(p5, fn){ * fill('ghostwhite'); * * beginShape(); - * curveVertex(-28, 41, 0); - * curveVertex(-28, 41, 0); - * curveVertex(-29, -33, 0); - * curveVertex(18, -31, 0); - * curveVertex(34, 41, 0); - * curveVertex(34, 41, 0); + * splineVertex(-28, 41, 0); + * splineVertex(-28, 41, 0); + * splineVertex(-29, -33, 0); + * splineVertex(18, -31, 0); + * splineVertex(34, 41, 0); + * splineVertex(34, 41, 0); * endShape(); * * // Draw the second ghost. @@ -1050,12 +1050,12 @@ function vertex(p5, fn){ * stroke('ghostwhite'); * * beginShape(); - * curveVertex(-28, 41, -20); - * curveVertex(-28, 41, -20); - * curveVertex(-29, -33, -20); - * curveVertex(18, -31, -20); - * curveVertex(34, 41, -20); - * curveVertex(34, 41, -20); + * splineVertex(-28, 41, -20); + * splineVertex(-28, 41, -20); + * splineVertex(-29, -33, -20); + * splineVertex(18, -31, -20); + * splineVertex(34, 41, -20); + * splineVertex(34, 41, -20); * endShape(); * } * @@ -1063,7 +1063,7 @@ function vertex(p5, fn){ */ fn.curveVertex = function(...args) { p5._validateParameters('curveVertex', args); - this._renderer.curveVertex(...args); + this._renderer.splineVertex(...args); return this; }; @@ -1091,7 +1091,7 @@ function vertex(p5, fn){ * built by calling vertex(), * bezierVertex(), * quadraticVertex(), and/or - * curveVertex(). Calling + * splineVertex(). Calling * `endShape()` will stop adding vertices to the * shape. Each shape will be outlined with the current stroke color and filled * with the current fill color. diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 34d8ff04c1..8f8a4f5252 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -2114,10 +2114,10 @@ function primitives3D(p5, fn){ z1 = z2 = z3 = z4 = 0; } this.beginShape(); - this.curveVertex(x1, y1, z1); - this.curveVertex(x2, y2, z2); - this.curveVertex(x3, y3, z3); - this.curveVertex(x4, y4, z4); + this.splineVertex(x1, y1, z1); + this.splineVertex(x2, y2, z2); + this.splineVertex(x3, y3, z3); + this.splineVertex(x4, y4, z4); this.endShape(); }; diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 04256dc75b..6922ec6505 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1,54 +1,54 @@ -import * as constants from '../core/constants'; -import GeometryBuilder from './GeometryBuilder'; -import { Renderer } from '../core/p5.Renderer'; -import { Matrix } from './p5.Matrix'; -import { Camera } from './p5.Camera'; -import { Vector } from '../math/p5.Vector'; -import { RenderBuffer } from './p5.RenderBuffer'; -import { DataArray } from './p5.DataArray'; -import { Shader } from './p5.Shader'; -import { Image } from '../image/p5.Image'; -import { Texture, MipmapTexture } from './p5.Texture'; -import { Framebuffer } from './p5.Framebuffer'; -import { Graphics } from '../core/p5.Graphics'; -import { Element } from '../dom/p5.Element'; -import { ShapeBuilder } from './ShapeBuilder'; -import { GeometryBufferCache } from './GeometryBufferCache'; - -import lightingShader from './shaders/lighting.glsl'; -import webgl2CompatibilityShader from './shaders/webgl2Compatibility.glsl'; -import normalVert from './shaders/normal.vert'; -import normalFrag from './shaders/normal.frag'; -import basicFrag from './shaders/basic.frag'; -import sphereMappingFrag from './shaders/sphereMapping.frag'; -import lightVert from './shaders/light.vert'; -import lightTextureFrag from './shaders/light_texture.frag'; -import phongVert from './shaders/phong.vert'; -import phongFrag from './shaders/phong.frag'; -import fontVert from './shaders/font.vert'; -import fontFrag from './shaders/font.frag'; -import lineVert from './shaders/line.vert'; -import lineFrag from './shaders/line.frag'; -import pointVert from './shaders/point.vert'; -import pointFrag from './shaders/point.frag'; -import imageLightVert from './shaders/imageLight.vert'; -import imageLightDiffusedFrag from './shaders/imageLightDiffused.frag'; -import imageLightSpecularFrag from './shaders/imageLightSpecular.frag'; - -import filterGrayFrag from './shaders/filters/gray.frag'; -import filterErodeFrag from './shaders/filters/erode.frag'; -import filterDilateFrag from './shaders/filters/dilate.frag'; -import filterBlurFrag from './shaders/filters/blur.frag'; -import filterPosterizeFrag from './shaders/filters/posterize.frag'; -import filterOpaqueFrag from './shaders/filters/opaque.frag'; -import filterInvertFrag from './shaders/filters/invert.frag'; -import filterThresholdFrag from './shaders/filters/threshold.frag'; -import filterShaderVert from './shaders/filters/default.vert'; -import { PrimitiveToVerticesConverter } from '../shape/custom_shapes'; +import * as constants from "../core/constants"; +import GeometryBuilder from "./GeometryBuilder"; +import { Renderer } from "../core/p5.Renderer"; +import { Matrix } from "./p5.Matrix"; +import { Camera } from "./p5.Camera"; +import { Vector } from "../math/p5.Vector"; +import { RenderBuffer } from "./p5.RenderBuffer"; +import { DataArray } from "./p5.DataArray"; +import { Shader } from "./p5.Shader"; +import { Image } from "../image/p5.Image"; +import { Texture, MipmapTexture } from "./p5.Texture"; +import { Framebuffer } from "./p5.Framebuffer"; +import { Graphics } from "../core/p5.Graphics"; +import { Element } from "../dom/p5.Element"; +import { ShapeBuilder } from "./ShapeBuilder"; +import { GeometryBufferCache } from "./GeometryBufferCache"; + +import lightingShader from "./shaders/lighting.glsl"; +import webgl2CompatibilityShader from "./shaders/webgl2Compatibility.glsl"; +import normalVert from "./shaders/normal.vert"; +import normalFrag from "./shaders/normal.frag"; +import basicFrag from "./shaders/basic.frag"; +import sphereMappingFrag from "./shaders/sphereMapping.frag"; +import lightVert from "./shaders/light.vert"; +import lightTextureFrag from "./shaders/light_texture.frag"; +import phongVert from "./shaders/phong.vert"; +import phongFrag from "./shaders/phong.frag"; +import fontVert from "./shaders/font.vert"; +import fontFrag from "./shaders/font.frag"; +import lineVert from "./shaders/line.vert"; +import lineFrag from "./shaders/line.frag"; +import pointVert from "./shaders/point.vert"; +import pointFrag from "./shaders/point.frag"; +import imageLightVert from "./shaders/imageLight.vert"; +import imageLightDiffusedFrag from "./shaders/imageLightDiffused.frag"; +import imageLightSpecularFrag from "./shaders/imageLightSpecular.frag"; + +import filterGrayFrag from "./shaders/filters/gray.frag"; +import filterErodeFrag from "./shaders/filters/erode.frag"; +import filterDilateFrag from "./shaders/filters/dilate.frag"; +import filterBlurFrag from "./shaders/filters/blur.frag"; +import filterPosterizeFrag from "./shaders/filters/posterize.frag"; +import filterOpaqueFrag from "./shaders/filters/opaque.frag"; +import filterInvertFrag from "./shaders/filters/invert.frag"; +import filterThresholdFrag from "./shaders/filters/threshold.frag"; +import filterShaderVert from "./shaders/filters/default.vert"; +import { PrimitiveToVerticesConverter } from "../shape/custom_shapes"; const STROKE_CAP_ENUM = {}; const STROKE_JOIN_ENUM = {}; -let lineDefs = ''; +let lineDefs = ""; const defineStrokeCapEnum = function (key, val) { lineDefs += `#define STROKE_CAP_${key} ${val}\n`; STROKE_CAP_ENUM[constants[key]] = val; @@ -58,40 +58,33 @@ const defineStrokeJoinEnum = function (key, val) { STROKE_JOIN_ENUM[constants[key]] = val; }; - // Define constants in line shaders for each type of cap/join, and also record // the values in JS objects -defineStrokeCapEnum('ROUND', 0); -defineStrokeCapEnum('PROJECT', 1); -defineStrokeCapEnum('SQUARE', 2); -defineStrokeJoinEnum('ROUND', 0); -defineStrokeJoinEnum('MITER', 1); -defineStrokeJoinEnum('BEVEL', 2); +defineStrokeCapEnum("ROUND", 0); +defineStrokeCapEnum("PROJECT", 1); +defineStrokeCapEnum("SQUARE", 2); +defineStrokeJoinEnum("ROUND", 0); +defineStrokeJoinEnum("MITER", 1); +defineStrokeJoinEnum("BEVEL", 2); const defaultShaders = { normalVert, normalFrag, basicFrag, sphereMappingFrag, - lightVert: - lightingShader + - lightVert, + lightVert: lightingShader + lightVert, lightTextureFrag, phongVert, - phongFrag: - lightingShader + - phongFrag, + phongFrag: lightingShader + phongFrag, fontVert, fontFrag, - lineVert: - lineDefs + lineVert, - lineFrag: - lineDefs + lineFrag, + lineVert: lineDefs + lineVert, + lineFrag: lineDefs + lineFrag, pointVert, pointFrag, imageLightVert, imageLightDiffusedFrag, - imageLightSpecularFrag + imageLightSpecularFrag, }; let sphereMapping = defaultShaders.sphereMappingFrag; for (const key in defaultShaders) { @@ -106,7 +99,7 @@ const filterShaderFrags = { [constants.POSTERIZE]: filterPosterizeFrag, [constants.OPAQUE]: filterOpaqueFrag, [constants.INVERT]: filterInvertFrag, - [constants.THRESHOLD]: filterThresholdFrag + [constants.THRESHOLD]: filterThresholdFrag, }; /** @@ -122,7 +115,7 @@ class RendererGL extends Renderer { super(pInst, w, h, isMainCanvas); // Create new canvas - this.canvas = this.elt = elt || document.createElement('canvas'); + this.canvas = this.elt = elt || document.createElement("canvas"); this._setAttributeDefaults(pInst); this._initContext(); // This redundant property is useful in reminding you that you are @@ -138,10 +131,10 @@ class RendererGL extends Renderer { this._pInst.canvas = this.canvas; } else { // hide if offscreen buffer by default - this.canvas.style.display = 'none'; + this.canvas.style.display = "none"; } - this.elt.id = 'defaultCanvas0'; - this.elt.classList.add('p5Canvas'); + this.elt.id = "defaultCanvas0"; + this.elt.classList.add("p5Canvas"); const dimensions = this._adjustDimensions(w, h); w = dimensions.adjustedWidth; @@ -157,12 +150,9 @@ class RendererGL extends Renderer { this.elt.style.height = `${h}px`; this._origViewport = { width: this.GL.drawingBufferWidth, - height: this.GL.drawingBufferHeight + height: this.GL.drawingBufferHeight, }; - this.viewport( - this._origViewport.width, - this._origViewport.height - ); + this.viewport(this._origViewport.width, this._origViewport.height); // Attach canvas element to DOM if (this._pInst._userNode) { @@ -170,12 +160,12 @@ class RendererGL extends Renderer { this._pInst._userNode.appendChild(this.elt); } else { //create main element - if (document.getElementsByTagName('main').length === 0) { - let m = document.createElement('main'); + if (document.getElementsByTagName("main").length === 0) { + let m = document.createElement("main"); document.body.appendChild(m); } //append canvas to main - document.getElementsByTagName('main')[0].appendChild(this.elt); + document.getElementsByTagName("main")[0].appendChild(this.elt); } this.isP3D = true; //lets us know we're in 3d mode @@ -188,8 +178,8 @@ class RendererGL extends Renderer { this.states.uViewMatrix = new Matrix(); this.states.uMVMatrix = new Matrix(); this.states.uPMatrix = new Matrix(); - this.states.uNMatrix = new Matrix('mat3'); - this.states.curMatrix = new Matrix('mat3'); + this.states.uNMatrix = new Matrix("mat3"); + this.states.curMatrix = new Matrix("mat3"); this.states.curCamera = new Camera(this); @@ -266,7 +256,7 @@ class RendererGL extends Renderer { if (this.webglVersion === constants.WEBGL2) { this.blendExt = this.GL; } else { - this.blendExt = this.GL.getExtension('EXT_blend_minmax'); + this.blendExt = this.GL.getExtension("EXT_blend_minmax"); } this._isBlending = false; @@ -318,26 +308,87 @@ class RendererGL extends Renderer { this.buffers = { fill: [ - new RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray), - new RenderBuffer(3, 'vertexNormals', 'normalBuffer', 'aNormal', this, this._vToNArray), - new RenderBuffer(4, 'vertexColors', 'colorBuffer', 'aVertexColor', this), - new RenderBuffer(3, 'vertexAmbients', 'ambientBuffer', 'aAmbientColor', this), - new RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, (arr) => arr.flat()) + new RenderBuffer( + 3, + "vertices", + "vertexBuffer", + "aPosition", + this, + this._vToNArray, + ), + new RenderBuffer( + 3, + "vertexNormals", + "normalBuffer", + "aNormal", + this, + this._vToNArray, + ), + new RenderBuffer( + 4, + "vertexColors", + "colorBuffer", + "aVertexColor", + this, + ), + new RenderBuffer( + 3, + "vertexAmbients", + "ambientBuffer", + "aAmbientColor", + this, + ), + new RenderBuffer(2, "uvs", "uvBuffer", "aTexCoord", this, (arr) => + arr.flat(), + ), ], stroke: [ - new RenderBuffer(4, 'lineVertexColors', 'lineColorBuffer', 'aVertexColor', this), - new RenderBuffer(3, 'lineVertices', 'lineVerticesBuffer', 'aPosition', this), - new RenderBuffer(3, 'lineTangentsIn', 'lineTangentsInBuffer', 'aTangentIn', this), - new RenderBuffer(3, 'lineTangentsOut', 'lineTangentsOutBuffer', 'aTangentOut', this), - new RenderBuffer(1, 'lineSides', 'lineSidesBuffer', 'aSide', this) + new RenderBuffer( + 4, + "lineVertexColors", + "lineColorBuffer", + "aVertexColor", + this, + ), + new RenderBuffer( + 3, + "lineVertices", + "lineVerticesBuffer", + "aPosition", + this, + ), + new RenderBuffer( + 3, + "lineTangentsIn", + "lineTangentsInBuffer", + "aTangentIn", + this, + ), + new RenderBuffer( + 3, + "lineTangentsOut", + "lineTangentsOutBuffer", + "aTangentOut", + this, + ), + new RenderBuffer(1, "lineSides", "lineSidesBuffer", "aSide", this), ], text: [ - new RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray), - new RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, (arr) => arr.flat()) + new RenderBuffer( + 3, + "vertices", + "vertexBuffer", + "aPosition", + this, + this._vToNArray, + ), + new RenderBuffer(2, "uvs", "uvBuffer", "aTexCoord", this, (arr) => + arr.flat(), + ), ], point: this.GL.createBuffer(), - user:[] - } + user: [], + }; this.geometryBufferCache = new GeometryBufferCache(this); @@ -360,7 +411,6 @@ class RendererGL extends Renderer { this._curveTightness = 6; - this.fontInfos = {}; this._curShader = undefined; @@ -385,7 +435,9 @@ class RendererGL extends Renderer { */ beginGeometry() { if (this.geometryBuilder) { - throw new Error('It looks like `beginGeometry()` is being called while another p5.Geometry is already being build.'); + throw new Error( + "It looks like `beginGeometry()` is being called while another p5.Geometry is already being build.", + ); } this.geometryBuilder = new GeometryBuilder(this); this.geometryBuilder.prevFillColor = [...this.states.curFillColor]; @@ -403,7 +455,9 @@ class RendererGL extends Renderer { */ endGeometry() { if (!this.geometryBuilder) { - throw new Error('Make sure you call beginGeometry() before endGeometry()!'); + throw new Error( + "Make sure you call beginGeometry() before endGeometry()!", + ); } const geometry = this.geometryBuilder.finish(); this.states.curFillColor = this.geometryBuilder.prevFillColor; @@ -433,7 +487,6 @@ class RendererGL extends Renderer { return this.endGeometry(); } - ////////////////////////////////////////////// // Shape drawing ////////////////////////////////////////////// @@ -462,16 +515,19 @@ class RendererGL extends Renderer { if (this.geometryBuilder) { this.geometryBuilder.addImmediate( this.shapeBuilder.geometry, - this.shapeBuilder.shapeMode + this.shapeBuilder.shapeMode, ); } else if (this.states.fillColor || this.states.strokeColor) { if (this.shapeBuilder.shapeMode === constants.POINTS) { - this._drawPoints(this.shapeBuilder.geometry.vertices, this.buffers.point); - } else { - this._drawGeometry( - this.shapeBuilder.geometry, - { mode: this.shapeBuilder.shapeMode, count: this.drawShapeCount } + this._drawPoints( + this.shapeBuilder.geometry.vertices, + this.buffers.point, ); + } else { + this._drawGeometry(this.shapeBuilder.geometry, { + mode: this.shapeBuilder.shapeMode, + count: this.drawShapeCount, + }); } } this.drawShapeCount = 1; @@ -489,7 +545,7 @@ class RendererGL extends Renderer { isQuadratic, isContour, shapeKind, - count = 1 + count = 1, ) { this.shapeBuilder.endShape( mode, @@ -497,19 +553,19 @@ class RendererGL extends Renderer { isBezier, isQuadratic, isContour, - shapeKind + shapeKind, ); if (this.geometryBuilder) { this.geometryBuilder.addImmediate( this.shapeBuilder.geometry, - this.shapeBuilder.shapeMode + this.shapeBuilder.shapeMode, ); } else if (this.states.fillColor || this.states.strokeColor) { - this._drawGeometry( - this.shapeBuilder.geometry, - { mode: this.shapeBuilder.shapeMode, count } - ); + this._drawGeometry(this.shapeBuilder.geometry, { + mode: this.shapeBuilder.shapeMode, + count, + }); } } @@ -538,7 +594,13 @@ class RendererGL extends Renderer { for (const propName in geometry.userVertexProperties) { const prop = geometry.userVertexProperties[propName]; this.buffers.user.push( - new RenderBuffer(prop.getDataSize(), prop.getSrcName(), prop.getDstName(), prop.getName(), this) + new RenderBuffer( + prop.getDataSize(), + prop.getSrcName(), + prop.getDstName(), + prop.getName(), + this, + ), ); } @@ -557,12 +619,7 @@ class RendererGL extends Renderer { this.buffers.user = []; } - _drawGeometryScaled( - model, - scaleX, - scaleY, - scaleZ - ) { + _drawGeometryScaled(model, scaleX, scaleY, scaleZ) { let originalModelMatrix = this.states.uModelMatrix.copy(); try { this.states.uModelMatrix.scale(scaleX, scaleY, scaleZ); @@ -573,7 +630,6 @@ class RendererGL extends Renderer { this._drawGeometry(model); } } finally { - this.states.uModelMatrix = originalModelMatrix; } } @@ -581,9 +637,10 @@ class RendererGL extends Renderer { _drawFills(geometry, { count, mode } = {}) { this._useVertexColor = geometry.vertexColors.length > 0; - const shader = this._drawingFilter && this.states.userFillShader - ? this.states.userFillShader - : this._getFillShader(); + const shader = + this._drawingFilter && this.states.userFillShader + ? this.states.userFillShader + : this._getFillShader(); shader.bindShader(); this._setGlobalUniforms(shader); this._setFillUniforms(shader); @@ -597,7 +654,7 @@ class RendererGL extends Renderer { this._applyColorBlend( this.states.curFillColor, - geometry.hasFillTransparency() + geometry.hasFillTransparency(), ); this._drawBuffers(geometry, { mode, count }); @@ -624,25 +681,23 @@ class RendererGL extends Renderer { this._applyColorBlend( this.states.curStrokeColor, - geometry.hasStrokeTransparency() + geometry.hasStrokeTransparency(), ); if (count === 1) { - gl.drawArrays( - gl.TRIANGLES, - 0, - geometry.lineVertices.length / 3 - ); + gl.drawArrays(gl.TRIANGLES, 0, geometry.lineVertices.length / 3); } else { try { gl.drawArraysInstanced( gl.TRIANGLES, 0, geometry.lineVertices.length / 3, - count + count, ); } catch (e) { - console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode'); + console.log( + "🌸 p5.js says: Instancing is only supported in WebGL2 mode", + ); } } @@ -662,7 +717,7 @@ class RendererGL extends Renderer { gl.ARRAY_BUFFER, this._vToNArray(vertices), Float32Array, - gl.STATIC_DRAW + gl.STATIC_DRAW, ); pointShader.enableAttrib(pointShader.attributes.aPosition, 3); @@ -682,9 +737,15 @@ class RendererGL extends Renderer { if (prop) { const adjustedLength = prop.getSrcArray().length / prop.getDataSize(); if (adjustedLength > geometry.vertices.length) { - this._pInst.constructor._friendlyError(`One of the geometries has a custom vertex property '${prop.getName()}' with more values than vertices. This is probably caused by directly using the Geometry.vertexProperty() method.`, 'vertexProperty()'); + this._pInst.constructor._friendlyError( + `One of the geometries has a custom vertex property '${prop.getName()}' with more values than vertices. This is probably caused by directly using the Geometry.vertexProperty() method.`, + "vertexProperty()", + ); } else if (adjustedLength < geometry.vertices.length) { - this._pInst.constructor._friendlyError(`One of the geometries has a custom vertex property '${prop.getName()}' with fewer values than vertices. This is probably caused by directly using the Geometry.vertexProperty() method.`, 'vertexProperty()'); + this._pInst.constructor._friendlyError( + `One of the geometries has a custom vertex property '${prop.getName()}' with fewer values than vertices. This is probably caused by directly using the Geometry.vertexProperty() method.`, + "vertexProperty()", + ); } } } @@ -707,9 +768,9 @@ class RendererGL extends Renderer { this._pInst.webglVersion !== constants.WEBGL2 && glBuffers.indexBufferType === gl.UNSIGNED_INT ) { - if (!gl.getExtension('OES_element_index_uint')) { + if (!gl.getExtension("OES_element_index_uint")) { throw new Error( - 'Unable to render a 3d model with > 65535 triangles. Your web browser does not support the WebGL Extension OES_element_index_uint.' + "Unable to render a 3d model with > 65535 triangles. Your web browser does not support the WebGL Extension OES_element_index_uint.", ); } } @@ -719,7 +780,7 @@ class RendererGL extends Renderer { gl.TRIANGLES, geometry.faces.length * 3, glBuffers.indexBufferType, - 0 + 0, ); } else { try { @@ -728,29 +789,24 @@ class RendererGL extends Renderer { geometry.faces.length * 3, glBuffers.indexBufferType, 0, - count + count, ); } catch (e) { - console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode'); + console.log( + "🌸 p5.js says: Instancing is only supported in WebGL2 mode", + ); } } } else { if (count === 1) { - gl.drawArrays( - mode, - 0, - geometry.vertices.length - ); + gl.drawArrays(mode, 0, geometry.vertices.length); } else { try { - gl.drawArraysInstanced( - mode, - 0, - geometry.vertices.length, - count - ); + gl.drawArraysInstanced(mode, 0, geometry.vertices.length, count); } catch (e) { - console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode'); + console.log( + "🌸 p5.js says: Instancing is only supported in WebGL2 mode", + ); } } } @@ -766,7 +822,7 @@ class RendererGL extends Renderer { _setAttributeDefaults(pInst) { // See issue #3850, safer to enable AA in Safari - const applyAA = navigator.userAgent.toLowerCase().includes('safari'); + const applyAA = navigator.userAgent.toLowerCase().includes("safari"); const defaults = { alpha: true, depth: true, @@ -775,7 +831,7 @@ class RendererGL extends Renderer { premultipliedAlpha: true, preserveDrawingBuffer: true, perPixelLighting: true, - version: 2 + version: 2, }; if (pInst._glAttributes === null) { pInst._glAttributes = defaults; @@ -788,11 +844,14 @@ class RendererGL extends Renderer { _initContext() { if (this._pInst._glAttributes?.version !== 1) { // Unless WebGL1 is explicitly asked for, try to create a WebGL2 context - this.drawingContext = - this.canvas.getContext('webgl2', this._pInst._glAttributes); + this.drawingContext = this.canvas.getContext( + "webgl2", + this._pInst._glAttributes, + ); } - this.webglVersion = - this.drawingContext ? constants.WEBGL2 : constants.WEBGL; + this.webglVersion = this.drawingContext + ? constants.WEBGL2 + : constants.WEBGL; // If this is the main canvas, make sure the global `webglVersion` is set this._pInst.webglVersion = this.webglVersion; if (!this.drawingContext) { @@ -800,11 +859,11 @@ class RendererGL extends Renderer { // disabled via `setAttributes({ version: 1 })` or because the device // doesn't support it), fall back to a WebGL1 context this.drawingContext = - this.canvas.getContext('webgl', this._pInst._glAttributes) || - this.canvas.getContext('experimental-webgl', this._pInst._glAttributes); + this.canvas.getContext("webgl", this._pInst._glAttributes) || + this.canvas.getContext("experimental-webgl", this._pInst._glAttributes); } if (this.drawingContext === null) { - throw new Error('Error creating webgl context'); + throw new Error("Error creating webgl context"); } else { const gl = this.drawingContext; gl.enable(gl.DEPTH_TEST); @@ -815,7 +874,7 @@ class RendererGL extends Renderer { // be encoded the same way as textures from everything else. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); this._viewport = this.drawingContext.getParameter( - this.drawingContext.VIEWPORT + this.drawingContext.VIEWPORT, ); } } @@ -832,19 +891,15 @@ class RendererGL extends Renderer { let maxTextureSize = this._maxTextureSize; let maxAllowedPixelDimensions = Math.floor( - maxTextureSize / this._pixelDensity - ); - let adjustedWidth = Math.min( - width, maxAllowedPixelDimensions - ); - let adjustedHeight = Math.min( - height, maxAllowedPixelDimensions + maxTextureSize / this._pixelDensity, ); + let adjustedWidth = Math.min(width, maxAllowedPixelDimensions); + let adjustedHeight = Math.min(height, maxAllowedPixelDimensions); if (adjustedWidth !== width || adjustedHeight !== height) { console.warn( - 'Warning: The requested width/height exceeds hardware limits. ' + - `Adjusting dimensions to width: ${adjustedWidth}, height: ${adjustedHeight}.` + "Warning: The requested width/height exceeds hardware limits. " + + `Adjusting dimensions to width: ${adjustedWidth}, height: ${adjustedHeight}.`, ); } @@ -863,7 +918,7 @@ class RendererGL extends Renderer { if (isPGraphics) { const pg = this._pInst; pg.canvas.parentNode.removeChild(pg.canvas); - pg.canvas = document.createElement('canvas'); + pg.canvas = document.createElement("canvas"); const node = pg._pInst._userNode || document.body; node.appendChild(pg.canvas); Element.call(pg, pg.canvas, pg._pInst); @@ -874,7 +929,7 @@ class RendererGL extends Renderer { if (c) { c.parentNode.removeChild(c); } - c = document.createElement('canvas'); + c = document.createElement("canvas"); c.id = defaultId; if (this._pInst._userNode) { this._pInst._userNode.appendChild(c); @@ -896,7 +951,7 @@ class RendererGL extends Renderer { renderer._applyDefaults(); - if (typeof callback === 'function') { + if (typeof callback === "function") { //setTimeout with 0 forces the task to the back of the queue, this ensures that //we finish switching out the renderer setTimeout(() => { @@ -905,7 +960,6 @@ class RendererGL extends Renderer { } } - _update() { // reset model view and apply initial camera transform // (containing only look at info; no projection). @@ -963,7 +1017,6 @@ class RendererGL extends Renderer { return modelMatrix.copy().mult(viewMatrix); } - ////////////////////////////////////////////// // COLOR ////////////////////////////////////////////// @@ -1049,13 +1102,13 @@ class RendererGL extends Renderer { stroke: this.states.strokeColor, fill: this.states.fillColor, normal: this.states._currentNormal, - } + }; } getSupportedIndividualVertexProperties() { return { textureCoordinates: true, - } + }; } strokeCap(cap) { @@ -1095,12 +1148,12 @@ class RendererGL extends Renderer { // use internal shader for filter constants BLUR, INVERT, etc let filterParameter = undefined; let operation = undefined; - if (typeof args[0] === 'string') { + if (typeof args[0] === "string") { operation = args[0]; let defaults = { [constants.BLUR]: 3, [constants.POSTERIZE]: 4, - [constants.THRESHOLD]: 0.5 + [constants.THRESHOLD]: 0.5, }; let useDefaultParam = operation in defaults && args[1] === undefined; filterParameter = useDefaultParam ? defaults[operation] : args[1]; @@ -1112,11 +1165,10 @@ class RendererGL extends Renderer { this.defaultFilterShaders[operation] = new Shader( fbo.renderer, filterShaderVert, - filterShaderFrags[operation] + filterShaderFrags[operation], ); } this.states.filterShader = this.defaultFilterShaders[operation]; - } // use custom user-supplied shader else { @@ -1134,7 +1186,7 @@ class RendererGL extends Renderer { let texelSize = [ 1 / (target.width * target.pixelDensity()), - 1 / (target.height * target.pixelDensity()) + 1 / (target.height * target.pixelDensity()), ]; // apply blur shader with multiple passes. @@ -1150,14 +1202,20 @@ class RendererGL extends Renderer { // draw main to temp buffer this.shader(this.states.filterShader); - this.states.filterShader.setUniform('texelSize', texelSize); - this.states.filterShader.setUniform('canvasSize', [target.width, target.height]); - this.states.filterShader.setUniform('radius', Math.max(1, filterParameter)); + this.states.filterShader.setUniform("texelSize", texelSize); + this.states.filterShader.setUniform("canvasSize", [ + target.width, + target.height, + ]); + this.states.filterShader.setUniform( + "radius", + Math.max(1, filterParameter), + ); // Horiz pass: draw `target` to `tmp` tmp.draw(() => { - this.states.filterShader.setUniform('direction', [1, 0]); - this.states.filterShader.setUniform('tex0', target); + this.states.filterShader.setUniform("direction", [1, 0]); + this.states.filterShader.setUniform("tex0", target); this.clear(); this.shader(this.states.filterShader); this.noLights(); @@ -1166,8 +1224,8 @@ class RendererGL extends Renderer { // Vert pass: draw `tmp` to `fbo` fbo.draw(() => { - this.states.filterShader.setUniform('direction', [0, 1]); - this.states.filterShader.setUniform('tex0', tmp); + this.states.filterShader.setUniform("direction", [0, 1]); + this.states.filterShader.setUniform("tex0", tmp); this.clear(); this.shader(this.states.filterShader); this.noLights(); @@ -1182,16 +1240,18 @@ class RendererGL extends Renderer { this.states.strokeColor = null; this.blendMode(constants.BLEND); this.shader(this.states.filterShader); - this.states.filterShader.setUniform('tex0', target); - this.states.filterShader.setUniform('texelSize', texelSize); - this.states.filterShader.setUniform('canvasSize', [target.width, target.height]); + this.states.filterShader.setUniform("tex0", target); + this.states.filterShader.setUniform("texelSize", texelSize); + this.states.filterShader.setUniform("canvasSize", [ + target.width, + target.height, + ]); // filterParameter uniform only used for POSTERIZE, and THRESHOLD // but shouldn't hurt to always set - this.states.filterShader.setUniform('filterParameter', filterParameter); + this.states.filterShader.setUniform("filterParameter", filterParameter); this.noLights(); this.plane(target.width, target.height); }); - } // draw fbo contents onto main renderer. this.push(); @@ -1206,10 +1266,14 @@ class RendererGL extends Renderer { this._drawingFilter = true; this.image( fbo, - 0, 0, - this.width, this.height, - -target.width / 2, -target.height / 2, - target.width, target.height + 0, + 0, + this.width, + this.height, + -target.width / 2, + -target.height / 2, + target.width, + target.height, ); this._drawingFilter = false; this.clearDepth(); @@ -1249,7 +1313,7 @@ class RendererGL extends Renderer { mode === constants.DODGE ) { console.warn( - 'BURN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, and DODGE only work for blendMode in 2D mode.' + "BURN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, and DODGE only work for blendMode in 2D mode.", ); } } @@ -1297,12 +1361,12 @@ class RendererGL extends Renderer { gl.stencilFunc( gl.ALWAYS, // the test 1, // reference value - 0xff // mask + 0xff, // mask ); gl.stencilOp( gl.KEEP, // what to do if the stencil test fails gl.KEEP, // what to do if the depth test fails - gl.REPLACE // what to do if both tests pass + gl.REPLACE, // what to do if both tests pass ); gl.disable(gl.DEPTH_TEST); @@ -1319,12 +1383,12 @@ class RendererGL extends Renderer { gl.stencilOp( gl.KEEP, // what to do if the stencil test fails gl.KEEP, // what to do if the depth test fails - gl.KEEP // what to do if both tests pass + gl.KEEP, // what to do if both tests pass ); gl.stencilFunc( this._clipInvert ? gl.EQUAL : gl.NOTEQUAL, // the test 0, // reference value - 0xff // mask + 0xff, // mask ); gl.enable(gl.DEPTH_TEST); @@ -1354,7 +1418,7 @@ class RendererGL extends Renderer { y, gl.RGBA, gl.UNSIGNED_BYTE, - this._pInst.height * this._pInst.pixelDensity() + this._pInst.height * this._pInst.pixelDensity(), ); } @@ -1369,7 +1433,8 @@ class RendererGL extends Renderer { //@todo_FES if (this._pInst._glAttributes.preserveDrawingBuffer !== true) { console.log( - 'loadPixels only works in WebGL when preserveDrawingBuffer ' + 'is true.' + "loadPixels only works in WebGL when preserveDrawingBuffer " + + "is true.", ); return; } @@ -1377,19 +1442,18 @@ class RendererGL extends Renderer { const pd = this._pixelDensity; const gl = this.GL; - this.pixels = - readPixelsWebGL( - this.pixels, - gl, - null, - 0, - 0, - this.width * pd, - this.height * pd, - gl.RGBA, - gl.UNSIGNED_BYTE, - this.height * pd - ); + this.pixels = readPixelsWebGL( + this.pixels, + gl, + null, + 0, + 0, + this.width * pd, + this.height * pd, + gl.RGBA, + gl.UNSIGNED_BYTE, + this.height * pd, + ); } updatePixels() { @@ -1400,7 +1464,17 @@ class RendererGL extends Renderer { this.resetMatrix(); this.clear(); this.states.imageMode = constants.CORNER; - this.image(fbo, 0, 0, fbo.width, fbo.height, -fbo.width/2, -fbo.height/2, fbo.width, fbo.height); + this.image( + fbo, + 0, + 0, + fbo.width, + fbo.height, + -fbo.width / 2, + -fbo.height / 2, + fbo.width, + fbo.height, + ); this.pop(); this.GL.clearDepth(1); this.GL.clear(this.GL.DEPTH_BUFFER_BIT); @@ -1418,14 +1492,12 @@ class RendererGL extends Renderer { format: constants.UNSIGNED_BYTE, useDepth: this._pInst._glAttributes.depth, depthFormat: constants.UNSIGNED_INT, - antialias: this._pInst._glAttributes.antialias + antialias: this._pInst._glAttributes.antialias, }); } return this._tempFramebuffer; } - - ////////////////////////////////////////////// // HASH | for geometry ////////////////////////////////////////////// @@ -1452,7 +1524,7 @@ class RendererGL extends Renderer { const props = {}; for (const key in this.drawingContext) { const val = this.drawingContext[key]; - if (typeof val !== 'object' && typeof val !== 'function') { + if (typeof val !== "object" && typeof val !== "function") { props[key] = val; } } @@ -1470,21 +1542,17 @@ class RendererGL extends Renderer { this.canvas.style.height = `${h}px`; this._origViewport = { width: this.GL.drawingBufferWidth, - height: this.GL.drawingBufferHeight + height: this.GL.drawingBufferHeight, }; - this.viewport( - this._origViewport.width, - this._origViewport.height - ); + this.viewport(this._origViewport.width, this._origViewport.height); this.states.curCamera._resize(); //resize pixels buffer - if (typeof this.pixels !== 'undefined') { - this.pixels = - new Uint8Array( - this.GL.drawingBufferWidth * this.GL.drawingBufferHeight * 4 - ); + if (typeof this.pixels !== "undefined") { + this.pixels = new Uint8Array( + this.GL.drawingBufferWidth * this.GL.drawingBufferHeight * 4, + ); } for (const framebuffer of this.framebuffers) { @@ -1552,10 +1620,22 @@ class RendererGL extends Renderer { Matrix.prototype.apply.apply(this.states.uModelMatrix, arguments); } else { this.states.uModelMatrix.apply([ - a, b, 0, 0, - c, d, 0, 0, - 0, 0, 1, 0, - e, f, 0, 1 + a, + b, + 0, + 0, + c, + d, + 0, + 0, + 0, + 0, + 1, + 0, + e, + f, + 0, + 1, ]); } } @@ -1593,7 +1673,7 @@ class RendererGL extends Renderer { } rotate(rad, axis) { - if (typeof axis === 'undefined') { + if (typeof axis === "undefined") { return this.rotateZ(rad); } Matrix.prototype.rotate.apply(this.states.uModelMatrix, arguments); @@ -1662,19 +1742,19 @@ class RendererGL extends Renderer { return this._getLineShader(); } - _getSphereMapping(img) { if (!this.sphereMapping) { - this.sphereMapping = this._pInst.createFilterShader( - sphereMapping - ); + this.sphereMapping = this._pInst.createFilterShader(sphereMapping); } this.states.uNMatrix.inverseTranspose(this.states.uViewMatrix); this.states.uNMatrix.invert3x3(this.states.uNMatrix); - this.sphereMapping.setUniform('uFovY', this.states.curCamera.cameraFOV); - this.sphereMapping.setUniform('uAspect', this.states.curCamera.aspectRatio); - this.sphereMapping.setUniform('uNewNormalMatrix', this.states.uNMatrix.mat3); - this.sphereMapping.setUniform('uSampler', img); + this.sphereMapping.setUniform("uFovY", this.states.curCamera.cameraFOV); + this.sphereMapping.setUniform("uAspect", this.states.curCamera.aspectRatio); + this.sphereMapping.setUniform( + "uNewNormalMatrix", + this.states.uNMatrix.mat3, + ); + this.sphereMapping.setUniform("uSampler", img); return this.sphereMapping; } @@ -1709,7 +1789,6 @@ class RendererGL extends Renderer { return this._getColorShader(); } - _getPointShader() { // select the point shader to use const point = this.states.userPointShader; @@ -1722,7 +1801,7 @@ class RendererGL extends Renderer { baseMaterialShader() { if (!this._pInst._glAttributes.perPixelLighting) { throw new Error( - 'The material shader does not support hooks without perPixelLighting. Try turning it back on.' + "The material shader does not support hooks without perPixelLighting. Try turning it back on.", ); } return this._getLightShader(); @@ -1733,25 +1812,25 @@ class RendererGL extends Renderer { if (this._pInst._glAttributes.perPixelLighting) { this._defaultLightShader = new Shader( this, - this._webGL2CompatibilityPrefix('vert', 'highp') + - defaultShaders.phongVert, - this._webGL2CompatibilityPrefix('frag', 'highp') + - defaultShaders.phongFrag, + this._webGL2CompatibilityPrefix("vert", "highp") + + defaultShaders.phongVert, + this._webGL2CompatibilityPrefix("frag", "highp") + + defaultShaders.phongFrag, { vertex: { - 'void beforeVertex': '() {}', - 'vec3 getLocalPosition': '(vec3 position) { return position; }', - 'vec3 getWorldPosition': '(vec3 position) { return position; }', - 'vec3 getLocalNormal': '(vec3 normal) { return normal; }', - 'vec3 getWorldNormal': '(vec3 normal) { return normal; }', - 'vec2 getUV': '(vec2 uv) { return uv; }', - 'vec4 getVertexColor': '(vec4 color) { return color; }', - 'void afterVertex': '() {}' + "void beforeVertex": "() {}", + "vec3 getLocalPosition": "(vec3 position) { return position; }", + "vec3 getWorldPosition": "(vec3 position) { return position; }", + "vec3 getLocalNormal": "(vec3 normal) { return normal; }", + "vec3 getWorldNormal": "(vec3 normal) { return normal; }", + "vec2 getUV": "(vec2 uv) { return uv; }", + "vec4 getVertexColor": "(vec4 color) { return color; }", + "void afterVertex": "() {}", }, fragment: { - 'void beforeFragment': '() {}', - 'Inputs getPixelInputs': '(Inputs inputs) { return inputs; }', - 'vec4 combineColors': `(ColorComponents components) { + "void beforeFragment": "() {}", + "Inputs getPixelInputs": "(Inputs inputs) { return inputs; }", + "vec4 combineColors": `(ColorComponents components) { vec4 color = vec4(0.); color.rgb += components.diffuse * components.baseColor; color.rgb += components.ambient * components.ambientColor; @@ -1760,18 +1839,18 @@ class RendererGL extends Renderer { color.a = components.opacity; return color; }`, - 'vec4 getFinalColor': '(vec4 color) { return color; }', - 'void afterFragment': '() {}' - } - } + "vec4 getFinalColor": "(vec4 color) { return color; }", + "void afterFragment": "() {}", + }, + }, ); } else { this._defaultLightShader = new Shader( this, - this._webGL2CompatibilityPrefix('vert', 'highp') + - defaultShaders.lightVert, - this._webGL2CompatibilityPrefix('frag', 'highp') + - defaultShaders.lightTextureFrag + this._webGL2CompatibilityPrefix("vert", "highp") + + defaultShaders.lightVert, + this._webGL2CompatibilityPrefix("frag", "highp") + + defaultShaders.lightTextureFrag, ); } } @@ -1787,27 +1866,27 @@ class RendererGL extends Renderer { if (!this._defaultNormalShader) { this._defaultNormalShader = new Shader( this, - this._webGL2CompatibilityPrefix('vert', 'mediump') + - defaultShaders.normalVert, - this._webGL2CompatibilityPrefix('frag', 'mediump') + - defaultShaders.normalFrag, + this._webGL2CompatibilityPrefix("vert", "mediump") + + defaultShaders.normalVert, + this._webGL2CompatibilityPrefix("frag", "mediump") + + defaultShaders.normalFrag, { vertex: { - 'void beforeVertex': '() {}', - 'vec3 getLocalPosition': '(vec3 position) { return position; }', - 'vec3 getWorldPosition': '(vec3 position) { return position; }', - 'vec3 getLocalNormal': '(vec3 normal) { return normal; }', - 'vec3 getWorldNormal': '(vec3 normal) { return normal; }', - 'vec2 getUV': '(vec2 uv) { return uv; }', - 'vec4 getVertexColor': '(vec4 color) { return color; }', - 'void afterVertex': '() {}' + "void beforeVertex": "() {}", + "vec3 getLocalPosition": "(vec3 position) { return position; }", + "vec3 getWorldPosition": "(vec3 position) { return position; }", + "vec3 getLocalNormal": "(vec3 normal) { return normal; }", + "vec3 getWorldNormal": "(vec3 normal) { return normal; }", + "vec2 getUV": "(vec2 uv) { return uv; }", + "vec4 getVertexColor": "(vec4 color) { return color; }", + "void afterVertex": "() {}", }, fragment: { - 'void beforeFragment': '() {}', - 'vec4 getFinalColor': '(vec4 color) { return color; }', - 'void afterFragment': '() {}' - } - } + "void beforeFragment": "() {}", + "vec4 getFinalColor": "(vec4 color) { return color; }", + "void afterFragment": "() {}", + }, + }, ); } @@ -1822,27 +1901,27 @@ class RendererGL extends Renderer { if (!this._defaultColorShader) { this._defaultColorShader = new Shader( this, - this._webGL2CompatibilityPrefix('vert', 'mediump') + - defaultShaders.normalVert, - this._webGL2CompatibilityPrefix('frag', 'mediump') + - defaultShaders.basicFrag, + this._webGL2CompatibilityPrefix("vert", "mediump") + + defaultShaders.normalVert, + this._webGL2CompatibilityPrefix("frag", "mediump") + + defaultShaders.basicFrag, { vertex: { - 'void beforeVertex': '() {}', - 'vec3 getLocalPosition': '(vec3 position) { return position; }', - 'vec3 getWorldPosition': '(vec3 position) { return position; }', - 'vec3 getLocalNormal': '(vec3 normal) { return normal; }', - 'vec3 getWorldNormal': '(vec3 normal) { return normal; }', - 'vec2 getUV': '(vec2 uv) { return uv; }', - 'vec4 getVertexColor': '(vec4 color) { return color; }', - 'void afterVertex': '() {}' + "void beforeVertex": "() {}", + "vec3 getLocalPosition": "(vec3 position) { return position; }", + "vec3 getWorldPosition": "(vec3 position) { return position; }", + "vec3 getLocalNormal": "(vec3 normal) { return normal; }", + "vec3 getWorldNormal": "(vec3 normal) { return normal; }", + "vec2 getUV": "(vec2 uv) { return uv; }", + "vec4 getVertexColor": "(vec4 color) { return color; }", + "void afterVertex": "() {}", }, fragment: { - 'void beforeFragment': '() {}', - 'vec4 getFinalColor': '(vec4 color) { return color; }', - 'void afterFragment': '() {}' - } - } + "void beforeFragment": "() {}", + "vec4 getFinalColor": "(vec4 color) { return color; }", + "void afterFragment": "() {}", + }, + }, ); } @@ -1881,25 +1960,25 @@ class RendererGL extends Renderer { if (!this._defaultPointShader) { this._defaultPointShader = new Shader( this, - this._webGL2CompatibilityPrefix('vert', 'mediump') + - defaultShaders.pointVert, - this._webGL2CompatibilityPrefix('frag', 'mediump') + - defaultShaders.pointFrag, + this._webGL2CompatibilityPrefix("vert", "mediump") + + defaultShaders.pointVert, + this._webGL2CompatibilityPrefix("frag", "mediump") + + defaultShaders.pointFrag, { vertex: { - 'void beforeVertex': '() {}', - 'vec3 getLocalPosition': '(vec3 position) { return position; }', - 'vec3 getWorldPosition': '(vec3 position) { return position; }', - 'float getPointSize': '(float size) { return size; }', - 'void afterVertex': '() {}' + "void beforeVertex": "() {}", + "vec3 getLocalPosition": "(vec3 position) { return position; }", + "vec3 getWorldPosition": "(vec3 position) { return position; }", + "float getPointSize": "(float size) { return size; }", + "void afterVertex": "() {}", }, fragment: { - 'void beforeFragment': '() {}', - 'vec4 getFinalColor': '(vec4 color) { return color; }', - 'bool shouldDiscard': '(bool outside) { return outside; }', - 'void afterFragment': '() {}' - } - } + "void beforeFragment": "() {}", + "vec4 getFinalColor": "(vec4 color) { return color; }", + "bool shouldDiscard": "(bool outside) { return outside; }", + "void afterFragment": "() {}", + }, + }, ); } return this._defaultPointShader; @@ -1913,29 +1992,29 @@ class RendererGL extends Renderer { if (!this._defaultLineShader) { this._defaultLineShader = new Shader( this, - this._webGL2CompatibilityPrefix('vert', 'mediump') + - defaultShaders.lineVert, - this._webGL2CompatibilityPrefix('frag', 'mediump') + - defaultShaders.lineFrag, + this._webGL2CompatibilityPrefix("vert", "mediump") + + defaultShaders.lineVert, + this._webGL2CompatibilityPrefix("frag", "mediump") + + defaultShaders.lineFrag, { vertex: { - 'void beforeVertex': '() {}', - 'vec3 getLocalPosition': '(vec3 position) { return position; }', - 'vec3 getWorldPosition': '(vec3 position) { return position; }', - 'float getStrokeWeight': '(float weight) { return weight; }', - 'vec2 getLineCenter': '(vec2 center) { return center; }', - 'vec2 getLinePosition': '(vec2 position) { return position; }', - 'vec4 getVertexColor': '(vec4 color) { return color; }', - 'void afterVertex': '() {}' + "void beforeVertex": "() {}", + "vec3 getLocalPosition": "(vec3 position) { return position; }", + "vec3 getWorldPosition": "(vec3 position) { return position; }", + "float getStrokeWeight": "(float weight) { return weight; }", + "vec2 getLineCenter": "(vec2 center) { return center; }", + "vec2 getLinePosition": "(vec2 position) { return position; }", + "vec4 getVertexColor": "(vec4 color) { return color; }", + "void afterVertex": "() {}", }, fragment: { - 'void beforeFragment': '() {}', - 'Inputs getPixelInputs': '(Inputs inputs) { return inputs; }', - 'vec4 getFinalColor': '(vec4 color) { return color; }', - 'bool shouldDiscard': '(bool outside) { return outside; }', - 'void afterFragment': '() {}' - } - } + "void beforeFragment": "() {}", + "Inputs getPixelInputs": "(Inputs inputs) { return inputs; }", + "vec4 getFinalColor": "(vec4 color) { return color; }", + "bool shouldDiscard": "(bool outside) { return outside; }", + "void afterFragment": "() {}", + }, + }, ); } @@ -1945,32 +2024,28 @@ class RendererGL extends Renderer { _getFontShader() { if (!this._defaultFontShader) { if (this.webglVersion === constants.WEBGL) { - this.GL.getExtension('OES_standard_derivatives'); + this.GL.getExtension("OES_standard_derivatives"); } this._defaultFontShader = new Shader( this, - this._webGL2CompatibilityPrefix('vert', 'mediump') + - defaultShaders.fontVert, - this._webGL2CompatibilityPrefix('frag', 'mediump') + - defaultShaders.fontFrag + this._webGL2CompatibilityPrefix("vert", "mediump") + + defaultShaders.fontVert, + this._webGL2CompatibilityPrefix("frag", "mediump") + + defaultShaders.fontFrag, ); } return this._defaultFontShader; } - - _webGL2CompatibilityPrefix( - shaderType, - floatPrecision - ) { - let code = ''; + _webGL2CompatibilityPrefix(shaderType, floatPrecision) { + let code = ""; if (this.webglVersion === constants.WEBGL2) { - code += '#version 300 es\n#define WEBGL2\n'; + code += "#version 300 es\n#define WEBGL2\n"; } - if (shaderType === 'vert') { - code += '#define VERTEX_SHADER\n'; - } else if (shaderType === 'frag') { - code += '#define FRAGMENT_SHADER\n'; + if (shaderType === "vert") { + code += "#define VERTEX_SHADER\n"; + } else if (shaderType === "frag") { + code += "#define FRAGMENT_SHADER\n"; } if (floatPrecision) { code += `precision ${floatPrecision} float;\n`; @@ -2028,19 +2103,21 @@ class RendererGL extends Renderer { let width = smallWidth; let height = Math.floor(smallWidth * (input.height / input.width)); newFramebuffer = new Framebuffer(this, { - width, height, density: 1 - }) + width, + height, + density: 1, + }); // create framebuffer is like making a new sketch, all functions on main // sketch it would be available on framebuffer if (!this.states.diffusedShader) { this.states.diffusedShader = this._pInst.createShader( defaultShaders.imageLightVert, - defaultShaders.imageLightDiffusedFrag + defaultShaders.imageLightDiffusedFrag, ); } newFramebuffer.draw(() => { this.shader(this.states.diffusedShader); - this.states.diffusedShader.setUniform('environmentMap', input); + this.states.diffusedShader.setUniform("environmentMap", input); this.states.strokeColor = null; this.noLights(); this.plane(width, height); @@ -2069,13 +2146,15 @@ class RendererGL extends Renderer { let tex; const levels = []; const framebuffer = new Framebuffer(this, { - width: size, height: size, density: 1 + width: size, + height: size, + density: 1, }); let count = Math.log(size) / Math.log(2); if (!this.states.specularShader) { this.states.specularShader = this._pInst.createShader( defaultShaders.imageLightVert, - defaultShaders.imageLightSpecularFrag + defaultShaders.imageLightSpecularFrag, ); } // currently only 8 levels @@ -2090,8 +2169,8 @@ class RendererGL extends Renderer { framebuffer.draw(() => { this.shader(this.states.specularShader); this.clear(); - this.states.specularShader.setUniform('environmentMap', input); - this.states.specularShader.setUniform('roughness', roughness); + this.states.specularShader.setUniform("environmentMap", input); + this.states.specularShader.setUniform("roughness", roughness); this.states.strokeColor = null; this.noLights(); this.plane(w, w); @@ -2122,43 +2201,46 @@ class RendererGL extends Renderer { const modelMatrix = this.states.uModelMatrix; const viewMatrix = this.states.uViewMatrix; const projectionMatrix = this.states.uPMatrix; - const modelViewMatrix = (modelMatrix.copy()).mult(viewMatrix); + const modelViewMatrix = modelMatrix.copy().mult(viewMatrix); this.states.uMVMatrix = this.calculateCombinedMatrix(); const modelViewProjectionMatrix = modelViewMatrix.copy(); modelViewProjectionMatrix.mult(projectionMatrix); shader.setUniform( - 'uPerspective', - this.states.curCamera.useLinePerspective ? 1 : 0 + "uPerspective", + this.states.curCamera.useLinePerspective ? 1 : 0, ); - shader.setUniform('uViewMatrix', viewMatrix.mat4); - shader.setUniform('uProjectionMatrix', projectionMatrix.mat4); - shader.setUniform('uModelMatrix', modelMatrix.mat4); - shader.setUniform('uModelViewMatrix', modelViewMatrix.mat4); + shader.setUniform("uViewMatrix", viewMatrix.mat4); + shader.setUniform("uProjectionMatrix", projectionMatrix.mat4); + shader.setUniform("uModelMatrix", modelMatrix.mat4); + shader.setUniform("uModelViewMatrix", modelViewMatrix.mat4); shader.setUniform( - 'uModelViewProjectionMatrix', - modelViewProjectionMatrix.mat4 + "uModelViewProjectionMatrix", + modelViewProjectionMatrix.mat4, ); if (shader.uniforms.uNormalMatrix) { this.states.uNMatrix.inverseTranspose(this.states.uMVMatrix); - shader.setUniform('uNormalMatrix', this.states.uNMatrix.mat3); + shader.setUniform("uNormalMatrix", this.states.uNMatrix.mat3); } if (shader.uniforms.uCameraRotation) { this.states.curMatrix.inverseTranspose(this.states.uViewMatrix); - shader.setUniform('uCameraRotation', this.states.curMatrix.mat3); + shader.setUniform("uCameraRotation", this.states.curMatrix.mat3); } - shader.setUniform('uViewport', this._viewport); + shader.setUniform("uViewport", this._viewport); } _setStrokeUniforms(strokeShader) { // set the uniform values - strokeShader.setUniform('uSimpleLines', this._simpleLines); - strokeShader.setUniform('uUseLineColor', this._useLineColor); - strokeShader.setUniform('uMaterialColor', this.states.curStrokeColor); - strokeShader.setUniform('uStrokeWeight', this.states.strokeWeight); - strokeShader.setUniform('uStrokeCap', STROKE_CAP_ENUM[this.curStrokeCap]); - strokeShader.setUniform('uStrokeJoin', STROKE_JOIN_ENUM[this.curStrokeJoin]); + strokeShader.setUniform("uSimpleLines", this._simpleLines); + strokeShader.setUniform("uUseLineColor", this._useLineColor); + strokeShader.setUniform("uMaterialColor", this.states.curStrokeColor); + strokeShader.setUniform("uStrokeWeight", this.states.strokeWeight); + strokeShader.setUniform("uStrokeCap", STROKE_CAP_ENUM[this.curStrokeCap]); + strokeShader.setUniform( + "uStrokeJoin", + STROKE_JOIN_ENUM[this.curStrokeJoin], + ); } _setFillUniforms(fillShader) { @@ -2168,54 +2250,61 @@ class RendererGL extends Renderer { this.mixedSpecularColor = this.mixedSpecularColor.map( (mixedSpecularColor, index) => this.states.curFillColor[index] * this.states._useMetalness + - mixedSpecularColor * (1 - this.states._useMetalness) + mixedSpecularColor * (1 - this.states._useMetalness), ); } // TODO: optimize - fillShader.setUniform('uUseVertexColor', this._useVertexColor); - fillShader.setUniform('uMaterialColor', this.states.curFillColor); - fillShader.setUniform('isTexture', !!this.states._tex); + fillShader.setUniform("uUseVertexColor", this._useVertexColor); + fillShader.setUniform("uMaterialColor", this.states.curFillColor); + fillShader.setUniform("isTexture", !!this.states._tex); if (this.states._tex) { - fillShader.setUniform('uSampler', this.states._tex); + fillShader.setUniform("uSampler", this.states._tex); } - fillShader.setUniform('uTint', this.states.tint); + fillShader.setUniform("uTint", this.states.tint); - fillShader.setUniform('uHasSetAmbient', this.states._hasSetAmbient); - fillShader.setUniform('uAmbientMatColor', this.states.curAmbientColor); - fillShader.setUniform('uSpecularMatColor', this.mixedSpecularColor); - fillShader.setUniform('uEmissiveMatColor', this.states.curEmissiveColor); - fillShader.setUniform('uSpecular', this.states._useSpecularMaterial); - fillShader.setUniform('uEmissive', this.states._useEmissiveMaterial); - fillShader.setUniform('uShininess', this.states._useShininess); - fillShader.setUniform('uMetallic', this.states._useMetalness); + fillShader.setUniform("uHasSetAmbient", this.states._hasSetAmbient); + fillShader.setUniform("uAmbientMatColor", this.states.curAmbientColor); + fillShader.setUniform("uSpecularMatColor", this.mixedSpecularColor); + fillShader.setUniform("uEmissiveMatColor", this.states.curEmissiveColor); + fillShader.setUniform("uSpecular", this.states._useSpecularMaterial); + fillShader.setUniform("uEmissive", this.states._useEmissiveMaterial); + fillShader.setUniform("uShininess", this.states._useShininess); + fillShader.setUniform("uMetallic", this.states._useMetalness); this._setImageLightUniforms(fillShader); - fillShader.setUniform('uUseLighting', this.states.enableLighting); + fillShader.setUniform("uUseLighting", this.states.enableLighting); const pointLightCount = this.states.pointLightDiffuseColors.length / 3; - fillShader.setUniform('uPointLightCount', pointLightCount); - fillShader.setUniform('uPointLightLocation', this.states.pointLightPositions); + fillShader.setUniform("uPointLightCount", pointLightCount); fillShader.setUniform( - 'uPointLightDiffuseColors', - this.states.pointLightDiffuseColors + "uPointLightLocation", + this.states.pointLightPositions, ); fillShader.setUniform( - 'uPointLightSpecularColors', - this.states.pointLightSpecularColors + "uPointLightDiffuseColors", + this.states.pointLightDiffuseColors, + ); + fillShader.setUniform( + "uPointLightSpecularColors", + this.states.pointLightSpecularColors, ); - const directionalLightCount = this.states.directionalLightDiffuseColors.length / 3; - fillShader.setUniform('uDirectionalLightCount', directionalLightCount); - fillShader.setUniform('uLightingDirection', this.states.directionalLightDirections); + const directionalLightCount = + this.states.directionalLightDiffuseColors.length / 3; + fillShader.setUniform("uDirectionalLightCount", directionalLightCount); + fillShader.setUniform( + "uLightingDirection", + this.states.directionalLightDirections, + ); fillShader.setUniform( - 'uDirectionalDiffuseColors', - this.states.directionalLightDiffuseColors + "uDirectionalDiffuseColors", + this.states.directionalLightDiffuseColors, ); fillShader.setUniform( - 'uDirectionalSpecularColors', - this.states.directionalLightSpecularColors + "uDirectionalSpecularColors", + this.states.directionalLightSpecularColors, ); // TODO: sum these here... @@ -2223,55 +2312,67 @@ class RendererGL extends Renderer { this.mixedAmbientLight = [...this.states.ambientLightColors]; if (this.states._useMetalness > 0) { - this.mixedAmbientLight = this.mixedAmbientLight.map((ambientColors => { + this.mixedAmbientLight = this.mixedAmbientLight.map((ambientColors) => { let mixing = ambientColors - this.states._useMetalness; return Math.max(0, mixing); - })); + }); } - fillShader.setUniform('uAmbientLightCount', ambientLightCount); - fillShader.setUniform('uAmbientColor', this.mixedAmbientLight); + fillShader.setUniform("uAmbientLightCount", ambientLightCount); + fillShader.setUniform("uAmbientColor", this.mixedAmbientLight); const spotLightCount = this.states.spotLightDiffuseColors.length / 3; - fillShader.setUniform('uSpotLightCount', spotLightCount); - fillShader.setUniform('uSpotLightAngle', this.states.spotLightAngle); - fillShader.setUniform('uSpotLightConc', this.states.spotLightConc); - fillShader.setUniform('uSpotLightDiffuseColors', this.states.spotLightDiffuseColors); + fillShader.setUniform("uSpotLightCount", spotLightCount); + fillShader.setUniform("uSpotLightAngle", this.states.spotLightAngle); + fillShader.setUniform("uSpotLightConc", this.states.spotLightConc); + fillShader.setUniform( + "uSpotLightDiffuseColors", + this.states.spotLightDiffuseColors, + ); + fillShader.setUniform( + "uSpotLightSpecularColors", + this.states.spotLightSpecularColors, + ); + fillShader.setUniform("uSpotLightLocation", this.states.spotLightPositions); fillShader.setUniform( - 'uSpotLightSpecularColors', - this.states.spotLightSpecularColors + "uSpotLightDirection", + this.states.spotLightDirections, ); - fillShader.setUniform('uSpotLightLocation', this.states.spotLightPositions); - fillShader.setUniform('uSpotLightDirection', this.states.spotLightDirections); - fillShader.setUniform('uConstantAttenuation', this.states.constantAttenuation); - fillShader.setUniform('uLinearAttenuation', this.states.linearAttenuation); - fillShader.setUniform('uQuadraticAttenuation', this.states.quadraticAttenuation); + fillShader.setUniform( + "uConstantAttenuation", + this.states.constantAttenuation, + ); + fillShader.setUniform("uLinearAttenuation", this.states.linearAttenuation); + fillShader.setUniform( + "uQuadraticAttenuation", + this.states.quadraticAttenuation, + ); } // getting called from _setFillUniforms _setImageLightUniforms(shader) { //set uniform values - shader.setUniform('uUseImageLight', this.states.activeImageLight != null); + shader.setUniform("uUseImageLight", this.states.activeImageLight != null); // true if (this.states.activeImageLight) { // this.states.activeImageLight has image as a key // look up the texture from the diffusedTexture map let diffusedLight = this.getDiffusedTexture(this.states.activeImageLight); - shader.setUniform('environmentMapDiffused', diffusedLight); + shader.setUniform("environmentMapDiffused", diffusedLight); let specularLight = this.getSpecularTexture(this.states.activeImageLight); - shader.setUniform('environmentMapSpecular', specularLight); + shader.setUniform("environmentMapSpecular", specularLight); } } _setPointUniforms(pointShader) { // set the uniform values - pointShader.setUniform('uMaterialColor', this.states.curStrokeColor); + pointShader.setUniform("uMaterialColor", this.states.curStrokeColor); // @todo is there an instance where this isn't stroke weight? // should be they be same var? pointShader.setUniform( - 'uPointSize', - this.states.strokeWeight * this._pixelDensity + "uPointSize", + this.states.strokeWeight * this._pixelDensity, ); } @@ -2279,13 +2380,7 @@ class RendererGL extends Renderer { * when passed more than two arguments it also updates or initializes * the data associated with the buffer */ - _bindBuffer( - buffer, - target, - values, - type, - usage - ) { + _bindBuffer(buffer, target, values, type, usage) { if (!target) target = this.GL.ARRAY_BUFFER; this.GL.bindBuffer(target, buffer); if (values !== undefined) { @@ -2314,8 +2409,8 @@ class RendererGL extends Renderer { Float64Array, Int16Array, Uint16Array, - Uint32Array - ].some(x => arr instanceof x); + Uint32Array, + ].some((x) => arr instanceof x); } /** @@ -2327,7 +2422,7 @@ class RendererGL extends Renderer { * [1, 2, 3, 4, 5, 6] */ _vToNArray(arr) { - return arr.flatMap(item => [item.x, item.y, item.z]); + return arr.flatMap((item) => [item.x, item.y, item.z]); } // function to calculate BezierVertex Coefficients @@ -2357,10 +2452,9 @@ class RendererGL extends Renderer { const p = [p1, p2, p3, p4]; return p; } +} -}; - -function rendererGL(p5, fn){ +function rendererGL(p5, fn) { p5.RendererGL = RendererGL; /** @@ -2518,15 +2612,15 @@ function rendererGL(p5, fn){ * @param {Object} obj object with key-value pairs */ fn.setAttributes = function (key, value) { - if (typeof this._glAttributes === 'undefined') { + 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.' + "You are trying to use setAttributes on a p5.Graphics object " + + "that does not use a WEBGL renderer.", ); return; } let unchanged = true; - if (typeof value !== 'undefined') { + if (typeof value !== "undefined") { //first time modifying the attributes if (this._glAttributes === null) { this._glAttributes = {}; @@ -2551,8 +2645,8 @@ function rendererGL(p5, fn){ if (!this._setupDone) { if (this._renderer.geometryBufferCache.numCached() > 0) { p5._friendlyError( - 'Sorry, Could not set the attributes, you need to call setAttributes() ' + - 'before calling the other drawing methods in setup()' + "Sorry, Could not set the attributes, you need to call setAttributes() " + + "before calling the other drawing methods in setup()", ); return; } @@ -2573,7 +2667,7 @@ function rendererGL(p5, fn){ fn._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.` + `${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.`, ); }; @@ -2606,7 +2700,7 @@ export function readPixelsWebGL( height, format, type, - flipY + flipY, ) { // Record the currently bound framebuffer so we can go back to it after, and // bind the framebuffer we want to read from @@ -2624,12 +2718,12 @@ export function readPixelsWebGL( gl.readPixels( x, - flipY ? (flipY - y - height) : y, + flipY ? flipY - y - height : y, width, height, format, type, - pixels + pixels, ); // Re-bind whatever was previously bound @@ -2663,15 +2757,7 @@ export function readPixelsWebGL( * @param {Number|undefined} flipY If provided, the total height with which to flip the y axis about * @returns {Number[]} pixels The channel data for the pixel at that location */ -export function readPixelWebGL( - gl, - framebuffer, - x, - y, - format, - type, - flipY -) { +export function readPixelWebGL(gl, framebuffer, x, y, format, type, flipY) { // Record the currently bound framebuffer so we can go back to it after, and // bind the framebuffer we want to read from const prevFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); @@ -2681,11 +2767,7 @@ export function readPixelWebGL( const TypedArrayClass = type === gl.UNSIGNED_BYTE ? Uint8Array : Float32Array; const pixels = new TypedArrayClass(channels); - gl.readPixels( - x, flipY ? (flipY - y - 1) : y, 1, 1, - format, type, - pixels - ); + gl.readPixels(x, flipY ? flipY - y - 1 : y, 1, 1, format, type, pixels); // Re-bind whatever was previously bound gl.bindFramebuffer(gl.FRAMEBUFFER, prevFramebuffer); @@ -2696,6 +2778,6 @@ export function readPixelWebGL( export default rendererGL; export { RendererGL }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== "undefined") { rendererGL(p5, p5.prototype); } diff --git a/test/manual-test-examples/addons/p5.sound/autoCorrelation/sketch.js b/test/manual-test-examples/addons/p5.sound/autoCorrelation/sketch.js index 733963440b..f86b6e94a1 100644 --- a/test/manual-test-examples/addons/p5.sound/autoCorrelation/sketch.js +++ b/test/manual-test-examples/addons/p5.sound/autoCorrelation/sketch.js @@ -36,7 +36,7 @@ function draw() { for (var i = 0; i < corrBuff.length; i++) { var w = map(i, 0, corrBuff.length, 0, width); var h = map(corrBuff[i], -1, 1, height, 0); - curveVertex(w, h); + splineVertex(w, h); } endShape(); } diff --git a/test/manual-test-examples/webgl/curves/sketch.js b/test/manual-test-examples/webgl/curves/sketch.js index 435ad59dd3..bdde1699e0 100644 --- a/test/manual-test-examples/webgl/curves/sketch.js +++ b/test/manual-test-examples/webgl/curves/sketch.js @@ -39,16 +39,16 @@ function draw() { fill(0, 77, 64); beginShape(); - curveVertex(10, 150, -4); - curveVertex(10, 150, -4); - curveVertex(60, 80, -4); - curveVertex(140, 100, -4); - curveVertex(200, 100, -4); - curveVertex(200, 110, -4); - curveVertex(160, 140, -4); - curveVertex(80, 160, -4); - curveVertex(10, 150, -4); - curveVertex(10, 150, -4); + splineVertex(10, 150, -4); + splineVertex(10, 150, -4); + splineVertex(60, 80, -4); + splineVertex(140, 100, -4); + splineVertex(200, 100, -4); + splineVertex(200, 110, -4); + splineVertex(160, 140, -4); + splineVertex(80, 160, -4); + splineVertex(10, 150, -4); + splineVertex(10, 150, -4); endShape(); angle += 0.01; diff --git a/test/unit/core/vertex.js b/test/unit/core/vertex.js index eed414a2e1..98077e03b1 100644 --- a/test/unit/core/vertex.js +++ b/test/unit/core/vertex.js @@ -31,10 +31,6 @@ suite('Vertex', function() { assert.ok(myp5.quadraticVertex); assert.typeOf(myp5.quadraticVertex, 'function'); }); - test('_friendlyError is called. vertex() should be used once before quadraticVertex()', function() { - myp5.quadraticVertex(80, 20, 50, 50, 10, 20); - expect(_friendlyErrorSpy).toHaveBeenCalledTimes(1); - }); }); suite('p5.prototype.bezierVertex', function() { @@ -42,10 +38,6 @@ suite('Vertex', function() { assert.ok(myp5.bezierVertex); assert.typeOf(myp5.bezierVertex, 'function'); }); - test('_friendlyError is called. vertex() should be used once before bezierVertex()', function() { - myp5.bezierVertex(25, 30, 25, -30, -25, 30); - expect(_friendlyErrorSpy).toHaveBeenCalledTimes(1); - }); }); suite('p5.prototype.curveVertex', function() { diff --git a/test/unit/visual/cases/shapes.js b/test/unit/visual/cases/shapes.js index 76eee44f0b..15bd1e1fdf 100644 --- a/test/unit/visual/cases/shapes.js +++ b/test/unit/visual/cases/shapes.js @@ -105,13 +105,13 @@ visualSuite('Shape drawing', function() { visualTest('Drawing with curves', function(p5, screenshot) { setup(p5); p5.beginShape(); - p5.curveVertex(10, 10); - p5.curveVertex(10, 10); - p5.curveVertex(15, 40); - p5.curveVertex(40, 35); - p5.curveVertex(25, 15); - p5.curveVertex(15, 25); - p5.curveVertex(15, 25); + p5.splineVertex(10, 10); + p5.splineVertex(10, 10); + p5.splineVertex(15, 40); + p5.splineVertex(40, 35); + p5.splineVertex(25, 15); + p5.splineVertex(15, 25); + p5.splineVertex(15, 25); p5.endShape(); screenshot(); }); @@ -120,13 +120,14 @@ visualSuite('Shape drawing', function() { setup(p5); p5.curveTightness(0.5); p5.beginShape(); - p5.curveVertex(10, 10); - p5.curveVertex(10, 10); - p5.curveVertex(15, 40); - p5.curveVertex(40, 35); - p5.curveVertex(25, 15); - p5.curveVertex(15, 25); - p5.curveVertex(15, 25); + p5.splineEnds(p5.HIDE); + p5.splineVertex(10, 10); + p5.splineVertex(10, 10); + p5.splineVertex(15, 40); + p5.splineVertex(40, 35); + p5.splineVertex(25, 15); + p5.splineVertex(15, 25); + p5.splineVertex(15, 25); p5.endShape(); screenshot(); }); @@ -134,15 +135,26 @@ visualSuite('Shape drawing', function() { visualTest('Drawing closed curve loops', function(p5, screenshot) { setup(p5); p5.beginShape(); - p5.curveVertex(10, 10); - p5.curveVertex(15, 40); - p5.curveVertex(40, 35); - p5.curveVertex(25, 15); - p5.curveVertex(15, 25); + p5.splineEnds(p5.HIDE); + p5.splineVertex(10, 10); + p5.splineVertex(15, 40); + p5.splineVertex(40, 35); + p5.splineVertex(25, 15); + p5.splineVertex(15, 25); // Repeat first 3 points - p5.curveVertex(10, 10); - p5.curveVertex(15, 40); - p5.curveVertex(40, 35); + p5.splineVertex(10, 10); + p5.splineVertex(15, 40); + p5.splineVertex(40, 35); + p5.endShape(); + + p5.strokeWeight(10); + p5.stroke('red') + p5.beginShape(p5.POINTS) + p5.vertex(10, 10); + p5.vertex(15, 40); + p5.vertex(40, 35); + p5.vertex(25, 15); + p5.vertex(15, 25); p5.endShape(); screenshot(); }); From 1cb1fde94bf754db06f91548a3fe650dfe64a52b Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 14 Dec 2024 11:45:22 -0500 Subject: [PATCH 097/111] Fix more tests referring to TESS --- src/shape/vertex.js | 17 +++++++-------- src/webgl/ShapeBuilder.js | 4 ++-- .../webgl/geometryImmediate/sketch.js | 2 +- test/unit/core/structure.js | 13 +----------- test/unit/visual/cases/shapes.js | 12 +---------- test/unit/visual/cases/webgl.js | 4 ++-- .../000.png | Bin 874 -> 771 bytes .../vertexProperty/on PATH shape mode/000.png | Bin 0 -> 878 bytes .../metadata.json | 0 .../vertexProperty/on TESS shape mode/000.png | Bin 879 -> 0 bytes test/unit/webgl/p5.RendererGL.js | 20 +++++++++--------- 11 files changed, 25 insertions(+), 47 deletions(-) create mode 100644 test/unit/visual/screenshots/WebGL/vertexProperty/on PATH shape mode/000.png rename test/unit/visual/screenshots/WebGL/vertexProperty/{on TESS shape mode => on PATH shape mode}/metadata.json (100%) delete mode 100644 test/unit/visual/screenshots/WebGL/vertexProperty/on TESS shape mode/000.png diff --git a/src/shape/vertex.js b/src/shape/vertex.js index 2d98f12cf2..34f452f45f 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -17,9 +17,9 @@ function vertex(p5, fn){ * vertices to a custom shape and endShape() stops * adding them. * - * The parameter, `kind`, sets the kind of shape to make. By default, any - * irregular polygon can be drawn. The available modes for kind are: + * The parameter, `kind`, sets the kind of shape to make. The available kinds are: * + * - `PATH` (the default) to draw shapes by tracing out the path along their edges. * - `POINTS` to draw a series of points. * - `LINES` to draw a series of unconnected line segments. * - `TRIANGLES` to draw a series of separate triangles. @@ -27,13 +27,12 @@ function vertex(p5, fn){ * - `TRIANGLE_STRIP` to draw a series of connected triangles in strip fashion. * - `QUADS` to draw a series of separate quadrilaterals (quads). * - `QUAD_STRIP` to draw quad strip using adjacent edges to form the next quad. - * - `TESS` to create a filling curve by explicit tessellation (WebGL only). * * After calling `beginShape()`, shapes can be built by calling * vertex(), * bezierVertex(), - * quadraticVertex(), and/or - * splineVertex(). Calling + * bezierVertex(), and/or + * splineVertex(). Calling * endShape() will stop adding vertices to the * shape. Each shape will be outlined with the current stroke color and filled * with the current fill color. @@ -47,8 +46,8 @@ function vertex(p5, fn){ * endShape(). * * @method beginShape - * @param {(POINTS|LINES|TRIANGLES|TRIANGLE_FAN|TRIANGLE_STRIP|QUADS|QUAD_STRIP|TESS)} [kind] either POINTS, LINES, TRIANGLES, TRIANGLE_FAN - * TRIANGLE_STRIP, QUADS, QUAD_STRIP or TESS. + * @param {(POINTS|LINES|TRIANGLES|TRIANGLE_FAN|TRIANGLE_STRIP|QUADS|QUAD_STRIP|PATH)} [kind=PATH] either POINTS, LINES, TRIANGLES, TRIANGLE_FAN + * TRIANGLE_STRIP, QUADS, QUAD_STRIP or PATH. Defaults to PATH. * @chainable * * @example @@ -334,7 +333,7 @@ function vertex(p5, fn){ * * // Start drawing the shape. * // Draw a series of quadrilaterals. - * beginShape(TESS); + * beginShape(PATH); * * // Add the vertices. * vertex(-30, -30, 0); @@ -373,7 +372,7 @@ function vertex(p5, fn){ * * // Start drawing the shape. * // Draw a series of quadrilaterals. - * beginShape(TESS); + * beginShape(PATH); * * // Add the vertices. * fill('red'); diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index c36658752d..946e28ad34 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -20,7 +20,7 @@ const INITIAL_VERTEX_SIZE = export class ShapeBuilder { constructor(renderer) { this.renderer = renderer; - this.shapeMode = constants.TESS; + this.shapeMode = constants.PATH; this.geometry = new Geometry(undefined, undefined, undefined, this.renderer); this.geometry.gid = '__IMMEDIATE_MODE_GEOMETRY__'; @@ -253,7 +253,7 @@ export class ShapeBuilder { * @private */ _tesselateShape() { - // TODO: handle non-TESS shape modes that have contours + // TODO: handle non-PATH shape modes that have contours this.shapeMode = constants.TRIANGLES; // const contours = [[]]; const contours = []; diff --git a/test/manual-test-examples/webgl/geometryImmediate/sketch.js b/test/manual-test-examples/webgl/geometryImmediate/sketch.js index 27ce74a562..8ec44bc3fd 100644 --- a/test/manual-test-examples/webgl/geometryImmediate/sketch.js +++ b/test/manual-test-examples/webgl/geometryImmediate/sketch.js @@ -82,7 +82,7 @@ function drawStrip(mode) { } function ngon(n, x, y, d) { - beginShape(TESS); + beginShape(PATH); for (let i = 0; i < n + 1; i++) { angle = TWO_PI / n * i; px = x + sin(angle) * d / 2; diff --git a/test/unit/core/structure.js b/test/unit/core/structure.js index cf972f5395..1dd708db78 100644 --- a/test/unit/core/structure.js +++ b/test/unit/core/structure.js @@ -58,18 +58,7 @@ suite('Structure', function() { suite('p5.prototype.push and p5.prototype.pop', function() { function getRenderState() { - var state = {}; - for (var key in myp5._renderer) { - var value = myp5._renderer[key]; - if ( - typeof value !== 'function' && - key !== '_cachedFillStyle' && - key !== '_cachedStrokeStyle' - ) { - state[key] = value; - } - } - return state; + return { ...myp5._renderer.states }; } function assertCanPreserveRenderState(work) { diff --git a/test/unit/visual/cases/shapes.js b/test/unit/visual/cases/shapes.js index 15bd1e1fdf..ecc710e2bf 100644 --- a/test/unit/visual/cases/shapes.js +++ b/test/unit/visual/cases/shapes.js @@ -146,16 +146,6 @@ visualSuite('Shape drawing', function() { p5.splineVertex(15, 40); p5.splineVertex(40, 35); p5.endShape(); - - p5.strokeWeight(10); - p5.stroke('red') - p5.beginShape(p5.POINTS) - p5.vertex(10, 10); - p5.vertex(15, 40); - p5.vertex(40, 35); - p5.vertex(25, 15); - p5.vertex(15, 25); - p5.endShape(); screenshot(); }); @@ -211,8 +201,8 @@ visualSuite('Shape drawing', function() { p5.vertex(15, 40); p5.vertex(40, 35); p5.vertex(25, 15); - p5.vertex(15, 25); p5.vertex(10, 10); + p5.vertex(15, 25); p5.endShape(); screenshot(); }); diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 8b77b6cc46..91bc52ddd2 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -151,12 +151,12 @@ visualSuite('WebGL', function() { outColor = vec4(vCol, 1.0); }`; visualTest( - 'on TESS shape mode', function(p5, screenshot) { + 'on PATH shape mode', function(p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); p5.background('white'); const myShader = p5.createShader(vertSrc, fragSrc); p5.shader(myShader); - p5.beginShape(p5.TESS); + p5.beginShape(p5.PATH); p5.noStroke(); for (let i = 0; i < 20; i++){ let x = 20 * p5.sin(i/20*p5.TWO_PI); diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with tightness/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with tightness/000.png index 70d1c096b2a872a9f02d90b0a31cf4c98831651a..8d123f745f2f7dcb2bcf88aa0728cc44199c5a56 100644 GIT binary patch delta 735 zcmV<50wDeB27?BWFnBND&i`lXa`)^Qnazs(+Vll?RMD$sZ>gOA%E2ag+aVtFC>#m)%Hlw zF9fpLY@pxoL#x%2UP$#oVUWRK0F6dNwLQ}FlR$>UAsh||;l+4tg+d{~M`aTNK?B0! zFr3e4*lxF$_8vgu{ zukI!4Qf7p9!bpL>UNU1s=mj5G+k7k{Qxvs6p92w_H}(MRWuFbJkr zzu*6i16;UPt663#Q7DALszElBNrQ+QNa+`iMj;RgNWpv+2tsR`1~TzN0`d8LP_0%e zn@sMDE_OPd$af!MkWbPqm8MWAFc?a_2!rqn7z;-y8r8+V7KEC`NFcdfj>U*bJ!OY# z6jewd#(%TK4rAZVK~d%Z1mRy(7K?>yG1&ad6mc?hQ7dDTYaxgeal$KN$}N%%g6@@0 zr$N@dzROF2@L0k7h%5~5JT{yolgYsKdL@-4 z#*L8%K^N=wdf@SRpi-%@dCd+=3|efn*^C*B?^w&_@@X`so_eRMGzg!R1L6C6z5Z*h zNF)OH`yGP8Ap1?#=hKft963+~7I8rYHp$U0t|E?Zx4>eqA_AM_XqR83$PevJBu};W Ra0&nb002ovPDHLkV1ni{S3Cd! delta 839 zcmV-N1GxNy2I>ZoFn2MNt`% z+wGQ5b37ggHr?{+e1S|Rll<-|irD#l&S93zW$;j6Ac)3lwSQtsk`(y+{hob3pP+$C zrFA?WS-;P-{U~kZ8{2|{8jL|mI*Y|ZDK8Tc0s|0L36LIHyGzAQ zwV^f0dc9`%`~52_kRBq2wZ-NTp~xg`z{q5*L69fc>wmQ|5S9EWTKFVTv96=P5e=Mp zc}^g&*XxJ8Y-D8d$Bx_@m-R&C^rKz!J%E=q{TX0yrnJ*tB<#xmWes0(M3Co9{ne4QsrlIa==R0jA= zYI!ll9IN(2Jo_(v+=zuvwVm~Cv8rh3nj8fP)i+|#ing%6E%r+`oNIdmJc^=>ns3w< zL3B{RpwlR}PIU>{#&uni({J)Pjx%ca$X)mlK!0>eUVv2nqlesz`oC?w@v1RRqhYil zc9|!p(J%tRO|+d9o7q01!#S_CTyYLYD?2@a!f`g66`lDuVQge&91(HCzVgOKR!$&z z$VcAT$jTX{?Yyy-RV@&LL3EnV8(Ue`25HL#Y-QCL2nK?qHBkw;$f{8gG67V|bt(Z> zhd{){=B?HdlL6~GnoE8(9uWeq1LKk(t%11Ovul2|3gQA2EQl?PYogPx&C`m*?RA@u(*}rQOVHgMS?=?+rt=dr03T;6Rf~i<8OB=A*_ce=)q-zKNfa0Kw zgVR0l=&DEuXE#9{o$k#bxLE{$fE2+WgHD|kmqy;;Nl5O;``-HokB~s%hPx!6`##S- z?_Io3r_;d*yu2}_U@49hDOezIo`~#7v=7@|r`nc>5>o)KPcR$>aMfdIOl+bAVAW&J z&W96-_$5F;De*@s$nl={vA^4mGQ;*vfQw~&kQGE62+%QaXCEe~k_eF>B=~*%G+aU!wg@w=_*4=hJm8RT?bC1xQK_7K z8lOrmM81*Wcip7n9P+{h@>+u5Dkfz9Q3$U-xE6~|MC=OCZvFx2dXDtYmC6pGv@_Tz zu@Jt^Az!)a57(ttxkD)3)`he<5bZjEt5zlIp53{t?m22wvV8h2l zyn;hwo+-xn2)L;;`}H;<{86@m`3lO_@uuTTsFL`L>9vk<(RTcg}5v)7m!no;Y>mrnGjd0 zxPV*;Lrf>6kqL2?iY;V;F`Q3HLkr>}6BRH^AM^-Um6BCHuA zazmn0(*@~3wusQo0f>H!cC)A5`(}aCAUBE7vH&=R^d_XX+wJzhvQx<#1CKY6;kdL$r41poj507*qoM6N<$ Eg8CPeJOBUy literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/WebGL/vertexProperty/on TESS shape mode/metadata.json b/test/unit/visual/screenshots/WebGL/vertexProperty/on PATH shape mode/metadata.json similarity index 100% rename from test/unit/visual/screenshots/WebGL/vertexProperty/on TESS shape mode/metadata.json rename to test/unit/visual/screenshots/WebGL/vertexProperty/on PATH shape mode/metadata.json diff --git a/test/unit/visual/screenshots/WebGL/vertexProperty/on TESS shape mode/000.png b/test/unit/visual/screenshots/WebGL/vertexProperty/on TESS shape mode/000.png deleted file mode 100644 index 76ef1d98a21dcb9ff7caf29e4cf5f5182e0b3c9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 879 zcmV-#1CacQP)Px&DM>^@RA@u(*}rQOVHgMS?WZPYbAzNZyiSfESH$G z^Wg*{ehAP{O8ij@a(w80?C`EOby|d_mOe%sBHjwnJ99){h)SouNN6qTVYDD(SAcf&A#5NDv)Cpy+u4{*i2N$S zpK_ymJ-D-Jj~wiog$|+7NylVD_+t+FN&Z)ef?c|ozDQ4n$d3~IkzE?LrKQSCL7H7c zy_=j$DumzXkRR;Qa0OAIa}SbJNrcGv5`5b}4VRG98-$r&d@6|$?sLdD_G!3;s8miq ziBBaKB411JyKd5O4tZ_@c_qPb6%#V|FhFT#bS)N}h}aRJo%{pP^&AkITtH4ShBFCiWI|k} z;sSC$2vAK(BNO5(6RH^AM^-UmMB6JK9 zxgk-h>4J11JtA~-0HWWb-Rx=izFD9&$W0>jEC5a+!wISFe!u^(>=bhF_cUYRjA0tU z>iRlXGUkHmLjxy?n11Ov_Mbq;aaL_UbCQV7*&D~Pe*k$hx_TM4%D4ak002ovPDHLk FV1oJ0iW~p{ diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 122cd366cc..c9b02f7da7 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -1641,11 +1641,11 @@ suite('p5.RendererGL', function() { assert.equal(renderer.shapeBuilder.geometry.edges.length, 7); }); - test('TESS preserves vertex data', function() { + test('PATH preserves vertex data', function() { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.textureMode(myp5.NORMAL); - myp5.beginShape(myp5.TESS); + myp5.beginShape(myp5.PATH); myp5.fill(255, 255, 255); myp5.normal(-1, -1, 1); myp5.vertexProperty('aCustom', [1, 1, 1]) @@ -1744,11 +1744,11 @@ suite('p5.RendererGL', function() { ]); }); - test('TESS does not affect stroke colors', function() { + test('PATH does not affect stroke colors', function() { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.textureMode(myp5.NORMAL); - myp5.beginShape(myp5.TESS); + myp5.beginShape(myp5.PATH); myp5.noFill(); myp5.stroke(255, 255, 255); myp5.vertex(-10, -10, 0, 0); @@ -1769,13 +1769,13 @@ suite('p5.RendererGL', function() { ]); }); - test('TESS does not affect texture coordinates', function() { + test('PATH does not affect texture coordinates', function() { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); const texture = new p5.Image(25, 25); myp5.textureMode(myp5.IMAGE); myp5.texture(texture); - myp5.beginShape(myp5.TESS); + myp5.beginShape(myp5.PATH); myp5.noFill(); myp5.vertex(-10, -10, 0, 0); myp5.vertex(10, -10, 25, 0); @@ -1792,7 +1792,7 @@ suite('p5.RendererGL', function() { ]); }); - test('TESS interpolates vertex data at intersections', function() { + test('PATH interpolates vertex data at intersections', function() { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); // Hourglass shape: @@ -1806,7 +1806,7 @@ suite('p5.RendererGL', function() { // // Tessy will add a vertex in the middle myp5.textureMode(myp5.NORMAL); - myp5.beginShape(myp5.TESS); + myp5.beginShape(myp5.PATH); myp5.fill(255, 255, 255); myp5.normal(-1, -1, 1); myp5.vertex(-10, -10, 0, 0); @@ -1892,11 +1892,11 @@ suite('p5.RendererGL', function() { ]); }); - test('TESS handles vertex data perpendicular to the camera', function() { + test('PATH handles vertex data perpendicular to the camera', function() { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.textureMode(myp5.NORMAL); - myp5.beginShape(myp5.TESS); + myp5.beginShape(myp5.PATH); myp5.vertex(-10, 0, -10); myp5.vertex(10, 0, -10); myp5.vertex(10, 0, 10); From 33fc6023d201c05a4544b0fa73f0b2f5a8706161 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 14 Dec 2024 12:03:58 -0500 Subject: [PATCH 098/111] Fix contour ends not having a join in WebGL --- preview/index.html | 34 ++++++++++++++++++++------------ src/webgl/ShapeBuilder.js | 12 ++++++++--- test/unit/webgl/p5.RendererGL.js | 16 +++++++++++++-- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/preview/index.html b/preview/index.html index 47feee9de2..88c4ada6e1 100644 --- a/preview/index.html +++ b/preview/index.html @@ -21,24 +21,32 @@ const sketch = function (p) { p.setup = function () { - p.createCanvas(100, 100); + p.createCanvas(100, 100, p.WEBGL); }; p.draw = function () { p.background(200); - //p.translate(-p.width/2, -p.height/2); - p.scale(2) + p.strokeCap(p.SQUARE); + p.strokeJoin(p.MITER); + p.translate(-p.width/2, -p.height/2); + p.scale(2); + p.stroke('black'); + p.strokeWeight(4); + p.translate(25, 25); p.beginShape(); - p.vertex(0, 0) - p.vertex(5, 5) - //p.splineEnds(p.HIDE); - p.splineVertex(10, 10); - p.splineVertex(15, 40); - p.splineVertex(40, 35); - p.splineVertex(25, 15); - p.splineVertex(15, 25); - p.splineVertex(0, 0); - p.endShape(); + // Exterior part of shape, clockwise winding + p.vertex(-20, -20); + p.vertex(20, -20); + p.vertex(20, 20); + p.vertex(-20, 20); + // Interior part of shape, counter-clockwise winding + p.beginContour(); + p.vertex(-10, -10); + p.vertex(-10, 10); + p.vertex(10, 10); + p.vertex(10, -10); + p.endContour(); + p.endShape(p.CLOSE); }; }; diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index 946e28ad34..41535345e7 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -184,6 +184,7 @@ export class ShapeBuilder { const res = []; let i = 0; const contourIndices = this.contourIndices.slice(); + let contourStart = -1; switch (shapeMode) { case constants.TRIANGLE_STRIP: for (i = 0; i < verts.length - 2; i++) { @@ -237,10 +238,15 @@ export class ShapeBuilder { default: // TODO: handle contours in other modes too for (i = 0; i < verts.length; i++) { - if (i !== contourIndices[0]) { - res.push([i - 1, i]); + if (i === contourIndices[0]) { + contourStart = contourIndices.shift(); + } else if ( + verts[contourStart] && + verts[i].equals(verts[contourStart]) + ) { + res.push([i - 1, contourStart]); } else { - contourIndices.shift(); + res.push([i - 1, i]); } } break; diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index c9b02f7da7..671acaf3eb 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -638,10 +638,22 @@ suite('p5.RendererGL', function() { myp5.endContour(); myp5.endShape(myp5.CLOSE); myp5.loadPixels(); - return [...myp5.pixels]; + const img = myp5._renderer.canvas.toDataURL(); + return { pixels: [...myp5.pixels], img }; }; - assert.deepEqual(getColors(myp5.P2D), getColors(myp5.WEBGL)); + let ok = true; + const colors2D = getColors(myp5.P2D); + const colorsGL = getColors(myp5.WEBGL); + for (let i = 0; i < colors2D.pixels.length; i++) { + if (colors2D.pixels[i] !== colorsGL.pixels[i]) { + ok = false; + break; + } + } + if (!ok) { + throw new Error(`Expected match:\n\n2D: ${colors2D.img}\n\nWebGL: ${colorsGL.img}`); + } }); suite('text shader', function() { From f7d025519d52f9021ef06ce05ebf78606758c081 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 14 Dec 2024 13:30:59 -0500 Subject: [PATCH 099/111] Fix some vertex property tests, switch some to visual tests, fix instancing --- preview/index.html | 24 ++--- src/shape/custom_shapes.js | 15 ++- src/webgl/p5.RendererGL.js | 2 +- test/unit/visual/cases/shapes.js | 35 +++++++ .../Per-control point fills/000.png | Bin 0 -> 727 bytes .../Per-control point fills/metadata.json | 3 + .../Per-control point strokes/000.png | Bin 0 -> 1142 bytes .../Per-control point strokes/metadata.json | 3 + test/unit/webgl/p5.RendererGL.js | 86 ++---------------- 9 files changed, 72 insertions(+), 96 deletions(-) create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point fills/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point fills/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point strokes/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point strokes/metadata.json diff --git a/preview/index.html b/preview/index.html index 88c4ada6e1..702811727d 100644 --- a/preview/index.html +++ b/preview/index.html @@ -29,24 +29,16 @@ p.strokeCap(p.SQUARE); p.strokeJoin(p.MITER); p.translate(-p.width/2, -p.height/2); - p.scale(2); - p.stroke('black'); - p.strokeWeight(4); - p.translate(25, 25); + p.noStroke(); p.beginShape(); - // Exterior part of shape, clockwise winding - p.vertex(-20, -20); - p.vertex(20, -20); - p.vertex(20, 20); - p.vertex(-20, 20); - // Interior part of shape, counter-clockwise winding - p.beginContour(); - p.vertex(-10, -10); - p.vertex(-10, 10); + p.bezierOrder(2); + p.fill('red'); p.vertex(10, 10); - p.vertex(10, -10); - p.endContour(); - p.endShape(p.CLOSE); + p.fill('lime'); + p.bezierVertex(40, 25); + p.fill('blue'); + p.bezierVertex(10, 40); + p.endShape(); }; }; diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 1aeebae6aa..1198b2ff7a 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -605,7 +605,9 @@ class Shape { } serializeToArray(val) { - if (val instanceof Number) { + if (val === null) { + return []; + } if (val instanceof Number) { return [val]; } else if (val instanceof Array) { return val; @@ -635,7 +637,9 @@ class Shape { } hydrateValue(queue, original) { - if (original instanceof Number) { + if (original === null) { + return null; + } else if (original instanceof Number) { return queue.shift(); } else if (original instanceof Array) { const array = []; @@ -925,11 +929,18 @@ class Shape { _index + 1, this.contours.length - _index - 1 ); + const prevVertexProperties = this.#vertexProperties; + this.#vertexProperties = { ...prevVertexProperties }; + for (const key in anchorVertex) { + if (['position', 'textureCoordinates'].includes(key)) continue; + this.#vertexProperties[key] = anchorVertex[key]; + } this.vertex( anchorVertex.position, anchorVertex.textureCoordinates, { isClosing: true } ); + this.#vertexProperties = prevVertexProperties; this.contours.push(...rest); } } diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 6922ec6505..8f407a5c2f 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -534,8 +534,8 @@ class RendererGL extends Renderer { } endShape(mode, count) { - super.endShape(mode, count); this.drawShapeCount = count; + super.endShape(mode, count); } legacyEndShape( diff --git a/test/unit/visual/cases/shapes.js b/test/unit/visual/cases/shapes.js index ecc710e2bf..66e6f8a860 100644 --- a/test/unit/visual/cases/shapes.js +++ b/test/unit/visual/cases/shapes.js @@ -338,6 +338,41 @@ visualSuite('Shape drawing', function() { screenshot(); }); + + visualTest('Per-control point fills', async function (p5, screenshot) { + setup(p5); + + p5.noStroke(); + p5.beginShape(); + p5.bezierOrder(2); + p5.fill('red'); + p5.vertex(10, 10); + p5.fill('lime'); + p5.bezierVertex(40, 25); + p5.fill('blue'); + p5.bezierVertex(10, 40); + p5.endShape(); + + screenshot(); + }); + + visualTest('Per-control point strokes', async function (p5, screenshot) { + setup(p5); + + p5.noFill(); + p5.strokeWeight(5); + p5.beginShape(); + p5.bezierOrder(2); + p5.stroke('red'); + p5.vertex(10, 10); + p5.stroke('lime'); + p5.bezierVertex(40, 25); + p5.stroke('blue'); + p5.bezierVertex(10, 40); + p5.endShape(); + + screenshot(); + }); } }); } diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point fills/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point fills/000.png new file mode 100644 index 0000000000000000000000000000000000000000..e07875af6e1c45254a791b28bfbbc4463ca3c7db GIT binary patch literal 727 zcmV;|0x127P)Px%kx4{BRA@u(ls`xtaTLeDLF*r+T3aMdA|cY&#D7UsDwOmMMQtfcNCQy_NsT6P zYn+PR0u>>Up{_11S%ogHj!pqbS4ZhkD(%n%i!qi9q(rRo{f@iK54l@zdH3G?ywAI5 zE0@dXxB-vN0+Gz3WkfO|u#8wn44QzoS_TbPb6U2MN4aC9P zD~&X+oI6oO&tO2$9$C5AAPTKDHBvtU>f5_eK(E8kYCx|bHpnsMSw`wW!N>;s?Gt+S zd2Jpx$PpFTM(S5BviSAd2J{MGgB;SPkP+3)$kR9QkH+-swP_wU$N_B$8&QuycD^JR z$Uc>Xji@U&8JNYh;jrN_Y$JQLt;vYfHH1WHRUVLC+RPx(FiAu~RA@u(m|tj9R~*N`=bW6R`cP(91p8n+X;-C##yV^Y4k3&%-Pk(1KeUjp zB=K=<47R7i273`?LLZbp#HbzBc0M=?KCDO`8>4w?Yqjf?L^o)eSX2-@>HX7o{LW3X z?P1>Am1PhfEJ*h;m@NdptW@rU)klAjzP#q9)fxrF9-MGaZem; zt$SULwejs7fsla7_(zH$c3i?fy$1;U~G7<8XN!~Q6mI{KU?DREXwAommPBbb}}4hBI8fmOz#bAjNV9=yE!ZTmz; z&Tt)sa$KLkfTC8 zGLk-yKkjF-#+U*jpbt7fL{nXJkx`$#%rlUQ>^LSe5X^w!CImOIyS@RRb@g}x;fay+vl(3Ybq-t@5W)aw z7RcwX99A%1J+2xVo?5`Ut62!CK?ogO=n8_a*T_3juNc(6u7M0qE`dO}Tmkp#stG~w zh=#9TJ%G0D+bbXB%uf&TZTc>_)W9V{Xcmmr&==f=-u4zxAzU%?QSuRH7ni~O1d(sn zwTkX Date: Sat, 14 Dec 2024 14:06:37 -0500 Subject: [PATCH 100/111] Fix the rest of the RendererGL tests! --- test/unit/webgl/p5.RendererGL.js | 85 ++++++++++++++------------------ 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 823fb0a7be..9c67afa893 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -2524,13 +2524,18 @@ suite('p5.RendererGL', function() { function() { myp5.createCanvas(50, 50, myp5.WEBGL); + myp5.noStroke(); myp5.beginShape(); myp5.vertexProperty('aCustom', 1); myp5.vertexProperty('aCustomVec3', [1, 2, 3]); myp5.vertex(0,0,0); + myp5.vertex(0,1,0); + myp5.vertex(1,1,0); + myp5.endShape(); + expect(myp5._renderer.shapeBuilder.geometry.userVertexProperties.aCustom).to.containSubset({ name: 'aCustom', - currentData: 1, + currentData: [1], dataSize: 1 }); expect(myp5._renderer.shapeBuilder.geometry.userVertexProperties.aCustomVec3).to.containSubset({ @@ -2538,23 +2543,6 @@ suite('p5.RendererGL', function() { currentData: [1, 2, 3], dataSize: 3 }); - assert.deepEqual(myp5._renderer.shapeBuilder.geometry.aCustomSrc, [1]); - assert.deepEqual(myp5._renderer.shapeBuilder.geometry.aCustomVec3Src, [1,2,3]); - expect(myp5._renderer.buffers.user).to.containSubset([ - { - size: 1, - src: 'aCustomSrc', - dst: 'aCustomBuffer', - attr: 'aCustom', - }, - { - size: 3, - src: 'aCustomVec3Src', - dst: 'aCustomVec3Buffer', - attr: 'aCustomVec3', - } - ]); - myp5.endShape(); } ); test('Immediate mode data and buffers deleted after beginShape', @@ -2568,28 +2556,27 @@ suite('p5.RendererGL', function() { myp5.endShape(); myp5.beginShape(); + myp5.endShape(); assert.isUndefined(myp5._renderer.shapeBuilder.geometry.aCustomSrc); assert.isUndefined(myp5._renderer.shapeBuilder.geometry.aCustomVec3Src); assert.deepEqual(myp5._renderer.shapeBuilder.geometry.userVertexProperties, {}); assert.deepEqual(myp5._renderer.buffers.user, []); - myp5.endShape(); } ); test('Data copied over from beginGeometry', function() { myp5.createCanvas(50, 50, myp5.WEBGL); - myp5.beginGeometry(); - myp5.beginShape(); - myp5.vertexProperty('aCustom', 1); - myp5.vertexProperty('aCustomVec3', [1,2,3]); - myp5.vertex(0,1,0); - myp5.vertex(-1,0,0); - myp5.vertex(1,0,0); - const immediateCopy = myp5._renderer.shapeBuilder.geometry; - myp5.endShape(); - const myGeo = myp5.endGeometry(); - assert.deepEqual(immediateCopy.aCustomSrc, myGeo.aCustomSrc); - assert.deepEqual(immediateCopy.aCustomVec3Src, myGeo.aCustomVec3Src); + const myGeo = myp5.buildGeometry(() => { + myp5.beginShape(); + myp5.vertexProperty('aCustom', 1); + myp5.vertexProperty('aCustomVec3', [1,2,3]); + myp5.vertex(0,1,0); + myp5.vertex(-1,0,0); + myp5.vertex(1,0,0); + myp5.endShape(); + }); + assert.deepEqual(myGeo.aCustomSrc, [1,1,1]); + assert.deepEqual(myGeo.aCustomVec3Src, [1,2,3,1,2,3,1,2,3]); } ); test('Retained mode buffers are created for rendering', @@ -2619,15 +2606,15 @@ suite('p5.RendererGL', function() { } try { - myp5.beginGeometry(); - myp5.beginShape(); - myp5.vertexProperty('aCustom', 1); - myp5.vertexProperty('aCustomVec3', [1,2,3]); - myp5.vertex(0,0,0); - myp5.vertex(1,0,0); - myp5.vertex(1,1,0); - myp5.endShape(); - const myGeo = myp5.endGeometry(); + const myGeo = myp5.buildGeometry(() => { + myp5.beginShape(); + myp5.vertexProperty('aCustom', 1); + myp5.vertexProperty('aCustomVec3', [1,2,3]); + myp5.vertex(0,0,0); + myp5.vertex(1,0,0); + myp5.vertex(1,1,0); + myp5.endShape(); + }); myp5.model(myGeo); expect(called).to.equal(true); } finally { @@ -2638,15 +2625,15 @@ suite('p5.RendererGL', function() { test('Retained mode buffers deleted after rendering', function() { myp5.createCanvas(50, 50, myp5.WEBGL); - myp5.beginGeometry(); - myp5.beginShape(); - myp5.vertexProperty('aCustom', 1); - myp5.vertexProperty('aCustomVec3', [1,2,3]); - myp5.vertex(0,0,0); - myp5.vertex(1,0,0); - myp5.vertex(1,1,0); - myp5.endShape(); - const myGeo = myp5.endGeometry(); + const myGeo = myp5.buildGeometry(() => { + myp5.beginShape(); + myp5.vertexProperty('aCustom', 1); + myp5.vertexProperty('aCustomVec3', [1,2,3]); + myp5.vertex(0,0,0); + myp5.vertex(1,0,0); + myp5.vertex(1,1,0); + myp5.endShape(); + }); myp5.model(myGeo); assert.equal(myp5._renderer.buffers.user.length, 0); } From 593322cf1b292e540eda55aecc11a14f3cdbc800 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 14 Dec 2024 14:24:49 -0500 Subject: [PATCH 101/111] Un-break typography tests --- src/type/text2d.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/type/text2d.js b/src/type/text2d.js index 23e9c49d89..446bf496b4 100644 --- a/src/type/text2d.js +++ b/src/type/text2d.js @@ -1096,11 +1096,11 @@ function text2d(p5, fn) { this.push(); // no stroke unless specified by user - if (states.doStroke && states.strokeSet) { + if (states.strokeColor && states.strokeSet) { this.textDrawingContext().strokeText(text, x, y); } - if (!this._clipping && states.doFill) { + if (!this._clipping && states.fillColor) { // if fill hasn't been set by user, use default text fill if (!states.fillSet) { From fe78cfe1f4cd4802dc0c0d5a81e83d547f42450a Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Sat, 14 Dec 2024 15:10:36 -0500 Subject: [PATCH 102/111] Add new tests --- src/shape/custom_shapes.js | 4 +- test/unit/visual/cases/shapes.js | 133 ++++++++++++++++-- .../000.png | Bin 0 -> 425 bytes .../metadata.json | 3 + .../2D mode/Drawing closed curves/000.png | Bin 0 -> 749 bytes .../Drawing closed curves/metadata.json | 3 + .../000.png | Bin 0 -> 366 bytes .../metadata.json | 3 + .../000.png | Bin 0 -> 365 bytes .../metadata.json | 3 + .../000.png | Bin 0 -> 519 bytes .../metadata.json | 3 + .../000.png | Bin 0 -> 634 bytes .../metadata.json | 3 + .../000.png | Bin 0 -> 366 bytes .../metadata.json | 3 + .../000.png | Bin 0 -> 455 bytes .../metadata.json | 3 + .../WebGL mode/Drawing closed curves/000.png | Bin 0 -> 769 bytes .../Drawing closed curves/metadata.json | 3 + .../000.png | Bin 0 -> 362 bytes .../metadata.json | 3 + .../000.png | Bin 0 -> 352 bytes .../metadata.json | 3 + .../000.png | Bin 0 -> 514 bytes .../metadata.json | 3 + .../000.png | Bin 0 -> 592 bytes .../metadata.json | 3 + .../000.png | Bin 0 -> 362 bytes .../metadata.json | 3 + 30 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curves/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curves/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single closed contour/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single closed contour/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single unclosed contour/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single unclosed contour/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves in the middle of other shapes/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves in the middle of other shapes/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Combining quadratic and cubic beziers/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Combining quadratic and cubic beziers/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curves/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curves/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single closed contour/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single closed contour/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single unclosed contour/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single unclosed contour/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves in the middle of other shapes/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves in the middle of other shapes/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with hidden ends/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with hidden ends/metadata.json create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with every subshape in a contour/000.png create mode 100644 test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with every subshape in a contour/metadata.json diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 1198b2ff7a..587905e422 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -871,6 +871,8 @@ class Shape { */ #generalVertex(kind, position, textureCoordinates) { let vertexKind = kind; + console.log(this.contours) + console.log(this.at(-1)) let lastContourKind = this.at(-1).kind; let vertex = this.#createVertex(position, textureCoordinates); @@ -907,7 +909,7 @@ class Shape { this.contours.push(new Contour(shapeKind)); } - endContour(closeMode = constants.OPEN, _index = -1) { + endContour(closeMode = constants.OPEN, _index = this.contours.length - 1) { const contour = this.at(_index); if (closeMode === constants.CLOSE) { // shape characteristics diff --git a/test/unit/visual/cases/shapes.js b/test/unit/visual/cases/shapes.js index 66e6f8a860..04305c04e5 100644 --- a/test/unit/visual/cases/shapes.js +++ b/test/unit/visual/cases/shapes.js @@ -106,27 +106,58 @@ visualSuite('Shape drawing', function() { setup(p5); p5.beginShape(); p5.splineVertex(10, 10); - p5.splineVertex(10, 10); p5.splineVertex(15, 40); p5.splineVertex(40, 35); p5.splineVertex(25, 15); p5.splineVertex(15, 25); - p5.splineVertex(15, 25); p5.endShape(); screenshot(); }); - visualTest('Drawing with curves with tightness', function(p5, screenshot) { + visualTest('Drawing with curves in the middle of other shapes', function(p5, screenshot) { + setup(p5); + p5.beginShape(); + p5.vertex(10, 10); + p5.vertex(40, 10); + p5.splineVertex(40, 40); + p5.splineVertex(10, 40); + p5.endShape(p5.CLOSE); + screenshot(); + }); + + visualTest('Drawing with curves with hidden ends', function(p5, screenshot) { setup(p5); - p5.curveTightness(0.5); p5.beginShape(); p5.splineEnds(p5.HIDE); p5.splineVertex(10, 10); + p5.splineVertex(15, 40); + p5.splineVertex(40, 35); + p5.splineVertex(25, 15); + p5.splineVertex(15, 25); + p5.endShape(); + screenshot(); + }); + + visualTest('Drawing closed curves', function(p5, screenshot) { + setup(p5); + p5.beginShape(); p5.splineVertex(10, 10); p5.splineVertex(15, 40); p5.splineVertex(40, 35); p5.splineVertex(25, 15); p5.splineVertex(15, 25); + p5.endShape(p5.CLOSE); + screenshot(); + }); + + visualTest('Drawing with curves with tightness', function(p5, screenshot) { + setup(p5); + p5.curveTightness(0.5); + p5.beginShape(); + p5.splineVertex(10, 10); + p5.splineVertex(15, 40); + p5.splineVertex(40, 35); + p5.splineVertex(25, 15); p5.splineVertex(15, 25); p5.endShape(); screenshot(); @@ -170,6 +201,31 @@ visualSuite('Shape drawing', function() { screenshot(); }); + visualTest('Combining quadratic and cubic beziers', function (p5, screenshot) { + setup(p5); + p5.strokeWeight(5); + p5.beginShape(); + p5.vertex(10, 10); + p5.vertex(30, 10); + + // Default cubic + p5.bezierVertex(35, 10); + p5.bezierVertex(40, 15); + p5.bezierVertex(40, 20); + + p5.vertex(40, 30); + + p5.bezierOrder(2); + p5.bezierVertex(40, 40); + p5.bezierVertex(30, 40); + + p5.vertex(10, 40); + + p5.endShape(p5.CLOSE); + + screenshot(); + }); + visualTest('Drawing with points', function(p5, screenshot) { setup(p5); p5.strokeWeight(5); @@ -222,6 +278,65 @@ visualSuite('Shape drawing', function() { screenshot(); }); + visualTest('Drawing with a single closed contour', function(p5, screenshot) { + setup(p5); + p5.beginShape(); + p5.vertex(10, 10); + p5.vertex(40, 10); + p5.vertex(40, 40); + p5.vertex(10, 40); + + p5.beginContour(); + p5.vertex(20, 20); + p5.vertex(20, 30); + p5.vertex(30, 30); + p5.vertex(30, 20); + p5.endContour(p5.CLOSE); + + p5.endShape(p5.CLOSE); + screenshot(); + }); + + visualTest('Drawing with a single unclosed contour', function(p5, screenshot) { + setup(p5); + p5.beginShape(); + p5.vertex(10, 10); + p5.vertex(40, 10); + p5.vertex(40, 40); + p5.vertex(10, 40); + + p5.beginContour(); + p5.vertex(20, 20); + p5.vertex(20, 30); + p5.vertex(30, 30); + p5.vertex(30, 20); + p5.endContour(); + + p5.endShape(p5.CLOSE); + screenshot(); + }); + + visualTest('Drawing with every subshape in a contour', function(p5, screenshot) { + setup(p5); + p5.beginShape(); + p5.beginContour(); + p5.vertex(10, 10); + p5.vertex(40, 10); + p5.vertex(40, 40); + p5.vertex(10, 40); + p5.endContour(p5.CLOSE); + + p5.beginContour(); + p5.vertex(20, 20); + p5.vertex(20, 30); + p5.vertex(30, 30); + p5.vertex(30, 20); + p5.endContour(p5.CLOSE); + + p5.endShape(); + screenshot(); + }); + if (mode === 'WebGL') { visualTest('3D vertex coordinates', function(p5, screenshot) { setup(p5); @@ -289,7 +404,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Per-vertex fills', async function(p5, screenshot) { + visualTest('Per-vertex fills', function(p5, screenshot) { setup(p5); p5.beginShape(p5.QUAD_STRIP); p5.fill(0); @@ -305,7 +420,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Per-vertex strokes', async function(p5, screenshot) { + visualTest('Per-vertex strokes', function(p5, screenshot) { setup(p5); p5.strokeWeight(5); p5.beginShape(p5.QUAD_STRIP); @@ -322,7 +437,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Per-vertex normals', async function(p5, screenshot) { + visualTest('Per-vertex normals', function(p5, screenshot) { setup(p5); p5.normalMaterial(); p5.beginShape(p5.QUAD_STRIP); @@ -339,7 +454,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Per-control point fills', async function (p5, screenshot) { + visualTest('Per-control point fills', function (p5, screenshot) { setup(p5); p5.noStroke(); @@ -356,7 +471,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Per-control point strokes', async function (p5, screenshot) { + visualTest('Per-control point strokes', function (p5, screenshot) { setup(p5); p5.noFill(); diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/000.png new file mode 100644 index 0000000000000000000000000000000000000000..88a283cca11016074e5adccb82d0e0355f408120 GIT binary patch literal 425 zcmV;a0apHrP)Px$V@X6oRA@u(nZXT(APk0KB`45b+(TW$lf!uA7 zP{2CpKBfb+3L;EJRYaH|MipsFk?Xn)G%lOx`L&_Z&^nI8K;t$K^aM-0H_wG8e|+t1MLl4*A*S62BsPWKE5o=8yvqewrw*hsD0myaz+FQ z5T#%s@c|}hVn8sM5>-Tj0L(N^<~+|o0ZLR62LccP6Pj}ljV^H@DOGaer69RkA$>KpG_*9da1V5oBe7Dxo1(GFGTvI-{(dBu zAI)9^px>4i{)$|ol Tk0DnN00000NkvXXu0mjf7q6)Q literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curves/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curves/000.png new file mode 100644 index 0000000000000000000000000000000000000000..9260c77d472cc16ed953cffaf20cf5a470acbcb7 GIT binary patch literal 749 zcmVPx%r%6OXRA@u(nZK?9K@`T%Z4{ot14twag(wJGg$Gb6R0^#`A}AFU3Y9{oP%2as zgkqymsFj{Tsd4jdGB?ZJWzLw9b?;7cn;W~%Z@%yRnek;=)|LLi`U*gNredIo&jg`B zk&jd)l}eFiS$>QCCLSqk0c1EFQX-M?EsPXMtyZIQx%~McqtS>ChXV};gU?oEzu!}} zTJ?3FNLfpO_&HDhCrC1xWaSBRORP$zVl=;`6hQ)=_Dwg3s<7~Xoo@Ifca!sdG!Z`nq#c7gkkav_|=Rr)i+p|FM z1@Foe?wHR8L7o(g#fPWBS8WG~Mx#NoSWJ3cVS||L@BqNU?Q*%W@0!^l!d_k%3@U0k z0pjtvvi7HheN{G_bsgzN&^RJ577Ke5 zbp@|Q5%CgnEsCEVcDvm#XJNOoU>Wg4k%=FSloe6Q1(VMNASQQL<=ufID!XR#nLrVf fyQ}iPx$C`m*?RA@u(nXwIoFbqY_9+XVL0x$tnut&rmOu+=uQ858!_PCSlJFu+;!sUJ> zDohgp_k%4c)^&YR2BSbC52FYo4+BCV0wNbF@L$&2wvG5oG3vS|zNDcdGL9qd`%Z1! zO7lg%KhHB&RYiT@i}R`zDa(?abK-!Rv5S6V3A^~25wT{GtwE}F#kHT7^ z*5U%TEQ^Xm(==j%O-6(lo~B7`DY)I~x=t*xNfXgjV(rTViG-dJt%4wO@*0WS{esAW z(pq8oaS-_?qE^A5IP6&z#B5p=Rg+~1(pok8VK?ki6^cYcTNIVdh9D3D5rbVUp|IAO zwX*n#oy~6ri?36~s@nKeo3pHoL@a9E5ep)BsBBpSL@a9E5t|K>H?l@2PlI^N-~a#s M07*qoM6N<$f+5nDng9R* literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single closed contour/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single closed contour/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single closed contour/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single unclosed contour/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single unclosed contour/000.png new file mode 100644 index 0000000000000000000000000000000000000000..e2c2b8aa95186f8aebad1fa7b1a5a46f92939e84 GIT binary patch literal 365 zcmV-z0h0cSP)Px$CrLy>RA@u(S+Na-Fbp(%P%;4vzywUe9*8}df(f9bVggF`;N|+C&sj+Tg^QF5 z$H8~!#D;8HmO~O483TD3i)7?sga{cSBaskzm$cS(6`gmCvMfcXYe0+)!ywzXNnO`2 ze?gy5(kK?f%R^SWl+yB* zmR?}UrHR_z#t6DGnscZ>B_Qa{j&96A{u~};_|hcFf|gAR}?{wRPx$!AV3xRA@u(nX!t4KoEvUe2tW;EvyYmz-O?qO5r;Qfso`Ctn(-q3W8f%Sf{k} z4Q%e1g@qtoc5Yek)-X0v*d6zqe|9GBctVIT_<+k}5XV$xMH~|ZSP@p_7ar)kPL2-4 zP&>=%(0V)`5+KVmJ0R#8iv#eTsD?EV^vv`5gu~%rahZBkRaL=uyR{3l*=*o? zz3Sg?aRh?oc@9NUXswd=2r9xLT3`M=;A0d)M9=d;2%&X{)1if{-|cpgrm41;Fo;%b zO9PI_BP2<(ensy0I}F31ttHNf7BzD*h@~w+GDAth&MQK#C943T)Di|E1-GR^loY%Q zMHplr5&!i)qSiqvimM1M2I0`Mly2u{J|?s{{kpbC&gV0X+(2M|?C}5q002ov JPDHLkV1oPy>3skI literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves in the middle of other shapes/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves in the middle of other shapes/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves in the middle of other shapes/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/000.png new file mode 100644 index 0000000000000000000000000000000000000000..7956ab38f6a4c23a972bcf0529cb0025be1fefe5 GIT binary patch literal 634 zcmV-=0)_pFP)Px%G)Y83RA@u(n6a)wK@dPkrSl6~B2g#^iJwvYTkc?wvDdX7AcW5ClK)1tZ}>KFp#w@?k>28}UYjGT=)~ zD6!;ay%BQigeH0;LWw0WD{ka)IH=$Xg#tN9lZoOWr_%{KoeuQ-eQ3AaCNYtIBMy?y zW+9bI!Duvs;cy7E*-XTaI7q2fx@TC!^m;wO59bgCK@(^sJl5+qG#U*B!U-b^qRT3p zc?lRXkT@H0X1IDr4CI*&DHt)3aFf4kYP|sjLEzN;A|QX2RWxFRSu7SXnM}wqA|NJZ z6$f)ZpUFEJJBY1~Fi@pZfm|*}f?)@t*w8R~Tc+$}>>#F1{;V0WUzW=yjK^cEeqjZ1 zDXVbQ)oK+onT%D5v4XgiRowfVL3kY{v{R@bc4cnKavk?g!2r(E8td1+rAe>E(0N5AT z>s8&j*jynwfpC?THh{hH_Cq6Hyrt7=Xf~TB@2iIk2>b%Gj}XOT5xU*3m4WC($wm<5 zcs#;%I#u^owOS48_4)%828v^|*}!VGg4^u|+wE2bjkhQ^(^b literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/000.png new file mode 100644 index 0000000000000000000000000000000000000000..551b1d0380068477a1dde96928f9bf3a15db5731 GIT binary patch literal 366 zcmV-!0g?WRP)Px$C`m*?RA@u(nXwIoFbqY_9+XVL0x$tnut&rmOu+=uQ858!_PCSlJFu+;!sUJ> zDohgp_k%4c)^&YR2BSbC52FYo4+BCV0wNbF@L$&2wvG5oG3vS|zNDcdGL9qd`%Z1! zO7lg%KhHB&RYiT@i}R`zDa(?abK-!Rv5S6V3A^~25wT{GtwE}F#kHT7^ z*5U%TEQ^Xm(==j%O-6(lo~B7`DY)I~x=t*xNfXgjV(rTViG-dJt%4wO@*0WS{esAW z(pq8oaS-_?qE^A5IP6&z#B5p=Rg+~1(pok8VK?ki6^cYcTNIVdh9D3D5rbVUp|IAO zwX*n#oy~6ri?36~s@nKeo3pHoL@a9E5ep)BsBBpSL@a9E5t|K>H?l@2PlI^N-~a#s M07*qoM6N<$f+5nDng9R* literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Combining quadratic and cubic beziers/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Combining quadratic and cubic beziers/000.png new file mode 100644 index 0000000000000000000000000000000000000000..cff0b299d6c219385f589e4d90c76bb972a9df90 GIT binary patch literal 455 zcmV;&0XY7NP)Px$fk{L`RA@u(nN1FZAP|M=m0Y>hyLb=v60SUqOIBW~%`>5CEHM1p4rWM9|J&{{v$4cbFMn5ig=FcU=bc@*KI(kgs(;s0YR+VipJ6_yKSYjttX<0C_+|6bO#A7myaab>FDwHkkH~5 zTDK`0Zvo=kk5pIvy0*JjZLC~sP<0k^oJ|?;J4KTC^gR z!IHm+LL@0ta-u$4F%TpOI{e~(Px%yGcYrRA@u(n6Zk&KoEu}2)==x4`5|srJ$f-9`Tt& zDDcQzdL)@llCJB1v;CVrQlSNq!C*j%M8dZ)QXs?O@BvUNmEIp@zu!~8-+zA)c*W0o zA{AP@-HyuTvaj=m1%e#$Yo4$`DwPTqi$&=?5h4M#b6fGGKwvo}7(*scL@1`yY0BsG zG@H%ndcC?T7LJ1mh-R}%jYi|kd#zTB@He;W5eo!{9FIraJgHWzR45d_K?oW*VH^cP zP8<#gTCG-=d9+w8D3{AU4#P>|V1eB4chWS?G*3iG&F6EWnmq(O`Hbl}b@46k>;zm1kZ_yCWVkf*@HjRKm&1 zpiCx1u~jseMi2WPA67#iVdYfR3%o03bhI_ zoXRf^B06AHcP|Dv#C!|#Eb%f2#{#L+aJ?#`MTY16pzOr-`sjWRrUxc zjJ%i|fOv7roIL`Ma8ApMxq(N#IAzWrf5sypM0+7mnvf_d00000NkvXXu0mjf+0#>G literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curves/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curves/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curves/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single closed contour/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single closed contour/000.png new file mode 100644 index 0000000000000000000000000000000000000000..9d5d7ff4cf7c2add59f4d02d75f29847ea014537 GIT binary patch literal 362 zcmV-w0hRuVP)Px$BuPX;RA@u(S-Ta3FbouXPa-41-iv734C{vouZf^MXxE1i6XPmj{U)?GaQ#Iuhu-sM9YU*-;u5Mjxjm zTOy(gKBC#PxDY>s-s49Ugn$KuMm2h~TMmg}N#y8=f~ai4&=ERf$%_HYXoaGc#Yb!< zn8j}eL#&fQp)o$$=?sNQ#30rsP&#sn%!VR##30rs&}Px$8c9S!RA@u(nXwInFcd^Tdr&d~6EFc&um@rfreFd}Dkh+0k3^P|D&Ra6V~}&y zAAj!d+1QXx)AXxP;g&;7i6Dr!i-gvmc|z+4tlX_aOK80u zpeWEeN5^yRgTC2mV+rvr9!g}(9HF&?3^hiReQ(ZD2eoR&T(yQ_u(B+@yR3Pht*+~@ zCr+Ah@Hz6{=#jl5IW}J^govOu5~AHNW#wR<&=Oh)k)h>6%ZFAHAF-XlBz`MUrmw6+ yN8KwQkoJ}k>Qt;cB@)YN+DC~{r()G5lgJZ-Ll{ptkY-~50000Px$yh%hsRA@u(nK7z_Fc5}E@EDdBTQArtdJb#vAlRrU5G<@bhxKkJTWn!%=_Rc8 zF+Q^F3XhBupBg4r44Ig3{$vs+M%%V8(4ZI$axfKHk%I{WtOzTz&;!rs6Ry{*)auYn ztFG$_Ac`V&AYxGIhzdIhHI#uMMBn!i1cAb7%7?n$ZeZKCIzTO6z2xP*&BudZQC-)mk0J*S~v;M^B@dE>7jV& zJ%S+A$i*Pi-KVGnH3T1C5p6)IA^7l$XazzE!KY9}8xYD_atcKlWPXA&*L815tjs~NO~4Af`~ChkJWd`9zKKxYMT9YFX)TZZzZP>)(I!;IZ^v<99LM)B zmQ4P0UYmlfb-)@s23g~^Ep1qlExo&DY*u8A*S56z0f=nVK%kD|;s5{u07*qoM6N<$ Ef_QP>c>n+a literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves in the middle of other shapes/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves in the middle of other shapes/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves in the middle of other shapes/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with hidden ends/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with hidden ends/000.png new file mode 100644 index 0000000000000000000000000000000000000000..88984a153a68168bb43e1abbf623627a54631ab5 GIT binary patch literal 592 zcmV-W0Px%3Q0skRA@u(n6b_QQ4ob^8>Q!~=e<*X!aUi(Ci?DVNJM7!2rgxzKDjD^io_H*k<@wMq;V zh9ONR6PnNG5Ov@njYi|%VI&L`gq2`LvSYPcQK!?Pe!ri*tOgV$wbf&>2pAYho*H?| zSY-wV@=6U93=AY2@^?8%7l1g9RdIoU{Aa7jiWz3PT&n(OARt9-l?Sum@5L*bIf$zo zS)f*{MU_fL1Y-^&Q6mcz1OZ9%Vh&O?dlbxZ}q z6Z3KCVzEf>vE6Q;?$vBI>3Y3VtyW9E%Jdqn_E eRZaObiu?h#id#UIp&C*E0000