Skip to content

Instantly share code, notes, and snippets.

@Sumbera
Last active August 29, 2015 14:04
Show Gist options
  • Save Sumbera/01cbd44a77b4283e6dcd to your computer and use it in GitHub Desktop.
Save Sumbera/01cbd44a77b4283e6dcd to your computer and use it in GitHub Desktop.
Polygon libtess fill in WebGL

libtess.js test on medium large polygon, more info here

<!doctype html>
<html>
<head>
<title>Polygon libtess fill in WebGL</title>
<meta charset="utf-8">
<style>
html, body {
height: 100%;
padding: 0;
margin: 0;
background: rgb(14, 21, 30);
height: 100%;
}
#map {
position: absolute;
height: 100%;
width: 100%;
background-color: #333;
}
</style>
<!-- vertex shader -->
<script id="vshader" type="x-shader/x-vertex">
uniform mat4 u_matrix;
attribute vec4 a_vertex;
attribute float a_pointSize;
attribute vec4 a_color;
varying vec4 v_color;
void main() {
// Set the size of the point
gl_PointSize = a_pointSize;
// multiply each vertex by a matrix.
gl_Position = u_matrix * a_vertex;
// pass the color to the fragment shader
v_color = a_color;
}
</script>
<!-- fragment shader -->
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 v_color;
void main() {
// -- squares
gl_FragColor = v_color; //vec4(0.8, 0.1,0.1, 0.9); //v_color;
gl_FragColor.a = 0.8;
}
</script>
<script src="http://www.sumbera.com/gist/js/libtess/libtess.cat.js"></script>
<script>
/*global libtess */
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];
polyVertArray[polyVertArray.length] = currentColor[0];
polyVertArray[polyVertArray.length] = currentColor[1];
polyVertArray[polyVertArray.length] = currentColor[2];
}
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;
})();
</script>
</head>
<body>
<div id="map" ></div>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
<script src="http://www.sumbera.com/gist/js/leaflet/canvas/L.CanvasOverlay.js"></script>
<script src="http://www.sumbera.com/gist/data/CZDistricts.js" charset="utf-8"></script>
<script>
var leafletMap = L.map('map').setView([50.00, 14.44], 8);
L.tileLayer("http://{s}.sm.mapstack.stamen.com/(toner-background,$fff[difference],$fff[@23],$fff[hsl-saturation@20],toner-lines[destination-in])/{z}/{x}/{y}.png")
//L.tileLayer("http://{s}.sm.mapstack.stamen.com/(toner-lite,$fff[difference],$fff[@23],$fff[hsl-saturation@20])/{z}/{x}/{y}.png")
.addTo(leafletMap);
var glLayer = L.canvasOverlay()
.drawing(drawingOnCanvas)
.addTo(leafletMap);
var canvas = glLayer.canvas();
glLayer.canvas.width = canvas.clientWidth;
glLayer.canvas.height = canvas.clientHeight;
var gl = canvas.getContext('experimental-webgl', { antialias: true });
var pixelsToWebGLMatrix = new Float32Array(16);
var mapMatrix = new Float32Array(16);
// -- WebGl setup
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, document.getElementById('vshader').text);
gl.compileShader(vertexShader);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, document.getElementById('fshader').text);
gl.compileShader(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);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
// gl.disable(gl.DEPTH_TEST);
// ----------------------------
// look up the locations for the inputs to our shaders.
var u_matLoc = gl.getUniformLocation(program, "u_matrix");
gl.aPointSize = gl.getAttribLocation(program, "a_pointSize");
// Set the matrix to some that makes 1 unit 1 pixel.
pixelsToWebGLMatrix.set([2 / canvas.width, 0, 0, 0, 0, -2 / canvas.height, 0, 0, 0, 0, 0, 0, -1, 1, 0, 1]);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.uniformMatrix4fv(u_matLoc, false, pixelsToWebGLMatrix);
var currentColor;
// -- data
var verts = [];
//-- verts only
/*
data.features[0].geometry.coordinates[0].map(function (d, i) {
pixel = LatLongToPixelXY(d[1], d[0]);
verts.push(pixel.x, pixel.y, 0.1, 0.6, 0.1);
});
*/
var start = new Date();
var all = [];
var numfeatures = data.features.length;
data.features.map(function (b, i) {
tessy.gluTessNormal(0, 0, 1);
tessy.gluTessBeginPolygon(verts);
tessy.gluTessBeginContour();
currentColor = [Math.random(), Math.random(), Math.random()]; //[0.1, 0.6, 0.1];
b.geometry.coordinates[0].map(function (d, i) {
pixel = LatLongToPixelXY(d[1], d[0],0);
var coords = [pixel.x, pixel.y, 0];
tessy.gluTessVertex(coords, coords);
});
tessy.gluTessEndContour();
tessy.gluTessEndPolygon();
all = verts.concat(all);
verts = [];
});
verts = all;
console.log("updated at " + new Date().setTime(new Date().getTime() - start.getTime()) + " ms ");
// tirangles or point count
var numPoints = verts.length / 5;
console.log("num points: " + numPoints);
var vertBuffer = gl.createBuffer();
var vertArray = new Float32Array(verts);
var fsize = vertArray.BYTES_PER_ELEMENT;
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertArray, gl.STATIC_DRAW);
var vertLoc = gl.getAttribLocation(program, "a_vertex");
gl.vertexAttribPointer(vertLoc, 2, gl.FLOAT, false, fsize * 5, 0);
gl.enableVertexAttribArray(vertLoc);
// -- offset for color buffer
var colorLoc = gl.getAttribLocation(program, "a_color");
gl.vertexAttribPointer(colorLoc, 3, gl.FLOAT, false, fsize * 5, fsize * 2);
gl.enableVertexAttribArray(colorLoc);
glLayer.redraw();
function drawingOnCanvas(canvasOverlay, params) {
if (gl == null) return;
gl.clear(gl.COLOR_BUFFER_BIT);
pixelsToWebGLMatrix.set([2 / canvas.width, 0, 0, 0, 0, -2 / canvas.height, 0, 0, 0, 0, 0, 0, -1, 1, 0, 1]);
gl.viewport(0, 0, canvas.width, canvas.height);
var pointSize = Math.max(leafletMap.getZoom() - 4.0, 1.0);
gl.vertexAttrib1f(gl.aPointSize, pointSize);
// -- set base matrix to translate canvas pixel coordinates -> webgl coordinates
mapMatrix.set(pixelsToWebGLMatrix);
var bounds = leafletMap.getBounds();
var topLeft = new L.LatLng(bounds.getNorth(), bounds.getWest());
var offset = LatLongToPixelXY(topLeft.lat, topLeft.lng);
// -- Scale to current zoom
var scale = Math.pow(2, leafletMap.getZoom());
scaleMatrix(mapMatrix, scale, scale);
translateMatrix(mapMatrix, -offset.x, -offset.y);
// -- attach matrix value to 'mapMatrix' uniform in shader
gl.uniformMatrix4fv(u_matLoc, false, mapMatrix);
gl.drawArrays(gl.TRIANGLES, 0, numPoints);
}
// Returns a random integer from 0 to range - 1.
function randomInt(range) {
return Math.floor(Math.random() * range);
}
/*
function latlonToPixels(lat, lon) {
initialResolution = 2 * Math.PI * 6378137 / 256, // at zoomlevel 0
originShift = 2 * Math.PI * 6378137 / 2;
// -- to meters
var mx = lon * originShift / 180;
var my = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
my = my * originShift / 180;
// -- to pixels at zoom level 0
var res = initialResolution;
x = (mx + originShift) / res,
y = (my + originShift) / res;
return { x: x, y: 256- y };
}
*/
// -- converts latlon to pixels at zoom level 0 (for 256x256 tile size) , inverts y coord )
// -- source : http://build-failed.blogspot.cz/2013/02/displaying-webgl-data-on-google-maps.html
function LatLongToPixelXY(latitude, longitude) {
var pi_180 = Math.PI / 180.0;
var pi_4 = Math.PI * 4;
var sinLatitude = Math.sin(latitude * pi_180);
var pixelY = (0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (pi_4)) * 256;
var pixelX = ((longitude + 180) / 360) * 256;
var pixel = { x: pixelX, y: pixelY };
return pixel;
}
function translateMatrix(matrix, tx, ty) {
// translation is in last column of matrix
matrix[12] += matrix[0] * tx + matrix[4] * ty;
matrix[13] += matrix[1] * tx + matrix[5] * ty;
matrix[14] += matrix[2] * tx + matrix[6] * ty;
matrix[15] += matrix[3] * tx + matrix[7] * ty;
}
function scaleMatrix(matrix, scaleX, scaleY) {
// scaling x and y, which is just scaling first two columns of matrix
matrix[0] *= scaleX;
matrix[1] *= scaleX;
matrix[2] *= scaleX;
matrix[3] *= scaleX;
matrix[4] *= scaleY;
matrix[5] *= scaleY;
matrix[6] *= scaleY;
matrix[7] *= scaleY;
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment