This commit is contained in:
Patrick Walton 2018-11-14 13:56:45 -08:00
parent a2522e3845
commit 208565603e
8 changed files with 531 additions and 29 deletions

1
.gitignore vendored
View File

@ -12,6 +12,7 @@
/demo/client/package-lock.json /demo/client/package-lock.json
/demo/server/target /demo/server/target
/demo/server/Rocket.toml /demo/server/Rocket.toml
/demo2/dist
.DS_Store .DS_Store
target target
node_modules node_modules

19
demo2/cover.fs.glsl Normal file
View File

@ -0,0 +1,19 @@
#version 300 es
// pathfinder/demo2/cover.fs.glsl
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
precision highp float;
out vec4 oFragColor;
void main() {
oFragColor = vec4(0.0, 0.0, 1.0, 1.0);
}

24
demo2/cover.vs.glsl Normal file
View File

@ -0,0 +1,24 @@
#version 300 es
// pathfinder/demo2/cover.vs.glsl
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
precision highp float;
uniform vec2 uFramebufferSize;
uniform vec2 uTileSize;
in vec2 aTessCoord;
in vec2 aTileOrigin;
void main() {
vec2 position = aTileOrigin + uTileSize * aTessCoord;
gl_Position = vec4(position / uFramebufferSize * 2.0 - 1.0, 0.0, 1.0);
}

5
demo2/declaration.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module "*.glsl";
declare module "*.jpg";
declare module "*.svg";
declare function require(s: string): any;

295
demo2/package-lock.json generated Normal file
View File

