diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 0d5535cb01..94e4dfdf2f 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -1600,7 +1600,7 @@ p5.RendererGL.prototype.bezierVertex = function(...args) { // Do the same for strokeColor. const strokeColors = []; for (m = 0; m < 4; m++) strokeColors.push([]); - strokeColors[0] = this.immediateMode.geometry.lineVertexColors.slice(-4); + strokeColors[0] = this.immediateMode.geometry.vertexStrokeColors.slice(-4); strokeColors[3] = this.curStrokeColor.slice(); if (argLength === 6) { @@ -1760,7 +1760,7 @@ p5.RendererGL.prototype.quadraticVertex = function(...args) { // Do the same for strokeColor. const strokeColors = []; for (m = 0; m < 3; m++) strokeColors.push([]); - strokeColors[0] = this.immediateMode.geometry.lineVertexColors.slice(-4); + strokeColors[0] = this.immediateMode.geometry.vertexStrokeColors.slice(-4); strokeColors[2] = this.curStrokeColor.slice(); if (argLength === 4) { diff --git a/src/webgl/p5.Geometry.js b/src/webgl/p5.Geometry.js index 3571761a17..7c615c3e2f 100644 --- a/src/webgl/p5.Geometry.js +++ b/src/webgl/p5.Geometry.js @@ -45,6 +45,12 @@ p5.Geometry = function(detailX, detailY, callback) { //based on faces for most objects; this.edges = []; this.vertexColors = []; + + // One color per vertex representing the stroke color at that vertex + this.vertexStrokeColors = []; + + // One color per line vertex, generated automatically based on + // vertexStrokeColors in _edgesToVertices() this.lineVertexColors = []; this.detailX = detailX !== undefined ? detailX : 1; this.detailY = detailY !== undefined ? detailY : 1; @@ -63,6 +69,7 @@ p5.Geometry.prototype.reset = function() { this.vertices.length = 0; this.edges.length = 0; this.vertexColors.length = 0; + this.vertexStrokeColors.length = 0; this.lineVertexColors.length = 0; this.vertexNormals.length = 0; this.uvs.length = 0; @@ -247,8 +254,20 @@ p5.Geometry.prototype._edgesToVertices = function() { for (let i = 0; i < this.edges.length; i++) { const endIndex0 = this.edges[i][0]; const endIndex1 = this.edges[i][1]; - var begin = this.vertices[endIndex0]; - var end = this.vertices[endIndex1]; + const begin = this.vertices[endIndex0]; + const end = this.vertices[endIndex1]; + const fromColor = this.vertexStrokeColors.length > 0 + ? this.vertexStrokeColors.slice( + endIndex0 * 4, + (endIndex0 + 1) * 4 + ) + : [0, 0, 0, 0]; + const toColor = this.vertexStrokeColors.length > 0 + ? this.vertexStrokeColors.slice( + endIndex1 * 4, + (endIndex1 + 1) * 4 + ) + : [0, 0, 0, 0]; const dir = end .copy() .sub(begin) @@ -265,23 +284,9 @@ p5.Geometry.prototype._edgesToVertices = function() { dirSub.push(-1); this.lineNormals.push(dirAdd, dirSub, dirAdd, dirAdd, dirSub, dirSub); this.lineVertices.push(a, b, c, c, b, d); - if (this.lineVertexColors.length > 0) { - var beginColor = [ - this.lineVertexColors[4*endIndex0], - this.lineVertexColors[4*endIndex0+1], - this.lineVertexColors[4*endIndex0+2], - this.lineVertexColors[4*endIndex0+3] - ]; - var endColor = [ - this.lineVertexColors[4*endIndex1], - this.lineVertexColors[4*endIndex1+1], - this.lineVertexColors[4*endIndex1+2], - this.lineVertexColors[4*endIndex1+3] - ]; - lineColorData.push( - beginColor, beginColor, endColor, endColor, beginColor, endColor - ); - } + lineColorData.push( + fromColor, fromColor, toColor, toColor, fromColor, toColor + ); } this.lineVertexColors = lineColorData; return this; diff --git a/src/webgl/p5.RendererGL.Immediate.js b/src/webgl/p5.RendererGL.Immediate.js index 9da349f494..2eac808a8a 100644 --- a/src/webgl/p5.RendererGL.Immediate.js +++ b/src/webgl/p5.RendererGL.Immediate.js @@ -41,6 +41,7 @@ const immediateBufferStrides = { vertices: 1, vertexNormals: 1, vertexColors: 4, + vertexStrokeColors: 4, uvs: 2 }; @@ -110,7 +111,7 @@ p5.RendererGL.prototype.vertex = function(x, y) { vertexColor[3] ); var lineVertexColor = this.curStrokeColor || [0.5, 0.5, 0.5, 1]; - this.immediateMode.geometry.lineVertexColors.push( + this.immediateMode.geometry.vertexStrokeColors.push( lineVertexColor[0], lineVertexColor[1], lineVertexColor[2], @@ -239,6 +240,7 @@ p5.RendererGL.prototype._processVertices = function(mode) { const convexShape = this.immediateMode.shapeMode === constants.TESS; // We tesselate when drawing curves or convex shapes const shouldTess = + this._doFill && (this.isBezier || this.isQuadratic || this.isCurve || convexShape) && this.immediateMode.shapeMode !== constants.LINES; @@ -352,7 +354,7 @@ p5.RendererGL.prototype._tesselateShape = function() { for ( let j = 0, polyTriLength = polyTriangles.length; j < polyTriLength; - j = j + 12 + j = j + p5.RendererGL.prototype.tessyVertexSize ) { colors.push(...polyTriangles.slice(j + 5, j + 9)); this.normal(...polyTriangles.slice(j + 9, j + 12)); @@ -413,7 +415,7 @@ p5.RendererGL.prototype._drawImmediateStroke = function() { const gl = this.GL; const shader = this._getImmediateStrokeShader(); this._useLineColor = - (this.immediateMode.geometry.lineVertexColors.length > 0); + (this.immediateMode.geometry.vertexStrokeColors.length > 0); this._setStrokeUniforms(shader); for (const buff of this.immediateMode.buffers.stroke) { buff._prepareBuffer(this.immediateMode.geometry, shader); diff --git a/src/webgl/p5.RendererGL.Retained.js b/src/webgl/p5.RendererGL.Retained.js index 05b9673f45..3efe28d70b 100644 --- a/src/webgl/p5.RendererGL.Retained.js +++ b/src/webgl/p5.RendererGL.Retained.js @@ -119,7 +119,7 @@ p5.RendererGL.prototype.drawBuffers = function(gId) { if (this._doStroke && geometry.lineVertexCount > 0) { const strokeShader = this._getRetainedStrokeShader(); - this._useLineColor = (geometry.model.lineVertexColors.length > 0); + this._useLineColor = (geometry.model.vertexStrokeColors.length > 0); this._setStrokeUniforms(strokeShader); for (const buff of this.retainedMode.buffers.stroke) { buff._prepareBuffer(geometry, strokeShader); diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 4cfb73cec3..1fe71400c0 100755 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1454,6 +1454,7 @@ p5.prototype._assert3d = function(name) { // function to initialize GLU Tesselator +p5.RendererGL.prototype.tessyVertexSize = 12; p5.RendererGL.prototype._initTessy = function initTesselator() { // function called for each vertex of tesselator output function vertexCallback(data, polyVertArray) { @@ -1474,7 +1475,7 @@ p5.RendererGL.prototype._initTessy = function initTesselator() { } // callback for when segments intersect and must be split function combinecallback(coords, data, weight) { - const result = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const result = new Array(p5.RendererGL.prototype.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; @@ -1507,7 +1508,11 @@ p5.RendererGL.prototype._triangulate = function(contours) { const z = contours[0] ? contours[0][2] : undefined; let allSameZ = true; for (const contour of contours) { - for (let j = 0; j < contour.length; j += 12) { + for ( + let j = 0; + j < contour.length; + j += p5.RendererGL.prototype.tessyVertexSize + ) { if (contour[j + 2] !== z) { allSameZ = false; break; @@ -1527,8 +1532,15 @@ p5.RendererGL.prototype._triangulate = function(contours) { for (let i = 0; i < contours.length; i++) { this._tessy.gluTessBeginContour(); const contour = contours[i]; - for (let j = 0; j < contour.length; j += 12) { - const coords = contour.slice(j, j + 12); + for ( + let j = 0; + j < contour.length; + j += p5.RendererGL.prototype.tessyVertexSize + ) { + const coords = contour.slice( + j, + j + p5.RendererGL.prototype.tessyVertexSize + ); this._tessy.gluTessVertex(coords, coords); } this._tessy.gluTessEndContour(); diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 2fe395c1fd..eb8a981774 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -909,6 +909,33 @@ suite('p5.RendererGL', function() { done(); }); + test('TESS does not affect stroke colors', function(done) { + var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); + + myp5.textureMode(myp5.NORMAL); + renderer.beginShape(myp5.TESS); + myp5.noFill(); + renderer.stroke(255, 255, 255); + renderer.vertex(-10, -10, 0, 0); + renderer.stroke(255, 0, 0); + renderer.vertex(10, -10, 1, 0); + renderer.stroke(0, 255, 0); + renderer.vertex(10, 10, 1, 1); + renderer.stroke(0, 0, 255); + renderer.vertex(-10, 10, 0, 1); + renderer.endShape(myp5.CLOSE); + + // Vertex colors are not run through tessy + assert.deepEqual(renderer.immediateMode.geometry.vertexStrokeColors, [ + 1, 1, 1, 1, + 1, 0, 0, 1, + 0, 1, 0, 1, + 0, 0, 1, 1 + ]); + + done(); + }); + test('TESS interpolates vertex data at intersections', function(done) { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); @@ -1050,6 +1077,9 @@ suite('p5.RendererGL', function() { done(); }); + }); + + suite('color interpolation', function() { test('strokes should interpolate colors between vertices', function(done) { const renderer = myp5.createCanvas(512, 4, myp5.WEBGL); @@ -1071,6 +1101,7 @@ suite('p5.RendererGL', function() { done(); }); + test('bezierVertex() should interpolate curFillColor', function(done) { const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); @@ -1089,6 +1120,28 @@ suite('p5.RendererGL', function() { done(); }); + + test('bezierVertex() should interpolate curStrokeColor', function(done) { + const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); + + // start color: (255, 255, 255) + // end color: (255, 0, 0) + // Intermediate values are expected to be approximately half the value. + + renderer.strokeWeight(5); + renderer.beginShape(); + myp5.noFill(); + renderer.stroke(255); + renderer.vertex(-128, -128); + renderer.stroke(255, 0, 0); + renderer.bezierVertex(128, -128, 128, 128, -128, 128); + renderer.endShape(); + + assert.deepEqual(myp5.get(190, 128), [255, 128, 128, 255]); + + done(); + }); + test('quadraticVertex() should interpolate curFillColor', function(done) { const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); @@ -1107,6 +1160,95 @@ suite('p5.RendererGL', function() { done(); }); + + test('quadraticVertex() should interpolate curStrokeColor', function(done) { + const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); + + // start color: (255, 255, 255) + // end color: (255, 0, 0) + // Intermediate values are expected to be approximately half the value. + + renderer.strokeWeight(5); + renderer.beginShape(); + myp5.noFill(); + renderer.stroke(255); + renderer.vertex(-128, -128); + renderer.stroke(255, 0, 0); + renderer.quadraticVertex(256, 0, -128, 128); + renderer.endShape(); + + assert.deepEqual(myp5.get(190, 128), [255, 128, 128, 255]); + + done(); + }); + + test('geometry without stroke colors use curStrokeColor', function(done) { + const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); + myp5.background(255); + myp5.fill(255); + myp5.stroke(0); + myp5.strokeWeight(4); + myp5.rectMode(myp5.CENTER); + myp5.rect(0, 0, myp5.width, myp5.height); + + assert.equal(renderer._useLineColor, false); + assert.deepEqual(myp5.get(128, 0), [0, 0, 0, 255]); + done(); + }); + + test('geometry with stroke colors use their colors', function(done) { + const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); + const myGeom = new p5.Geometry(1, 1, function() { + this.gid = 'strokeColorTest'; + this.vertices.push(myp5.createVector(-128, -128)); + this.vertices.push(myp5.createVector(-128, 128)); + this.vertices.push(myp5.createVector(128, 128)); + this.vertices.push(myp5.createVector(128, -128)); + this.faces.push([0, 1, 2]); + this.faces.push([0, 2, 3]); + this.edges.push([0, 1]); + this.edges.push([1, 2]); + this.edges.push([2, 3]); + this.edges.push([3, 0]); + this.vertexStrokeColors.push( + 0, 0, 0, 1, + 1, 0, 0, 1, + 0, 0, 1, 1, + 0, 1, 0, 1 + ); + this._edgesToVertices(); + }); + myp5.background(255); + myp5.fill(255); + myp5.strokeWeight(4); + myp5.stroke(0); + myp5.model(myGeom); + + assert.equal(renderer._useLineColor, true); + assert.deepEqual(myp5.get(128, 0), [127, 0, 128, 255]); + done(); + }); + + test('immediate mode uses stroke colors', function(done) { + const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); + myp5.background(255); + myp5.fill(255); + myp5.strokeWeight(4); + myp5.beginShape(); + myp5.stroke(0); + myp5.vertex(-128, -128); + myp5.stroke(255, 0, 0); + myp5.vertex(-128, 128); + myp5.stroke(0, 0, 255); + myp5.vertex(128, 128); + myp5.stroke(0, 255, 0); + myp5.vertex(128, -128); + myp5.endShape(myp5.CLOSE); + + assert.equal(renderer._useLineColor, true); + assert.deepEqual(myp5.get(128, 0), [127, 0, 128, 255]); + done(); + }); }); suite('setAttributes', function() {