Use WebGL for Polygon tool

main
Isaiah Odhner 2018-06-16 17:49:00 -04:00
parent f12039fd1e
commit e5b3c5be8b
8 changed files with 279 additions and 260 deletions

View File

@ -181,10 +181,15 @@ might be a pointer events spec interpretation issue, and it could easily be that
* Polygon
* Aliasing
* Handle self-intersecting shapes like MS Paint, with an `"evenodd"` [winding rule](https://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/)
* Line width
* Issue with extra undoables
* Close and finalize the polygon when switching to a different tool
* Don't start making the polygon until you click and drag more than the auto-finalization distance
* Cancel the polygon if you end up within the auto-finalization distance on the first gesture
* Preview invertily (like Free-Form Select) when fill-only is selected for the shape style option
* Use WebGL for the preview so strokes don't change slightly when finalizing
* Bug: jumping to 0, 0 (only saw it happen once so far; could it have to do with the dialog box?)
* Bug: unclosed polygon (last segment of stroke) (only saw it happen once so far)
* Ellipse

View File

@ -58,6 +58,7 @@
<script src="lib/palette.js"></script>
<script src="lib/FileSaver.js"></script>
<script src="lib/font-detective.js"></script>
<script src="./lib/libtess.min.js"></script>
<script src="src/helpers.js"></script>
<script src="src/storage.js"></script>
<script src="src/$Component.js"></script>

View File

@ -27,6 +27,10 @@
Copyright in any portions created by third parties is as indicated
elsewhere herein. All Rights Reserved.
*/
// wrapper added because of https://github.com/brendankenny/libtess.js/issues/15
var libtess=(function() {
'use strict';var n;function t(a,b){return a.b===b.b&&a.a===b.a}function u(a,b){return a.b<b.b||a.b===b.b&&a.a<=b.a}function v(a,b,c){var d=b.b-a.b,e=c.b-b.b;return 0<d+e?d<e?b.a-a.a+d/(d+e)*(a.a-c.a):b.a-c.a+e/(d+e)*(c.a-a.a):0}function x(a,b,c){var d=b.b-a.b,e=c.b-b.b;return 0<d+e?(b.a-c.a)*d+(b.a-a.a)*e:0}function z(a,b){return a.a<b.a||a.a===b.a&&a.b<=b.b}function aa(a,b,c){var d=b.a-a.a,e=c.a-b.a;return 0<d+e?d<e?b.b-a.b+d/(d+e)*(a.b-c.b):b.b-c.b+e/(d+e)*(c.b-a.b):0}
function ba(a,b,c){var d=b.a-a.a,e=c.a-b.a;return 0<d+e?(b.b-c.b)*d+(b.b-a.b)*e:0}function ca(a){return u(a.b.a,a.a)}function da(a){return u(a.a,a.b.a)}function A(a,b,c,d){a=0>a?0:a;c=0>c?0:c;return a<=c?0===c?(b+d)/2:b+a/(a+c)*(d-b):d+c/(a+c)*(b-d)};function ea(a){var b=B(a.b);C(b,a.c);C(b.b,a.c);D(b,a.a);return b}function E(a,b){var c=!1,d=!1;a!==b&&(b.a!==a.a&&(d=!0,F(b.a,a.a)),b.d!==a.d&&(c=!0,G(b.d,a.d)),H(b,a),d||(C(b,a.a),a.a.c=a),c||(D(b,a.d),a.d.a=a))}function I(a){var b=a.b,c=!1;a.d!==a.b.d&&(c=!0,G(a.d,a.b.d));a.c===a?F(a.a,null):(a.b.d.a=J(a),a.a.c=a.c,H(a,J(a)),c||D(a,a.d));b.c===b?(F(b.a,null),G(b.d,null)):(a.d.a=J(b),b.a.c=b.c,H(b,J(b)));fa(a)}
function K(a){var b=B(a),c=b.b;H(b,a.e);b.a=a.b.a;C(c,b.a);b.d=c.d=a.d;b=b.b;H(a.b,J(a.b));H(a.b,b);a.b.a=b.a;b.b.a.c=b.b;b.b.d=a.b.d;b.f=a.f;b.b.f=a.b.f;return b}function L(a,b){var c=!1,d=B(a),e=d.b;b.d!==a.d&&(c=!0,G(b.d,a.d));H(d,a.e);H(e,b);d.a=a.b.a;e.a=b.a;d.d=e.d=a.d;a.d.a=e;c||D(d,a.d);return d}function B(a){var b=new M,c=new M,d=a.b.h;c.h=d;d.b.h=b;b.h=a;a.b.h=c;b.b=c;b.c=b;b.e=c;c.b=b;c.c=c;return c.e=b}function H(a,b){var c=a.c,d=b.c;c.b.e=b;d.b.e=a;a.c=d;b.c=c}
@ -56,3 +60,7 @@ function Fa(a){if(0===a.a)return Ka(a.b);var b=a.c[a.d[a.a-1]];if(0!==a.b.a&&u(G
function W(a,b){for(var c=a.d,d=a.e,e=a.c,f=b,g=c[f];;){var h=f<<1;h<a.a&&u(d[c[h+1]],d[c[h]])&&(h+=1);var k=c[h];if(h>a.a||u(d[g],d[k])){c[f]=g;e[g]=f;break}c[f]=k;e[k]=f;f=h}}function va(a,b){for(var c=a.d,d=a.e,e=a.c,f=b,g=c[f];;){var h=f>>1,k=c[h];if(0===h||u(d[k],d[g])){c[f]=g;e[g]=f;break}c[f]=k;e[k]=f;f=h}};function ma(){this.e=this.a=null;this.f=0;this.c=this.b=this.h=this.d=!1}function S(a){return a.e.c.b}function R(a){return a.e.a.b};this.libtess={GluTesselator:X,windingRule:{GLU_TESS_WINDING_ODD:100130,GLU_TESS_WINDING_NONZERO:100131,GLU_TESS_WINDING_POSITIVE:100132,GLU_TESS_WINDING_NEGATIVE:100133,GLU_TESS_WINDING_ABS_GEQ_TWO:100134},primitiveType:{GL_LINE_LOOP:2,GL_TRIANGLES:4,GL_TRIANGLE_STRIP:5,GL_TRIANGLE_FAN:6},errorType:{GLU_TESS_MISSING_BEGIN_POLYGON:100151,GLU_TESS_MISSING_END_POLYGON:100153,GLU_TESS_MISSING_BEGIN_CONTOUR:100152,GLU_TESS_MISSING_END_CONTOUR:100154,GLU_TESS_COORD_TOO_LARGE:100155,GLU_TESS_NEED_COMBINE_CALLBACK:100156},
gluEnum:{GLU_TESS_MESH:100112,GLU_TESS_TOLERANCE:100142,GLU_TESS_WINDING_RULE:100140,GLU_TESS_BOUNDARY_ONLY:100141,GLU_INVALID_ENUM:100900,GLU_INVALID_VALUE:100901,GLU_TESS_BEGIN:100100,GLU_TESS_VERTEX:100101,GLU_TESS_END:100102,GLU_TESS_ERROR:100103,GLU_TESS_EDGE_FLAG:100104,GLU_TESS_COMBINE:100105,GLU_TESS_BEGIN_DATA:100106,GLU_TESS_VERTEX_DATA:100107,GLU_TESS_END_DATA:100108,GLU_TESS_ERROR_DATA:100109,GLU_TESS_EDGE_FLAG_DATA:100110,GLU_TESS_COMBINE_DATA:100111}};X.prototype.gluDeleteTess=X.prototype.x;
X.prototype.gluTessProperty=X.prototype.B;X.prototype.gluGetTessProperty=X.prototype.y;X.prototype.gluTessNormal=X.prototype.A;X.prototype.gluTessCallback=X.prototype.z;X.prototype.gluTessVertex=X.prototype.C;X.prototype.gluTessBeginPolygon=X.prototype.u;X.prototype.gluTessBeginContour=X.prototype.t;X.prototype.gluTessEndContour=X.prototype.v;X.prototype.gluTessEndPolygon=X.prototype.w; if (typeof module !== 'undefined') { module.exports = this.libtess; }
// wrapper added because of https://github.com/brendankenny/libtess.js/issues/15
return this.libtess;
}).apply(new Object()); // need to provide a valid "this" due to strict mode; simply use a dummy object

View File

@ -515,6 +515,7 @@ function stretch_and_skew(xscale, yscale, hsa, vsa){
function cut_polygon(points, x_min, y_min, x_max, y_max, from_canvas){
// Cut out the polygon given by points bounded by x/y_min/max from from_canvas
// TODO: use webgl polygon code
from_canvas = from_canvas || canvas;
@ -608,3 +609,220 @@ function cut_polygon(points, x_min, y_min, x_max, y_max, from_canvas){
return cutout;
}
(function(){
var tessy = (function initTesselator() {
// function called for each vertex of tesselator output
function vertexCallback(data, polyVertArray) {
// console.log(data[0], data[1]);
polyVertArray[polyVertArray.length] = data[0];
polyVertArray[polyVertArray.length] = data[1];
}
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
function combinecallback(coords, data, weight) {
// console.log('combine callback');
return [coords[0], coords[1], coords[2]];
}
function edgeCallback(flag) {
// don't really care about the flag, but need no-strip/no-fan behavior
// console.log('edge flag: ' + flag);
}
var tessy = new libtess.GluTesselator();
// tessy.gluTessProperty(libtess.gluEnum.GLU_TESS_WINDING_RULE, libtess.windingRule.GLU_TESS_WINDING_POSITIVE);
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);
return tessy;
})();
function triangulate(contours) {
// libtess will take 3d verts and flatten to a plane for tesselation
// since only doing 2d tesselation here, provide z=1 normal to skip
// iterating over verts only to get the same answer.
// comment out to test normal-generation code
tessy.gluTessNormal(0, 0, 1);
var triangleVerts = [];
tessy.gluTessBeginPolygon(triangleVerts);
for (var i = 0; i < contours.length; i++) {
tessy.gluTessBeginContour();
var contour = contours[i];
for (var j = 0; j < contour.length; j += 2) {
var coords = [contour[j], contour[j + 1], 0];
tessy.gluTessVertex(coords, coords);
}
tessy.gluTessEndContour();
}
// finish polygon
tessy.gluTessEndPolygon();
return triangleVerts;
}
var gl;
var polyProgram;
var positionLoc;
var colorLoc;
var polygonArrayBuffer;
var triangleCount = 0;
function initWebGL(canvas) {
gl = canvas.getContext('webgl', { antialias: false });
// TODO: experimental-webgl for Edge? (ugh)
polyProgram = createShaderProgram();
positionLoc = gl.getAttribLocation(polyProgram, 'position');
gl.enableVertexAttribArray(positionLoc);
colorLoc = gl.getUniformLocation(polyProgram, 'color');
}
// TODO: to be consistent, maybe this should be setColor and then draw but whatever
function draw(fillPolygon, color) {
gl.uniform4fv(colorLoc, color);
var renderType = fillPolygon ? gl.TRIANGLES : gl.LINE_LOOP;
gl.drawArrays(renderType, 0, triangleCount);
}
function initArrayBuffer(triangleVerts) {
// triangleVerts = contours[0];
triangleCount = triangleVerts.length / 2;
// put triangle coordinates into a WebGL ArrayBuffer and bind to
// shader's 'position' attribute variable
var rawData = new Float32Array(triangleVerts);
polygonArrayBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, polygonArrayBuffer);
gl.bufferData(gl.ARRAY_BUFFER, rawData, gl.STATIC_DRAW);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
}
function createShaderProgram() {
// create vertex shader
var vertexSrc = [
'attribute vec4 position;',
'void main() {',
' /* already in normalized coordinates, so just pass through */',
' gl_Position = position;',
'}'
].join('');
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSrc);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
console.log(
'Vertex shader failed to compile. Log: ' +
gl.getShaderInfoLog(vertexShader)
);
}
// create fragment shader
var fragmentSrc = [
'precision mediump float;',
'uniform vec4 color;',
'void main() {',
' gl_FragColor = color;',
'}'
].join('');
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSrc);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
console.log(
'Fragment shader failed to compile. Log: ' +
gl.getShaderInfoLog(fragmentShader)
);
}
// link shaders to create our program
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
return program;
}
var polygonFillCanvas = document.createElement('canvas');
// document.body.appendChild(polygonFillCanvas);
initWebGL(polygonFillCanvas);
// window.draw_polygon = function(ctx, points, x_min, x_max, y_min, y_max){
window.draw_polygon = function(ctx, points, stroke, fill){
var stroke_color = ctx.strokeStyle;
var fill_color = ctx.fillStyle;
var numPoints = points.length;
var numCoords = numPoints * 2
var x_min = +Infinity;
var x_max = -Infinity;
var y_min = +Infinity;
var y_max = -Infinity;
for (var i = 0; i < numPoints; i++) {
var {x, y} = points[i];
x_min = Math.min(x, x_min);
x_max = Math.max(x, x_max);
y_min = Math.min(y, y_min);
y_max = Math.max(y, y_max);
}
x_max += 1;
y_max += 1;
polygonFillCanvas.width = x_max - x_min;
polygonFillCanvas.height = y_max - y_min;
gl.viewport(0, 0, polygonFillCanvas.width, polygonFillCanvas.height);
var coords = new Float32Array(numCoords);
for (var i = 0; i < numPoints; i++) {
coords[i*2+0] = (points[i].x - x_min) / polygonFillCanvas.width * 2 - 1;
coords[i*2+1] = 1 - (points[i].y - y_min) / polygonFillCanvas.height * 2;
// TODO: investigate: does this cause resolution/information loss? can we change the coordinate system?
}
var contours = [coords];
var polyTriangles = triangulate(contours);
initArrayBuffer(polyTriangles);
if(fill){
draw(true, get_rgba_from_color(fill_color).map((colorComponent)=> colorComponent / 255));
}
if(stroke){
for(var i=0; i<contours.length; i++){
var contour = contours[i];
initArrayBuffer(contour);
draw(false, get_rgba_from_color(stroke_color).map((colorComponent)=> colorComponent / 255));
}
}
// TODO: remove me
// $canvas_area.append(polygonFillCanvas);
// ctx.fillStyle = "rgba(255, 0, 255, 0.1)";
// ctx.fillRect(x_min, y_min, polygonFillCanvas.width, polygonFillCanvas.height);
ctx.drawImage(polygonFillCanvas, x_min, y_min);
};
})();

