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-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-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-16 17:14:16 -05:00
|
|
|
const TILE_SIZE: number = 32.0;
|
|
|
|
const STENCIL_FRAMEBUFFER_SIZE: number = TILE_SIZE * 128;
|
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 18:34:01 -05:00
|
|
|
const EPSILON: number = 1e-6;
|
|
|
|
|
2018-11-15 17:37:32 -05:00
|
|
|
interface SVGPath {
|
|
|
|
abs(): SVGPath;
|
|
|
|
translate(x: number, y: number): SVGPath;
|
2018-11-16 17:14:16 -05:00
|
|
|
matrix(m: number[]): SVGPath;
|
2018-11-15 17:37:32 -05:00
|
|
|
iterate(f: (segment: string[], index: number, x: number, y: number) => string[][] | void):
|
|
|
|
SVGPath;
|
|
|
|
}
|
|
|
|
|
2018-11-16 18:34:01 -05:00
|
|
|
class Point2D {
|
2018-11-15 21:06:19 -05:00
|
|
|
x: number;
|
|
|
|
y: number;
|
2018-11-16 18:34:01 -05:00
|
|
|
|
|
|
|
constructor(x: number, y: number) {
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
approxEq(other: Point2D): boolean {
|
|
|
|
return Math.abs(this.x - other.x) <= EPSILON && Math.abs(this.y - other.y) <= EPSILON;
|
|
|
|
}
|
2018-11-15 21:06:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
interface Size2D {
|
|
|
|
width: number;
|
|
|
|
height: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Rect {
|
|
|
|
origin: Point2D;
|
|
|
|
size: Size2D;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Vector3D {
|
|
|
|
x: number;
|
|
|
|
y: number;
|
|
|
|
z: number;
|
|
|
|
}
|
|
|
|
|
2018-11-16 17:14:16 -05:00
|
|
|
class Matrix2D {
|
|
|
|
a: number; b: number;
|
|
|
|
c: number; d: number;
|
|
|
|
tx: number; ty: number;
|
|
|
|
|
|
|
|
constructor(a: number, b: number, c: number, d: number, tx: number, ty: number) {
|
|
|
|
this.a = a; this.b = b;
|
|
|
|
this.c = c; this.d = d;
|
|
|
|
this.tx = tx; this.ty = ty;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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-14 16:56:45 -05:00
|
|
|
private gl: WebGL2RenderingContext;
|
2018-11-16 17:14:16 -05:00
|
|
|
private disjointTimerQueryExt: any;
|
2018-11-15 17:37:32 -05:00
|
|
|
private stencilTexture: WebGLTexture;
|
|
|
|
private stencilFramebuffer: WebGLFramebuffer;
|
|
|
|
private coverProgram:
|
|
|
|
Program<'FramebufferSize' | 'TileSize' | 'StencilTexture' | 'StencilTextureSize',
|
2018-11-15 21:06:19 -05:00
|
|
|
'TessCoord' | 'TileOrigin' | 'TileIndex' | 'Color'>;
|
2018-11-15 22:23:42 -05:00
|
|
|
private stencilProgram: Program<'FramebufferSize' | 'TileSize',
|
|
|
|
'TessCoord' | 'From' | 'To' | 'TileIndex'>;
|
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-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;
|
|
|
|
private primitiveCount: number | null;
|
|
|
|
|
2018-11-14 13:33:53 -05:00
|
|
|
constructor(svg: XMLDocument) {
|
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-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
|
|
|
|
|
|
|
this.stencilTexture = unwrapNull(gl.createTexture());
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, this.stencilTexture);
|
|
|
|
gl.texImage2D(gl.TEXTURE_2D,
|
|
|
|
0,
|
|
|
|
gl.R16F,
|
|
|
|
STENCIL_FRAMEBUFFER_SIZE,
|
|
|
|
STENCIL_FRAMEBUFFER_SIZE,
|
|
|
|
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-11-15 17:37:32 -05:00
|
|
|
const stencilProgram = new Program(gl,
|
|
|
|
STENCIL_VERTEX_SHADER_SOURCE,
|
|
|
|
STENCIL_FRAGMENT_SHADER_SOURCE,
|
|
|
|
['FramebufferSize', 'TileSize'],
|
2018-11-15 22:23:42 -05:00
|
|
|
['TessCoord', 'From', '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-15 22:23:42 -05:00
|
|
|
gl.vertexAttribPointer(stencilProgram.attributes.From, 2, gl.FLOAT, false, 16, 0);
|
|
|
|
gl.vertexAttribDivisor(stencilProgram.attributes.From, 1);
|
|
|
|
gl.vertexAttribPointer(stencilProgram.attributes.To, 2, gl.FLOAT, false, 16, 8);
|
|
|
|
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);
|
|
|
|
gl.enableVertexAttribArray(stencilProgram.attributes.To);
|
2018-11-15 17:37:32 -05:00
|
|
|
gl.enableVertexAttribArray(stencilProgram.attributes.TileIndex);
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
gl.viewport(0, 0, STENCIL_FRAMEBUFFER_SIZE, STENCIL_FRAMEBUFFER_SIZE);
|
|
|
|
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,
|
|
|
|
STENCIL_FRAMEBUFFER_SIZE,
|
|
|
|
STENCIL_FRAMEBUFFER_SIZE);
|
|
|
|
gl.uniform2f(this.stencilProgram.uniforms.TileSize, TILE_SIZE, TILE_SIZE);
|
|
|
|
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);
|
|
|
|
|
|
|
|
// Cover.
|
|
|
|
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);
|
|
|
|
|
|
|
|
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.activeTexture(gl.TEXTURE0);
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, this.stencilTexture);
|
|
|
|
gl.uniform1i(this.coverProgram.uniforms.StencilTexture, 0);
|
|
|
|
gl.uniform2f(this.coverProgram.uniforms.StencilTextureSize,
|
|
|
|
STENCIL_FRAMEBUFFER_SIZE,
|
|
|
|
STENCIL_FRAMEBUFFER_SIZE);
|
|
|
|
gl.blendEquation(gl.FUNC_ADD);
|
|
|
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
|
|
gl.enable(gl.BLEND);
|
|
|
|
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, scene.tiles.length);
|
|
|
|
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);
|
|
|
|
console.log(this.scene.tiles.length, "tiles");
|
|
|
|
}
|
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
|
|
|
|
|
|
|
// Construct stencil VBOs.
|
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-11-15 21:06:19 -05:00
|
|
|
for (let tileIndex = 0; tileIndex < scene.tiles.length; tileIndex++) {
|
|
|
|
const tile = scene.tiles[tileIndex];
|
2018-11-16 00:23:03 -05:00
|
|
|
let firstPoint = {x: 0.0, y: 0.0}, lastPoint = {x: 0.0, y: 0.0};
|
2018-11-15 17:37:32 -05:00
|
|
|
tile.path.iterate(segment => {
|
2018-11-16 00:23:03 -05:00
|
|
|
let point;
|
|
|
|
if (segment[0] === 'Z') {
|
|
|
|
point = firstPoint;
|
|
|
|
} else {
|
|
|
|
point = {
|
2018-11-16 18:34:01 -05:00
|
|
|
x: parseFloat(segment[segment.length - 2]),
|
|
|
|
y: parseFloat(segment[segment.length - 1]),
|
2018-11-16 00:23:03 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-11-16 18:34:01 -05:00
|
|
|
/*
|
2018-11-15 17:37:32 -05:00
|
|
|
if (!(point.x > -1.0))
|
|
|
|
throw new Error("x too low");
|
|
|
|
if (!(point.y > -1.0))
|
|
|
|
throw new Error("y too low");
|
|
|
|
if (!(point.x < TILE_SIZE + 1.0))
|
|
|
|
throw new Error("x too high:" + point.x);
|
|
|
|
if (!(point.y < TILE_SIZE + 1.0))
|
|
|
|
throw new Error("y too high");
|
2018-11-16 18:34:01 -05:00
|
|
|
*/
|
2018-11-16 00:23:03 -05:00
|
|
|
|
|
|
|
if (segment[0] === 'M') {
|
|
|
|
firstPoint = point;
|
|
|
|
} else {
|
2018-11-15 17:37:32 -05:00
|
|
|
stencilVertexPositions.push(lastPoint.x, lastPoint.y, point.x, point.y);
|
2018-11-15 22:23:42 -05:00
|
|
|
stencilVertexTileIndices.push(tileIndex);
|
2018-11-16 17:14:16 -05:00
|
|
|
primitiveCount++;
|
2018-11-15 17:37:32 -05:00
|
|
|
}
|
|
|
|
lastPoint = point;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
console.log(stencilVertexPositions);
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
// Populate the cover VBO.
|
2018-11-15 21:06:19 -05:00
|
|
|
const coverVertexBufferData = new Int16Array(scene.tiles.length * 5);
|
|
|
|
for (let tileIndex = 0; tileIndex < scene.tiles.length; tileIndex++) {
|
|
|
|
const tile = scene.tiles[tileIndex];
|
|
|
|
const color = scene.pathColors[tile.pathIndex];
|
|
|
|
coverVertexBufferData[tileIndex * 5 + 0] = Math.floor(tile.origin.x);
|
|
|
|
coverVertexBufferData[tileIndex * 5 + 1] = Math.floor(tile.origin.y);
|
|
|
|
coverVertexBufferData[tileIndex * 5 + 2] = tileIndex;
|
|
|
|
coverVertexBufferData[tileIndex * 5 + 3] = color.r | (color.g << 8);
|
|
|
|
coverVertexBufferData[tileIndex * 5 + 4] = color.b | (color.a << 8);
|
2018-11-14 16:56:45 -05:00
|
|
|
}
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer);
|
|
|
|
gl.bufferData(gl.ARRAY_BUFFER, coverVertexBufferData, gl.DYNAMIC_DRAW);
|
|
|
|
console.log(coverVertexBufferData);
|
|
|
|
|
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 {
|
|
|
|
tiles: Tile[];
|
|
|
|
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-11-15 21:06:19 -05:00
|
|
|
const tiles: Tile[] = [], pathColors = [];
|
2018-11-14 13:33:53 -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-11-15 21:06:19 -05:00
|
|
|
const style = window.getComputedStyle(pathElement);
|
|
|
|
let paint: string;
|
|
|
|
if (style.fill != null && style.fill !== 'none') {
|
|
|
|
paint = style.fill;
|
2018-11-15 22:23:42 -05:00
|
|
|
/*} else if (style.stroke != null && style.stroke !== 'none') {
|
|
|
|
paint = style.stroke;*/
|
2018-11-15 21:06:19 -05:00
|
|
|
} else {
|
|
|
|
pathColors.push({r: 0, g: 0, b: 0, a: 0});
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const color = parseColor(paint).rgba;
|
|
|
|
pathColors.push({
|
|
|
|
r: color[0],
|
|
|
|
g: color[1],
|
|
|
|
b: color[2],
|
|
|
|
a: Math.round(color[3] * 255.),
|
|
|
|
});
|
|
|
|
|
2018-11-16 17:14:16 -05:00
|
|
|
let path = SVGPath(unwrapNull(pathElement.getAttribute('d')));
|
|
|
|
path = path.matrix([
|
|
|
|
GLOBAL_TRANSFORM.a, GLOBAL_TRANSFORM.b,
|
|
|
|
GLOBAL_TRANSFORM.c, GLOBAL_TRANSFORM.d,
|
|
|
|
GLOBAL_TRANSFORM.tx, GLOBAL_TRANSFORM.ty,
|
|
|
|
]);
|
|
|
|
|
2018-11-16 18:38:41 -05:00
|
|
|
path = flattenPath(path);
|
2018-11-15 17:37:32 -05:00
|
|
|
path = canonicalizePath(path);
|
2018-11-14 13:33:53 -05:00
|
|
|
const boundingRect = this.boundingRectOfPath(path);
|
|
|
|
|
2018-11-14 16:56:45 -05:00
|
|
|
/*console.log("path " + pathElementIndex, path.toString(), ":",
|
|
|
|
boundingRect.origin.x,
|
|
|
|
boundingRect.origin.y,
|
|
|
|
boundingRect.size.width,
|
|
|
|
boundingRect.size.height);*/
|
2018-11-14 13:33:53 -05:00
|
|
|
|
2018-11-15 21:06:19 -05:00
|
|
|
|
2018-11-14 16:56:45 -05:00
|
|
|
let y = boundingRect.origin.y - boundingRect.origin.y % TILE_SIZE;
|
2018-11-14 13:33:53 -05:00
|
|
|
while (true) {
|
2018-11-14 16:56:45 -05:00
|
|
|
let x = boundingRect.origin.x - boundingRect.origin.x % TILE_SIZE;
|
2018-11-14 13:33:53 -05:00
|
|
|
while (true) {
|
|
|
|
const tileBounds = {
|
2018-11-16 18:34:01 -05:00
|
|
|
origin: new Point2D(x, y),
|
2018-11-14 13:33:53 -05:00
|
|
|
size: {width: TILE_SIZE, height: TILE_SIZE},
|
|
|
|
};
|
|
|
|
const tilePath = this.clipPathToRect(path, tileBounds);
|
|
|
|
|
2018-11-16 18:34:01 -05:00
|
|
|
if (tilePath.toString().length > 0) {
|
|
|
|
tilePath.translate(-tileBounds.origin.x, -tileBounds.origin.y);
|
|
|
|
if (!pathIsSquare(tilePath, TILE_SIZE))
|
|
|
|
tiles.push(new Tile(pathElementIndex, tilePath, tileBounds.origin));
|
|
|
|
}
|
2018-11-14 13:33:53 -05:00
|
|
|
|
|
|
|
if (x >= boundingRect.origin.x + boundingRect.size.width)
|
|
|
|
break;
|
|
|
|
x += TILE_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (y >= boundingRect.origin.y + boundingRect.size.height)
|
|
|
|
break;
|
|
|
|
y += TILE_SIZE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-16 18:34:01 -05:00
|
|
|
console.log(tiles);
|
2018-11-15 21:06:19 -05:00
|
|
|
this.tiles = tiles;
|
|
|
|
this.pathColors = pathColors;
|
|
|
|
}
|
|
|
|
|
|
|
|
private boundingRectOfPath(path: SVGPath): Rect {
|
|
|
|
let minX: number | null = null, minY: number | null = null;
|
|
|
|
let maxX: number | null = null, maxY: number | null = null;
|
|
|
|
path.iterate(segment => {
|
|
|
|
for (let i = 1; i < segment.length; i += 2) {
|
|
|
|
const x = parseFloat(segment[i]), y = parseFloat(segment[i + 1]);
|
|
|
|
minX = minX == null ? x : Math.min(minX, x);
|
|
|
|
minY = minY == null ? y : Math.min(minY, y);
|
|
|
|
maxX = maxX == null ? x : Math.max(maxX, x);
|
|
|
|
maxY = maxY == null ? y : Math.max(maxY, y);
|
|
|
|
//console.log("x", x, "y", y, "maxX", maxX, "maxY", maxY, "segment", segment);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (minX == null || minY == null || maxX == null || maxY == null)
|
2018-11-16 18:34:01 -05:00
|
|
|
return {origin: new Point2D(0, 0), size: {width: 0, height: 0}};
|
|
|
|
return {origin: new Point2D(minX, minY), size: {width: maxX - minX, height: maxY - minY}};
|
2018-11-14 13:33:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
private clipPathToRect(path: SVGPath, tileBounds: Rect): SVGPath {
|
|
|
|
path = this.clipPathToEdge('left', tileBounds.origin.x, path);
|
|
|
|
path = this.clipPathToEdge('top', tileBounds.origin.y, path);
|
|
|
|
path = this.clipPathToEdge('right', tileBounds.origin.x + tileBounds.size.width, path);
|
|
|
|
path = this.clipPathToEdge('bottom', tileBounds.origin.y + tileBounds.size.height, path);
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
private clipPathToEdge(edge: Edge, edgePos: number, input: SVGPath): SVGPath {
|
2018-11-16 18:34:01 -05:00
|
|
|
let pathStart: Point2D | null = null, from = new Point2D(0, 0), firstPoint = false;
|
2018-11-14 13:33:53 -05:00
|
|
|
let output: string[][] = [];
|
2018-11-15 17:37:32 -05:00
|
|
|
input.iterate(segment => {
|
2018-11-14 13:33:53 -05:00
|
|
|
const event = segment[0];
|
|
|
|
let to;
|
|
|
|
switch (event) {
|
|
|
|
case 'M':
|
2018-11-16 18:34:01 -05:00
|
|
|
from = new Point2D(parseFloat(segment[segment.length - 2]),
|
|
|
|
parseFloat(segment[segment.length - 1]));
|
2018-11-14 13:33:53 -05:00
|
|
|
pathStart = from;
|
|
|
|
firstPoint = true;
|
|
|
|
return;
|
|
|
|
case 'Z':
|
|
|
|
if (pathStart == null)
|
|
|
|
return;
|
|
|
|
to = pathStart;
|
|
|
|
break;
|
|
|
|
default:
|
2018-11-16 18:34:01 -05:00
|
|
|
to = new Point2D(parseFloat(segment[segment.length - 2]),
|
|
|
|
parseFloat(segment[segment.length - 1]));
|
2018-11-14 13:33:53 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.pointIsInside(edge, edgePos, to)) {
|
|
|
|
if (!this.pointIsInside(edge, edgePos, from)) {
|
|
|
|
this.addLine(this.computeLineIntersection(edge, edgePos, from, to),
|
|
|
|
output,
|
|
|
|
firstPoint);
|
|
|
|
firstPoint = false;
|
|
|
|
}
|
|
|
|
this.addLine(to, output, firstPoint);
|
|
|
|
firstPoint = false;
|
|
|
|
} else if (this.pointIsInside(edge, edgePos, from)) {
|
|
|
|
this.addLine(this.computeLineIntersection(edge, edgePos, from, to),
|
|
|
|
output,
|
|
|
|
firstPoint);
|
|
|
|
firstPoint = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
from = to;
|
|
|
|
|
|
|
|
if (event === 'Z') {
|
|
|
|
output.push(['Z']);
|
|
|
|
pathStart = null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return SVGPath(output.map(segment => segment.join(" ")).join(" "));
|
|
|
|
}
|
|
|
|
|
|
|
|
private addLine(to: Point2D, output: string[][], firstPoint: boolean) {
|
|
|
|
if (firstPoint)
|
|
|
|
output.push(['M', "" + to.x, "" + to.y]);
|
|
|
|
else
|
|
|
|
output.push(['L', "" + to.x, "" + to.y]);
|
|
|
|
}
|
|
|
|
|
|
|
|
private pointIsInside(edge: Edge, edgePos: number, point: Point2D): boolean {
|
|
|
|
switch (edge) {
|
|
|
|
case 'left': return point.x >= edgePos;
|
|
|
|
case 'top': return point.y >= edgePos;
|
|
|
|
case 'right': return point.x <= edgePos;
|
|
|
|
case 'bottom': return point.y <= edgePos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private computeLineIntersection(edge: Edge,
|
|
|
|
edgePos: number,
|
|
|
|
startPoint: Point2D,
|
|
|
|
endpoint: Point2D):
|
|
|
|
Point2D {
|
|
|
|
const start = {x: startPoint.x, y: startPoint.y, z: 1.0};
|
|
|
|
const end = {x: endpoint.x, y: endpoint.y, z: 1.0};
|
|
|
|
|
|
|
|
let edgeVector: Vector3D;
|
|
|
|
switch (edge) {
|
|
|
|
case 'left':
|
|
|
|
case 'right':
|
|
|
|
edgeVector = {x: 1.0, y: 0.0, z: -edgePos};
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
edgeVector = {x: 0.0, y: 1.0, z: -edgePos};
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-11-15 21:06:19 -05:00
|
|
|
const intersection = cross(cross(start, end), edgeVector);
|
2018-11-16 18:34:01 -05:00
|
|
|
return new Point2D(intersection.x / intersection.z, intersection.y / intersection.z);
|
2018-11-14 13:33:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class Tile {
|
|
|
|
pathIndex: number;
|
|
|
|
path: SVGPath;
|
|
|
|
origin: Point2D;
|
|
|
|
|
|
|
|
constructor(pathIndex: number, path: SVGPath, origin: Point2D) {
|
|
|
|
this.pathIndex = pathIndex;
|
|
|
|
this.path = path;
|
|
|
|
this.origin = origin;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-14 16:56:45 -05:00
|
|
|
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};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-16 18:38:41 -05:00
|
|
|
class PathSegment {
|
|
|
|
command: string;
|
|
|
|
points: Point2D[];
|
|
|
|
|
|
|
|
constructor(segment: string[]) {
|
|
|
|
const points = [];
|
|
|
|
for (let i = 1; i < segment.length; i += 2)
|
|
|
|
points.push(new Point2D(parseFloat(segment[i]), parseFloat(segment[i + 1])));
|
|
|
|
this.points = points;
|
|
|
|
this.command = segment[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function flattenPath(path: SVGPath): SVGPath {
|
|
|
|
return path.abs().iterate(segment => {
|
|
|
|
if (segment[0] === 'Q')
|
|
|
|
return [['L', segment[1], segment[2]], ['L', segment[3], segment[4]]];
|
|
|
|
if (segment[0] === 'C') {
|
|
|
|
return [
|
|
|
|
['L', segment[1], segment[2]],
|
|
|
|
['L', segment[3], segment[4]],
|
|
|
|
['L', segment[5], segment[6]],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
return [segment];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-11-14 13:33:53 -05:00
|
|
|
function canonicalizePath(path: SVGPath): SVGPath {
|
2018-11-15 17:37:32 -05:00
|
|
|
return path.abs().iterate(segment => {
|
2018-11-14 13:33:53 -05:00
|
|
|
if (segment[0] === 'H')
|
|
|
|
return [['L', segment[1], '0']];
|
|
|
|
if (segment[0] === 'V')
|
|
|
|
return [['L', '0', segment[1]]];
|
|
|
|
return [segment];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-11-15 21:06:19 -05:00
|
|
|
function cross(a: Vector3D, b: Vector3D): Vector3D {
|
|
|
|
return {
|
|
|
|
x: a.y*b.z - a.z*b.y,
|
|
|
|
y: a.z*b.x - a.x*b.z,
|
|
|
|
z: a.x*b.y - a.y*b.x,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
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-16 18:34:01 -05:00
|
|
|
function pathIsSquare(path: SVGPath, squareLength: number): boolean {
|
|
|
|
const SQUARE_VERTICES = [
|
|
|
|
new Point2D(0.0, 0.0),
|
|
|
|
new Point2D(0.0, squareLength),
|
|
|
|
new Point2D(squareLength, squareLength),
|
|
|
|
new Point2D(squareLength, 0.0),
|
|
|
|
];
|
|
|
|
let result = true;
|
|
|
|
path.iterate((segment, index) => {
|
|
|
|
if (index < SQUARE_VERTICES.length) {
|
|
|
|
const point = new Point2D(parseFloat(segment[1]), parseFloat(segment[2]));
|
|
|
|
result = result && point.approxEq(SQUARE_VERTICES[index]);
|
|
|
|
} else if (index === SQUARE_VERTICES.length) {
|
|
|
|
const point = new Point2D(parseFloat(segment[1]), parseFloat(segment[2]));
|
|
|
|
result = result && (segment[0] === 'Z' || point.approxEq(SQUARE_VERTICES[0]));
|
|
|
|
} else if (index === SQUARE_VERTICES.length + 1) {
|
|
|
|
result = result && segment[0] === 'Z';
|
|
|
|
} else {
|
|
|
|
result = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-11-14 13:33:53 -05:00
|
|
|
function main(): void {
|
|
|
|
window.fetch(SVG).then(svg => {
|
|
|
|
svg.text().then(svgText => {
|
|
|
|
const svg = staticCast((new DOMParser).parseFromString(svgText, 'image/svg+xml'),
|
|
|
|
XMLDocument);
|
2018-11-15 17:37:32 -05:00
|
|
|
try {
|
2018-11-16 17:14:16 -05:00
|
|
|
const app = new App(svg);
|
|
|
|
app.buildScene();
|
|
|
|
app.prepare();
|
|
|
|
app.redraw();
|
2018-11-15 17:37:32 -05:00
|
|
|
} catch (e) {
|
|
|
|
console.error("error", e, e.stack);
|
|
|
|
}
|
2018-11-14 13:33:53 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => main(), false);
|
|
|
|
|
|
|
|
function staticCast<T>(value: any, constructor: { new(...args: any[]): T }): T {
|
|
|
|
if (!(value instanceof constructor))
|
|
|
|
throw new Error("Invalid dynamic cast");
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
function unwrapNull<T>(value: T | null): T {
|
|
|
|
if (value == null)
|
|
|
|
throw new Error("Unexpected null");
|
|
|
|
return value;
|
|
|
|
}
|