@ -0,0 +1,295 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"@choojs/findup": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz",
"integrity": "sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==",
"dev": true,
"requires": {
"commander": "2.19.0"
}
},
"@types/webgl2": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
},
"commander": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
"dev": true
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
"events": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
"dev": true
},
"glsl-inject-defines": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/glsl-inject-defines/-/glsl-inject-defines-1.0.3.tgz",
"integrity": "sha1-3RqswsF/yyvT/DJBHGYz0Ne2D9Q=",
"dev": true,
"requires": {
"glsl-token-inject-block": "1.1.0",
"glsl-token-string": "1.0.1",
"glsl-tokenizer": "2.1.5"
}
},
"glsl-resolve": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/glsl-resolve/-/glsl-resolve-0.0.1.tgz",
"integrity": "sha1-iUvvc5ENeSyBtRQxgANdCnivdtM=",
"dev": true,
"requires": {
"resolve": "0.6.3",
"xtend": "2.2.0"
},
"dependencies": {
"resolve": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz",
"integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY=",
"dev": true
}
}
},
"glsl-token-assignments": {
"version": "2.0.2",
"resolved": "http://registry.npmjs.org/glsl-token-assignments/-/glsl-token-assignments-2.0.2.tgz",
"integrity": "sha1-pdgqt4SZwuimuDy2lJXm5mXOAZ8=",
"dev": true
},
"glsl-token-defines": {
"version": "1.0.0",
"resolved": "http://registry.npmjs.org/glsl-token-defines/-/glsl-token-defines-1.0.0.tgz",
"integrity": "sha1-y4kqqVmTYjFyhHDU90AySJaX+p0=",
"dev": true,
"requires": {
"glsl-tokenizer": "2.1.5"
}
},
"glsl-token-depth": {
"version": "1.1.2",
"resolved": "http://registry.npmjs.org/glsl-token-depth/-/glsl-token-depth-1.1.2.tgz",
"integrity": "sha1-I8XjDuK9JViEtKKLyFC495HpXYQ=",
"dev": true
},
"glsl-token-descope": {
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/glsl-token-descope/-/glsl-token-descope-1.0.2.tgz",
"integrity": "sha1-D8kKsyYYa4L1l7LnfcniHvzTIHY=",
"dev": true,
"requires": {
"glsl-token-assignments": "2.0.2",
"glsl-token-depth": "1.1.2",
"glsl-token-properties": "1.0.1",
"glsl-token-scope": "1.1.2"
}
},
"glsl-token-inject-block": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/glsl-token-inject-block/-/glsl-token-inject-block-1.1.0.tgz",
"integrity": "sha1-4QFfWYDBCRgkraomJfHf3ovQADQ=",
"dev": true
},
"glsl-token-properties": {
"version": "1.0.1",
"resolved": "http://registry.npmjs.org/glsl-token-properties/-/glsl-token-properties-1.0.1.tgz",
"integrity": "sha1-SD3D2Dnw1LXGFx0VkfJJvlPCip4=",
"dev": true
},
"glsl-token-scope": {
"version": "1.1.2",
"resolved": "http://registry.npmjs.org/glsl-token-scope/-/glsl-token-scope-1.1.2.tgz",
"integrity": "sha1-oXKOeN8kRE+cuT/RjvD3VQOmQ7E=",
"dev": true
},
"glsl-token-string": {
"version": "1.0.1",
"resolved": "http://registry.npmjs.org/glsl-token-string/-/glsl-token-string-1.0.1.tgz",
"integrity": "sha1-WUQdL4V958NEnJRWZgIezjWOSOw=",
"dev": true
},
"glsl-token-whitespace-trim": {
"version": "1.0.0",
"resolved": "http://registry.npmjs.org/glsl-token-whitespace-trim/-/glsl-token-whitespace-trim-1.0.0.tgz",
"integrity": "sha1-RtHf6Yx1vX1QTAXX0RsbPpzJOxA=",
"dev": true
},
"glsl-tokenizer": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/glsl-tokenizer/-/glsl-tokenizer-2.1.5.tgz",
"integrity": "sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA==",
"dev": true,
"requires": {
"through2": "0.6.5"
}
},
"glslify-bundle": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/glslify-bundle/-/glslify-bundle-5.1.1.tgz",
"integrity": "sha512-plaAOQPv62M1r3OsWf2UbjN0hUYAB7Aph5bfH58VxJZJhloRNbxOL9tl/7H71K7OLJoSJ2ZqWOKk3ttQ6wy24A==",
"dev": true,
"requires": {
"glsl-inject-defines": "1.0.3",
"glsl-token-defines": "1.0.0",
"glsl-token-depth": "1.1.2",
"glsl-token-descope": "1.0.2",
"glsl-token-scope": "1.1.2",
"glsl-token-string": "1.0.1",
"glsl-token-whitespace-trim": "1.0.0",
"glsl-tokenizer": "2.1.5",
"murmurhash-js": "1.0.0",
"shallow-copy": "0.0.1"
}
},
"glslify-deps": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/glslify-deps/-/glslify-deps-1.3.1.tgz",
"integrity": "sha512-Ogm179MCazwIRyEqs3g3EOY4Y3XIAa0yl8J5RE9rJC6QH1w8weVOp2RZu0mvnYy/2xIas1w166YR2eZdDkWQxg==",
"dev": true,
"requires": {
"@choojs/findup": "0.2.1",
"events": "1.1.1",
"glsl-resolve": "0.0.1",
"glsl-tokenizer": "2.1.5",
"graceful-fs": "4.1.15",
"inherits": "2.0.3",
"map-limit": "0.0.1",
"resolve": "1.8.1"
}
},
"graceful-fs": {
"version": "4.1.15",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
"dev": true
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
"dev": true
},
"map-limit": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz",
"integrity": "sha1-63lhAxwPDo0AG/LVb6toXViCLzg=",
"dev": true,
"requires": {
"once": "1.3.3"
}
},
"murmurhash-js": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
"integrity": "sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=",
"dev": true
},
"once": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
"integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
"dev": true,
"requires": {
"wrappy": "1.0.2"
}
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"readable-stream": {
"version": "1.0.34",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"dev": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "0.0.1",
"string_decoder": "0.10.31"
}
},
"resolve": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
"integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==",
"dev": true,
"requires": {
"path-parse": "1.0.6"
}
},
"shallow-copy": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz",
"integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=",
"dev": true
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
"dev": true
},
"svgpath": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/svgpath/-/svgpath-2.2.1.tgz",
"integrity": "sha1-CDS7Z8iadkcrK9BswQH6e1F7Iiw="
},
"through2": {
"version": "0.6.5",
"resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
"integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
"dev": true,
"requires": {
"readable-stream": "1.0.34",
"xtend": "4.0.1"
},
"dependencies": {
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
"dev": true
}
}
},
"typescript": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz",
"integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==",
"dev": true
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"xtend": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz",
"integrity": "sha1-7vax8ZjByN6vrYsXZaBNrUoBxak=",
"dev": true
}
}
}

11
demo2/package.json Normal file
View File

@ -0,0 +1,11 @@
{
"devDependencies": {
"glslify-bundle": "^5.1.1",
"glslify-deps": "^1.3.1",
"typescript": "^3.1.6"
},
"dependencies": {
"@types/webgl2": "0.0.4",
"svgpath": "^2.2.1"
}
}

View File

@ -1,3 +1,7 @@
html, body {
background: #e0e0e0;
}
.tile { .tile {
position: absolute; position: absolute;
top: 0; top: 0;

View File

@ -8,14 +8,23 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
import COVER_VERTEX_SHADER_SOURCE from "./cover.vs.glsl";
import COVER_FRAGMENT_SHADER_SOURCE from "./cover.fs.glsl";
import SVG from "../resources/svg/Ghostscript_Tiger.svg"; import SVG from "../resources/svg/Ghostscript_Tiger.svg";
const SVGPath = require('svgpath'); const SVGPath = require('svgpath');
const SVG_NS: string = "http://www.w3.org/2000/svg";
const TILE_SIZE: number = 16.0; const TILE_SIZE: number = 16.0;
const GLOBAL_OFFSET: Point2D = {x: 400.0, y: 200.0}; const GLOBAL_OFFSET: Point2D = {x: 400.0, y: 200.0};
const SVG_NS: string = "http://www.w3.org/2000/svg"; const QUAD_VERTEX_POSITIONS: Uint8Array = new Uint8Array([
0, 0,
1, 0,
0, 1,
1, 1,
]);
type Point2D = {x: number, y: number}; type Point2D = {x: number, y: number};
type Size2D = {width: number, height: number}; type Size2D = {width: number, height: number};
@ -27,13 +36,109 @@ type Edge = 'left' | 'top' | 'right' | 'bottom';
type SVGPath = any; type SVGPath = any;
class App { class App {
private canvas: HTMLCanvasElement;
private svg: XMLDocument; private svg: XMLDocument;
private gl: WebGL2RenderingContext;
private coverProgram: Program<'FramebufferSize' | 'TileSize', 'TessCoord' | 'TileOrigin'>;
private quadVertexBuffer: WebGLBuffer;
private coverVertexBuffer: WebGLBuffer;
private coverVertexArray: WebGLVertexArrayObject;
constructor(svg: XMLDocument) { constructor(svg: XMLDocument) {
this.canvas = staticCast(document.getElementById('canvas'), HTMLCanvasElement);
this.svg = svg; this.svg = svg;
const gl = unwrapNull(this.canvas.getContext('webgl2'));
this.gl = gl;
const coverProgram = new Program(gl,
COVER_VERTEX_SHADER_SOURCE,
COVER_FRAGMENT_SHADER_SOURCE,
['FramebufferSize', 'TileSize'],
['TessCoord', 'TileOrigin']);
this.coverProgram = coverProgram;
this.quadVertexBuffer = unwrapNull(gl.createBuffer());
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTEX_POSITIONS, gl.STATIC_DRAW);
this.coverVertexBuffer = unwrapNull(gl.createBuffer());
gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer);
// Initialize cover VAO.
this.coverVertexArray = unwrapNull(gl.createVertexArray());
gl.bindVertexArray(this.coverVertexArray);
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.vertexAttribPointer(coverProgram.attributes.TessCoord,
2,
gl.UNSIGNED_BYTE,
false,
0,
0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer);
gl.vertexAttribPointer(coverProgram.attributes.TileOrigin, 2, gl.SHORT, false, 0, 0);
gl.vertexAttribDivisor(coverProgram.attributes.TileOrigin, 1);
gl.enableVertexAttribArray(coverProgram.attributes.TessCoord);
gl.enableVertexAttribArray(coverProgram.attributes.TileOrigin);
// TODO(pcwalton)
} }
run(): void { run(): void {
const gl = this.gl, canvas = this.canvas;
const tiles = this.createTiles();
const coverVertexBufferData = new Int16Array(tiles.length * 2);
for (let tileIndex = 0; tileIndex < tiles.length; tileIndex++) {
coverVertexBufferData[tileIndex * 2 + 0] = Math.floor(tiles[tileIndex].origin.x);
coverVertexBufferData[tileIndex * 2 + 1] = Math.floor(tiles[tileIndex].origin.y);
}
gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, coverVertexBufferData, gl.DYNAMIC_DRAW);
console.log(coverVertexBufferData);
const framebufferSize = {width: canvas.width, height: canvas.height};
gl.viewport(0, 0, framebufferSize.width, framebufferSize.height);
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindVertexArray(this.coverVertexArray);
gl.useProgram(this.coverProgram.program);
gl.uniform2f(this.coverProgram.uniforms.FramebufferSize,
framebufferSize.width,
framebufferSize.height);
gl.uniform2f(this.coverProgram.uniforms.TileSize, TILE_SIZE, TILE_SIZE);
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, tiles.length);
/*
for (const tile of tiles) {
const newSVG = staticCast(document.createElementNS(SVG_NS, 'svg'), SVGElement);
newSVG.setAttribute('class', "tile");
newSVG.style.left = (GLOBAL_OFFSET.x + tile.origin.x) + "px";
newSVG.style.top = (GLOBAL_OFFSET.y + tile.origin.y) + "px";
newSVG.style.width = TILE_SIZE + "px";
newSVG.style.height = TILE_SIZE + "px";
const newPath = document.createElementNS(SVG_NS, 'path');
newPath.setAttribute('d',
tile.path
.translate(-tile.origin.x, -tile.origin.y)
.toString());
let color = "#";
for (let i = 0; i < 6; i++)
color += Math.floor(Math.random() * 16).toString(16);
newPath.setAttribute('fill', color);
newSVG.appendChild(newPath);
document.body.appendChild(newSVG);
}
*/
}
private createTiles(): Tile[] {
const svgElement = unwrapNull(this.svg.documentElement).cloneNode(true); const svgElement = unwrapNull(this.svg.documentElement).cloneNode(true);
document.body.appendChild(svgElement); document.body.appendChild(svgElement);
@ -41,18 +146,22 @@ class App {
const tiles: Tile[] = []; const tiles: Tile[] = [];
for (let pathElementIndex = 0; for (let pathElementIndex = 0;
pathElementIndex < 15; pathElementIndex < pathElements.length;
pathElementIndex++) { pathElementIndex++) {
const pathElement = pathElements[pathElementIndex]; const pathElement = pathElements[pathElementIndex];
const path = canonicalizePath(SVGPath(unwrapNull(pathElement.getAttribute('d')))); const path = canonicalizePath(SVGPath(unwrapNull(pathElement.getAttribute('d'))));
const boundingRect = this.boundingRectOfPath(path); const boundingRect = this.boundingRectOfPath(path);
//console.log("path " + pathElementIndex, path.toString(), ":", boundingRect); /*console.log("path " + pathElementIndex, path.toString(), ":",
boundingRect.origin.x,
boundingRect.origin.y,
boundingRect.size.width,
boundingRect.size.height);*/
let y = boundingRect.origin.y; let y = boundingRect.origin.y - boundingRect.origin.y % TILE_SIZE;
while (true) { while (true) {
let x = boundingRect.origin.x; let x = boundingRect.origin.x - boundingRect.origin.x % TILE_SIZE;
while (true) { while (true) {
const tileBounds = { const tileBounds = {
origin: {x, y}, origin: {x, y},
@ -60,7 +169,8 @@ class App {
}; };
const tilePath = this.clipPathToRect(path, tileBounds); const tilePath = this.clipPathToRect(path, tileBounds);
tiles.push(new Tile(pathElementIndex, tilePath, tileBounds.origin)); if (tilePath.toString().length > 0)
tiles.push(new Tile(pathElementIndex, tilePath, tileBounds.origin));
if (x >= boundingRect.origin.x + boundingRect.size.width) if (x >= boundingRect.origin.x + boundingRect.size.width)
break; break;
@ -71,32 +181,11 @@ class App {
break; break;
y += TILE_SIZE; y += TILE_SIZE;
} }
for (const tile of tiles) {
const newSVG = staticCast(document.createElementNS(SVG_NS, 'svg'), SVGElement);
newSVG.setAttribute('class', "tile");
newSVG.style.left = (GLOBAL_OFFSET.x + tile.origin.x) + "px";
newSVG.style.top = (GLOBAL_OFFSET.y + tile.origin.y) + "px";
newSVG.style.width = TILE_SIZE + "px";
newSVG.style.height = TILE_SIZE + "px";
const newPath = document.createElementNS(SVG_NS, 'path');
newPath.setAttribute('d',
tile.path
.translate(-tile.origin.x, -tile.origin.y)
.toString());
let color = "#";
for (let i = 0; i < 6; i++)
color += Math.floor(Math.random() * 16).toString(16);
newPath.setAttribute('fill', color);
newSVG.appendChild(newPath);
document.body.appendChild(newSVG);
}
} }
document.body.removeChild(svgElement); document.body.removeChild(svgElement);
return tiles;
} }
private clipPathToRect(path: SVGPath, tileBounds: Rect): SVGPath { private clipPathToRect(path: SVGPath, tileBounds: Rect): SVGPath {
@ -240,6 +329,60 @@ class Tile {
} }
} }
class Program<U extends string, A extends string> {
program: WebGLProgram;
uniforms: {[key in U]: WebGLUniformLocation};
attributes: {[key in A]: number};
private vertexShader: WebGLShader;
private fragmentShader: WebGLShader;
constructor(gl: WebGL2RenderingContext,
vertexShaderSource: string,
fragmentShaderSource: string,
uniformNames: U[],
attributeNames: A[]) {
this.vertexShader = unwrapNull(gl.createShader(gl.VERTEX_SHADER));
gl.shaderSource(this.vertexShader, vertexShaderSource);
gl.compileShader(this.vertexShader);
if (!gl.getShaderParameter(this.vertexShader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(this.vertexShader));
throw new Error("Vertex shader compilation failed!");
}
this.fragmentShader = unwrapNull(gl.createShader(gl.FRAGMENT_SHADER));
gl.shaderSource(this.fragmentShader, fragmentShaderSource);
gl.compileShader(this.fragmentShader);
if (!gl.getShaderParameter(this.fragmentShader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(this.fragmentShader));
throw new Error("Fragment shader compilation failed!");
}
this.program = unwrapNull(gl.createProgram());
gl.attachShader(this.program, this.vertexShader);
gl.attachShader(this.program, this.fragmentShader);
gl.linkProgram(this.program);
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(this.program));
throw new Error("Program linking failed!");
}
const uniforms: {[key in U]?: WebGLUniformLocation} = {};
for (const uniformName of uniformNames) {
uniforms[uniformName] = unwrapNull(gl.getUniformLocation(this.program,
"u" + uniformName));
}
this.uniforms = uniforms as {[key in U]: WebGLUniformLocation};
const attributes: {[key in A]?: number} = {};
for (const attributeName of attributeNames) {
attributes[attributeName] = unwrapNull(gl.getAttribLocation(this.program,
"a" + attributeName));
}
this.attributes = attributes as {[key in A]: number};
}
}
function canonicalizePath(path: SVGPath): SVGPath { function canonicalizePath(path: SVGPath): SVGPath {
return path.abs().iterate((segment: string[], index: number, x: number, y: number) => { return path.abs().iterate((segment: string[], index: number, x: number, y: number) => {
if (segment[0] === 'H') if (segment[0] === 'H')