View File

@ -523,10 +523,14 @@ tools = [{
var i = this.points.length - 1;
this.points[i].x = x;
this.points[i].y = y;
// this.x_min = Math.min(x, this.x_min);
// this.x_max = Math.max(x + 1, this.x_max);
// this.y_min = Math.min(y, this.y_min);
// this.y_max = Math.max(y + 1, this.y_max);
var dx = this.points[i].x - this.points[0].x;
var dy = this.points[i].y - this.points[0].y;
var d = Math.sqrt(dx*dx + dy*dy);
if(d < stroke_size * 5.1010101){ // arbitrary 101
if(d < stroke_size * 5.1010101){ // arbitrary 101 (TODO: find correct value (or formula))
this.complete(ctx, x, y);
}
@ -536,11 +540,11 @@ tools = [{
var tool = this;
if(tool.points.length < 1){
tool.x_min = x;
tool.x_max = x+1;
tool.y_min = y;
tool.y_max = y+1;
tool.points = [];
// tool.x_min = x;
// tool.x_max = x+1;
// tool.y_min = y;
// tool.y_max = y+1;
// tool.points = [];
// @TODO: stop needing this:
tool.canvas_base = canvas;
@ -570,12 +574,12 @@ tools = [{
}else{
// Add the point
tool.points.push({x: x, y: y});
// Update the boundaries of the polygon
// @TODO: this boundary stuff in less places (DRY)
tool.x_min = Math.min(x, tool.x_min);
tool.x_max = Math.max(x, tool.x_max);
tool.y_min = Math.min(y, tool.y_min);
tool.y_max = Math.max(y, tool.y_max);
// // Update the boundaries of the polygon
// // @TODO: this boundary stuff in less places (DRY)
// tool.x_min = Math.min(x, tool.x_min);
// tool.x_max = Math.max(x + 1, tool.x_max);
// tool.y_min = Math.min(y, tool.y_min);
// tool.y_max = Math.max(y + 1, tool.y_max);
}
}
tool.last_click_pointerdown = {x: x, y: y, time: +new Date};
@ -610,24 +614,36 @@ tools = [{
ctx.copy(this.canvas_base);
// Draw an antialiased polygon
ctx.beginPath();
ctx.moveTo(this.points[0].x, this.points[0].y);
for(var i=1; i<this.points.length; i++){
ctx.lineTo(this.points[i].x, this.points[i].y);
}
ctx.lineTo(this.points[0].x, this.points[0].y);
ctx.closePath();
// ctx.beginPath();
// ctx.moveTo(this.points[0].x, this.points[0].y);
// for(var i=1; i<this.points.length; i++){
// ctx.lineTo(this.points[i].x, this.points[i].y);
// }
// ctx.lineTo(this.points[0].x, this.points[0].y);
// ctx.closePath();
ctx.lineWidth = stroke_size;
ctx.lineJoin = "bevel";
if(this.$options.fill){
ctx.fillStyle = fill_color;
ctx.fill();
}
if(this.$options.stroke){
ctx.strokeStyle = stroke_color;
ctx.stroke();
}
// ctx.lineWidth = stroke_size;
// ctx.lineJoin = "bevel";
// if(this.$options.fill){
ctx.fillStyle = fill_color;
// ctx.fill();
// }
// if(this.$options.stroke){
ctx.strokeStyle = stroke_color;
// ctx.stroke();
// }
// Draw an ANTI-anti-aliased polygon :)
draw_polygon(
ctx,
this.points,
// this.x_min,
// this.x_max,
// this.y_min,
// this.y_max,
this.$options.stroke,
this.$options.fill
);
/*
if(this.$options.fill){

View File

@ -1,78 +0,0 @@
<!DOCTYPE html>
<!-- saved from url=(0092)https://rawgit.com/brendankenny/libtess.js/gh-pages/examples/simple_triangulation/index.html -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Simple Triangulation</title>
<style>
body {
margin: 0;
padding: 0;
background: #58548e;
}
#poly-view {
image-rendering: optimizeSpeed; /* STOP SMOOTHING, GIVE ME SPEED */
image-rendering: -moz-crisp-edges; /* Firefox */
image-rendering: -o-crisp-edges; /* Opera */
image-rendering: -webkit-optimize-contrast; /* Chrome (and eventually Safari) */
image-rendering: pixelated; /* Chrome */
image-rendering: optimize-contrast; /* CSS3 Proposed */
-ms-interpolation-mode: nearest-neighbor; /* IE8+ */
}
</style>
<script src="./lib/libtess.min.js"></script>
<script src="./src/webgl-draw.js"></script>
<script src="./src/triangulate.js"></script>
<script>
/* global draw, initArrayBuffer, initWebGL, triangulate */
var filledMode = true;
var NUM_VERTS = 500;
var contours;
var polyTriangles;
function init() {
var polygonFillCanvas = document.getElementById('poly-view');
initWebGL(polygonFillCanvas);
contours = [
new Float32Array(generateFunkyFreshLoop(NUM_VERTS, true, 0.5, 0.5)),
new Float32Array(generateFunkyFreshLoop(NUM_VERTS, false, 14, 3))
];
polyTriangles = triangulate(contours);
initArrayBuffer(polyTriangles);
draw(filledMode, [0.1, 0.8, 0.08, 1.0]);
for(var i=0; i<contours.length; i++){
var contour = contours[i];
initArrayBuffer(contour);
draw(false, [0, 0, 0, 1.0]);
}
}
function generateFunkyFreshLoop(count, ccw, radius, noiseSize) {
var verts = new Array(count * 2);
var thetaPer = Math.PI * 2 / count;
var backwards = ccw ? -1 : 1;
for (var i = 0; i < count; i++) {
var theta = thetaPer * i * backwards;
var randomRadius = radius * (1 + Math.sin(i/5+Math.cos(i+radius+noiseSize) / 2) * noiseSize);
verts[i*2] = Math.cos(theta) * randomRadius;
verts[i*2+1] = Math.sin(theta) * randomRadius;
}
return verts;
}
document.addEventListener('DOMContentLoaded', init, false);
</script>
</head>
<body>
<canvas id="poly-view" width="800" height="800"></canvas>
</body></html>

View File

@ -1,65 +0,0 @@
/*global libtess */
/* exported triangulate */
var tessy = (function initTesselator() {
// function called for each vertex of tesselator output
function vertexCallback(data, polyVertArray) {
// console.log(data[0], data[1]);
polyVertArray[polyVertArray.length] = data[0];
polyVertArray[polyVertArray.length] = data[1];
}
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
function combinecallback(coords, data, weight) {
// console.log('combine callback');
return [coords[0], coords[1], coords[2]];
}
function edgeCallback(flag) {
// don't really care about the flag, but need no-strip/no-fan behavior
// console.log('edge flag: ' + flag);
}
var tessy = new libtess.GluTesselator();
// tessy.gluTessProperty(libtess.gluEnum.GLU_TESS_WINDING_RULE, libtess.windingRule.GLU_TESS_WINDING_POSITIVE);
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);
return tessy;
})();
function triangulate(contours) {
// libtess will take 3d verts and flatten to a plane for tesselation
// since only doing 2d tesselation here, provide z=1 normal to skip
// iterating over verts only to get the same answer.
// comment out to test normal-generation code
tessy.gluTessNormal(0, 0, 1);
var triangleVerts = [];
tessy.gluTessBeginPolygon(triangleVerts);
for (var i = 0; i < contours.length; i++) {
tessy.gluTessBeginContour();
var contour = contours[i];
for (var j = 0; j < contour.length; j += 2) {
var coords = [contour[j], contour[j + 1], 0];
tessy.gluTessVertex(coords, coords);
}
tessy.gluTessEndContour();
}
// finish polygon
tessy.gluTessEndPolygon();
return triangleVerts;
}

View File

@ -1,86 +0,0 @@
/* exported initWebGL, draw, initArrayBuffer */
var gl;
var polyProgram;
var positionLoc;
var colorLoc;
var polygonArrayBuffer;
var triangleCount = 0;
function initWebGL(canvas) {
gl = canvas.getContext('webgl', { antialias: false });
// TODO: experimental-webgl for Edge? (ugh)
gl.viewport(0, 0, canvas.width, canvas.height);
polyProgram = createShaderProgram();
positionLoc = gl.getAttribLocation(polyProgram, 'position');
gl.enableVertexAttribArray(positionLoc);
colorLoc = gl.getUniformLocation(polyProgram, 'color');
}
// TODO: to be consistent, maybe this should be setColor and then draw but whatever
function draw(fillPolygon, color) {
gl.uniform4fv(colorLoc, color);
var renderType = fillPolygon ? gl.TRIANGLES : gl.LINE_LOOP;
gl.drawArrays(renderType, 0, triangleCount);
}
function initArrayBuffer(triangleVerts) {
// triangleVerts = contours[0];
triangleCount = triangleVerts.length / 2;
// put triangle coordinates into a WebGL ArrayBuffer and bind to
// shader's 'position' attribute variable
var rawData = new Float32Array(triangleVerts);
polygonArrayBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, polygonArrayBuffer);
gl.bufferData(gl.ARRAY_BUFFER, rawData, gl.STATIC_DRAW);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
}
function createShaderProgram() {
// create vertex shader
var vertexSrc = [
'attribute vec4 position;',
'void main() {',
' /* already in normalized coordinates, so just pass through */',
' gl_Position = position;',
'}'
].join('');
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSrc);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
console.log('Vertex shader failed to compile. Log: ' +
gl.getShaderInfoLog(vertexShader));
}
// create fragment shader
var fragmentSrc = [
'precision mediump float;',
'uniform vec4 color;',
'void main() {',
' gl_FragColor = color;',
'}'
].join('');
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSrc);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
console.log('Fragment shader failed to compile. Log: ' +
gl.getShaderInfoLog(fragmentShader));
}
// link shaders to create our program
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
return program;
}