diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js
index 98b64726cf..7092ef58fc 100644
--- a/src/webgl/3d_primitives.js
+++ b/src/webgl/3d_primitives.js
@@ -2084,7 +2084,7 @@ function primitives3D(p5, fn){
const _vertex = [];
_vertex.push(new Vector(x, y, z));
- this._drawPoints(_vertex, this.immediateMode.buffers.point);
+ this._drawPoints(_vertex, this.buffers.point);
return this;
};
@@ -2097,8 +2097,8 @@ function primitives3D(p5, fn){
const x3 = args[4],
y3 = args[5];
- const gId = 'tri';
- if (!this.geometryInHash(gId)) {
+ const gid = 'tri';
+ if (!this.geometryInHash(gid)) {
const _triangle = function() {
const vertices = [];
vertices.push(new Vector(0, 0, 0));
@@ -2112,7 +2112,8 @@ function primitives3D(p5, fn){
const triGeom = new Geometry(1, 1, _triangle);
triGeom._edgesToVertices();
triGeom.computeNormals();
- this.createBuffers(gId, triGeom);
+ triGeom.gid = gid;
+ this.createBuffers(triGeom);
}
// only one triangle is cached, one point is at the origin, and the
@@ -2134,7 +2135,7 @@ function primitives3D(p5, fn){
this.states.uModelMatrix = mult;
- this.drawBuffers(gId);
+ this._drawGeometry(this.geometryBufferCache[gid].model);
} finally {
this.states.uModelMatrix = uModelMatrix;
}
@@ -2166,18 +2167,18 @@ function primitives3D(p5, fn){
const detail = args[7] || 25;
let shape;
- let gId;
+ let gid;
// check if it is an ellipse or an arc
if (Math.abs(stop - start) >= constants.TWO_PI) {
shape = 'ellipse';
- gId = `${shape}|${detail}|`;
+ gid = `${shape}|${detail}|`;
} else {
shape = 'arc';
- gId = `${shape}|${start}|${stop}|${mode}|${detail}|`;
+ gid = `${shape}|${start}|${stop}|${mode}|${detail}|`;
}
- if (!this.geometryInHash(gId)) {
+ if (!this.geometryInHash(gid)) {
const _arc = function() {
// if the start and stop angles are not the same, push vertices to the array
@@ -2255,7 +2256,8 @@ function primitives3D(p5, fn){
);
}
- this.createBuffers(gId, arcGeom);
+ arcGeom.gid = gid;
+ this.createBuffers(arcGeom);
}
const uModelMatrix = this.states.uModelMatrix.copy();
@@ -2264,7 +2266,7 @@ function primitives3D(p5, fn){
this.states.uModelMatrix.translate([x, y, 0]);
this.states.uModelMatrix.scale(width, height, 1);
- this.drawBuffers(gId);
+ this._drawGeometry(this.geometryBufferCache[gid].model);
} finally {
this.states.uModelMatrix = uModelMatrix;
}
@@ -2284,8 +2286,8 @@ function primitives3D(p5, fn){
const perPixelLighting = this._pInst._glAttributes.perPixelLighting;
const detailX = args[4] || (perPixelLighting ? 1 : 24);
const detailY = args[5] || (perPixelLighting ? 1 : 16);
- const gId = `rect|${detailX}|${detailY}`;
- if (!this.geometryInHash(gId)) {
+ const gid = `rect|${detailX}|${detailY}`;
+ if (!this.geometryInHash(gid)) {
const _rect = function() {
for (let i = 0; i <= this.detailY; i++) {
const v = i / this.detailY;
@@ -2311,7 +2313,8 @@ function primitives3D(p5, fn){
.computeFaces()
.computeNormals()
._edgesToVertices();
- this.createBuffers(gId, rectGeom);
+ rectGeom.gid = gid;
+ this.createBuffers(rectGeom);
}
// only a single rectangle (of a given detail) is cached: a square with
@@ -2323,7 +2326,7 @@ function primitives3D(p5, fn){
this.states.uModelMatrix.translate([x, y, 0]);
this.states.uModelMatrix.scale(width, height, 1);
- this.drawBuffers(gId);
+ this._drawGeometry(this.geometryBufferCache[gid].model);
} finally {
this.states.uModelMatrix = uModelMatrix;
}
@@ -2392,11 +2395,11 @@ function primitives3D(p5, fn){
this.vertex(x1, y1);
}
- this.immediateMode.geometry.uvs.length = 0;
- for (const vert of this.immediateMode.geometry.vertices) {
+ 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.immediateMode.geometry.uvs.push(u, v);
+ this.shapeBuilder.geometry.uvs.push(u, v);
}
this.endShape(constants.CLOSE);
@@ -2408,10 +2411,10 @@ function primitives3D(p5, fn){
RendererGL.prototype.quad = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, detailX=2, detailY=2) {
/* eslint-enable max-len */
- const gId =
+ const gid =
`quad|${x1}|${y1}|${z1}|${x2}|${y2}|${z2}|${x3}|${y3}|${z3}|${x4}|${y4}|${z4}|${detailX}|${detailY}`;
- if (!this.geometryInHash(gId)) {
+ if (!this.geometryInHash(gid)) {
const quadGeom = new Geometry(detailX, detailY, function() {
//algorithm adapted from c++ to js
//https://stackoverflow.com/questions/16989181/whats-the-correct-way-to-draw-a-distorted-plane-in-opengl/16993202#16993202
@@ -2459,9 +2462,10 @@ function primitives3D(p5, fn){
quadGeom.edges.push([startVertex, endVertex]);
}
quadGeom._edgesToVertices();
- this.createBuffers(gId, quadGeom);
+ quadGeom.gid = gid;
+ this.createBuffers(quadGeom);
}
- this.drawBuffers(gId);
+ this._drawGeometry(this.geometryBufferCache[gid].model);
return this;
};
@@ -2605,7 +2609,7 @@ function primitives3D(p5, fn){
};
RendererGL.prototype.bezierVertex = function(...args) {
- if (this.immediateMode._bezierVertex.length === 0) {
+ if (this.shapeBuilder._bezierVertex.length === 0) {
throw Error('vertex() must be used once before calling bezierVertex()');
} else {
let w_x = [];
@@ -2643,7 +2647,7 @@ function primitives3D(p5, fn){
}
const LUTLength = this._lookUpTableBezier.length;
- const immediateGeometry = this.immediateMode.geometry;
+ const immediateGeometry = this.shapeBuilder.geometry;
// fillColors[0]: start point color
// fillColors[1],[2]: control point color
@@ -2673,8 +2677,8 @@ function primitives3D(p5, fn){
if (argLength === 6) {
this.isBezier = true;
- w_x = [this.immediateMode._bezierVertex[0], args[0], args[2], args[4]];
- w_y = [this.immediateMode._bezierVertex[1], args[1], args[3], args[5]];
+ 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]);
@@ -2744,14 +2748,14 @@ function primitives3D(p5, fn){
const prop = immediateGeometry.userVertexProperties[propName];
prop.setCurrentData(userVertexProperties[propName][2]);
}
- this.immediateMode._bezierVertex[0] = args[4];
- this.immediateMode._bezierVertex[1] = args[5];
+ this.shapeBuilder._bezierVertex[0] = args[4];
+ this.shapeBuilder._bezierVertex[1] = args[5];
} else if (argLength === 9) {
this.isBezier = true;
- w_x = [this.immediateMode._bezierVertex[0], args[0], args[3], args[6]];
- w_y = [this.immediateMode._bezierVertex[1], args[1], args[4], args[7]];
- w_z = [this.immediateMode._bezierVertex[2], args[2], args[5], args[8]];
+ 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]);
@@ -2821,15 +2825,15 @@ function primitives3D(p5, fn){
const prop = immediateGeometry.userVertexProperties[propName];
prop.setCurrentData(userVertexProperties[propName][2]);
}
- this.immediateMode._bezierVertex[0] = args[6];
- this.immediateMode._bezierVertex[1] = args[7];
- this.immediateMode._bezierVertex[2] = args[8];
+ 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.immediateMode._quadraticVertex.length === 0) {
+ if (this.shapeBuilder._quadraticVertex.length === 0) {
throw Error('vertex() must be used once before calling quadraticVertex()');
} else {
let w_x = [];
@@ -2867,7 +2871,7 @@ function primitives3D(p5, fn){
}
const LUTLength = this._lookUpTableQuadratic.length;
- const immediateGeometry = this.immediateMode.geometry;
+ const immediateGeometry = this.shapeBuilder.geometry;
// fillColors[0]: start point color
// fillColors[1]: control point color
@@ -2897,8 +2901,8 @@ function primitives3D(p5, fn){
if (argLength === 4) {
this.isQuadratic = true;
- w_x = [this.immediateMode._quadraticVertex[0], args[0], args[2]];
- w_y = [this.immediateMode._quadraticVertex[1], args[1], args[3]];
+ 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.
@@ -2961,14 +2965,14 @@ function primitives3D(p5, fn){
const prop = immediateGeometry.userVertexProperties[propName];
prop.setCurrentData(userVertexProperties[propName][2]);
}
- this.immediateMode._quadraticVertex[0] = args[2];
- this.immediateMode._quadraticVertex[1] = args[3];
+ this.shapeBuilder._quadraticVertex[0] = args[2];
+ this.shapeBuilder._quadraticVertex[1] = args[3];
} else if (argLength === 6) {
this.isQuadratic = true;
- w_x = [this.immediateMode._quadraticVertex[0], args[0], args[3]];
- w_y = [this.immediateMode._quadraticVertex[1], args[1], args[4]];
- w_z = [this.immediateMode._quadraticVertex[2], args[2], args[5]];
+ 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.
@@ -3032,9 +3036,9 @@ function primitives3D(p5, fn){
const prop = immediateGeometry.userVertexProperties[propName];
prop.setCurrentData(userVertexProperties[propName][2]);
}
- this.immediateMode._quadraticVertex[0] = args[3];
- this.immediateMode._quadraticVertex[1] = args[4];
- this.immediateMode._quadraticVertex[2] = args[5];
+ this.shapeBuilder._quadraticVertex[0] = args[3];
+ this.shapeBuilder._quadraticVertex[1] = args[4];
+ this.shapeBuilder._quadraticVertex[2] = args[5];
}
}
};
@@ -3075,21 +3079,21 @@ function primitives3D(p5, fn){
const LUTLength = this._lookUpTableBezier.length;
if (argLength === 2) {
- this.immediateMode._curveVertex.push(args[0]);
- this.immediateMode._curveVertex.push(args[1]);
- if (this.immediateMode._curveVertex.length === 8) {
+ 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.immediateMode._curveVertex[0],
- this.immediateMode._curveVertex[2],
- this.immediateMode._curveVertex[4],
- this.immediateMode._curveVertex[6]
+ this.shapeBuilder._curveVertex[0],
+ this.shapeBuilder._curveVertex[2],
+ this.shapeBuilder._curveVertex[4],
+ this.shapeBuilder._curveVertex[6]
]);
w_y = this._bezierToCatmull([
- this.immediateMode._curveVertex[1],
- this.immediateMode._curveVertex[3],
- this.immediateMode._curveVertex[5],
- this.immediateMode._curveVertex[7]
+ this.shapeBuilder._curveVertex[1],
+ this.shapeBuilder._curveVertex[3],
+ this.shapeBuilder._curveVertex[5],
+ this.shapeBuilder._curveVertex[7]
]);
for (i = 0; i < LUTLength; i++) {
_x =
@@ -3105,32 +3109,32 @@ function primitives3D(p5, fn){
this.vertex(_x, _y);
}
for (i = 0; i < argLength; i++) {
- this.immediateMode._curveVertex.shift();
+ this.shapeBuilder._curveVertex.shift();
}
}
} else if (argLength === 3) {
- this.immediateMode._curveVertex.push(args[0]);
- this.immediateMode._curveVertex.push(args[1]);
- this.immediateMode._curveVertex.push(args[2]);
- if (this.immediateMode._curveVertex.length === 12) {
+ 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.immediateMode._curveVertex[0],
- this.immediateMode._curveVertex[3],
- this.immediateMode._curveVertex[6],
- this.immediateMode._curveVertex[9]
+ this.shapeBuilder._curveVertex[0],
+ this.shapeBuilder._curveVertex[3],
+ this.shapeBuilder._curveVertex[6],
+ this.shapeBuilder._curveVertex[9]
]);
w_y = this._bezierToCatmull([
- this.immediateMode._curveVertex[1],
- this.immediateMode._curveVertex[4],
- this.immediateMode._curveVertex[7],
- this.immediateMode._curveVertex[10]
+ this.shapeBuilder._curveVertex[1],
+ this.shapeBuilder._curveVertex[4],
+ this.shapeBuilder._curveVertex[7],
+ this.shapeBuilder._curveVertex[10]
]);
w_z = this._bezierToCatmull([
- this.immediateMode._curveVertex[2],
- this.immediateMode._curveVertex[5],
- this.immediateMode._curveVertex[8],
- this.immediateMode._curveVertex[11]
+ this.shapeBuilder._curveVertex[2],
+ this.shapeBuilder._curveVertex[5],
+ this.shapeBuilder._curveVertex[8],
+ this.shapeBuilder._curveVertex[11]
]);
for (i = 0; i < LUTLength; i++) {
_x =
@@ -3151,7 +3155,7 @@ function primitives3D(p5, fn){
this.vertex(_x, _y, _z);
}
for (i = 0; i < argLength; i++) {
- this.immediateMode._curveVertex.shift();
+ this.shapeBuilder._curveVertex.shift();
}
}
}
@@ -3343,9 +3347,9 @@ function primitives3D(p5, fn){
detailX = 1,
detailY = 1
) {
- const gId = `plane|${detailX}|${detailY}`;
+ const gid = `plane|${detailX}|${detailY}`;
- if (!this.geometryInHash(gId)) {
+ if (!this.geometryInHash(gid)) {
const _plane = function() {
let u, v, p;
for (let i = 0; i <= this.detailY; i++) {
@@ -3368,10 +3372,11 @@ function primitives3D(p5, fn){
' than 1 detailX or 1 detailY'
);
}
- this.createBuffers(gId, planeGeom);
+ planeGeom.gid = gid;
+ this.createBuffers(planeGeom);
}
- this.drawBuffersScaled(gId, width, height, 1);
+ this.drawBuffersScaled(this.geometryBufferCache[gid].model, width, height, 1);
}
RendererGL.prototype.box = function(
@@ -3390,8 +3395,8 @@ function primitives3D(p5, fn){
detailY = perPixelLighting ? 1 : 4;
}
- const gId = `box|${detailX}|${detailY}`;
- if (!this.geometryInHash(gId)) {
+ const gid = `box|${detailX}|${detailY}`;
+ if (!this.geometryInHash(gid)) {
const _box = function() {
const cubeIndices = [
[0, 4, 2, 6], // -1, 0, 0],// -x
@@ -3450,9 +3455,10 @@ function primitives3D(p5, fn){
//initialize our geometry buffer with
//the key val pair:
//geometry Id, Geom object
- this.createBuffers(gId, boxGeom);
+ boxGeom.gid = gid;
+ this.createBuffers(boxGeom);
}
- this.drawBuffersScaled(gId, width, height, depth);
+ this.drawBuffersScaled(this.geometryBufferCache[gid].model, width, height, depth);
}
RendererGL.prototype.sphere = function(
@@ -3470,9 +3476,9 @@ function primitives3D(p5, fn){
detailX = 24,
detailY = 16
) {
- const gId = `ellipsoid|${detailX}|${detailY}`;
+ const gid = `ellipsoid|${detailX}|${detailY}`;
- if (!this.geometryInHash(gId)) {
+ if (!this.geometryInHash(gid)) {
const _ellipsoid = function() {
for (let i = 0; i <= this.detailY; i++) {
const v = i / this.detailY;
@@ -3502,10 +3508,11 @@ function primitives3D(p5, fn){
' than 24 detailX or 24 detailY'
);
}
- this.createBuffers(gId, ellipsoidGeom);
+ ellipsoidGeom.gid = gid;
+ this.createBuffers(ellipsoidGeom);
}
- this.drawBuffersScaled(gId, radiusX, radiusY, radiusZ);
+ this.drawBuffersScaled(this.geometryBufferCache[gid].model, radiusX, radiusY, radiusZ);
}
RendererGL.prototype.cylinder = function(
@@ -3516,8 +3523,8 @@ function primitives3D(p5, fn){
bottomCap = true,
topCap = true
) {
- const gId = `cylinder|${detailX}|${detailY}|${bottomCap}|${topCap}`;
- if (!this.geometryInHash(gId)) {
+ const gid = `cylinder|${detailX}|${detailY}|${bottomCap}|${topCap}`;
+ if (!this.geometryInHash(gid)) {
const cylinderGeom = new p5.Geometry(detailX, detailY);
_truncatedCone.call(
cylinderGeom,
@@ -3538,10 +3545,11 @@ function primitives3D(p5, fn){
' than 24 detailX or 16 detailY'
);
}
- this.createBuffers(gId, cylinderGeom);
+ cylinderGeom.gid = gid;
+ this.createBuffers(cylinderGeom);
}
- this.drawBuffersScaled(gId, radius, height, radius);
+ this.drawBuffersScaled(this.geometryBufferCache[gid].model, radius, height, radius);
}
RendererGL.prototype.cone = function(
@@ -3551,8 +3559,8 @@ function primitives3D(p5, fn){
detailY = 1,
cap = true
) {
- const gId = `cone|${detailX}|${detailY}|${cap}`;
- if (!this.geometryInHash(gId)) {
+ const gid = `cone|${detailX}|${detailY}|${cap}`;
+ if (!this.geometryInHash(gid)) {
const coneGeom = new Geometry(detailX, detailY);
_truncatedCone.call(coneGeom, 1, 0, 1, detailX, detailY, cap, false);
if (detailX <= 24 && detailY <= 16) {
@@ -3563,10 +3571,11 @@ function primitives3D(p5, fn){
' than 24 detailX or 16 detailY'
);
}
- this.createBuffers(gId, coneGeom);
+ coneGeom.gid = gid;
+ this.createBuffers(coneGeom);
}
- this.drawBuffersScaled(gId, radius, height, radius);
+ this.drawBuffersScaled(this.geometryBufferCache[gid].model, radius, height, radius);
}
RendererGL.prototype.torus = function(
@@ -3584,9 +3593,9 @@ function primitives3D(p5, fn){
}
const tubeRatio = (tubeRadius / radius).toPrecision(4);
- const gId = `torus|${tubeRatio}|${detailX}|${detailY}`;
+ const gid = `torus|${tubeRatio}|${detailX}|${detailY}`;
- if (!this.geometryInHash(gId)) {
+ if (!this.geometryInHash(gid)) {
const _torus = function() {
for (let i = 0; i <= this.detailY; i++) {
const v = i / this.detailY;
@@ -3625,9 +3634,10 @@ function primitives3D(p5, fn){
' than 24 detailX or 16 detailY'
);
}
- this.createBuffers(gId, torusGeom);
+ torusGeom.gid = gid;
+ this.createBuffers(torusGeom);
}
- this.drawBuffersScaled(gId, radius, radius, radius);
+ this.drawBuffersScaled(this.geometryBufferCache[gid].model, radius, radius, radius);
}
}
diff --git a/src/webgl/GeometryBuilder.js b/src/webgl/GeometryBuilder.js
index a6889ba457..a5691db31e 100644
--- a/src/webgl/GeometryBuilder.js
+++ b/src/webgl/GeometryBuilder.js
@@ -108,9 +108,7 @@ class GeometryBuilder {
* Adds geometry from the renderer's immediate mode into the builder's
* combined geometry.
*/
- addImmediate() {
- const geometry = this.renderer.immediateMode.geometry;
- const shapeMode = this.renderer.immediateMode.shapeMode;
+ addImmediate(geometry, shapeMode) {
const faces = [];
if (this.renderer.states.doFill) {
@@ -143,7 +141,7 @@ class GeometryBuilder {
* combined geometry.
*/
addRetained(geometry) {
- this.addGeometry(geometry.model);
+ this.addGeometry(geometry);
}
/**
diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js
new file mode 100644
index 0000000000..258f8c07e8
--- /dev/null
+++ b/src/webgl/ShapeBuilder.js
@@ -0,0 +1,610 @@
+import * as constants from '../core/constants';
+import { Geometry } from './p5.Geometry';
+import libtess from 'libtess'; // Fixed with exporting module from libtess
+import { Vector } from '../math/p5.Vector';
+import { RenderBuffer } from './p5.RenderBuffer';
+
+const INITIAL_BUFFER_STRIDES = {
+ vertices: 1,
+ vertexNormals: 1,
+ vertexColors: 4,
+ vertexStrokeColors: 4,
+ uvs: 2
+};
+
+// The total number of properties per vertex, before additional
+// user attributes are added.
+const INITIAL_VERTEX_SIZE =
+ Object.values(INITIAL_BUFFER_STRIDES).reduce((acc, next) => acc + next);
+
+export class ShapeBuilder {
+ constructor(renderer) {
+ this.renderer = renderer;
+ this.shapeMode = constants.TESS;
+ this.geometry = new Geometry();
+ this.geometry.gid = '__IMMEDIATE_MODE_GEOMETRY__';
+
+ this.contourIndices = [];
+ this._useUserVertexProperties = undefined;
+
+ this._bezierVertex = [];
+ this._quadraticVertex = [];
+ this._curveVertex = [];
+
+ // Used to distinguish between user calls to vertex() and internal calls
+ this.isProcessingVertices = false;
+
+ // Used for converting shape outlines into triangles for rendering
+ this._tessy = this._initTessy();
+ this.tessyVertexSize = INITIAL_VERTEX_SIZE;
+ this.bufferStrides = { ...INITIAL_BUFFER_STRIDES };
+ }
+
+ beginShape(mode = constants.TESS) {
+ this.shapeMode = mode;
+ if (this._useUserVertexProperties === true){
+ this._resetUserVertexProperties();
+ }
+ this.geometry.reset();
+ this.contourIndices = [];
+ }
+
+ 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();
+ }
+
+ 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;
+ }
+ } 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;
+ }
+
+ 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){
+ const prop = properties[propName];
+ delete this.bufferStrides[propName];
+ prop.delete();
+ }
+ this._useUserVertexProperties = false;
+ this.tessyVertexSize = INITIAL_VERTEX_SIZE;
+ 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.doStroke;
+ 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.doFill &&
+ (
+ 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.
+ * @private
+ * @returns {Number[]} indices for custom shape vertices indicating edges.
+ */
+ _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++) {
+ res.push([i, i + 1]);
+ res.push([i, i + 2]);
+ }
+ res.push([i, i + 1]);
+ break;
+ case constants.TRIANGLE_FAN:
+ for (i = 1; i < verts.length - 1; i++) {
+ res.push([0, i]);
+ res.push([i, i + 1]);
+ }
+ res.push([0, verts.length - 1]);
+ break;
+ case constants.TRIANGLES:
+ for (i = 0; i < verts.length - 2; i = i + 3) {
+ res.push([i, i + 1]);
+ res.push([i + 1, i + 2]);
+ res.push([i + 2, i]);
+ }
+ break;
+ case constants.LINES:
+ for (i = 0; i < verts.length - 1; i = i + 2) {
+ res.push([i, i + 1]);
+ }
+ break;
+ case constants.QUADS:
+ // Quads have been broken up into two triangles by `vertex()`:
+ // 0 3--5
+ // | \ \ |
+ // 1--2 4
+ 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]);
+ }
+ break;
+ case constants.QUAD_STRIP:
+ // 0---2---4
+ // | | |
+ // 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 + 1]);
+ break;
+ 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]);
+ } else {
+ if (shouldClose || contourStart) {
+ res.push([i, contourStart]);
+ }
+ if (contourIndices.length > 0) {
+ contourStart = contourIndices.shift();
+ }
+ }
+ }
+ break;
+ }
+ if (shapeMode !== constants.TESS && shouldClose) {
+ res.push([verts.length - 1, 0]);
+ }
+ return res;
+ }
+
+ /**
+ * Called from _processVertices() when applicable. This function tesselates immediateMode.geometry.
+ * @private
+ */
+ _tesselateShape() {
+ // TODO: handle non-TESS shape modes that have contours
+ this.shapeMode = constants.TRIANGLES;
+ const contours = [[]];
+ for (let i = 0; i < this.geometry.vertices.length; i++) {
+ if (
+ this.contourIndices.length > 0 &&
+ this.contourIndices[0] === i
+ ) {
+ this.contourIndices.shift();
+ contours.push([]);
+ }
+ contours[contours.length-1].push(
+ this.geometry.vertices[i].x,
+ this.geometry.vertices[i].y,
+ this.geometry.vertices[i].z,
+ this.geometry.uvs[i * 2],
+ this.geometry.uvs[i * 2 + 1],
+ this.geometry.vertexColors[i * 4],
+ this.geometry.vertexColors[i * 4 + 1],
+ this.geometry.vertexColors[i * 4 + 2],
+ this.geometry.vertexColors[i * 4 + 3],
+ this.geometry.vertexNormals[i].x,
+ this.geometry.vertexNormals[i].y,
+ this.geometry.vertexNormals[i].z
+ );
+ for (const propName in this.geometry.userVertexProperties) {
+ const prop = this.geometry.userVertexProperties[propName];
+ const start = i * prop.getDataSize();
+ const end = start + prop.getDataSize();
+ const vals = prop.getSrcArray().slice(start, end);
+ contours[contours.length-1].push(...vals);
+ }
+ }
+
+ const polyTriangles = this._triangulate(contours);
+ const originalVertices = this.geometry.vertices;
+ this.geometry.vertices = [];
+ this.geometry.vertexNormals = [];
+ this.geometry.uvs = [];
+ for (const propName in this.geometry.userVertexProperties){
+ const prop = this.geometry.userVertexProperties[propName];
+ prop.resetSrcArray();
+ }
+ const colors = [];
+ for (
+ let j = 0, polyTriLength = polyTriangles.length;
+ j < polyTriLength;
+ j = j + this.tessyVertexSize
+ ) {
+ colors.push(...polyTriangles.slice(j + 5, j + 9));
+ this.renderer.normal(...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;
+ }
+ }
+ this.vertex(...polyTriangles.slice(j, j + 5));
+ }
+ if (this.renderer.geometryBuilder) {
+ // Tesselating the face causes the indices of edge vertices to stop being
+ // correct. When rendering, this is not a problem, since _edgesToVertices
+ // will have been called before this, and edge vertex indices are no longer
+ // needed. However, the geometry builder still needs this information, so
+ // when one is active, we need to update the indices.
+ //
+ // We record index mappings in a Map so that once we have found a
+ // corresponding vertex, we don't need to loop to find it again.
+ const newIndex = new Map();
+ this.geometry.edges =
+ this.geometry.edges.map(edge => edge.map(origIdx => {
+ if (!newIndex.has(origIdx)) {
+ const orig = originalVertices[origIdx];
+ let newVertIndex = this.geometry.vertices.findIndex(
+ v =>
+ orig.x === v.x &&
+ orig.y === v.y &&
+ orig.z === v.z
+ );
+ if (newVertIndex === -1) {
+ // The tesselation process didn't output a vertex with the exact
+ // coordinate as before, potentially due to numerical issues. This
+ // doesn't happen often, but in this case, pick the closest point
+ let closestDist = Infinity;
+ let closestIndex = 0;
+ for (
+ let i = 0;
+ i < this.geometry.vertices.length;
+ i++
+ ) {
+ const vert = this.geometry.vertices[i];
+ const dX = orig.x - vert.x;
+ const dY = orig.y - vert.y;
+ const dZ = orig.z - vert.z;
+ const dist = dX*dX + dY*dY + dZ*dZ;
+ if (dist < closestDist) {
+ closestDist = dist;
+ closestIndex = i;
+ }
+ }
+ newVertIndex = closestIndex;
+ }
+ newIndex.set(origIdx, newVertIndex);
+ }
+ return newIndex.get(origIdx);
+ }));
+ }
+ this.geometry.vertexColors = colors;
+ }
+
+ _initTessy() {
+ // function called for each vertex of tesselator output
+ function vertexCallback(data, polyVertArray) {
+ for (const element of data) {
+ polyVertArray.push(element);
+ }
+ }
+
+ function begincallback(type) {
+ if (type !== libtess.primitiveType.GL_TRIANGLES) {
+ console.log(`expected TRIANGLES but got type: ${type}`);
+ }
+ }
+
+ function errorcallback(errno) {
+ console.log('error callback');
+ console.log(`error number: ${errno}`);
+ }
+
+ // callback for when segments intersect and must be split
+ const combinecallback = (coords, data, weight) => {
+ const result = new Array(this.tessyVertexSize).fill(0);
+ for (let i = 0; i < weight.length; i++) {
+ for (let j = 0; j < result.length; j++) {
+ if (weight[i] === 0 || !data[i]) continue;
+ result[j] += data[i][j] * weight[i];
+ }
+ }
+ return result;
+ };
+
+ function edgeCallback(flag) {
+ // don't really care about the flag, but need no-strip/no-fan behavior
+ }
+
+ const tessy = new libtess.GluTesselator();
+ tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_VERTEX_DATA, vertexCallback);
+ tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_BEGIN, begincallback);
+ tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_ERROR, errorcallback);
+ tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_COMBINE, combinecallback);
+ tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_EDGE_FLAG, edgeCallback);
+ tessy.gluTessProperty(
+ libtess.gluEnum.GLU_TESS_WINDING_RULE,
+ libtess.windingRule.GLU_TESS_WINDING_NONZERO
+ );
+
+ return tessy;
+ }
+
+ /**
+ * Runs vertices through libtess to convert them into triangles
+ * @private
+ */
+ _triangulate(contours) {
+ // libtess will take 3d verts and flatten to a plane for tesselation.
+ // libtess is capable of calculating a plane to tesselate on, but
+ // if all of the vertices have the same z values, we'll just
+ // assume the face is facing the camera, letting us skip any performance
+ // issues or bugs in libtess's automatic calculation.
+ const z = contours[0] ? contours[0][2] : undefined;
+ let allSameZ = true;
+ for (const contour of contours) {
+ for (
+ let j = 0;
+ j < contour.length;
+ j += this.tessyVertexSize
+ ) {
+ if (contour[j + 2] !== z) {
+ allSameZ = false;
+ break;
+ }
+ }
+ }
+ if (allSameZ) {
+ this._tessy.gluTessNormal(0, 0, 1);
+ } else {
+ // Let libtess pick a plane for us
+ this._tessy.gluTessNormal(0, 0, 0);
+ }
+
+ const triangleVerts = [];
+ this._tessy.gluTessBeginPolygon(triangleVerts);
+
+ for (const contour of contours) {
+ this._tessy.gluTessBeginContour();
+ for (
+ let j = 0;
+ j < contour.length;
+ j += this.tessyVertexSize
+ ) {
+ const coords = contour.slice(
+ j,
+ j + this.tessyVertexSize
+ );
+ this._tessy.gluTessVertex(coords, coords);
+ }
+ this._tessy.gluTessEndContour();
+ }
+
+ // finish polygon
+ this._tessy.gluTessEndPolygon();
+
+ return triangleVerts;
+ }
+};
diff --git a/src/webgl/index.js b/src/webgl/index.js
index 74ed708fad..d66c4b0acc 100644
--- a/src/webgl/index.js
+++ b/src/webgl/index.js
@@ -14,7 +14,6 @@ import shader from './p5.Shader';
import camera from './p5.Camera';
import texture from './p5.Texture';
import rendererGL from './p5.RendererGL';
-import rendererGLImmediate from './p5.RendererGL.Immediate';
import rendererGLRetained from './p5.RendererGL.Retained';
export default function(p5){
@@ -34,6 +33,5 @@ export default function(p5){
dataArray(p5, p5.prototype);
shader(p5, p5.prototype);
texture(p5, p5.prototype);
- rendererGLImmediate(p5, p5.prototype);
rendererGLRetained(p5, p5.prototype);
}
diff --git a/src/webgl/loading.js b/src/webgl/loading.js
index 3fadd77c5d..4163cb04c7 100755
--- a/src/webgl/loading.js
+++ b/src/webgl/loading.js
@@ -1112,7 +1112,7 @@ function loading(p5, fn){
*
*
*/
- fn.model = function (model) {
+ fn.model = function (model, count = 1) {
this._assert3d('model');
p5._validateParameters('model', arguments);
if (model.vertices.length > 0) {
@@ -1123,10 +1123,10 @@ function loading(p5, fn){
}
model._edgesToVertices();
- this._renderer.createBuffers(model.gid, model);
+ this._renderer._getOrMakeCachedBuffers(model);
}
- this._renderer.drawBuffers(model.gid);
+ this._renderer._drawGeometry(model, { count });
}
};
}
diff --git a/src/webgl/p5.RenderBuffer.js b/src/webgl/p5.RenderBuffer.js
index e2f597a832..87c80ce45b 100644
--- a/src/webgl/p5.RenderBuffer.js
+++ b/src/webgl/p5.RenderBuffer.js
@@ -1,5 +1,5 @@
class RenderBuffer {
- constructor(size, src, dst, attr, renderer, map){
+ constructor(size, src, dst, attr, renderer, map) {
this.size = size; // the number of FLOATs in each vertex
this.src = src; // the name of the model's source array
this.dst = dst; // the name of the geometry's buffer
@@ -9,73 +9,67 @@ class RenderBuffer {
}
/**
- * Enables and binds the buffers used by shader when the appropriate data exists in geometry.
- * Must always be done prior to drawing geometry in WebGL.
- * @param {p5.Geometry} geometry Geometry that is going to be drawn
- * @param {p5.Shader} shader Active shader
- * @private
- */
+ * Enables and binds the buffers used by shader when the appropriate data exists in geometry.
+ * Must always be done prior to drawing geometry in WebGL.
+ * @param {p5.Geometry} geometry Geometry that is going to be drawn
+ * @param {p5.Shader} shader Active shader
+ * @private
+ */
_prepareBuffer(geometry, shader) {
const attributes = shader.attributes;
const gl = this._renderer.GL;
- let model;
- if (geometry.model) {
- model = geometry.model;
- } else {
- model = geometry;
- }
+ const glBuffers = this._renderer._getOrMakeCachedBuffers(geometry);
// loop through each of the buffer definitions
const attr = attributes[this.attr];
if (!attr) {
return;
}
- // check if the model has the appropriate source array
- let buffer = geometry[this.dst];
- const src = model[this.src];
- if (!src){
- return;
- }
- if (src.length > 0) {
- // check if we need to create the GL buffer
+ // check if the geometry has the appropriate source array
+ let buffer = glBuffers[this.dst];
+ const src = geometry[this.src];
+ if (src && src.length > 0) {
+ // check if we need to create the GL buffer
const createBuffer = !buffer;
if (createBuffer) {
- // create and remember the buffer
- geometry[this.dst] = buffer = gl.createBuffer();
+ // create and remember the buffer
+ glBuffers[this.dst] = buffer = gl.createBuffer();
}
// bind the buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// check if we need to fill the buffer with data
- if (createBuffer || model.dirtyFlags[this.src] !== false) {
+ if (createBuffer || geometry.dirtyFlags[this.src] !== false) {
const map = this.map;
- // get the values from the model, possibly transformed
+ // get the values from the geometry, possibly transformed
const values = map ? map(src) : src;
// fill the buffer with the values
this._renderer._bindBuffer(buffer, gl.ARRAY_BUFFER, values);
- // mark the model's source array as clean
- model.dirtyFlags[this.src] = false;
+ // mark the geometry's source array as clean
+ geometry.dirtyFlags[this.src] = false;
}
// enable the attribute
shader.enableAttrib(attr, this.size);
} else {
const loc = attr.location;
- if (loc === -1 || !this._renderer.registerEnabled.has(loc)) { return; }
+ if (loc === -1 || !this._renderer.registerEnabled.has(loc)) {
+ return;
+ }
// Disable register corresponding to unused attribute
gl.disableVertexAttribArray(loc);
// Record register availability
this._renderer.registerEnabled.delete(loc);
}
}
-};
+}
-function renderBuffer(p5, fn){
+function renderBuffer(p5, fn) {
p5.RenderBuffer = RenderBuffer;
}
export default renderBuffer;
export { RenderBuffer };
-if(typeof p5 !== 'undefined'){
+if (typeof p5 !== "undefined") {
renderBuffer(p5, p5.prototype);
}
diff --git a/src/webgl/p5.RendererGL.Immediate.js b/src/webgl/p5.RendererGL.Immediate.js
deleted file mode 100644
index c8454b6b8e..0000000000
--- a/src/webgl/p5.RendererGL.Immediate.js
+++ /dev/null
@@ -1,669 +0,0 @@
-/**
- * Welcome to RendererGL Immediate Mode.
- * Immediate mode is used for drawing custom shapes
- * from a set of vertices. Immediate Mode is activated
- * when you call beginShape() & de-activated when you call endShape().
- * Immediate mode is a style of programming borrowed
- * from OpenGL's (now-deprecated) immediate mode.
- * It differs from p5.js' default, Retained Mode, which caches
- * geometries and buffers on the CPU to reduce the number of webgl
- * draw calls. Retained mode is more efficient & performative,
- * however, Immediate Mode is useful for sketching quick
- * geometric ideas.
- */
-import * as constants from '../core/constants';
-import { RendererGL } from './p5.RendererGL';
-import { Vector } from '../math/p5.Vector';
-import { RenderBuffer } from './p5.RenderBuffer';
-
-function rendererGLImmediate(p5, fn){
- /**
- * Begin shape drawing. This is a helpful way of generating
- * custom shapes quickly. However in WEBGL mode, application
- * performance will likely drop as a result of too many calls to
- * beginShape() / endShape(). As a high performance alternative,
- * please use p5.js geometry primitives.
- * @private
- * @method beginShape
- * @param {Number} mode webgl primitives mode. beginShape supports the
- * following modes:
- * POINTS,LINES,LINE_STRIP,LINE_LOOP,TRIANGLES,
- * TRIANGLE_STRIP, TRIANGLE_FAN, QUADS, QUAD_STRIP,
- * and TESS(WEBGL only)
- * @chainable
- */
- RendererGL.prototype.beginShape = function(mode) {
- this.immediateMode.shapeMode =
- mode !== undefined ? mode : constants.TESS;
- if (this._useUserVertexProperties === true){
- this._resetUserVertexProperties();
- }
- this.immediateMode.geometry.reset();
- this.immediateMode.contourIndices = [];
- return this;
- };
-
- RendererGL.prototype.immediateBufferStrides = {
- vertices: 1,
- vertexNormals: 1,
- vertexColors: 4,
- vertexStrokeColors: 4,
- uvs: 2
- };
-
- RendererGL.prototype.beginContour = function() {
- if (this.immediateMode.shapeMode !== constants.TESS) {
- throw new Error('WebGL mode can only use contours with beginShape(TESS).');
- }
- this.immediateMode.contourIndices.push(
- this.immediateMode.geometry.vertices.length
- );
- };
-
- /**
- * adds a vertex to be drawn in a custom Shape.
- * @private
- * @method vertex
- * @param {Number} x x-coordinate of vertex
- * @param {Number} y y-coordinate of vertex
- * @param {Number} z z-coordinate of vertex
- * @chainable
- * @TODO implement handling of p5.Vector args
- */
- RendererGL.prototype.vertex = function(x, y) {
- // WebGL 1 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.immediateMode.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.immediateMode.geometry.vertices.length % 6 === 3) {
- for (const key in this.immediateBufferStrides) {
- const stride = this.immediateBufferStrides[key];
- const buffer = this.immediateMode.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.immediateMode.geometry.vertices.push(vert);
- this.immediateMode.geometry.vertexNormals.push(this.states._currentNormal);
-
- for (const propName in this.immediateMode.geometry.userVertexProperties){
- const geom = this.immediateMode.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();
- }
-
- const vertexColor = this.states.curFillColor || [0.5, 0.5, 0.5, 1.0];
- this.immediateMode.geometry.vertexColors.push(
- vertexColor[0],
- vertexColor[1],
- vertexColor[2],
- vertexColor[3]
- );
- const lineVertexColor = this.states.curStrokeColor || [0.5, 0.5, 0.5, 1];
- this.immediateMode.geometry.vertexStrokeColors.push(
- lineVertexColor[0],
- lineVertexColor[1],
- lineVertexColor[2],
- lineVertexColor[3]
- );
-
- if (this.states.textureMode === constants.IMAGE && !this.isProcessingVertices) {
- if (this.states._tex !== null) {
- if (this.states._tex.width > 0 && this.states._tex.height > 0) {
- u /= this.states._tex.width;
- v /= this.states._tex.height;
- }
- } else if (
- this.states.userFillShader !== undefined ||
- this.states.userStrokeShader !== undefined ||
- this.states.userPointShader !== undefined ||
- this.states.userImageShader !== undefined
- ) {
- // Do nothing if user-defined shaders are present
- } else if (
- this.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.immediateMode.geometry.uvs.push(u, v);
-
- this.immediateMode._bezierVertex[0] = x;
- this.immediateMode._bezierVertex[1] = y;
- this.immediateMode._bezierVertex[2] = z;
-
- this.immediateMode._quadraticVertex[0] = x;
- this.immediateMode._quadraticVertex[1] = y;
- this.immediateMode._quadraticVertex[2] = z;
-
- return this;
- };
-
- RendererGL.prototype.vertexProperty = function(propertyName, data){
- if(!this._useUserVertexProperties){
- this._useUserVertexProperties = true;
- this.immediateMode.geometry.userVertexProperties = {};
- }
- const propertyExists = this.immediateMode.geometry.userVertexProperties[propertyName];
- let prop;
- if (propertyExists){
- prop = this.immediateMode.geometry.userVertexProperties[propertyName];
- }
- else {
- prop = this.immediateMode.geometry._userVertexPropertyHelper(propertyName, data);
- this.tessyVertexSize += prop.getDataSize();
- this.immediateBufferStrides[prop.getSrcName()] = prop.getDataSize();
- this.immediateMode.buffers.user.push(
- new RenderBuffer(prop.getDataSize(), prop.getSrcName(), prop.getDstName(), propertyName, this)
- );
- }
- prop.setCurrentData(data);
- };
-
- RendererGL.prototype._resetUserVertexProperties = function(){
- const properties = this.immediateMode.geometry.userVertexProperties;
- for (const propName in properties){
- const prop = properties[propName];
- delete this.immediateBufferStrides[propName];
- prop.delete();
- }
- this._useUserVertexProperties = false;
- this.tessyVertexSize = 12;
- this.immediateMode.geometry.userVertexProperties = {};
- this.immediateMode.buffers.user = [];
- };
-
- /**
- * Sets the normal to use for subsequent vertices.
- * @private
- * @method normal
- * @param {Number} x
- * @param {Number} y
- * @param {Number} z
- * @chainable
- *
- * @method normal
- * @param {Vector} v
- * @chainable
- */
- RendererGL.prototype.normal = function(xorv, y, z) {
- if (xorv instanceof Vector) {
- this.states._currentNormal = xorv;
- } else {
- this.states._currentNormal = new Vector(xorv, y, z);
- }
-
- return this;
- };
-
- /**
- * End shape drawing and render vertices to screen.
- * @chainable
- */
- RendererGL.prototype.endShape = function(
- mode,
- isCurve,
- isBezier,
- isQuadratic,
- isContour,
- shapeKind,
- count = 1
- ) {
- if (this.immediateMode.shapeMode === constants.POINTS) {
- this._drawPoints(
- this.immediateMode.geometry.vertices,
- this.immediateMode.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.immediateMode.geometry.vertices.length === 3 &&
- this.immediateMode.shapeMode === constants.TESS
- ) {
- this.immediateMode.shapeMode === constants.TRIANGLES;
- }
-
- this.isProcessingVertices = true;
- this._processVertices(...arguments);
- this.isProcessingVertices = false;
-
- // LINE_STRIP and LINES are not used for rendering, instead
- // they only indicate a way to modify vertices during the _processVertices() step
- let is_line = false;
- if (
- this.immediateMode.shapeMode === constants.LINE_STRIP ||
- this.immediateMode.shapeMode === constants.LINES
- ) {
- this.immediateMode.shapeMode = constants.TRIANGLE_FAN;
- is_line = true;
- }
-
- // 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.immediateMode.shapeMode === constants.QUADS) {
- this.immediateMode.shapeMode = constants.TRIANGLES;
- } else if (this.immediateMode.shapeMode === constants.QUAD_STRIP) {
- this.immediateMode.shapeMode = constants.TRIANGLE_STRIP;
- }
-
- if (this.states.doFill && !is_line) {
- if (
- !this.geometryBuilder &&
- this.immediateMode.geometry.vertices.length >= 3
- ) {
- this._drawImmediateFill(count);
- }
- }
- if (this.states.doStroke) {
- if (
- !this.geometryBuilder &&
- this.immediateMode.geometry.lineVertices.length >= 1
- ) {
- this._drawImmediateStroke();
- }
- }
-
- if (this.geometryBuilder) {
- this.geometryBuilder.addImmediate();
- }
-
- this.isBezier = false;
- this.isQuadratic = false;
- this.isCurve = false;
- this.immediateMode._bezierVertex.length = 0;
- this.immediateMode._quadraticVertex.length = 0;
- this.immediateMode._curveVertex.length = 0;
-
- return this;
- };
-
- /**
- * Called from endShape(). This function calculates the stroke vertices for custom shapes and
- * tesselates shapes when applicable.
- * @private
- * @param {Number} mode webgl primitives mode. beginShape supports the
- * following modes:
- * POINTS,LINES,LINE_STRIP,LINE_LOOP,TRIANGLES,
- * TRIANGLE_STRIP, TRIANGLE_FAN and TESS(WEBGL only)
- */
- RendererGL.prototype._processVertices = function(mode) {
- if (this.immediateMode.geometry.vertices.length === 0) return;
-
- const calculateStroke = this.states.doStroke;
- const shouldClose = mode === constants.CLOSE;
- if (calculateStroke) {
- this.immediateMode.geometry.edges = this._calculateEdges(
- this.immediateMode.shapeMode,
- this.immediateMode.geometry.vertices,
- shouldClose
- );
- if (!this.geometryBuilder) {
- this.immediateMode.geometry._edgesToVertices();
- }
- }
- // For hollow shapes, user must set mode to TESS
- const convexShape = this.immediateMode.shapeMode === constants.TESS;
- // If the shape has a contour, we have to re-triangulate to cut out the
- // contour region
- const hasContour = this.immediateMode.contourIndices.length > 0;
- // We tesselate when drawing curves or convex shapes
- const shouldTess =
- this.states.doFill &&
- (
- this.isBezier ||
- this.isQuadratic ||
- this.isCurve ||
- convexShape ||
- hasContour
- ) &&
- this.immediateMode.shapeMode !== constants.LINES;
-
- if (shouldTess) {
- this._tesselateShape();
- }
- };
-
- /**
- * Called from _processVertices(). This function calculates the stroke vertices for custom shapes and
- * tesselates shapes when applicable.
- * @private
- * @returns {Number[]} indices for custom shape vertices indicating edges.
- */
- RendererGL.prototype._calculateEdges = function(
- shapeMode,
- verts,
- shouldClose
- ) {
- const res = [];
- let i = 0;
- const contourIndices = this.immediateMode.contourIndices.slice();
- let contourStart = 0;
- switch (shapeMode) {
- case constants.TRIANGLE_STRIP:
- for (i = 0; i < verts.length - 2; i++) {
- res.push([i, i + 1]);
- res.push([i, i + 2]);
- }
- res.push([i, i + 1]);
- break;
- case constants.TRIANGLE_FAN:
- for (i = 1; i < verts.length - 1; i++) {
- res.push([0, i]);
- res.push([i, i + 1]);
- }
- res.push([0, verts.length - 1]);
- break;
- case constants.TRIANGLES:
- for (i = 0; i < verts.length - 2; i = i + 3) {
- res.push([i, i + 1]);
- res.push([i + 1, i + 2]);
- res.push([i + 2, i]);
- }
- break;
- case constants.LINES:
- for (i = 0; i < verts.length - 1; i = i + 2) {
- res.push([i, i + 1]);
- }
- break;
- case constants.QUADS:
- // Quads have been broken up into two triangles by `vertex()`:
- // 0 3--5
- // | \ \ |
- // 1--2 4
- 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]);
- }
- break;
- case constants.QUAD_STRIP:
- // 0---2---4
- // | | |
- // 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 + 1]);
- break;
- 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]);
- } else {
- if (shouldClose || contourStart) {
- res.push([i, contourStart]);
- }
- if (contourIndices.length > 0) {
- contourStart = contourIndices.shift();
- }
- }
- }
- break;
- }
- if (shapeMode !== constants.TESS && shouldClose) {
- res.push([verts.length - 1, 0]);
- }
- return res;
- };
-
- /**
- * Called from _processVertices() when applicable. This function tesselates immediateMode.geometry.
- * @private
- */
- RendererGL.prototype._tesselateShape = function() {
- // TODO: handle non-TESS shape modes that have contours
- this.immediateMode.shapeMode = constants.TRIANGLES;
- const contours = [[]];
- for (let i = 0; i < this.immediateMode.geometry.vertices.length; i++) {
- if (
- this.immediateMode.contourIndices.length > 0 &&
- this.immediateMode.contourIndices[0] === i
- ) {
- this.immediateMode.contourIndices.shift();
- contours.push([]);
- }
- contours[contours.length-1].push(
- this.immediateMode.geometry.vertices[i].x,
- this.immediateMode.geometry.vertices[i].y,
- this.immediateMode.geometry.vertices[i].z,
- this.immediateMode.geometry.uvs[i * 2],
- this.immediateMode.geometry.uvs[i * 2 + 1],
- this.immediateMode.geometry.vertexColors[i * 4],
- this.immediateMode.geometry.vertexColors[i * 4 + 1],
- this.immediateMode.geometry.vertexColors[i * 4 + 2],
- this.immediateMode.geometry.vertexColors[i * 4 + 3],
- this.immediateMode.geometry.vertexNormals[i].x,
- this.immediateMode.geometry.vertexNormals[i].y,
- this.immediateMode.geometry.vertexNormals[i].z
- );
- for (const propName in this.immediateMode.geometry.userVertexProperties){
- const prop = this.immediateMode.geometry.userVertexProperties[propName];
- const start = i * prop.getDataSize();
- const end = start + prop.getDataSize();
- const vals = prop.getSrcArray().slice(start, end);
- contours[contours.length-1].push(...vals);
- }
- }
- const polyTriangles = this._triangulate(contours);
- const originalVertices = this.immediateMode.geometry.vertices;
- this.immediateMode.geometry.vertices = [];
- this.immediateMode.geometry.vertexNormals = [];
- this.immediateMode.geometry.uvs = [];
- for (const propName in this.immediateMode.geometry.userVertexProperties){
- const prop = this.immediateMode.geometry.userVertexProperties[propName];
- prop.resetSrcArray();
- }
- const colors = [];
- for (
- let j = 0, polyTriLength = polyTriangles.length;
- j < polyTriLength;
- j = j + this.tessyVertexSize
- ) {
- colors.push(...polyTriangles.slice(j + 5, j + 9));
- this.normal(...polyTriangles.slice(j + 9, j + 12));
- {
- let offset = 12;
- for (const propName in this.immediateMode.geometry.userVertexProperties){
- const prop = this.immediateMode.geometry.userVertexProperties[propName];
- const size = prop.getDataSize();
- const start = j + offset;
- const end = start + size;
- prop.setCurrentData(polyTriangles.slice(start, end));
- offset += size;
- }
- }
- this.vertex(...polyTriangles.slice(j, j + 5));
- }
- if (this.geometryBuilder) {
- // Tesselating the face causes the indices of edge vertices to stop being
- // correct. When rendering, this is not a problem, since _edgesToVertices
- // will have been called before this, and edge vertex indices are no longer
- // needed. However, the geometry builder still needs this information, so
- // when one is active, we need to update the indices.
- //
- // We record index mappings in a Map so that once we have found a
- // corresponding vertex, we don't need to loop to find it again.
- const newIndex = new Map();
- this.immediateMode.geometry.edges =
- this.immediateMode.geometry.edges.map(edge => edge.map(origIdx => {
- if (!newIndex.has(origIdx)) {
- const orig = originalVertices[origIdx];
- let newVertIndex = this.immediateMode.geometry.vertices.findIndex(
- v =>
- orig.x === v.x &&
- orig.y === v.y &&
- orig.z === v.z
- );
- if (newVertIndex === -1) {
- // The tesselation process didn't output a vertex with the exact
- // coordinate as before, potentially due to numerical issues. This
- // doesn't happen often, but in this case, pick the closest point
- let closestDist = Infinity;
- let closestIndex = 0;
- for (
- let i = 0;
- i < this.immediateMode.geometry.vertices.length;
- i++
- ) {
- const vert = this.immediateMode.geometry.vertices[i];
- const dX = orig.x - vert.x;
- const dY = orig.y - vert.y;
- const dZ = orig.z - vert.z;
- const dist = dX*dX + dY*dY + dZ*dZ;
- if (dist < closestDist) {
- closestDist = dist;
- closestIndex = i;
- }
- }
- newVertIndex = closestIndex;
- }
- newIndex.set(origIdx, newVertIndex);
- }
- return newIndex.get(origIdx);
- }));
- }
- this.immediateMode.geometry.vertexColors = colors;
- };
-
- /**
- * Called from endShape(). Responsible for calculating normals, setting shader uniforms,
- * enabling all appropriate buffers, applying color blend, and drawing the fill geometry.
- * @private
- */
- RendererGL.prototype._drawImmediateFill = function(count = 1) {
- const gl = this.GL;
- this._useVertexColor = (this.immediateMode.geometry.vertexColors.length > 0);
-
- let shader;
- shader = this._getFillShader();
-
- this._setFillUniforms(shader);
-
- for (const buff of this.immediateMode.buffers.fill) {
- buff._prepareBuffer(this.immediateMode.geometry, shader);
- }
- for (const buff of this.immediateMode.buffers.user){
- buff._prepareBuffer(this.immediateMode.geometry, shader);
- }
- shader.disableRemainingAttributes();
-
- this._applyColorBlend(
- this.states.curFillColor,
- this.immediateMode.geometry.hasFillTransparency()
- );
-
- if (count === 1) {
- gl.drawArrays(
- this.immediateMode.shapeMode,
- 0,
- this.immediateMode.geometry.vertices.length
- );
- }
- else {
- try {
- gl.drawArraysInstanced(
- this.immediateMode.shapeMode,
- 0,
- this.immediateMode.geometry.vertices.length,
- count
- );
- }
- catch (e) {
- console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode');
- }
- }
- shader.unbindShader();
- };
-
- /**
- * Called from endShape(). Responsible for calculating normals, setting shader uniforms,
- * enabling all appropriate buffers, applying color blend, and drawing the stroke geometry.
- * @private
- */
- RendererGL.prototype._drawImmediateStroke = function() {
- const gl = this.GL;
-
- this._useLineColor =
- (this.immediateMode.geometry.vertexStrokeColors.length > 0);
-
- const shader = this._getImmediateStrokeShader();
- this._setStrokeUniforms(shader);
- for (const buff of this.immediateMode.buffers.stroke) {
- buff._prepareBuffer(this.immediateMode.geometry, shader);
- }
- for (const buff of this.immediateMode.buffers.user){
- buff._prepareBuffer(this.immediateMode.geometry, shader);
- }
- shader.disableRemainingAttributes();
- this._applyColorBlend(
- this.states.curStrokeColor,
- this.immediateMode.geometry.hasFillTransparency()
- );
-
- gl.drawArrays(
- gl.TRIANGLES,
- 0,
- this.immediateMode.geometry.lineVertices.length / 3
- );
- shader.unbindShader();
- };
-}
-
-export default rendererGLImmediate;
-
-if(typeof p5 !== 'undefined'){
- rendererGLImmediate(p5, p5.prototype);
-}
diff --git a/src/webgl/p5.RendererGL.Retained.js b/src/webgl/p5.RendererGL.Retained.js
index 780e4183cc..1d6ad770b0 100644
--- a/src/webgl/p5.RendererGL.Retained.js
+++ b/src/webgl/p5.RendererGL.Retained.js
@@ -16,34 +16,13 @@ function rendererGLRetained(p5, fn){
this._freeBuffers(geometry.gid);
};
- /**
- * _initBufferDefaults
- * @private
- * @description initializes buffer defaults. runs each time a new geometry is
- * registered
- * @param {String} gId key of the geometry object
- * @returns {Object} a new buffer object
- */
- RendererGL.prototype._initBufferDefaults = function(gId) {
- this._freeBuffers(gId);
-
- //@TODO remove this limit on hashes in retainedMode.geometry
- if (Object.keys(this.retainedMode.geometry).length > 1000) {
- const key = Object.keys(this.retainedMode.geometry)[0];
- this._freeBuffers(key);
- }
-
- //create a new entry in our retainedMode.geometry
- return (this.retainedMode.geometry[gId] = {});
- };
-
- RendererGL.prototype._freeBuffers = function(gId) {
- const buffers = this.retainedMode.geometry[gId];
+ RendererGL.prototype._freeBuffers = function(gid) {
+ const buffers = this.geometryBufferCache[gid];
if (!buffers) {
return;
}
- delete this.retainedMode.geometry[gId];
+ delete this.geometryBufferCache[gid];
const gl = this.GL;
if (buffers.indexBuffer) {
@@ -60,23 +39,38 @@ function rendererGLRetained(p5, fn){
}
// free all the buffers
- freeBuffers(this.retainedMode.buffers.stroke);
- freeBuffers(this.retainedMode.buffers.fill);
- freeBuffers(this.retainedMode.buffers.user);
- this.retainedMode.buffers.user = [];
+ freeBuffers(this.buffers.stroke);
+ freeBuffers(this.buffers.fill);
+ freeBuffers(this.buffers.user);
};
/**
- * creates a buffers object that holds the WebGL render buffers
+ * Creates a buffers object that holds the WebGL render buffers
* for a geometry.
* @private
- * @param {String} gId key of the geometry object
* @param {p5.Geometry} model contains geometry data
*/
- RendererGL.prototype.createBuffers = function(gId, model) {
+ RendererGL.prototype.createBuffers = function(model) {
const gl = this.GL;
+
+ const gid = model.gid;
+ if (!gid) {
+ throw new Error('The p5.Geometry you passed in has no gid property!');
+ }
+
//initialize the gl buffers for our geom groups
- const buffers = this._initBufferDefaults(gId);
+ this._freeBuffers(gid);
+
+ //@TODO remove this limit on hashes in geometryBufferCache
+ if (Object.keys(this.geometryBufferCache).length > 1000) {
+ const key = Object.keys(this.geometryBufferCache)[0];
+ this._freeBuffers(key);
+ }
+
+ //create a new entry in our geometryBufferCache
+ const buffers = {};
+ this.geometryBufferCache[gid] = buffers;
+
buffers.model = model;
let indexBuffer = buffers.indexBuffer;
@@ -108,107 +102,25 @@ function rendererGLRetained(p5, fn){
gl.deleteBuffer(indexBuffer);
buffers.indexBuffer = null;
}
+ // TODO: delete?
// the vertex count comes directly from the model
buffers.vertexCount = model.vertices ? model.vertices.length : 0;
}
+ // TODO: delete?
buffers.lineVertexCount = model.lineVertices
? model.lineVertices.length / 3
: 0;
- for (const propName in model.userVertexProperties){
+ for (const propName in model.userVertexProperties) {
const prop = model.userVertexProperties[propName];
- this.retainedMode.buffers.user.push(
+ this.buffers.user.push(
new RenderBuffer(prop.getDataSize(), prop.getSrcName(), prop.getDstName(), prop.getName(), this)
);
}
return buffers;
};
- /**
- * Draws buffers given a geometry key ID
- * @private
- * @param {String} gId ID in our geom hash
- * @chainable
- */
- RendererGL.prototype.drawBuffers = function(gId) {
- const gl = this.GL;
- const geometry = this.retainedMode.geometry[gId];
-
- if (
- !this.geometryBuilder &&
- this.states.doFill &&
- geometry.vertexCount > 0
- ) {
- this._useVertexColor = (geometry.model.vertexColors.length > 0);
-
- let fillShader;
- if (this._drawingFilter && this.states.userFillShader) {
- fillShader = this.states.userFillShader;
- } else {
- fillShader = this._getFillShader();
- }
- this._setFillUniforms(fillShader);
-
- for (const buff of this.retainedMode.buffers.fill) {
- buff._prepareBuffer(geometry, fillShader);
- }
- for (const buff of this.retainedMode.buffers.user){
- const prop = geometry.model.userVertexProperties[buff.attr];
- const adjustedLength = prop.getSrcArray().length / prop.getDataSize();
- if(adjustedLength > geometry.model.vertices.length){
- p5._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.model.vertices.length){
- p5._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()');
- }
- buff._prepareBuffer(geometry, fillShader);
- }
- fillShader.disableRemainingAttributes();
- if (geometry.indexBuffer) {
- //vertex index buffer
- this._bindBuffer(geometry.indexBuffer, gl.ELEMENT_ARRAY_BUFFER);
- }
- this._applyColorBlend(
- this.states.curFillColor,
- geometry.model.hasFillTransparency()
- );
- this._drawElements(gl.TRIANGLES, gId);
- fillShader.unbindShader();
- }
-
- if (!this.geometryBuilder && this.states.doStroke && geometry.lineVertexCount > 0) {
- this._useLineColor = (geometry.model.vertexStrokeColors.length > 0);
- const strokeShader = this._getRetainedStrokeShader();
- this._setStrokeUniforms(strokeShader);
- for (const buff of this.retainedMode.buffers.stroke) {
- buff._prepareBuffer(geometry, strokeShader);
- }
- for (const buff of this.retainedMode.buffers.user){
- const prop = geometry.model.userVertexProperties[buff.attr];
- const adjustedLength = prop.getSrcArray().length / prop.getDataSize();
- if(adjustedLength > geometry.model.vertices.length){
- p5._friendlyError(`One of the geometries has a custom vertex property ${prop.name} with more values than vertices. This is probably caused by directly using the Geometry.vertexProperty() method.`, 'vertexProperty()');
- } else if(adjustedLength < geometry.model.vertices.length){
- p5._friendlyError(`One of the geometries has a custom vertex property ${prop.name} with fewer values than vertices. This is probably caused by directly using the Geometry.vertexProperty() method.`, 'vertexProperty()');
- }
- buff._prepareBuffer(geometry, strokeShader);
- }
- strokeShader.disableRemainingAttributes();
- this._applyColorBlend(
- this.states.curStrokeColor,
- geometry.model.hasStrokeTransparency()
- );
- this._drawArrays(gl.TRIANGLES, gId);
- strokeShader.unbindShader();
- }
-
- if (this.geometryBuilder) {
- this.geometryBuilder.addRetained(geometry);
- }
-
- return this;
- };
-
/**
* Calls drawBuffers() with a scaled model/view matrix.
*
@@ -219,13 +131,13 @@ function rendererGLRetained(p5, fn){
*
* @private
* @method drawBuffersScaled
- * @param {String} gId ID in our geom hash
+ * @param {String} gid ID in our geom hash
* @param {Number} scaleX the amount to scale in the X direction
* @param {Number} scaleY the amount to scale in the Y direction
* @param {Number} scaleZ the amount to scale in the Z direction
*/
RendererGL.prototype.drawBuffersScaled = function(
- gId,
+ model,
scaleX,
scaleY,
scaleZ
@@ -234,50 +146,16 @@ function rendererGLRetained(p5, fn){
try {
this.states.uModelMatrix.scale(scaleX, scaleY, scaleZ);
- this.drawBuffers(gId);
+ if (this.geometryBuilder) {
+ this.geometryBuilder.addRetained(model);
+ } else {
+ this._drawGeometry(model);
+ }
} finally {
this.states.uModelMatrix = originalModelMatrix;
}
};
- RendererGL.prototype._drawArrays = function(drawMode, gId) {
- this.GL.drawArrays(
- drawMode,
- 0,
- this.retainedMode.geometry[gId].lineVertexCount
- );
- return this;
- };
-
- RendererGL.prototype._drawElements = function(drawMode, gId) {
- const buffers = this.retainedMode.geometry[gId];
- const gl = this.GL;
- // render the fill
- if (buffers.indexBuffer) {
- // If this model is using a Uint32Array we need to ensure the
- // OES_element_index_uint WebGL extension is enabled.
- if (
- this._pInst.webglVersion !== constants.WEBGL2 &&
- buffers.indexBufferType === gl.UNSIGNED_INT
- ) {
- 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.'
- );
- }
- }
- // we're drawing faces
- gl.drawElements(
- gl.TRIANGLES,
- buffers.vertexCount,
- buffers.indexBufferType,
- 0
- );
- } else {
- // drawing vertices
- gl.drawArrays(drawMode || gl.TRIANGLES, 0, buffers.vertexCount);
- }
- };
RendererGL.prototype._drawPoints = function(vertices, vertexBuffer) {
const gl = this.GL;
diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js
index 2ba2e45cee..219d8c2d51 100644
--- a/src/webgl/p5.RendererGL.js
+++ b/src/webgl/p5.RendererGL.js
@@ -1,6 +1,5 @@
import * as constants from '../core/constants';
import GeometryBuilder from './GeometryBuilder';
-import libtess from 'libtess'; // Fixed with exporting module from libtess
import { Renderer } from '../core/p5.Renderer';
import { Matrix } from './p5.Matrix';
import { Camera } from './p5.Camera';
@@ -14,6 +13,7 @@ import { Texture, MipmapTexture } from './p5.Texture';
import { Framebuffer } from './p5.Framebuffer';
import { Graphics } from '../core/p5.Graphics';
import { Element } from '../core/p5.Element';
+import { ShapeBuilder } from './ShapeBuilder';
import lightingShader from './shaders/lighting.glsl';
import webgl2CompatibilityShader from './shaders/webgl2Compatibility.glsl';
@@ -307,64 +307,33 @@ class RendererGL extends Renderer {
this.states.userPointShader = undefined;
this.states.userImageShader = undefined;
- this._useUserVertexProperties = undefined;
-
- // Default drawing is done in Retained Mode
- // Geometry and Material hashes stored here
- this.retainedMode = {
- geometry: {},
- buffers: {
- 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)
- ],
- 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 BufferDef(3, 'vertexSpeculars', 'specularBuffer', 'aSpecularColor'),
- new RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
- ],
- text: [
- new RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray),
- new RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
- ],
- user:[]
- }
- };
-
- // Immediate Mode
- // Geometry and Material hashes stored here
- this.immediateMode = {
- geometry: new Geometry(),
- shapeMode: constants.TRIANGLE_FAN,
- contourIndices: [],
- _bezierVertex: [],
- _quadraticVertex: [],
- _curveVertex: [],
- 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, this._flatten)
- ],
- 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)
- ],
- point: this.GL.createBuffer(),
- user:[]
- }
- };
+ // Used by beginShape/endShape functions to construct a p5.Geometry
+ this.shapeBuilder = new ShapeBuilder(this);
+
+ 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, this._flatten)
+ ],
+ 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)
+ ],
+ text: [
+ new RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray),
+ new RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
+ ],
+ point: this.GL.createBuffer(),
+ user:[]
+ }
+
+ this.geometryBufferCache = {};
this.pointSize = 5.0; //default point size
this.curStrokeWeight = 1;
@@ -397,15 +366,16 @@ class RendererGL extends Renderer {
// current curveDetail in the Quadratic lookUpTable
this._lutQuadraticDetail = 0;
- // Used to distinguish between user calls to vertex() and internal calls
- this.isProcessingVertices = false;
- this._tessy = this._initTessy();
this.fontInfos = {};
this._curShader = undefined;
}
+ //////////////////////////////////////////////
+ // Geometry Building
+ //////////////////////////////////////////////
+
/**
* Starts creating a new p5.Geometry. Subsequent shapes drawn will be added
* to the geometry and then returned when
@@ -466,6 +436,234 @@ class RendererGL extends Renderer {
return this.endGeometry();
}
+
+ //////////////////////////////////////////////
+ // Shape drawing
+ //////////////////////////////////////////////
+
+ beginShape(...args) {
+ this.shapeBuilder.beginShape(...args);
+ }
+
+ endShape(
+ mode,
+ isCurve,
+ isBezier,
+ isQuadratic,
+ isContour,
+ shapeKind,
+ count = 1
+ ) {
+ this.shapeBuilder.endShape(
+ mode,
+ isCurve,
+ isBezier,
+ isQuadratic,
+ isContour,
+ shapeKind
+ );
+
+ if (this.geometryBuilder) {
+ this.geometryBuilder.addImmediate(
+ this.shapeBuilder.geometry,
+ this.shapeBuilder.shapeMode
+ );
+ } else if (this.states.doFill || this.states.doStroke) {
+ this._drawGeometry(
+ this.shapeBuilder.geometry,
+ { mode: this.shapeBuilder.shapeMode, count }
+ );
+ }
+ }
+
+ beginContour(...args) {
+ this.shapeBuilder.beginContour(...args);
+ }
+
+ vertex(...args) {
+ this.shapeBuilder.vertex(...args);
+ }
+
+ vertexProperty(...args) {
+ this.shapeBuilder.vertexProperty(...args);
+ }
+
+ normal(xorv, y, z) {
+ if (xorv instanceof Vector) {
+ this.states._currentNormal = xorv;
+ } else {
+ this.states._currentNormal = new Vector(xorv, y, z);
+ }
+ }
+
+ //////////////////////////////////////////////
+ // Rendering
+ //////////////////////////////////////////////
+
+ _drawGeometry(geometry, { mode = constants.TRIANGLES, count = 1 } = {}) {
+ if (
+ this.states.doFill &&
+ geometry.vertices.length >= 3 &&
+ ![constants.LINES, constants.POINTS].includes(mode)
+ ) {
+ this._drawFills(geometry, { mode, count });
+ }
+
+ if (this.states.doStroke && geometry.lineVertices.length >= 1) {
+ this._drawStrokes(geometry, { count });
+ }
+
+ this.buffers.user = [];
+ }
+
+ _drawFills(geometry, { count, mode } = {}) {
+ this._useVertexColor = geometry.vertexColors.length > 0;
+
+ const shader = this._drawingFilter && this.states.userFillShader
+ ? this.states.userFillShader
+ : this._getFillShader();
+ this._setFillUniforms(shader);
+
+ for (const buff of this.buffers.fill) {
+ buff._prepareBuffer(geometry, shader);
+ }
+ this._prepareUserAttributes(geometry, shader);
+ shader.disableRemainingAttributes();
+
+ this._applyColorBlend(
+ this.states.curFillColor,
+ geometry.hasFillTransparency()
+ );
+
+ this._drawBuffers(geometry, { mode, count });
+
+ shader.unbindShader();
+ }
+
+ _drawStrokes(geometry, { count } = {}) {
+ const gl = this.GL;
+
+ this._useLineColor = geometry.vertexStrokeColors.length > 0;
+
+ const shader = this._getStrokeShader();
+ this._setStrokeUniforms(shader);
+
+ for (const buff of this.buffers.stroke) {
+ buff._prepareBuffer(geometry, shader);
+ }
+ this._prepareUserAttributes(geometry, shader);
+ shader.disableRemainingAttributes();
+
+ this._applyColorBlend(
+ this.states.curStrokeColor,
+ geometry.hasStrokeTransparency()
+ );
+
+ if (count === 1) {
+ gl.drawArrays(
+ gl.TRIANGLES,
+ 0,
+ geometry.lineVertices.length / 3
+ );
+ } else {
+ try {
+ gl.drawArraysInstanced(
+ gl.TRIANGLES,
+ 0,
+ geometry.lineVertices.length / 3,
+ count
+ );
+ } catch (e) {
+ console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode');
+ }
+ }
+
+ shader.unbindShader();
+ }
+
+ _prepareUserAttributes(geometry, shader) {
+ for (const buff of this.buffers.user) {
+ // Check for the right data size
+ const prop = geometry.userVertexProperties[buff.attr];
+ if (prop) {
+ const adjustedLength = prop.getSrcArray().length / prop.getDataSize();
+ if (adjustedLength > geometry.vertices.length) {
+ p5._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) {
+ p5._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()');
+ }
+ }
+ buff._prepareBuffer(geometry, shader);
+ }
+ }
+
+ _drawBuffers(geometry, { mode = this.GL.TRIANGLES, count }) {
+ const gl = this.GL;
+ const glBuffers = this.geometryBufferCache[geometry.gid];
+
+ if (glBuffers?.indexBuffer) {
+ // If this model is using a Uint32Array we need to ensure the
+ // OES_element_index_uint WebGL extension is enabled.
+ if (
+ this._pInst.webglVersion !== constants.WEBGL2 &&
+ glBuffers.indexBufferType === gl.UNSIGNED_INT
+ ) {
+ 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.'
+ );
+ }
+ }
+
+ if (count === 1) {
+ gl.drawElements(
+ gl.TRIANGLES,
+ glBuffers.vertexCount,
+ glBuffers.indexBufferType,
+ 0
+ );
+ } else {
+ try {
+ gl.drawElementsInstanced(
+ gl.TRIANGLES,
+ glBuffers.vertexCount,
+ glBuffers.indexBufferType,
+ 0,
+ count
+ );
+ } catch (e) {
+ console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode');
+ }
+ }
+ } else {
+ if (count === 1) {
+ gl.drawArrays(
+ mode,
+ 0,
+ geometry.vertices.length
+ );
+ } else {
+ try {
+ gl.drawArraysInstanced(
+ mode,
+ 0,
+ geometry.vertices.length,
+ count
+ );
+ } catch (e) {
+ console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode');
+ }
+ }
+ }
+ }
+
+ _getOrMakeCachedBuffers(geometry) {
+ if (!this.geometryInHash(geometry.gid)) {
+ this.createBuffers(geometry);
+ }
+ return this.geometryBufferCache[geometry.gid]
+ }
+
//////////////////////////////////////////////
// Setting
//////////////////////////////////////////////
@@ -1167,8 +1365,8 @@ class RendererGL extends Renderer {
// HASH | for geometry
//////////////////////////////////////////////
- geometryInHash(gId) {
- return this.retainedMode.geometry[gId] !== undefined;
+ geometryInHash(gid) {
+ return this.geometryBufferCache[gid] !== undefined;
}
viewport(w, h) {
@@ -1390,7 +1588,7 @@ class RendererGL extends Renderer {
* and the shader must be valid in that context.
*/
- _getImmediateStrokeShader() {
+ _getStrokeShader() {
// select the stroke shader to use
const stroke = this.states.userStrokeShader;
if (stroke) {
@@ -1400,10 +1598,6 @@ class RendererGL extends Renderer {
}
- _getRetainedStrokeShader() {
- return this._getImmediateStrokeShader();
- }
-
_getSphereMapping(img) {
if (!this.sphereMapping) {
this.sphereMapping = this._pInst.createFilterShader(
@@ -1460,10 +1654,6 @@ class RendererGL extends Renderer {
return point;
}
- _getRetainedLineShader() {
- return this._getImmediateLineShader();
- }
-
baseMaterialShader() {
if (!this._pInst._glAttributes.perPixelLighting) {
throw new Error(
@@ -1858,15 +2048,15 @@ class RendererGL extends Renderer {
return new Framebuffer(this, options);
}
- _setStrokeUniforms(baseStrokeShader) {
- baseStrokeShader.bindShader();
+ _setStrokeUniforms(strokeShader) {
+ strokeShader.bindShader();
// set the uniform values
- baseStrokeShader.setUniform('uUseLineColor', this._useLineColor);
- baseStrokeShader.setUniform('uMaterialColor', this.states.curStrokeColor);
- baseStrokeShader.setUniform('uStrokeWeight', this.curStrokeWeight);
- baseStrokeShader.setUniform('uStrokeCap', STROKE_CAP_ENUM[this.curStrokeCap]);
- baseStrokeShader.setUniform('uStrokeJoin', STROKE_JOIN_ENUM[this.curStrokeJoin]);
+ strokeShader.setUniform('uUseLineColor', this._useLineColor);
+ strokeShader.setUniform('uMaterialColor', this.states.curStrokeColor);
+ strokeShader.setUniform('uStrokeWeight', this.curStrokeWeight);
+ strokeShader.setUniform('uStrokeCap', STROKE_CAP_ENUM[this.curStrokeCap]);
+ strokeShader.setUniform('uStrokeJoin', STROKE_JOIN_ENUM[this.curStrokeJoin]);
}
_setFillUniforms(fillShader) {
@@ -2081,106 +2271,7 @@ class RendererGL extends Renderer {
const p = [p1, p2, p3, p4];
return p;
}
- _initTessy() {
- this.tessyVertexSize = 12;
- // function called for each vertex of tesselator output
- function vertexCallback(data, polyVertArray) {
- for (const element of data) {
- polyVertArray.push(element);
- }
- }
-
- function begincallback(type) {
- if (type !== libtess.primitiveType.GL_TRIANGLES) {
- console.log(`expected TRIANGLES but got type: ${type}`);
- }
- }
- function errorcallback(errno) {
- console.log('error callback');
- console.log(`error number: ${errno}`);
- }
- // callback for when segments intersect and must be split
- const combinecallback = (coords, data, weight) => {
- const result = new Array(this.tessyVertexSize).fill(0);
- for (let i = 0; i < weight.length; i++) {
- for (let j = 0; j < result.length; j++) {
- if (weight[i] === 0 || !data[i]) continue;
- result[j] += data[i][j] * weight[i];
- }
- }
- return result;
- };
-
- function edgeCallback(flag) {
- // don't really care about the flag, but need no-strip/no-fan behavior
- }
-
- const tessy = new libtess.GluTesselator();
- tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_VERTEX_DATA, vertexCallback);
- tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_BEGIN, begincallback);
- tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_ERROR, errorcallback);
- tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_COMBINE, combinecallback);
- tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_EDGE_FLAG, edgeCallback);
- tessy.gluTessProperty(
- libtess.gluEnum.GLU_TESS_WINDING_RULE,
- libtess.windingRule.GLU_TESS_WINDING_NONZERO
- );
-
- return tessy;
- }
-
- _triangulate(contours) {
- // libtess will take 3d verts and flatten to a plane for tesselation.
- // libtess is capable of calculating a plane to tesselate on, but
- // if all of the vertices have the same z values, we'll just
- // assume the face is facing the camera, letting us skip any performance
- // issues or bugs in libtess's automatic calculation.
- const z = contours[0] ? contours[0][2] : undefined;
- let allSameZ = true;
- for (const contour of contours) {
- for (
- let j = 0;
- j < contour.length;
- j += this.tessyVertexSize
- ) {
- if (contour[j + 2] !== z) {
- allSameZ = false;
- break;
- }
- }
- }
- if (allSameZ) {
- this._tessy.gluTessNormal(0, 0, 1);
- } else {
- // Let libtess pick a plane for us
- this._tessy.gluTessNormal(0, 0, 0);
- }
-
- const triangleVerts = [];
- this._tessy.gluTessBeginPolygon(triangleVerts);
-
- for (const contour of contours) {
- this._tessy.gluTessBeginContour();
- for (
- let j = 0;
- j < contour.length;
- j += this.tessyVertexSize
- ) {
- const coords = contour.slice(
- j,
- j + this.tessyVertexSize
- );
- this._tessy.gluTessVertex(coords, coords);
- }
- this._tessy.gluTessEndContour();
- }
-
- // finish polygon
- this._tessy.gluTessEndPolygon();
-
- return triangleVerts;
- }
};
function rendererGL(p5, fn){
@@ -2372,8 +2463,8 @@ function rendererGL(p5, fn){
}
if (!this._setupDone) {
- for (const x in this._renderer.retainedMode.geometry) {
- if (this._renderer.retainedMode.geometry.hasOwnProperty(x)) {
+ for (const x in this._renderer.geometryBufferCache) {
+ if (this._renderer.geometryBufferCache.hasOwnProperty(x)) {
p5._friendlyError(
'Sorry, Could not set the attributes, you need to call setAttributes() ' +
'before calling the other drawing methods in setup()'
diff --git a/test/unit/core/rendering.js b/test/unit/core/rendering.js
index b2c9b81834..9f0fa44762 100644
--- a/test/unit/core/rendering.js
+++ b/test/unit/core/rendering.js
@@ -116,8 +116,10 @@ suite('Rendering', function() {
glStub = vi.spyOn(p5.RendererGL.prototype, '_getMaxTextureSize');
const fakeMaxTextureSize = 100;
glStub.mockReturnValue(fakeMaxTextureSize);
+ const prevRatio = window.devicePixelRatio;
+ window.devicePixelRatio = 1;
myp5.createCanvas(200, 200, myp5.WEBGL);
- myp5.pixelDensity(1);
+ window.devicePixelRatio = prevRatio;
assert.equal(myp5.width, 100);
assert.equal(myp5.height, 100);
});
diff --git a/test/unit/webgl/p5.Framebuffer.js b/test/unit/webgl/p5.Framebuffer.js
index cbf78d07f4..9dfe186ff1 100644
--- a/test/unit/webgl/p5.Framebuffer.js
+++ b/test/unit/webgl/p5.Framebuffer.js
@@ -3,8 +3,11 @@ import { vi } from 'vitest';
suite('p5.Framebuffer', function() {
let myp5;
+ let prevPixelRatio;
beforeAll(function() {
+ prevPixelRatio = window.devicePixelRatio;
+ window.devicePixelRatio = 1;
myp5 = new p5(function(p) {
p.setup = function() {};
p.draw = function() {};
@@ -13,6 +16,7 @@ suite('p5.Framebuffer', function() {
afterAll(function() {
myp5.remove();
+ window.devicePixelRatio = prevPixelRatio;
});
suite('formats and channels', function() {
diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js
index bb3c39e115..9ae68a70eb 100644
--- a/test/unit/webgl/p5.RendererGL.js
+++ b/test/unit/webgl/p5.RendererGL.js
@@ -1320,7 +1320,7 @@ suite('p5.RendererGL', function() {
myp5.stroke(255);
myp5.triangle(0, 0, 1, 0, 0, 1);
- var buffers = renderer.retainedMode.geometry['tri'];
+ var buffers = renderer.geometryBufferCache['tri'];
assert.isObject(buffers);
assert.isDefined(buffers.indexBuffer);
@@ -1442,13 +1442,13 @@ suite('p5.RendererGL', function() {
[3, 1, 7]
];
assert.equal(
- renderer.immediateMode.geometry.vertices.length,
+ renderer.shapeBuilder.geometry.vertices.length,
expectedVerts.length
);
expectedVerts.forEach(function([x, y, z], i) {
- assert.equal(renderer.immediateMode.geometry.vertices[i].x, x);
- assert.equal(renderer.immediateMode.geometry.vertices[i].y, y);
- assert.equal(renderer.immediateMode.geometry.vertices[i].z, z);
+ assert.equal(renderer.shapeBuilder.geometry.vertices[i].x, x);
+ assert.equal(renderer.shapeBuilder.geometry.vertices[i].y, y);
+ assert.equal(renderer.shapeBuilder.geometry.vertices[i].z, z);
});
const expectedUVs = [
@@ -1468,7 +1468,7 @@ suite('p5.RendererGL', function() {
[1, 0],
[1, 1]
].flat();
- assert.deepEqual(renderer.immediateMode.geometry.uvs, expectedUVs);
+ assert.deepEqual(renderer.shapeBuilder.geometry.uvs, expectedUVs);
const expectedColors = [
[1, 0, 0, 1],
@@ -1488,7 +1488,7 @@ suite('p5.RendererGL', function() {
[1, 0, 1, 1]
].flat();
assert.deepEqual(
- renderer.immediateMode.geometry.vertexColors,
+ renderer.shapeBuilder.geometry.vertexColors,
expectedColors
);
@@ -1510,13 +1510,13 @@ suite('p5.RendererGL', function() {
[21, 22, 23]
];
assert.equal(
- renderer.immediateMode.geometry.vertexNormals.length,
+ renderer.shapeBuilder.geometry.vertexNormals.length,
expectedNormals.length
);
expectedNormals.forEach(function([x, y, z], i) {
- assert.equal(renderer.immediateMode.geometry.vertexNormals[i].x, x);
- assert.equal(renderer.immediateMode.geometry.vertexNormals[i].y, y);
- assert.equal(renderer.immediateMode.geometry.vertexNormals[i].z, z);
+ assert.equal(renderer.shapeBuilder.geometry.vertexNormals[i].x, x);
+ assert.equal(renderer.shapeBuilder.geometry.vertexNormals[i].y, y);
+ assert.equal(renderer.shapeBuilder.geometry.vertexNormals[i].z, z);
});
});
@@ -1534,7 +1534,7 @@ suite('p5.RendererGL', function() {
renderer.vertex(3, 1);
renderer.endShape();
- assert.equal(renderer.immediateMode.geometry.edges.length, 8);
+ assert.equal(renderer.shapeBuilder.geometry.edges.length, 8);
});
test('QUAD_STRIP mode makes edges for strip outlines', function() {
@@ -1551,7 +1551,7 @@ suite('p5.RendererGL', function() {
renderer.endShape();
// Two full quads (2 * 4) plus two edges connecting them
- assert.equal(renderer.immediateMode.geometry.edges.length, 10);
+ assert.equal(renderer.shapeBuilder.geometry.edges.length, 10);
});
test('TRIANGLE_FAN mode makes edges for each triangle', function() {
@@ -1569,7 +1569,7 @@ suite('p5.RendererGL', function() {
renderer.vertex(-5, 0);
renderer.endShape();
- assert.equal(renderer.immediateMode.geometry.edges.length, 7);
+ assert.equal(renderer.shapeBuilder.geometry.edges.length, 7);
});
test('TESS preserves vertex data', function() {
@@ -1595,59 +1595,59 @@ suite('p5.RendererGL', function() {
renderer.vertex(-10, 10, 0, 1);
renderer.endShape(myp5.CLOSE);
- assert.equal(renderer.immediateMode.geometry.vertices.length, 6);
+ assert.equal(renderer.shapeBuilder.geometry.vertices.length, 6);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[0].array(),
+ renderer.shapeBuilder.geometry.vertices[0].array(),
[10, -10, 0]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[1].array(),
+ renderer.shapeBuilder.geometry.vertices[1].array(),
[-10, 10, 0]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[2].array(),
+ renderer.shapeBuilder.geometry.vertices[2].array(),
[-10, -10, 0]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[3].array(),
+ renderer.shapeBuilder.geometry.vertices[3].array(),
[-10, 10, 0]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[4].array(),
+ renderer.shapeBuilder.geometry.vertices[4].array(),
[10, -10, 0]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[5].array(),
+ renderer.shapeBuilder.geometry.vertices[5].array(),
[10, 10, 0]
);
- assert.equal(renderer.immediateMode.geometry.vertexNormals.length, 6);
+ assert.equal(renderer.shapeBuilder.geometry.vertexNormals.length, 6);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[0].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[0].array(),
[1, -1, 1]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[1].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[1].array(),
[-1, 1, 1]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[2].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[2].array(),
[-1, -1, 1]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[3].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[3].array(),
[-1, 1, 1]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[4].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[4].array(),
[1, -1, 1]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[5].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[5].array(),
[1, 1, 1]
);
- assert.deepEqual(renderer.immediateMode.geometry.aCustomSrc, [
+ assert.deepEqual(renderer.shapeBuilder.geometry.aCustomSrc, [
1, 0, 0,
0, 0, 1,
1, 1, 1,
@@ -1656,7 +1656,7 @@ suite('p5.RendererGL', function() {
0, 1, 0
]);
- assert.deepEqual(renderer.immediateMode.geometry.vertexColors, [
+ assert.deepEqual(renderer.shapeBuilder.geometry.vertexColors, [
1, 0, 0, 1,
0, 0, 1, 1,
1, 1, 1, 1,
@@ -1665,7 +1665,7 @@ suite('p5.RendererGL', function() {
0, 1, 0, 1
]);
- assert.deepEqual(renderer.immediateMode.geometry.uvs, [
+ assert.deepEqual(renderer.shapeBuilder.geometry.uvs, [
1, 0,
0, 1,
0, 0,
@@ -1692,7 +1692,7 @@ suite('p5.RendererGL', function() {
renderer.endShape(myp5.CLOSE);
// Vertex colors are not run through tessy
- assert.deepEqual(renderer.immediateMode.geometry.vertexStrokeColors, [
+ assert.deepEqual(renderer.shapeBuilder.geometry.vertexStrokeColors, [
1, 1, 1, 1,
1, 0, 0, 1,
0, 1, 0, 1,
@@ -1715,7 +1715,7 @@ suite('p5.RendererGL', function() {
renderer.endShape(myp5.CLOSE);
// UVs are correctly translated through tessy
- assert.deepEqual(renderer.immediateMode.geometry.uvs, [
+ assert.deepEqual(renderer.shapeBuilder.geometry.uvs, [
0, 0,
1, 0,
1, 1,
@@ -1752,59 +1752,59 @@ suite('p5.RendererGL', function() {
renderer.vertex(-10, 10, 0, 1);
renderer.endShape(myp5.CLOSE);
- assert.equal(renderer.immediateMode.geometry.vertices.length, 6);
+ assert.equal(renderer.shapeBuilder.geometry.vertices.length, 6);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[0].array(),
+ renderer.shapeBuilder.geometry.vertices[0].array(),
[0, 0, 0]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[1].array(),
+ renderer.shapeBuilder.geometry.vertices[1].array(),
[-10, 10, 0]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[2].array(),
+ renderer.shapeBuilder.geometry.vertices[2].array(),
[-10, -10, 0]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[3].array(),
+ renderer.shapeBuilder.geometry.vertices[3].array(),
[10, 10, 0]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[4].array(),
+ renderer.shapeBuilder.geometry.vertices[4].array(),
[0, 0, 0]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[5].array(),
+ renderer.shapeBuilder.geometry.vertices[5].array(),
[10, -10, 0]
);
- assert.equal(renderer.immediateMode.geometry.vertexNormals.length, 6);
+ assert.equal(renderer.shapeBuilder.geometry.vertexNormals.length, 6);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[0].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[0].array(),
[0, 0, 1]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[1].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[1].array(),
[-1, 1, 1]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[2].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[2].array(),
[-1, -1, 1]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[3].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[3].array(),
[1, 1, 1]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[4].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[4].array(),
[0, 0, 1]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertexNormals[5].array(),
+ renderer.shapeBuilder.geometry.vertexNormals[5].array(),
[1, -1, 1]
);
- assert.deepEqual(renderer.immediateMode.geometry.vertexColors, [
+ assert.deepEqual(renderer.shapeBuilder.geometry.vertexColors, [
0.5, 0.5, 0.5, 1,
0, 0, 1, 1,
1, 1, 1, 1,
@@ -1813,7 +1813,7 @@ suite('p5.RendererGL', function() {
1, 0, 0, 1
]);
- assert.deepEqual(renderer.immediateMode.geometry.uvs, [
+ assert.deepEqual(renderer.shapeBuilder.geometry.uvs, [
0.5, 0.5,
0, 1,
0, 0,
@@ -1834,29 +1834,29 @@ suite('p5.RendererGL', function() {
renderer.vertex(-10, 0, 10);
renderer.endShape(myp5.CLOSE);
- assert.equal(renderer.immediateMode.geometry.vertices.length, 6);
+ assert.equal(renderer.shapeBuilder.geometry.vertices.length, 6);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[0].array(),
+ renderer.shapeBuilder.geometry.vertices[0].array(),
[10, 0, 10]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[1].array(),
+ renderer.shapeBuilder.geometry.vertices[1].array(),
[-10, 0, -10]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[2].array(),
+ renderer.shapeBuilder.geometry.vertices[2].array(),
[10, 0, -10]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[3].array(),
+ renderer.shapeBuilder.geometry.vertices[3].array(),
[-10, 0, -10]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[4].array(),
+ renderer.shapeBuilder.geometry.vertices[4].array(),
[10, 0, 10]
);
assert.deepEqual(
- renderer.immediateMode.geometry.vertices[5].array(),
+ renderer.shapeBuilder.geometry.vertices[5].array(),
[-10, 0, 10]
);
});
@@ -2515,19 +2515,19 @@ suite('p5.RendererGL', function() {
myp5.vertexProperty('aCustom', 1);
myp5.vertexProperty('aCustomVec3', [1, 2, 3]);
myp5.vertex(0,0,0);
- expect(myp5._renderer.immediateMode.geometry.userVertexProperties.aCustom).to.containSubset({
+ expect(myp5._renderer.shapeBuilder.geometry.userVertexProperties.aCustom).to.containSubset({
name: 'aCustom',
currentData: 1,
dataSize: 1
});
- expect(myp5._renderer.immediateMode.geometry.userVertexProperties.aCustomVec3).to.containSubset({
+ expect(myp5._renderer.shapeBuilder.geometry.userVertexProperties.aCustomVec3).to.containSubset({
name: 'aCustomVec3',
currentData: [1, 2, 3],
dataSize: 3
});
- assert.deepEqual(myp5._renderer.immediateMode.geometry.aCustomSrc, [1]);
- assert.deepEqual(myp5._renderer.immediateMode.geometry.aCustomVec3Src, [1,2,3]);
- expect(myp5._renderer.immediateMode.buffers.user).to.containSubset([
+ 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',
@@ -2555,10 +2555,10 @@ suite('p5.RendererGL', function() {
myp5.endShape();
myp5.beginShape();
- assert.isUndefined(myp5._renderer.immediateMode.geometry.aCustomSrc);
- assert.isUndefined(myp5._renderer.immediateMode.geometry.aCustomVec3Src);
- assert.deepEqual(myp5._renderer.immediateMode.geometry.userVertexProperties, {});
- assert.deepEqual(myp5._renderer.immediateMode.buffers.user, []);
+ 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();
}
);
@@ -2572,7 +2572,7 @@ suite('p5.RendererGL', function() {
myp5.vertex(0,1,0);
myp5.vertex(-1,0,0);
myp5.vertex(1,0,0);
- const immediateCopy = myp5._renderer.immediateMode.geometry;
+ const immediateCopy = myp5._renderer.shapeBuilder.geometry;
myp5.endShape();
const myGeo = myp5.endGeometry();
assert.deepEqual(immediateCopy.aCustomSrc, myGeo.aCustomSrc);
@@ -2590,8 +2590,8 @@ suite('p5.RendererGL', function() {
myp5.vertex(1,0,0);
myp5.endShape();
const myGeo = myp5.endGeometry();
- myp5._renderer.createBuffers(myGeo.gId, myGeo);
- expect(myp5._renderer.retainedMode.buffers.user).to.containSubset([
+ myp5._renderer.createBuffers(myGeo);
+ expect(myp5._renderer.buffers.user).to.containSubset([
{
size: 1,
src: 'aCustomSrc',
@@ -2607,7 +2607,7 @@ suite('p5.RendererGL', function() {
]);
}
);
- test('Retained mode buffers deleted after rendering',
+ test.only('Retained mode buffers deleted after rendering',
function() {
myp5.createCanvas(50, 50, myp5.WEBGL);
myp5.beginGeometry();
@@ -2616,10 +2616,11 @@ suite('p5.RendererGL', function() {
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();
myp5.model(myGeo);
- assert.equal(myp5._renderer.retainedMode.buffers.user.length, 0);
+ assert.equal(myp5._renderer.buffers.user.length, 0);
}
);
test.skip('Friendly error if different sizes used',
@@ -2647,6 +2648,7 @@ suite('p5.RendererGL', function() {
const oldLog = console.log;
console.log = myLog;
let myGeo = new p5.Geometry();
+ myGeo.gid = 'myGeo';
myGeo.vertices.push(new p5.Vector(0,0,0));
myGeo.vertexProperty('aCustom', 1);
myGeo.vertexProperty('aCustom', 2);
@@ -2663,6 +2665,7 @@ suite('p5.RendererGL', function() {
const oldLog = console.log;
console.log = myLog;
let myGeo = new p5.Geometry();
+ myGeo.gid = 'myGeo';
myGeo.vertices.push(new p5.Vector(0,0,0));
myGeo.vertices.push(new p5.Vector(0,0,0));
myGeo.vertexProperty('aCustom', 1);
diff --git a/test/unit/webgl/p5.Texture.js b/test/unit/webgl/p5.Texture.js
index 2c0ba6f36d..4d05ec280c 100644
--- a/test/unit/webgl/p5.Texture.js
+++ b/test/unit/webgl/p5.Texture.js
@@ -8,8 +8,11 @@ suite('p5.Texture', function() {
var imgElementNotPowerOfTwo;
var imgElementPowerOfTwo;
var canvas;
+ let prevPixelRatio;
beforeEach(function() {
+ prevPixelRatio = window.devicePixelRatio;
+ window.devicePixelRatio = 1;
return new Promise(done => {
myp5 = new p5(function(p) {
p.setup = async function() {
@@ -42,6 +45,7 @@ suite('p5.Texture', function() {
});
afterEach(function() {
+ window.devicePixelRatio = prevPixelRatio;
myp5.remove();
});