Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion src/webgl/p5.RendererGL.Immediate.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import './p5.RenderBuffer';
* @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)
* TRIANGLE_STRIP, TRIANGLE_FAN, QUADS, QUAD_STRIP,
* and TESS(WEBGL only)
* @chainable
*/
p5.RendererGL.prototype.beginShape = function(mode) {
Expand All @@ -36,6 +37,13 @@ p5.RendererGL.prototype.beginShape = function(mode) {
return this;
};

const immediateBufferStrides = {
vertices: 1,
vertexNormals: 1,
vertexColors: 4,
uvs: 2
};

/**
* adds a vertex to be drawn in a custom Shape.
* @private
Expand All @@ -47,6 +55,29 @@ p5.RendererGL.prototype.beginShape = function(mode) {
* @TODO implement handling of <a href="#/p5.Vector">p5.Vector</a> args
*/
p5.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--2 0--2 3
// | | --> | / / |
// 1--3 1 4--5
// When vertex index 3 is being added, add the necessary duplicates.
if (this.immediateMode.geometry.vertices.length % 6 === 3) {
for (const key in immediateBufferStrides) {
const stride = immediateBufferStrides[key];
const buffer = this.immediateMode.geometry[key];
buffer.push(
...buffer.slice(buffer.length - stride, buffer.length),
...buffer.slice(buffer.length - 2 * stride, buffer.length - stride)
);
}
}
}

let z, u, v;

// default to (x, y) mode: all other arguments assumed to be 0.
Expand Down Expand Up @@ -233,6 +264,29 @@ p5.RendererGL.prototype._calculateEdges = function(
res.push([i, i + 1]);
}
break;
case constants.QUADS:
// Quads have been broken up into two triangles by `vertex()`:
// 0---2 3
// | / / |
// 1 4---5
for (i = 0; i < verts.length - 5; i += 6) {
res.push([i, i + 1]);
res.push([i, i + 2]);
res.push([i + 4, i + 5]);
res.push([i + 3, 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:
for (i = 0; i < verts.length - 1; i++) {
res.push([i, i + 1]);
Expand Down Expand Up @@ -289,6 +343,15 @@ p5.RendererGL.prototype._drawImmediateFill = function() {
this.immediateMode.shapeMode = constants.TRIANGLE_FAN;
}

// WebGL 1 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;
}

this._applyColorBlend(this.curFillColor);
gl.drawArrays(
this.immediateMode.shapeMode,
Expand Down
25 changes: 25 additions & 0 deletions test/manual-test-examples/webgl/geometryQuads/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<link rel="stylesheet" href="../../styles.css">
<script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
<script language="javascript" type="text/javascript" src="sketch.js"></script>
<script src="../stats.js"></script>
</head>

<body>
<div id="gl-info">
Left: A solid rectangle broken up into triangles<br>
<em>TESTS: TRIANGLE_STRIP</em><br><br>
Center: Three rectangles<br>
<em> TESTS: QUADS</em><br><br>
Right: A solid rectangle broken up into quads<br>
<em>TESTS: QUAD_STRIP</em>
</div>
</body>

</html>
48 changes: 48 additions & 0 deletions test/manual-test-examples/webgl/geometryQuads/sketch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
let angle, px, py;
let img;
const sz = 25;

function preload() {
img = loadImage('../assets/UV_Grid_Sm.jpg');
}

function setup() {
createCanvas(600, 600, WEBGL);
setAttributes('antialias', true);
fill(63, 81, 181);
strokeWeight(2);
noLoop();
}

function draw() {
background(250);

// Reference: TRIANGLE_STRIP
push();
translate(-width / 4, -250);
drawQuads(TRIANGLE_STRIP);
pop();

// Test 1: QUADS
push();
translate(0, -250);
drawQuads(QUADS);
pop();

// Test 2: QUAD_STRIP
push();
translate(width / 4, -250);
drawQuads(QUAD_STRIP);
pop();
}

function drawQuads(mode) {
beginShape(mode);
for (let y = 0; y <= 500; y += 100) {
for (const side of [-1, 1]) {
fill(random(255), random(255), random(255));
vertex(side * 40, y);
}
}
endShape();
}
80 changes: 80 additions & 0 deletions test/unit/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -522,4 +522,84 @@ suite('p5.RendererGL', function() {
});
});
});

suite('beginShape() in WEBGL mode', function() {
test('QUADS mode converts into triangles', function(done) {
var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
renderer.beginShape(myp5.QUADS);
renderer.vertex(0, 0);
renderer.vertex(0, 1);
renderer.vertex(1, 0);
renderer.vertex(1, 1);

renderer.vertex(2, 0);
renderer.vertex(2, 1);
renderer.vertex(3, 0);
renderer.vertex(3, 1);
renderer.endShape();

const expected = [
[0, 0],
[0, 1],
[1, 0],

[1, 0],
[0, 1],
[1, 1],

[2, 0],
[2, 1],
[3, 0],

[3, 0],
[2, 1],
[3, 1]
];
assert.equal(
renderer.immediateMode.geometry.vertices.length,
expected.length
);
expected.forEach(function([x, y], i) {
assert.equal(renderer.immediateMode.geometry.vertices[i].x, x);
assert.equal(renderer.immediateMode.geometry.vertices[i].y, y);
});
done();
});

test('QUADS mode makes edges for quad outlines', function(done) {
var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
renderer.beginShape(myp5.QUADS);
renderer.vertex(0, 0);
renderer.vertex(0, 1);
renderer.vertex(1, 0);
renderer.vertex(1, 1);

renderer.vertex(2, 0);
renderer.vertex(2, 1);
renderer.vertex(3, 0);
renderer.vertex(3, 1);
renderer.endShape();

assert.equal(renderer.immediateMode.geometry.edges.length, 8);
done();
});

test('QUAD_STRIP mode makes edges for strip outlines', function(done) {
var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
renderer.beginShape(myp5.QUAD_STRIP);
renderer.vertex(0, 0);
renderer.vertex(0, 1);
renderer.vertex(1, 0);
renderer.vertex(1, 1);
renderer.vertex(2, 0);
renderer.vertex(2, 1);
renderer.vertex(3, 0);
renderer.vertex(3, 1);
renderer.endShape();

// Two full quads (2 * 4) plus two edges connecting them
assert.equal(renderer.immediateMode.geometry.edges.length, 10);
done();
});
});
});