Use WebGL for Polygon tool
parent
f12039fd1e
commit
e5b3c5be8b
9
TODO.md
9
TODO.md
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
74
src/tools.js
74
src/tools.js
|
@ -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){
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue