pathfinder/demo2/pathfinder.ts

673 lines
29 KiB
TypeScript
Raw Normal View History

2018-11-14 13:33:53 -05:00
// pathfinder/demo2/pathfinder.ts
//
// 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.
2018-11-14 16:56:45 -05:00
import COVER_VERTEX_SHADER_SOURCE from "./cover.vs.glsl";
import COVER_FRAGMENT_SHADER_SOURCE from "./cover.fs.glsl";
2018-12-03 19:19:10 -05:00
import OPAQUE_VERTEX_SHADER_SOURCE from "./opaque.vs.glsl";
import OPAQUE_FRAGMENT_SHADER_SOURCE from "./opaque.fs.glsl";
2018-11-15 17:37:32 -05:00
import STENCIL_VERTEX_SHADER_SOURCE from "./stencil.vs.glsl";
import STENCIL_FRAGMENT_SHADER_SOURCE from "./stencil.fs.glsl";
2018-11-14 13:33:53 -05:00
import SVG from "../resources/svg/Ghostscript_Tiger.svg";
2018-11-16 23:43:27 -05:00
import AREA_LUT from "../resources/textures/area-lut.png";
2018-11-30 18:42:19 -05:00
import {Matrix2D, Point2D, Rect, Size2D, Vector3D, approxEq, cross, lerp} from "./geometry";
2018-12-03 20:54:44 -05:00
import {flattenPath, Outline} from "./path-utils";
2018-12-02 19:39:22 -05:00
import {SVGPath, TILE_SIZE, TileDebugger, Tiler, testIntervals, TileStrip} from "./tiling";
2018-11-26 21:30:04 -05:00
import {staticCast, unwrapNull} from "./util";
2018-11-14 13:33:53 -05:00
2018-11-15 17:37:32 -05:00
const SVGPath: (path: string) => SVGPath = require('svgpath');
2018-11-15 21:06:19 -05:00
const parseColor: (color: string) => any = require('parse-color');
2018-11-14 13:33:53 -05:00
2018-11-14 16:56:45 -05:00
const SVG_NS: string = "http://www.w3.org/2000/svg";
2018-11-26 13:41:00 -05:00
const STENCIL_FRAMEBUFFER_SIZE: Size2D = {
width: TILE_SIZE.width * 128,
2018-11-26 13:41:00 -05:00
height: TILE_SIZE.height * 256,
};
2018-11-14 13:33:53 -05:00
2018-11-14 16:56:45 -05:00
const QUAD_VERTEX_POSITIONS: Uint8Array = new Uint8Array([
0, 0,
1, 0,
1, 1,
2018-11-15 22:23:42 -05:00
0, 1,
2018-11-14 16:56:45 -05:00
]);
2018-11-14 13:33:53 -05:00
2018-11-16 17:14:16 -05:00
const GLOBAL_TRANSFORM: Matrix2D = new Matrix2D(3.0, 0.0, 0.0, 3.0, 800.0, 550.0);
2018-11-15 21:06:19 -05:00
interface Color {
r: number;
g: number;
b: number;
a: number;
}
2018-11-14 13:33:53 -05:00
type Edge = 'left' | 'top' | 'right' | 'bottom';
class App {
2018-11-14 16:56:45 -05:00
private canvas: HTMLCanvasElement;
2018-11-14 13:33:53 -05:00
private svg: XMLDocument;
2018-11-16 23:43:27 -05:00
private areaLUT: HTMLImageElement;
2018-11-14 13:33:53 -05:00
2018-11-14 16:56:45 -05:00
private gl: WebGL2RenderingContext;
2018-11-16 17:14:16 -05:00
private disjointTimerQueryExt: any;
2018-11-16 23:43:27 -05:00
private areaLUTTexture: WebGLTexture;
2018-11-15 17:37:32 -05:00
private stencilTexture: WebGLTexture;
private stencilFramebuffer: WebGLFramebuffer;
2018-11-16 23:43:27 -05:00
private stencilProgram: Program<'FramebufferSize' | 'TileSize' | 'AreaLUT',
2018-11-26 11:24:09 -05:00
'TessCoord' | 'From' | 'Ctrl' | 'To' | 'TileIndex'>;
2018-12-03 19:19:10 -05:00
private opaqueProgram: Program<'FramebufferSize' | 'TileSize',
'TessCoord' | 'TileOrigin' | 'Color'>;
2018-11-15 17:37:32 -05:00
private coverProgram:
Program<'FramebufferSize' | 'TileSize' | 'StencilTexture' | 'StencilTextureSize',
2018-11-15 21:06:19 -05:00
'TessCoord' | 'TileOrigin' | 'TileIndex' | 'Color'>;
2018-11-14 16:56:45 -05:00
private quadVertexBuffer: WebGLBuffer;
2018-11-15 17:37:32 -05:00
private stencilVertexPositionsBuffer: WebGLBuffer;
private stencilVertexTileIndicesBuffer: WebGLBuffer;
private stencilVertexArray: WebGLVertexArrayObject;
2018-12-03 19:19:10 -05:00
private opaqueVertexBuffer: WebGLBuffer;
private opaqueVertexArray: WebGLVertexArrayObject;
2018-11-14 16:56:45 -05:00
private coverVertexBuffer: WebGLBuffer;
private coverVertexArray: WebGLVertexArrayObject;
2018-11-16 17:14:16 -05:00
private scene: Scene | null;
2018-12-02 19:39:22 -05:00
private primitiveCount: number;
private tileCount: number;
2018-12-03 19:19:10 -05:00
private opaqueTileCount: number;
2018-11-16 17:14:16 -05:00
2018-11-16 23:43:27 -05:00
constructor(svg: XMLDocument, areaLUT: HTMLImageElement) {
2018-11-16 17:14:16 -05:00
const canvas = staticCast(document.getElementById('canvas'), HTMLCanvasElement);
this.canvas = canvas;
2018-11-14 13:33:53 -05:00
this.svg = svg;
2018-11-16 23:43:27 -05:00
this.areaLUT = areaLUT;
2018-11-14 16:56:45 -05:00
2018-11-16 17:14:16 -05:00
const devicePixelRatio = window.devicePixelRatio;
canvas.width = window.innerWidth * devicePixelRatio;
canvas.height = window.innerHeight * devicePixelRatio;
canvas.style.width = window.innerWidth + "px";
canvas.style.height = window.innerHeight + "px";
2018-11-15 17:37:32 -05:00
const gl = unwrapNull(this.canvas.getContext('webgl2', {antialias: false}));
2018-11-14 16:56:45 -05:00
this.gl = gl;
2018-11-15 17:37:32 -05:00
gl.getExtension('EXT_color_buffer_float');
2018-11-16 17:14:16 -05:00
this.disjointTimerQueryExt = gl.getExtension('EXT_disjoint_timer_query');
2018-11-15 17:37:32 -05:00
2018-11-16 23:43:27 -05:00
this.areaLUTTexture = unwrapNull(gl.createTexture());
gl.bindTexture(gl.TEXTURE_2D, this.areaLUTTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, areaLUT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
2018-11-15 17:37:32 -05:00
this.stencilTexture = unwrapNull(gl.createTexture());
gl.bindTexture(gl.TEXTURE_2D, this.stencilTexture);
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.R16F,
2018-11-26 13:41:00 -05:00
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height,
2018-11-15 17:37:32 -05:00
0,
gl.RED,
gl.HALF_FLOAT,
null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
this.stencilFramebuffer = unwrapNull(gl.createFramebuffer());
gl.bindFramebuffer(gl.FRAMEBUFFER, this.stencilFramebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
this.stencilTexture,
0);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)
throw new Error("Stencil framebuffer incomplete!");
2018-11-14 16:56:45 -05:00
const coverProgram = new Program(gl,
COVER_VERTEX_SHADER_SOURCE,
COVER_FRAGMENT_SHADER_SOURCE,
2018-11-15 17:37:32 -05:00
[
'FramebufferSize',
'TileSize',
'StencilTexture',
'StencilTextureSize'
],
2018-11-15 21:06:19 -05:00
['TessCoord', 'TileOrigin', 'TileIndex', 'Color']);
2018-11-14 16:56:45 -05:00
this.coverProgram = coverProgram;
2018-12-03 19:19:10 -05:00
const opaqueProgram = new Program(gl,
OPAQUE_VERTEX_SHADER_SOURCE,
OPAQUE_FRAGMENT_SHADER_SOURCE,
['FramebufferSize', 'TileSize'],
['TessCoord', 'TileOrigin', 'Color']);
this.opaqueProgram = opaqueProgram;
2018-11-15 17:37:32 -05:00
const stencilProgram = new Program(gl,
STENCIL_VERTEX_SHADER_SOURCE,
STENCIL_FRAGMENT_SHADER_SOURCE,
2018-11-16 23:43:27 -05:00
['FramebufferSize', 'TileSize', 'AreaLUT'],
2018-11-26 11:24:09 -05:00
['TessCoord', 'From', 'Ctrl', 'To', 'TileIndex']);
2018-11-15 17:37:32 -05:00
this.stencilProgram = stencilProgram;
// Initialize quad VBO.
2018-11-14 16:56:45 -05:00
this.quadVertexBuffer = unwrapNull(gl.createBuffer());
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTEX_POSITIONS, gl.STATIC_DRAW);
2018-11-15 17:37:32 -05:00
// Initialize stencil VBOs.
this.stencilVertexPositionsBuffer = unwrapNull(gl.createBuffer());
this.stencilVertexTileIndicesBuffer = unwrapNull(gl.createBuffer());
// Initialize stencil VAO.
this.stencilVertexArray = unwrapNull(gl.createVertexArray());
gl.bindVertexArray(this.stencilVertexArray);
gl.useProgram(this.stencilProgram.program);
2018-11-15 22:23:42 -05:00
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.vertexAttribPointer(stencilProgram.attributes.TessCoord,
2,
gl.UNSIGNED_BYTE,
false,
0,
0);
2018-11-15 17:37:32 -05:00
gl.bindBuffer(gl.ARRAY_BUFFER, this.stencilVertexPositionsBuffer);
2018-11-26 11:24:09 -05:00
gl.vertexAttribPointer(stencilProgram.attributes.From, 2, gl.FLOAT, false, 24, 0);
2018-11-15 22:23:42 -05:00
gl.vertexAttribDivisor(stencilProgram.attributes.From, 1);
2018-11-26 11:24:09 -05:00
gl.vertexAttribPointer(stencilProgram.attributes.Ctrl, 2, gl.FLOAT, false, 24, 8);
gl.vertexAttribDivisor(stencilProgram.attributes.Ctrl, 1);
gl.vertexAttribPointer(stencilProgram.attributes.To, 2, gl.FLOAT, false, 24, 16);
2018-11-15 22:23:42 -05:00
gl.vertexAttribDivisor(stencilProgram.attributes.To, 1);
2018-11-15 17:37:32 -05:00
gl.bindBuffer(gl.ARRAY_BUFFER, this.stencilVertexTileIndicesBuffer);
gl.vertexAttribIPointer(stencilProgram.attributes.TileIndex,
1,
gl.UNSIGNED_SHORT,
0,
0);
2018-11-15 22:23:42 -05:00
gl.vertexAttribDivisor(stencilProgram.attributes.TileIndex, 1);
gl.enableVertexAttribArray(stencilProgram.attributes.TessCoord);
gl.enableVertexAttribArray(stencilProgram.attributes.From);
2018-11-26 11:24:09 -05:00
gl.enableVertexAttribArray(stencilProgram.attributes.Ctrl);
2018-11-15 22:23:42 -05:00
gl.enableVertexAttribArray(stencilProgram.attributes.To);
2018-11-15 17:37:32 -05:00
gl.enableVertexAttribArray(stencilProgram.attributes.TileIndex);
2018-12-03 19:19:10 -05:00
// Initialize opaque VBO.
this.opaqueVertexBuffer = unwrapNull(gl.createBuffer());
// Initialize opaque VAO.
this.opaqueVertexArray = unwrapNull(gl.createVertexArray());
gl.bindVertexArray(this.opaqueVertexArray);
gl.useProgram(this.opaqueProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.vertexAttribPointer(opaqueProgram.attributes.TessCoord,
2,
gl.UNSIGNED_BYTE,
false,
0,
0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.opaqueVertexBuffer);
gl.vertexAttribPointer(opaqueProgram.attributes.TileOrigin, 2, gl.SHORT, false, 10, 0);
gl.vertexAttribDivisor(opaqueProgram.attributes.TileOrigin, 1);
gl.vertexAttribPointer(opaqueProgram.attributes.Color, 4, gl.UNSIGNED_BYTE, true, 10, 6);
gl.vertexAttribDivisor(opaqueProgram.attributes.Color, 1);
gl.enableVertexAttribArray(opaqueProgram.attributes.TessCoord);
gl.enableVertexAttribArray(opaqueProgram.attributes.TileOrigin);
gl.enableVertexAttribArray(opaqueProgram.attributes.Color);
2018-11-15 17:37:32 -05:00
// Initialize cover VBO.
2018-11-14 16:56:45 -05:00
this.coverVertexBuffer = unwrapNull(gl.createBuffer());
// Initialize cover VAO.
this.coverVertexArray = unwrapNull(gl.createVertexArray());
gl.bindVertexArray(this.coverVertexArray);
2018-11-15 17:37:32 -05:00
gl.useProgram(this.coverProgram.program);
2018-11-14 16:56:45 -05:00
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);
2018-11-15 21:06:19 -05:00
gl.vertexAttribPointer(coverProgram.attributes.TileOrigin, 2, gl.SHORT, false, 10, 0);
2018-11-14 16:56:45 -05:00
gl.vertexAttribDivisor(coverProgram.attributes.TileOrigin, 1);
2018-11-15 21:06:19 -05:00
gl.vertexAttribIPointer(coverProgram.attributes.TileIndex, 1, gl.UNSIGNED_SHORT, 10, 4);
2018-11-15 17:37:32 -05:00
gl.vertexAttribDivisor(coverProgram.attributes.TileIndex, 1);
2018-11-15 21:06:19 -05:00
gl.vertexAttribPointer(coverProgram.attributes.Color, 4, gl.UNSIGNED_BYTE, true, 10, 6);
gl.vertexAttribDivisor(coverProgram.attributes.Color, 1);
2018-11-14 16:56:45 -05:00
gl.enableVertexAttribArray(coverProgram.attributes.TessCoord);
gl.enableVertexAttribArray(coverProgram.attributes.TileOrigin);
2018-11-15 17:37:32 -05:00
gl.enableVertexAttribArray(coverProgram.attributes.TileIndex);
2018-11-15 21:06:19 -05:00
gl.enableVertexAttribArray(coverProgram.attributes.Color);
2018-11-14 16:56:45 -05:00
2018-11-16 18:34:01 -05:00
// Set up event handlers.
this.canvas.addEventListener('click', event => this.onClick(event), false);
2018-11-16 17:14:16 -05:00
this.scene = null;
this.primitiveCount = 0;
2018-12-02 19:39:22 -05:00
this.tileCount = 0;
2018-12-03 19:19:10 -05:00
this.opaqueTileCount = 0;
2018-11-16 17:14:16 -05:00
}
redraw(): void {
const gl = this.gl, canvas = this.canvas, scene = unwrapNull(this.scene);
// Start timer.
let timerQuery = null;
if (this.disjointTimerQueryExt != null) {
timerQuery = unwrapNull(gl.createQuery());
gl.beginQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT, timerQuery);
}
// Stencil.
gl.bindFramebuffer(gl.FRAMEBUFFER, this.stencilFramebuffer);
2018-11-26 13:41:00 -05:00
gl.viewport(0, 0, STENCIL_FRAMEBUFFER_SIZE.width, STENCIL_FRAMEBUFFER_SIZE.height);
2018-11-16 17:14:16 -05:00
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindVertexArray(this.stencilVertexArray);
gl.useProgram(this.stencilProgram.program);
gl.uniform2f(this.stencilProgram.uniforms.FramebufferSize,
2018-11-26 13:41:00 -05:00
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
gl.uniform2f(this.stencilProgram.uniforms.TileSize, TILE_SIZE.width, TILE_SIZE.height);
2018-11-16 23:43:27 -05:00
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.areaLUTTexture);
gl.uniform1i(this.stencilProgram.uniforms.AreaLUT, 0);
2018-11-16 17:14:16 -05:00
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE);
gl.enable(gl.BLEND);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, unwrapNull(this.primitiveCount));
gl.disable(gl.BLEND);
2018-11-17 00:44:33 -05:00
// Read back stencil and dump it.
/*const totalStencilFramebufferSize = STENCIL_FRAMEBUFFER_SIZE.width *
2018-11-26 21:30:04 -05:00
STENCIL_FRAMEBUFFER_SIZE.height * 4;
const stencilData = new Float32Array(totalStencilFramebufferSize);
2018-11-17 00:44:33 -05:00
gl.readPixels(0, 0,
2018-11-26 21:30:04 -05:00
STENCIL_FRAMEBUFFER_SIZE.width, STENCIL_FRAMEBUFFER_SIZE.height,
2018-11-17 00:44:33 -05:00
gl.RGBA,
gl.FLOAT,
stencilData);
2018-11-26 21:30:04 -05:00
const stencilDumpData = new Uint8ClampedArray(totalStencilFramebufferSize);
2018-11-17 00:44:33 -05:00
for (let i = 0; i < stencilData.length; i++)
stencilDumpData[i] = stencilData[i] * 255.0;
const stencilDumpCanvas = document.createElement('canvas');
2018-11-26 21:30:04 -05:00
stencilDumpCanvas.width = STENCIL_FRAMEBUFFER_SIZE.width;
stencilDumpCanvas.height = STENCIL_FRAMEBUFFER_SIZE.height;
stencilDumpCanvas.style.width =
(STENCIL_FRAMEBUFFER_SIZE.width / window.devicePixelRatio) + "px";
stencilDumpCanvas.style.height =
(STENCIL_FRAMEBUFFER_SIZE.height / window.devicePixelRatio) + "px";
2018-11-17 00:44:33 -05:00
const stencilDumpCanvasContext = unwrapNull(stencilDumpCanvas.getContext('2d'));
const stencilDumpImageData = new ImageData(stencilDumpData,
2018-11-26 21:30:04 -05:00
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
2018-11-17 00:44:33 -05:00
stencilDumpCanvasContext.putImageData(stencilDumpImageData, 0, 0);
document.body.appendChild(stencilDumpCanvas);
//console.log(stencilData);
2018-11-26 21:51:52 -05:00
*/
2018-11-17 00:44:33 -05:00
2018-12-03 19:19:10 -05:00
// Draw opaque tiles.
2018-11-16 17:14:16 -05:00
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
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);
2018-12-03 19:19:10 -05:00
gl.bindVertexArray(this.opaqueVertexArray);
gl.useProgram(this.opaqueProgram.program);
gl.uniform2f(this.opaqueProgram.uniforms.FramebufferSize,
framebufferSize.width,
framebufferSize.height);
gl.uniform2f(this.opaqueProgram.uniforms.TileSize, TILE_SIZE.width, TILE_SIZE.height);
gl.disable(gl.BLEND);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, this.opaqueTileCount);
// Cover.
2018-11-16 17:14:16 -05:00
gl.bindVertexArray(this.coverVertexArray);
gl.useProgram(this.coverProgram.program);
gl.uniform2f(this.coverProgram.uniforms.FramebufferSize,
framebufferSize.width,
framebufferSize.height);
2018-11-26 13:41:00 -05:00
gl.uniform2f(this.coverProgram.uniforms.TileSize, TILE_SIZE.width, TILE_SIZE.height);
2018-11-16 17:14:16 -05:00
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.stencilTexture);
gl.uniform1i(this.coverProgram.uniforms.StencilTexture, 0);
gl.uniform2f(this.coverProgram.uniforms.StencilTextureSize,
2018-11-26 13:41:00 -05:00
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
2018-11-16 17:14:16 -05:00
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
2018-12-02 19:39:22 -05:00
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, this.tileCount);
2018-11-16 17:14:16 -05:00
gl.disable(gl.BLEND);
// End timer.
if (timerQuery != null) {
gl.endQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT);
waitForQuery(gl, this.disjointTimerQueryExt, timerQuery);
}
2018-11-14 13:33:53 -05:00
}
2018-11-16 17:14:16 -05:00
buildScene(): void {
this.scene = new Scene(this.svg);
}
2018-11-14 16:56:45 -05:00
2018-11-16 17:14:16 -05:00
prepare(): void {
const gl = this.gl, scene = unwrapNull(this.scene);
2018-11-15 17:37:32 -05:00
2018-12-03 19:19:10 -05:00
// Construct opaque tile VBOs.
this.opaqueTileCount = 0;
const opaqueVertexData: number[] = [];
const opaqueTiles: number[][] = [];
for (let pathIndex = scene.pathTileStrips.length - 1; pathIndex >= 0; pathIndex--) {
const pathTileStrips = scene.pathTileStrips[pathIndex];
for (const tileStrip of pathTileStrips) {
for (const tile of tileStrip.tiles) {
// TODO(pcwalton)
const color = scene.pathColors[pathIndex];
if (!tile.isFilled())
continue;
if (opaqueTiles[tile.tileLeft] == null)
opaqueTiles[tile.tileLeft] = [];
if (opaqueTiles[tile.tileLeft][tileStrip.tileTop] != null)
continue;
opaqueTiles[tile.tileLeft][tileStrip.tileTop] = pathIndex;
opaqueVertexData.push(Math.floor(tile.tileLeft),
Math.floor(tileStrip.tileTop),
0,
color.r | (color.g << 8),
color.b | (color.a << 8));
this.opaqueTileCount++;
}
}
}
2018-12-02 19:39:22 -05:00
// Construct stencil and cover VBOs.
this.tileCount = 0;
2018-11-16 17:14:16 -05:00
let primitiveCount = 0;
2018-11-15 17:37:32 -05:00
const stencilVertexPositions: number[] = [], stencilVertexTileIndices: number[] = [];
2018-12-02 19:39:22 -05:00
const coverVertexData: number[] = [];
for (let pathIndex = 0; pathIndex < scene.pathTileStrips.length; pathIndex++) {
const pathTileStrips = scene.pathTileStrips[pathIndex];
for (const tileStrip of pathTileStrips) {
for (const tile of tileStrip.tiles) {
2018-12-03 19:19:10 -05:00
const color = scene.pathColors[pathIndex];
2018-12-03 17:08:08 -05:00
if (tile.isFilled())
continue;
2018-12-03 19:19:10 -05:00
if (opaqueTiles[tile.tileLeft] != null &&
opaqueTiles[tile.tileLeft][tileStrip.tileTop] != null &&
pathIndex <= opaqueTiles[tile.tileLeft][tileStrip.tileTop]) {
continue;
}
2018-12-02 19:39:22 -05:00
for (const edge of tile.edges) {
2018-12-03 15:30:12 -05:00
let ctrl;
if (edge.ctrl == null)
ctrl = edge.from.lerp(edge.to, 0.5);
else
ctrl = edge.ctrl;
2018-12-02 19:39:22 -05:00
stencilVertexPositions.push(edge.from.x, edge.from.y,
ctrl.x, ctrl.y,
edge.to.x, edge.to.y);
stencilVertexTileIndices.push(this.tileCount);
primitiveCount++;
}
2018-11-16 00:23:03 -05:00
2018-12-02 19:39:22 -05:00
coverVertexData.push(Math.floor(tile.tileLeft),
Math.floor(tileStrip.tileTop),
this.tileCount,
color.r | (color.g << 8),
color.b | (color.a << 8));
2018-11-16 23:43:27 -05:00
2018-12-02 19:39:22 -05:00
this.tileCount++;
}
}
2018-11-15 17:37:32 -05:00
}
// Populate the stencil VBOs.
gl.bindBuffer(gl.ARRAY_BUFFER, this.stencilVertexPositionsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(stencilVertexPositions), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, this.stencilVertexTileIndicesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Uint16Array(stencilVertexTileIndices), gl.STATIC_DRAW);
2018-12-03 19:19:10 -05:00
// Populate the opaque VBO.
gl.bindBuffer(gl.ARRAY_BUFFER, this.opaqueVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Int16Array(opaqueVertexData), gl.DYNAMIC_DRAW);
2018-11-15 17:37:32 -05:00
// Populate the cover VBO.
2018-11-14 16:56:45 -05:00
gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer);
2018-12-02 19:39:22 -05:00
gl.bufferData(gl.ARRAY_BUFFER, new Int16Array(coverVertexData), gl.DYNAMIC_DRAW);
2018-12-02 21:07:54 -05:00
//console.log(coverVertexData);
2018-11-14 16:56:45 -05:00
2018-11-16 17:14:16 -05:00
this.primitiveCount = primitiveCount;
console.log(primitiveCount + " primitives");
2018-11-14 16:56:45 -05:00
}
2018-11-16 18:34:01 -05:00
private onClick(event: MouseEvent): void {
this.redraw();
}
2018-11-15 21:06:19 -05:00
}
class Scene {
2018-12-02 19:39:22 -05:00
pathTileStrips: TileStrip[][];
2018-11-15 21:06:19 -05:00
pathColors: Color[];
2018-11-14 16:56:45 -05:00
2018-11-15 21:06:19 -05:00
constructor(svg: XMLDocument) {
const svgElement = unwrapNull(svg.documentElement).cloneNode(true);
2018-11-14 13:33:53 -05:00
document.body.appendChild(svgElement);
const pathElements = Array.from(document.getElementsByTagName('path'));
2018-12-03 19:19:10 -05:00
const pathColors: any[] = [];
2018-12-02 19:39:22 -05:00
this.pathTileStrips = [];
2018-11-14 13:33:53 -05:00
2018-12-02 19:39:22 -05:00
//const tileDebugger = new TileDebugger(document);
2018-11-30 18:42:19 -05:00
let fillCount = 0, strokeCount = 0;
2018-12-03 19:19:10 -05:00
const paths: SVGPath[] = [];
2018-12-02 21:07:54 -05:00
for (let pathElementIndex = 0;
2018-11-14 16:56:45 -05:00
pathElementIndex < pathElements.length;
2018-11-14 13:33:53 -05:00
pathElementIndex++) {
const pathElement = pathElements[pathElementIndex];
2018-12-03 19:19:10 -05:00
const pathString = unwrapNull(pathElement.getAttribute('d'));
2018-11-14 13:33:53 -05:00
2018-11-15 21:06:19 -05:00
const style = window.getComputedStyle(pathElement);
if (style.fill != null && style.fill !== 'none') {
fillCount++;
2018-12-03 20:16:44 -05:00
this.addPath(paths, pathColors, style.fill, pathString, null);
2018-12-03 19:19:10 -05:00
}
if (style.stroke != null && style.stroke !== 'none') {
strokeCount++;
2018-12-03 19:19:10 -05:00
const strokeWidth =
style.strokeWidth == null ? 1.0 : parseFloat(style.strokeWidth);
2018-12-03 20:16:44 -05:00
this.addPath(paths, pathColors, style.stroke, pathString, strokeWidth);
2018-11-15 21:06:19 -05:00
}
2018-12-02 21:07:54 -05:00
}
console.log("", fillCount, "fills,", strokeCount, "strokes");
2018-11-26 21:30:04 -05:00
2018-12-02 21:07:54 -05:00
const startTime = window.performance.now();
2018-11-30 18:42:19 -05:00
2018-12-02 21:07:54 -05:00
for (const path of paths) {
2018-12-02 19:39:22 -05:00
const tiler = new Tiler(path);
tiler.tile();
//tileDebugger.addTiler(tiler, paint, "" + realPathIndex);
2018-12-02 21:07:54 -05:00
//console.log("path", pathElementIndex, "tiles", tiler.getStrips());
2018-11-14 13:33:53 -05:00
2018-12-02 19:39:22 -05:00
const pathTileStrips = tiler.getTileStrips();
this.pathTileStrips.push(pathTileStrips);
2018-11-14 13:33:53 -05:00
}
2018-12-02 21:07:54 -05:00
const endTime = window.performance.now();
console.log("elapsed time for tiling: " + (endTime - startTime) + "ms");
2018-11-16 00:23:03 -05:00
/*
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());
const color = pathColors[tile.pathIndex];
newPath.setAttribute('fill',
"rgba(" + color.r + "," + color.g + "," + color.b + "," +
(color.a / 255.0));
newSVG.appendChild(newPath);
document.body.appendChild(newSVG);
}*/
2018-11-14 13:33:53 -05:00
document.body.removeChild(svgElement);
2018-11-14 16:56:45 -05:00
2018-11-30 18:42:19 -05:00
const svgContainer = document.createElement('div');
svgContainer.style.position = 'relative';
svgContainer.style.width = "2000px";
svgContainer.style.height = "2000px";
2018-12-02 19:39:22 -05:00
//svgContainer.appendChild(tileDebugger.svg);
2018-11-30 18:42:19 -05:00
document.body.appendChild(svgContainer);
2018-11-15 21:06:19 -05:00
this.pathColors = pathColors;
}
2018-12-03 19:19:10 -05:00
2018-12-03 20:16:44 -05:00
private addPath(paths: SVGPath[],
pathColors: any[],
paint: string,
pathString: string,
strokeWidth: number | null):
void {
2018-12-03 19:19:10 -05:00
const color = parseColor(paint).rgba;
pathColors.push({
r: color[0],
g: color[1],
b: color[2],
a: Math.round(color[3] * 255.),
});
2018-12-03 20:16:44 -05:00
let path: SVGPath = SVGPath(pathString);
2018-12-03 19:19:10 -05:00
path = path.matrix([
GLOBAL_TRANSFORM.a, GLOBAL_TRANSFORM.b,
GLOBAL_TRANSFORM.c, GLOBAL_TRANSFORM.d,
GLOBAL_TRANSFORM.tx, GLOBAL_TRANSFORM.ty,
]);
path = flattenPath(path);
2018-12-03 20:16:44 -05:00
if (strokeWidth != null) {
const outline = new Outline(path);
outline.calculateNormals();
outline.stroke(strokeWidth * GLOBAL_TRANSFORM.a);
const strokedPathString = outline.toSVGPathString();
path = SVGPath(strokedPathString);
}
2018-12-03 19:19:10 -05:00
paths.push(path);
}
2018-11-14 13:33:53 -05:00
}
2018-11-14 16:56:45 -05:00
class Program<U extends string, A extends string> {
program: WebGLProgram;
2018-11-16 23:43:27 -05:00
uniforms: {[key in U]: WebGLUniformLocation | null};
2018-11-14 16:56:45 -05:00
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!");
}
2018-11-16 23:43:27 -05:00
const uniforms: {[key in U]?: WebGLUniformLocation | null} = {};
for (const uniformName of uniformNames)
uniforms[uniformName] = gl.getUniformLocation(this.program, "u" + uniformName);
this.uniforms = uniforms as {[key in U]: WebGLUniformLocation | null};
2018-11-14 16:56:45 -05:00
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};
}
}
2018-11-16 17:14:16 -05:00
function waitForQuery(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, query: WebGLQuery):
void {
const queryResultAvailable = disjointTimerQueryExt.QUERY_RESULT_AVAILABLE_EXT;
const queryResult = disjointTimerQueryExt.QUERY_RESULT_EXT;
if (!disjointTimerQueryExt.getQueryObjectEXT(query, queryResultAvailable)) {
setTimeout(() => waitForQuery(gl, disjointTimerQueryExt, query), 10);
return;
}
const elapsed = disjointTimerQueryExt.getQueryObjectEXT(query, queryResult) / 1000000.0;
console.log(elapsed + "ms elapsed");
}
2018-11-14 13:33:53 -05:00
function main(): void {
window.fetch(SVG).then(svg => {
svg.text().then(svgText => {
2018-12-02 15:44:02 -05:00
//testIntervals();
2018-11-30 18:42:19 -05:00
2018-11-14 13:33:53 -05:00
const svg = staticCast((new DOMParser).parseFromString(svgText, 'image/svg+xml'),
XMLDocument);
2018-11-16 23:43:27 -05:00
const image = new Image;
image.src = AREA_LUT;
image.addEventListener('load', event => {
try {
const app = new App(svg, image);
app.buildScene();
app.prepare();
app.redraw();
} catch (e) {
console.error("error", e, e.stack);
}
}, false);
2018-11-14 13:33:53 -05:00
});
});
}
document.addEventListener('DOMContentLoaded', () => main(), false);