Remove the old Pathfinder 3 demo

This commit is contained in:
Patrick Walton 2019-02-08 14:41:06 -08:00
parent 4062f824fd
commit 39d6b1d9c2
18 changed files with 0 additions and 4386 deletions

14
.gitignore vendored
View File

@ -1,18 +1,4 @@
/font-renderer/target
/partitioner/target
/path-utils/target
/utils/frontend/target
/utils/gamma-lut/target /utils/gamma-lut/target
/demo/client/target
/demo/client/*.html
/demo/client/*.js
/demo/client/src/*.js
/demo/client/src/*.js.map
/demo/client/node_modules
/demo/client/package-lock.json
/demo/server/target
/demo/server/Rocket.toml
/demo2/dist
.DS_Store .DS_Store
target target
node_modules node_modules

View File

@ -1,26 +0,0 @@
#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;
uniform sampler2D uStencilTexture;
in vec2 vTexCoord;
in float vBackdrop;
in vec4 vColor;
out vec4 oFragColor;
void main() {
float coverage = abs(texture(uStencilTexture, vTexCoord).r + vBackdrop);
oFragColor = vec4(vColor.rgb, vColor.a * coverage);
}

View File

@ -1,45 +0,0 @@
#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;
uniform vec2 uStencilTextureSize;
uniform sampler2D uFillColorsTexture;
uniform vec2 uFillColorsTextureSize;
uniform vec2 uViewBoxOrigin;
in vec2 aTessCoord;
in vec2 aTileOrigin;
in int aBackdrop;
in uint aObject;
out vec2 vTexCoord;
out float vBackdrop;
out vec4 vColor;
vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x);
uvec2 tileOffset = uvec2(tileIndex % tilesPerRow, tileIndex / tilesPerRow);
return vec2(tileOffset) * uTileSize;
}
void main() {
uint tileIndex = uint(gl_InstanceID);
vec2 position = (aTileOrigin + aTessCoord) * uTileSize + uViewBoxOrigin;
vec2 texCoord = computeTileOffset(tileIndex, uStencilTextureSize.x) + aTessCoord * uTileSize;
vTexCoord = texCoord / uStencilTextureSize;
vBackdrop = float(aBackdrop);
vColor = texture(uFillColorsTexture, vec2(float(aObject) / uFillColorsTextureSize.x, 0.0));
gl_Position = vec4((position / uFramebufferSize * 2.0 - 1.0) * vec2(1.0, -1.0), 0.0, 1.0);
}

View File

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

View File

@ -1,151 +0,0 @@
// pathfinder/demo2/geometry.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.
export const EPSILON: number = 1e-6;
export class Point2D {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
Object.freeze(this);
}
approxEq(other: Point2D, epsilon: number | undefined): boolean {
return approxEq(this.x, other.x, epsilon) && approxEq(this.y, other.y, epsilon);
}
lerp(other: Point2D, t: number): Point2D {
return new Point2D(lerp(this.x, other.x, t), lerp(this.y, other.y, t));
}
translate(x: number, y: number): Point2D {
return new Point2D(this.x + x, this.y + y);
}
add(other: Point2D): Point2D {
return new Point2D(this.x + other.x, this.y + other.y);
}
sub(other: Point2D): Point2D {
return new Point2D(this.x - other.x, this.y - other.y);
}
normalize(): Point2D {
const length = this.length();
return new Point2D(this.x / length, this.y / length);
}
length(): number {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
scale(factor: number): Point2D {
return new Point2D(this.x * factor, this.y * factor);
}
}
export class Size2D {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
Object.freeze(this);
}
}
export class Rect {
origin: Point2D;
size: Size2D;
constructor(origin: Point2D, size: Size2D) {
this.origin = origin;
this.size = size;
Object.freeze(this);
}
unionWithPoint(point: Point2D): Rect {
let newOrigin = this.origin, newSize = this.size;
if (point.x < this.origin.x) {
newSize = {
width: newSize.width + newOrigin.x - point.x,
height: newSize.height,
};
newOrigin = new Point2D(point.x, newOrigin.y);
} else if (point.x > this.maxX()) {
newSize = {
width: newSize.width + point.x - this.maxX(),
height: newSize.height,
};
}
if (point.y < this.origin.y) {
newSize = {
width: newSize.width,
height: newSize.height + newOrigin.y - point.y,
};
newOrigin = new Point2D(newOrigin.x, point.y);
} else if (point.y > this.maxY()) {
newSize = {
width: newSize.width,
height: newSize.height + point.y - this.maxY(),
};
}
return new Rect(newOrigin, newSize);
}
maxX(): number {
return this.origin.x + this.size.width;
}
maxY(): number {
return this.origin.y + this.size.height;
}
}
export interface Vector3D {
x: number;
y: number;
z: number;
}
export 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;
}
}
export function approxEq(a: number, b: number, epsilon: number | undefined): boolean {
return Math.abs(a - b) <= (epsilon == null ? EPSILON : epsilon);
}
export function lerp(a: number, b: number, t: number): number {
return a + (b - a) * t;
}
export 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,
};
}

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Pathfinder Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="pathfinder.scss" />
</head>
<body>
<canvas id=canvas width=640 height=480></canvas>
<label class="btn btn-primary m-2" id="open-wrapper">
Open…<input type="file" id="open" hidden>
</label>
<script src="pathfinder.ts"></script>
</body>
</html>

View File

@ -1,21 +0,0 @@
#version 300 es
// pathfinder/demo2/opaque.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;
in vec4 vColor;
out vec4 oFragColor;
void main() {
oFragColor = vColor;
}

View File

@ -1,31 +0,0 @@
#version 300 es
// pathfinder/demo2/opaque.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;
uniform sampler2D uFillColorsTexture;
uniform vec2 uFillColorsTextureSize;
uniform vec2 uViewBoxOrigin;
in vec2 aTessCoord;
in vec2 aTileOrigin;
in uint aObject;
out vec4 vColor;
void main() {
vec2 position = (aTileOrigin + aTessCoord) * uTileSize + uViewBoxOrigin;
vColor = texture(uFillColorsTexture, vec2(float(aObject) / uFillColorsTextureSize.x, 0.0));
gl_Position = vec4((position / uFramebufferSize * 2.0 - 1.0) * vec2(1.0, -1.0), 0.0, 1.0);
}

2165
demo2/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +0,0 @@
{
"devDependencies": {
"glslify-bundle": "^5.1.1",
"glslify-deps": "^1.3.1",
"sass": "^1.15.2",
"typescript": "^3.1.6"
},
"dependencies": {
"@types/webgl2": "0.0.4",
"bootstrap": "^4.1.3",
"parse-color": "^1.0.0",
"svg-path-outline": "^1.0.1",
"svgpath": "^2.2.1"
}
}

View File

@ -1,301 +0,0 @@
// pathfinder/demo2/path-utils.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.
import {Point2D, Rect, EPSILON} from "./geometry";
import {SVGPath, Edge} from "./tiling";
import { ENGINE_METHOD_DIGESTS } from "constants";
import { AssertionError } from "assert";
import { unwrapNull, unwrapUndef } from "./util";
const SVGPath: (path: string) => SVGPath = require('svgpath');
export 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];
}
to(): Point2D | null {
return this.points[this.points.length - 1];
}
toStringPieces(): string[] {
const pieces = [this.command];
for (const point of this.points) {
pieces.push(" " + point.x);
pieces.push(" " + point.y);
}
return pieces;
}
toString(): string {
return this.toStringPieces().join(" ");
}
}
export function flattenPath(path: SVGPath): SVGPath {
let lastPoint: Point2D | null = null;
return path.unshort().abs().iterate(segmentPieces => {
let segment = new PathSegment(segmentPieces);
if (segment.command === 'C' && lastPoint != null) {
const cubicEdge = new CubicEdge(lastPoint,
segment.points[0],
segment.points[1],
segment.points[2]);
//console.log("cubic edge", cubicEdge);
const edges: Edge[] = cubicEdge.toQuadraticEdges();
/*const edges: Edge[] = [
new Edge(lastPoint,
segment.points[0].lerp(segment.points[1], 0.5),
segment.points[2]),
];*/
const newSegments = edges.map(edge => edge.toSVGPieces());
//console.log("... resulting new segments:", newSegments);
lastPoint = segment.to();
return newSegments;
}
if (segment.command === 'H' && lastPoint != null)
segment = new PathSegment(['L', segmentPieces[1], "" + lastPoint.y]);
if (segment.command === 'V' && lastPoint != null)
segment = new PathSegment(['L', "" + lastPoint.x, segmentPieces[1]]);
lastPoint = segment.to();
return [segment.toStringPieces()];
});
}
export function makePathMonotonic(path: SVGPath): SVGPath {
let lastPoint: Point2D | null = null;
return path.iterate(segmentPieces => {
let segment = new PathSegment(segmentPieces);
if (segment.command === 'Q' && lastPoint != null) {
const edge = new Edge(lastPoint, segment.points[0], segment.points[1]);
const minX = Math.min(edge.from.x, edge.to.x);
const maxX = Math.max(edge.from.x, edge.to.x);
const edgesX: Edge[] = [];
if (edge.ctrl!.x < minX || edge.ctrl!.x > maxX) {
const t = (edge.from.x - edge.ctrl!.x) /
(edge.from.x - 2.0 * edge.ctrl!.x + edge.to.x);
const subdivided = edge.subdivideAt(t);
if (t < -EPSILON || t > 1.0 + EPSILON)
throw new Error("Bad t value when making monotonic X!");
edgesX.push(subdivided.prev, subdivided.next);
} else {
edgesX.push(edge);
}
const newEdges = [];
for (const edge of edgesX) {
const minY = Math.min(edge.from.y, edge.to.y);
const maxY = Math.max(edge.from.y, edge.to.y);
if (edge.ctrl!.y < minY || edge.ctrl!.y > maxY) {
const t = (edge.from.y - edge.ctrl!.y) /
(edge.from.y - 2.0 * edge.ctrl!.y + edge.to.y);
if (t < -EPSILON || t > 1.0 + EPSILON)
throw new Error("Bad t value when making monotonic Y!");
const subdivided = edge.subdivideAt(t);
newEdges.push(subdivided.prev, subdivided.next);
} else {
newEdges.push(edge);
}
}
lastPoint = segment.to();
return newEdges.map(newEdge => newEdge.toSVGPieces());
}
lastPoint = segment.to();
return [segment.toStringPieces()];
});
}
export class Outline {
suboutlines: Suboutline[];
constructor(path: SVGPath) {
this.suboutlines = [];
let suboutline = new Suboutline;
path.iterate(segmentPieces => {
const segment = new PathSegment(segmentPieces);
if (segment.command === 'M') {
if (!suboutline.isEmpty()) {
this.suboutlines.push(suboutline);
suboutline = new Suboutline;
}
}
for (let pointIndex = 0; pointIndex < segment.points.length; pointIndex++) {
suboutline.points.push(new OutlinePoint(segment.points[pointIndex],
pointIndex < segment.points.length - 1));
}
});
if (!suboutline.isEmpty())
this.suboutlines.push(suboutline);
}
calculateNormals(): void {
for (const suboutline of this.suboutlines)
suboutline.calculateNormals();
}
stroke(radius: number): void {
for (const suboutline of this.suboutlines)
suboutline.stroke(radius);
}
toSVGPathString(): string {
return this.suboutlines.map(suboutline => suboutline.toSVGPathString()).join(" ");
}
}
export class Suboutline {
points: OutlinePoint[];
normals: Point2D[] | null;
constructor() {
this.points = [];
this.normals = null;
}
isEmpty(): boolean {
return this.points.length === 0;
}
calculateNormals(): void {
this.normals = [];
for (let pointIndex = 0; pointIndex < this.points.length; pointIndex++) {
const prevPointIndex = pointIndex === 0 ? this.points.length - 1 : pointIndex - 1;
const nextPointIndex = pointIndex === this.points.length - 1 ? 0 : pointIndex + 1;
const prevPoint = this.points[prevPointIndex].position;
const point = this.points[pointIndex].position;
const nextPoint = this.points[nextPointIndex].position;
let prevVector = prevPoint.sub(point), nextVector = nextPoint.sub(point);
this.normals.push(prevVector.add(nextVector).normalize());
}
}
stroke(radius: number): void {
if (this.normals == null)
throw new Error("Calculate normals first!");
const newPoints = [];
for (let pointIndex = 0; pointIndex < this.points.length; pointIndex++) {
const point = this.points[pointIndex], normal = this.normals[pointIndex];
const newPosition = point.position.sub(normal.scale(radius));
newPoints.push(new OutlinePoint(newPosition, point.offCurve));
}
for (let pointIndex = this.points.length - 1; pointIndex >= 0; pointIndex--) {
const point = this.points[pointIndex], normal = this.normals[pointIndex];
const newPosition = point.position.add(normal.scale(radius));
newPoints.push(new OutlinePoint(newPosition, point.offCurve));
}
this.points = newPoints;
this.normals = null;
}
toSVGPathString(): string {
let string = "";
const queuedPositions = [];
for (let pointIndex = 0; pointIndex < this.points.length; pointIndex++) {
const point = this.points[pointIndex];
queuedPositions.push(point.position);
if (pointIndex > 0 && point.offCurve)
continue;
let command: string;
if (pointIndex === 0)
command = 'M';
else if (queuedPositions.length === 1)
command = 'L';
else
command = 'Q';
string += command + " ";
for (const position of queuedPositions)
string += position.x + " " + position.y + " ";
queuedPositions.splice(0);
}
string += "Z";
return string;
}
}
export class OutlinePoint {
position: Point2D;
offCurve: boolean;
constructor(position: Point2D, offCurve: boolean) {
this.position = position;
this.offCurve = offCurve;
}
}
class CubicEdge {
from: Point2D;
ctrl0: Point2D;
ctrl1: Point2D;
to: Point2D;
constructor(from: Point2D, ctrl0: Point2D, ctrl1: Point2D, to: Point2D) {
this.from = from;
this.ctrl0 = ctrl0;
this.ctrl1 = ctrl1;
this.to = to;
}
subdivideAt(t: number): SubdividedCubicEdges {
const p0 = this.from, p1 = this.ctrl0, p2 = this.ctrl1, p3 = this.to;
const p01 = p0.lerp(p1, t), p12 = p1.lerp(p2, t), p23 = p2.lerp(p3, t);
const p012 = p01.lerp(p12, t), p123 = p12.lerp(p23, t);
const p0123 = p012.lerp(p123, t);
return {
prev: new CubicEdge(p0, p01, p012, p0123),
next: new CubicEdge(p0123, p123, p23, p3),
};
}
toQuadraticEdges(): Edge[] {
const MAX_APPROXIMATION_ITERATIONS: number = 32;
const TOLERANCE: number = 0.1;
const results = [], worklist: CubicEdge[] = [this];
while (worklist.length > 0) {
let current = unwrapUndef(worklist.pop());
for (let iteration = 0; iteration < MAX_APPROXIMATION_ITERATIONS; iteration++) {
const deltaCtrl0 = current.from.sub(current.ctrl0.scale(3.0))
.add(current.ctrl1.scale(3.0).sub(current.to));
const deltaCtrl1 = current.ctrl0.scale(3.0)
.sub(current.from)
.add(current.to.sub(current.ctrl1.scale(3.0)));
const maxError = Math.max(deltaCtrl0.length(), deltaCtrl1.length()) / 6.0;
if (maxError < TOLERANCE)
break;
const subdivided = current.subdivideAt(0.5);
worklist.push(subdivided.next);
current = subdivided.prev;
}
const approxCtrl0 = current.ctrl0.scale(3.0).sub(current.from).scale(0.5);
const approxCtrl1 = current.ctrl1.scale(3.0).sub(current.to).scale(0.5);
results.push(new Edge(current.from, approxCtrl0.lerp(approxCtrl1, 0.5), current.to));
}
return results;
}
}
interface SubdividedCubicEdges {
prev: CubicEdge;
next: CubicEdge;
}

View File

@ -1,13 +0,0 @@
@import "./node_modules/bootstrap/scss/bootstrap.scss";
html, body {
height: 100%;
width: 100%;
}
#open-wrapper {
display: block;
position: absolute;
right: 0;
bottom: 0;
}

View File

@ -1,734 +0,0 @@
// 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.
import COVER_VERTEX_SHADER_SOURCE from "./cover.vs.glsl";
import COVER_FRAGMENT_SHADER_SOURCE from "./cover.fs.glsl";
import OPAQUE_VERTEX_SHADER_SOURCE from "./opaque.vs.glsl";
import OPAQUE_FRAGMENT_SHADER_SOURCE from "./opaque.fs.glsl";
import STENCIL_VERTEX_SHADER_SOURCE from "./stencil.vs.glsl";
import STENCIL_FRAGMENT_SHADER_SOURCE from "./stencil.fs.glsl";
import AREA_LUT from "../resources/textures/area-lut.png";
import {Matrix2D, Size2D, Rect, Point2D} from "./geometry";
import {SVGPath, TILE_SIZE} from "./tiling";
import {staticCast, unwrapNull} from "./util";
const SVGPath: (path: string) => SVGPath = require('svgpath');
const STENCIL_FRAMEBUFFER_SIZE: Size2D = {
width: TILE_SIZE.width * 256,
height: TILE_SIZE.height * 256,
};
const QUAD_VERTEX_POSITIONS: Uint8Array = new Uint8Array([
0, 0,
1, 0,
1, 1,
0, 1,
]);
const FILL_INSTANCE_SIZE: number = 8;
const SOLID_TILE_INSTANCE_SIZE: number = 6;
const MASK_TILE_INSTANCE_SIZE: number = 8;
interface Color {
r: number;
g: number;
b: number;
a: number;
}
type Edge = 'left' | 'top' | 'right' | 'bottom';
type FillProgram =
Program<'FramebufferSize' | 'TileSize' | 'AreaLUT',
'TessCoord' | 'FromPx' | 'ToPx' | 'FromSubpx' | 'ToSubpx' | 'TileIndex'>;
type SolidTileProgram =
Program<'FramebufferSize' |
'TileSize' |
'FillColorsTexture' | 'FillColorsTextureSize' |
'ViewBoxOrigin',
'TessCoord' | 'TileOrigin' | 'Object'>;
type MaskTileProgram =
Program<'FramebufferSize' |
'TileSize' |
'StencilTexture' | 'StencilTextureSize' |
'FillColorsTexture' | 'FillColorsTextureSize' |
'ViewBoxOrigin',
'TessCoord' | 'TileOrigin' | 'Backdrop' | 'Object'>;
class App {
private canvas: HTMLCanvasElement;
private openButton: HTMLInputElement;
private areaLUT: HTMLImageElement;
private gl: WebGL2RenderingContext;
private disjointTimerQueryExt: any;
private areaLUTTexture: WebGLTexture;
private fillColorsTexture: WebGLTexture;
private stencilTexture: WebGLTexture;
private stencilFramebuffer: WebGLFramebuffer;
private fillProgram: FillProgram;
private solidTileProgram: SolidTileProgram;
private maskTileProgram: MaskTileProgram;
private quadVertexBuffer: WebGLBuffer;
private solidTileVertexBuffer: WebGLBuffer;
private solidVertexArray: WebGLVertexArrayObject;
private batchBuffers: BatchBuffers[];
private viewBox: Rect;
private solidTileCount: number;
private shaderCount: number;
constructor(areaLUT: HTMLImageElement) {
const canvas = staticCast(document.getElementById('canvas'), HTMLCanvasElement);
const openButton = staticCast(document.getElementById('open'), HTMLInputElement);
this.canvas = canvas;
this.openButton = openButton;
this.areaLUT = areaLUT;
this.openButton.addEventListener('change', event => this.loadFile(), false);
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";
const gl = unwrapNull(this.canvas.getContext('webgl2', {antialias: false}));
this.gl = gl;
gl.getExtension('EXT_color_buffer_float');
this.disjointTimerQueryExt = gl.getExtension('EXT_disjoint_timer_query');
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);
this.fillColorsTexture = unwrapNull(gl.createTexture());
/*
const benchData = new Uint8Array(1600 * 1600 * 4);
for (let i = 0; i < benchData.length; i++)
benchData[i] = (Math.random() * 256) | 0;
const benchTexture = unwrapNull(gl.createTexture());
gl.bindTexture(gl.TEXTURE_2D, benchTexture);
const startTime = performance.now();
for (let i = 0; i < 100; i++)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1600, 1600, 0, gl.RGBA, gl.UNSIGNED_BYTE, benchData);
const elapsedTime = (performance.now() - startTime) / 100;
console.log("texture upload: ", elapsedTime, "ms");
*/
this.stencilTexture = unwrapNull(gl.createTexture());
gl.bindTexture(gl.TEXTURE_2D, this.stencilTexture);
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.R16F,
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height,
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!");
const maskTileProgram = new Program(gl,
COVER_VERTEX_SHADER_SOURCE,
COVER_FRAGMENT_SHADER_SOURCE,
[
'FramebufferSize',
'TileSize',
'StencilTexture',
'StencilTextureSize',
'FillColorsTexture',
'FillColorsTextureSize',
'ViewBoxOrigin',
],
[
'TessCoord',
'TileOrigin',
'TileIndex',
'Backdrop',
'Object',
]);
this.maskTileProgram = maskTileProgram;
const solidTileProgram = new Program(gl,
OPAQUE_VERTEX_SHADER_SOURCE,
OPAQUE_FRAGMENT_SHADER_SOURCE,
[
'FramebufferSize',
'TileSize',
'FillColorsTexture',
'FillColorsTextureSize',
'ViewBoxOrigin',
],
['TessCoord', 'TileOrigin', 'Object']);
this.solidTileProgram = solidTileProgram;
const fillProgram = new Program(gl,
STENCIL_VERTEX_SHADER_SOURCE,
STENCIL_FRAGMENT_SHADER_SOURCE,
['FramebufferSize', 'TileSize', 'AreaLUT'],
[
'TessCoord',
'FromPx', 'ToPx',
'FromSubpx', 'ToSubpx',
'TileIndex'
]);
this.fillProgram = fillProgram;
// Initialize quad VBO.
this.quadVertexBuffer = unwrapNull(gl.createBuffer());
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTEX_POSITIONS, gl.STATIC_DRAW);
// Initialize tile VBOs and IBOs.
this.solidTileVertexBuffer = unwrapNull(gl.createBuffer());
// Initialize solid tile VAO.
this.solidVertexArray = unwrapNull(gl.createVertexArray());
gl.bindVertexArray(this.solidVertexArray);
gl.useProgram(this.solidTileProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.vertexAttribPointer(solidTileProgram.attributes.TessCoord,
2,
gl.UNSIGNED_BYTE,
false,
0,
0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.solidTileVertexBuffer);
gl.vertexAttribPointer(solidTileProgram.attributes.TileOrigin,
2,
gl.SHORT,
false,
SOLID_TILE_INSTANCE_SIZE,
0);
gl.vertexAttribDivisor(solidTileProgram.attributes.TileOrigin, 1);
gl.vertexAttribIPointer(solidTileProgram.attributes.Object,
1,
gl.UNSIGNED_SHORT,
SOLID_TILE_INSTANCE_SIZE,
4);
gl.vertexAttribDivisor(solidTileProgram.attributes.Object, 1);
gl.enableVertexAttribArray(solidTileProgram.attributes.TessCoord);
gl.enableVertexAttribArray(solidTileProgram.attributes.TileOrigin);
gl.enableVertexAttribArray(solidTileProgram.attributes.Object);
this.batchBuffers = [];
this.viewBox = new Rect(new Point2D(0.0, 0.0), new Size2D(0.0, 0.0));
// Set up event handlers.
this.canvas.addEventListener('click', event => this.onClick(event), false);
this.solidTileCount = 0;
this.shaderCount = 0;
}
redraw(): void {
const gl = this.gl, canvas = this.canvas;
//console.log("viewBox", this.viewBox);
// Initialize timers.
let fillTimerQuery = null, solidTimerQuery = null, maskTimerQuery = null;
if (this.disjointTimerQueryExt != null) {
fillTimerQuery = unwrapNull(gl.createQuery());
solidTimerQuery = unwrapNull(gl.createQuery());
maskTimerQuery = unwrapNull(gl.createQuery());
}
// Clear.
if (solidTimerQuery != null)
gl.beginQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT, solidTimerQuery);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
const framebufferSize = {width: canvas.width, height: canvas.height};
gl.viewport(0, 0, framebufferSize.width, framebufferSize.height);
gl.clearColor(0.85, 0.85, 0.85, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw solid tiles.
gl.bindVertexArray(this.solidVertexArray);
gl.useProgram(this.solidTileProgram.program);
gl.uniform2f(this.solidTileProgram.uniforms.FramebufferSize,
framebufferSize.width,
framebufferSize.height);
gl.uniform2f(this.solidTileProgram.uniforms.TileSize, TILE_SIZE.width, TILE_SIZE.height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.fillColorsTexture);
gl.uniform1i(this.solidTileProgram.uniforms.FillColorsTexture, 0);
// FIXME(pcwalton): Maybe this should be an ivec2 or uvec2?
gl.uniform2f(this.solidTileProgram.uniforms.FillColorsTextureSize,
this.shaderCount,
1.0);
gl.uniform2f(this.solidTileProgram.uniforms.ViewBoxOrigin,
this.viewBox.origin.x,
this.viewBox.origin.y);
gl.disable(gl.BLEND);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, this.solidTileCount);
if (solidTimerQuery != null)
gl.endQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT);
// Draw batches.
if (fillTimerQuery != null)
gl.beginQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT, fillTimerQuery);
for (const batch of this.batchBuffers) {
// Fill.
gl.bindFramebuffer(gl.FRAMEBUFFER, this.stencilFramebuffer);
gl.viewport(0, 0, STENCIL_FRAMEBUFFER_SIZE.width, STENCIL_FRAMEBUFFER_SIZE.height);
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindVertexArray(batch.fillVertexArray);
gl.useProgram(this.fillProgram.program);
gl.uniform2f(this.fillProgram.uniforms.FramebufferSize,
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
gl.uniform2f(this.fillProgram.uniforms.TileSize, TILE_SIZE.width, TILE_SIZE.height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.areaLUTTexture);
gl.uniform1i(this.fillProgram.uniforms.AreaLUT, 0);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE);
gl.enable(gl.BLEND);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, unwrapNull(batch.fillPrimitiveCount));
console.log("drawing ", batch.fillPrimitiveCount, " fills");
gl.disable(gl.BLEND);
// Read back stencil and dump it.
//this.dumpStencil();
// Draw masked tiles.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, framebufferSize.width, framebufferSize.height);
gl.bindVertexArray(batch.maskVertexArray);
gl.useProgram(this.maskTileProgram.program);
gl.uniform2f(this.maskTileProgram.uniforms.FramebufferSize,
framebufferSize.width,
framebufferSize.height);
gl.uniform2f(this.maskTileProgram.uniforms.TileSize,
TILE_SIZE.width,
TILE_SIZE.height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.stencilTexture);
gl.uniform1i(this.maskTileProgram.uniforms.StencilTexture, 0);
gl.uniform2f(this.maskTileProgram.uniforms.StencilTextureSize,
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, this.fillColorsTexture);
gl.uniform1i(this.maskTileProgram.uniforms.FillColorsTexture, 1);
// FIXME(pcwalton): Maybe this should be an ivec2 or uvec2?
gl.uniform2f(this.maskTileProgram.uniforms.FillColorsTextureSize,
this.shaderCount,
1.0);
gl.uniform2f(this.maskTileProgram.uniforms.ViewBoxOrigin,
this.viewBox.origin.x,
this.viewBox.origin.y);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
gl.enable(gl.BLEND);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, batch.maskTileCount);
gl.disable(gl.BLEND);
}
if (fillTimerQuery != null)
gl.endQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT);
// End timer.
if (fillTimerQuery != null && solidTimerQuery != null) {
processQueries(gl, this.disjointTimerQueryExt, {
fill: fillTimerQuery,
solid: solidTimerQuery,
});
}
}
private dumpStencil(): void {
const gl = this.gl;
const totalStencilFramebufferSize = STENCIL_FRAMEBUFFER_SIZE.width *
STENCIL_FRAMEBUFFER_SIZE.height * 4;
const stencilData = new Float32Array(totalStencilFramebufferSize);
gl.readPixels(0, 0,
STENCIL_FRAMEBUFFER_SIZE.width, STENCIL_FRAMEBUFFER_SIZE.height,
gl.RGBA,
gl.FLOAT,
stencilData);
const stencilDumpData = new Uint8ClampedArray(totalStencilFramebufferSize);
for (let i = 0; i < stencilData.length; i++)
stencilDumpData[i] = stencilData[i] * 255.0;
const stencilDumpCanvas = document.createElement('canvas');
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";
const stencilDumpCanvasContext = unwrapNull(stencilDumpCanvas.getContext('2d'));
const stencilDumpImageData = new ImageData(stencilDumpData,
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
stencilDumpCanvasContext.putImageData(stencilDumpImageData, 0, 0);
document.body.appendChild(stencilDumpCanvas);
//console.log(stencilData);
}
private loadFile(): void {
console.log("loadFile");
// TODO(pcwalton)
const file = unwrapNull(unwrapNull(this.openButton.files)[0]);
const reader = new FileReader;
reader.addEventListener('loadend', () => {
const gl = this.gl;
const arrayBuffer = staticCast(reader.result, ArrayBuffer);
const root = new RIFFChunk(new DataView(arrayBuffer));
for (const subchunk of root.subchunks(4)) {
const self = this;
const id = subchunk.stringID();
if (id === 'head') {
const headerData = subchunk.contents();
const version = headerData.getUint32(0, true);
if (version !== 0)
throw new Error("Unknown version!");
// Ignore the batch count and fetch the view box.
this.viewBox = new Rect(new Point2D(headerData.getFloat32(8, true),
headerData.getFloat32(12, true)),
new Size2D(headerData.getFloat32(16, true),
headerData.getFloat32(20, true)));
continue;
} else if (id === 'soli') {
self.solidTileCount = uploadArrayBuffer(subchunk,
this.solidTileVertexBuffer,
SOLID_TILE_INSTANCE_SIZE);
} else if (id === 'shad') {
this.shaderCount = subchunk.length() / 4;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.fillColorsTexture);
const textureDataView = subchunk.contents();
const textureData = new Uint8Array(textureDataView.buffer,
textureDataView.byteOffset,
textureDataView.byteLength);
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.RGBA,
this.shaderCount,
1,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
textureData);
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);
} else if (id === 'batc') {
const batch = new BatchBuffers(this.gl,
this.fillProgram,
this.maskTileProgram,
this.quadVertexBuffer);
for (const subsubchunk of subchunk.subchunks()) {
const id = subsubchunk.stringID();
console.log("id=", id);
if (id === 'fill') {
batch.fillPrimitiveCount = uploadArrayBuffer(subsubchunk,
batch.fillVertexBuffer,
FILL_INSTANCE_SIZE);
} else if (id === 'mask') {
batch.maskTileCount = uploadArrayBuffer(subsubchunk,
batch.maskTileVertexBuffer,
MASK_TILE_INSTANCE_SIZE);
}
}
this.batchBuffers.push(batch);
}
function uploadArrayBuffer(chunk: RIFFChunk,
buffer: WebGLBuffer,
instanceSize: number):
number {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, chunk.contents(), gl.DYNAMIC_DRAW);
return chunk.length() / instanceSize;
}
}
this.redraw();
}, false);
reader.readAsArrayBuffer(file);
}
private onClick(event: MouseEvent): void {
this.redraw();
}
}
class BatchBuffers {
fillVertexBuffer: WebGLBuffer;
fillVertexArray: WebGLVertexArrayObject;
maskTileVertexBuffer: WebGLBuffer;
maskVertexArray: WebGLVertexArrayObject;
fillPrimitiveCount: number;
maskTileCount: number;
constructor(gl: WebGL2RenderingContext,
fillProgram: FillProgram,
maskTileProgram: MaskTileProgram,
quadVertexBuffer: WebGLBuffer) {
// Initialize fill VBOs.
this.fillVertexBuffer = unwrapNull(gl.createBuffer());
// Initialize fill VAO.
this.fillVertexArray = unwrapNull(gl.createVertexArray());
gl.bindVertexArray(this.fillVertexArray);
gl.useProgram(fillProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, quadVertexBuffer);
gl.vertexAttribPointer(fillProgram.attributes.TessCoord,
2,
gl.UNSIGNED_BYTE,
false,
0,
0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.fillVertexBuffer);
gl.vertexAttribIPointer(fillProgram.attributes.FromPx,
1,
gl.UNSIGNED_BYTE,
FILL_INSTANCE_SIZE,
0);
gl.vertexAttribDivisor(fillProgram.attributes.FromPx, 1);
gl.vertexAttribIPointer(fillProgram.attributes.ToPx,
1,
gl.UNSIGNED_BYTE,
FILL_INSTANCE_SIZE,
1);
gl.vertexAttribDivisor(fillProgram.attributes.ToPx, 1);
gl.vertexAttribPointer(fillProgram.attributes.FromSubpx,
2,
gl.UNSIGNED_BYTE,
true,
FILL_INSTANCE_SIZE,
2);
gl.vertexAttribDivisor(fillProgram.attributes.FromSubpx, 1);
gl.vertexAttribPointer(fillProgram.attributes.ToSubpx,
2,
gl.UNSIGNED_BYTE,
true,
FILL_INSTANCE_SIZE,
4);
gl.vertexAttribDivisor(fillProgram.attributes.ToSubpx, 1);
gl.vertexAttribIPointer(fillProgram.attributes.TileIndex,
1,
gl.UNSIGNED_SHORT,
FILL_INSTANCE_SIZE,
6);
gl.vertexAttribDivisor(fillProgram.attributes.TileIndex, 1);
gl.enableVertexAttribArray(fillProgram.attributes.TessCoord);
gl.enableVertexAttribArray(fillProgram.attributes.FromPx);
gl.enableVertexAttribArray(fillProgram.attributes.ToPx);
gl.enableVertexAttribArray(fillProgram.attributes.FromSubpx);
gl.enableVertexAttribArray(fillProgram.attributes.ToSubpx);
gl.enableVertexAttribArray(fillProgram.attributes.TileIndex);
// Initialize tile VBOs.
this.maskTileVertexBuffer = unwrapNull(gl.createBuffer());
// Initialize mask tile VAO.
this.maskVertexArray = unwrapNull(gl.createVertexArray());
gl.bindVertexArray(this.maskVertexArray);
gl.useProgram(maskTileProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, quadVertexBuffer);
gl.vertexAttribPointer(maskTileProgram.attributes.TessCoord,
2,
gl.UNSIGNED_BYTE,
false,
0,
0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.maskTileVertexBuffer);
gl.vertexAttribPointer(maskTileProgram.attributes.TileOrigin,
2,
gl.SHORT,
false,
MASK_TILE_INSTANCE_SIZE,
0);
gl.vertexAttribDivisor(maskTileProgram.attributes.TileOrigin, 1);
gl.vertexAttribIPointer(maskTileProgram.attributes.Backdrop,
1,
gl.SHORT,
MASK_TILE_INSTANCE_SIZE,
4);
gl.vertexAttribDivisor(maskTileProgram.attributes.Backdrop, 1);
gl.vertexAttribIPointer(maskTileProgram.attributes.Object,
1,
gl.UNSIGNED_SHORT,
MASK_TILE_INSTANCE_SIZE,
6);
gl.vertexAttribDivisor(maskTileProgram.attributes.Object, 1);
gl.enableVertexAttribArray(maskTileProgram.attributes.TessCoord);
gl.enableVertexAttribArray(maskTileProgram.attributes.TileOrigin);
gl.enableVertexAttribArray(maskTileProgram.attributes.Backdrop);
gl.enableVertexAttribArray(maskTileProgram.attributes.Object);
this.fillPrimitiveCount = 0;
this.maskTileCount = 0;
}
}
class Program<U extends string, A extends string> {
program: WebGLProgram;
uniforms: {[key in U]: WebGLUniformLocation | null};
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 | null} = {};
for (const uniformName of uniformNames)
uniforms[uniformName] = gl.getUniformLocation(this.program, "u" + uniformName);
this.uniforms = uniforms as {[key in U]: WebGLUniformLocation | null};
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};
}
}
class RIFFChunk {
private data: DataView;
constructor(data: DataView) {
this.data = data;
}
stringID(): string {
return String.fromCharCode(this.data.getUint8(0),
this.data.getUint8(1),
this.data.getUint8(2),
this.data.getUint8(3));
}
length(): number {
return this.data.getUint32(4, true);
}
contents(): DataView {
return new DataView(this.data.buffer, this.data.byteOffset + 8, this.length());
}
subchunks(initialOffset?: number | undefined): RIFFChunk[] {
const subchunks = [];
const contents = this.contents(), length = this.length();
let offset = initialOffset == null ? 0 : initialOffset;
while (offset < length) {
const subchunk = new RIFFChunk(new DataView(contents.buffer,
contents.byteOffset + offset,
length - offset));
subchunks.push(subchunk);
offset += subchunk.length() + 8;
}
return subchunks;
}
}
interface Queries {
fill: WebGLQuery;
solid: WebGLQuery;
};
function getQueryResult(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, query: WebGLQuery):
Promise<number> {
function go(resolve: (n: number) => void): void {
const queryResultAvailable = disjointTimerQueryExt.QUERY_RESULT_AVAILABLE_EXT;
const queryResult = disjointTimerQueryExt.QUERY_RESULT_EXT;
if (!disjointTimerQueryExt.getQueryObjectEXT(query, queryResultAvailable)) {
setTimeout(() => go(resolve), 10);
return;
}
resolve(disjointTimerQueryExt.getQueryObjectEXT(query, queryResult) / 1000000.0);
}
return new Promise((resolve, reject) => go(resolve));
}
function processQueries(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, queries: Queries):
void {
Promise.all([
getQueryResult(gl, disjointTimerQueryExt, queries.fill),
getQueryResult(gl, disjointTimerQueryExt, queries.solid),
]).then(results => {
const [fillResult, solidResult] = results;
console.log(fillResult, "ms fill/mask,", solidResult, "ms solid");
});
}
function loadAreaLUT(): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {;
const image = new Image;
image.src = AREA_LUT;
image.addEventListener('load', event => resolve(image), false);
});
}
function main(): void {
loadAreaLUT().then(image => new App(image));
}
document.addEventListener('DOMContentLoaded', () => main(), false);

View File

@ -1,42 +0,0 @@
#version 300 es
// pathfinder/demo2/stencil.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;
uniform sampler2D uAreaLUT;
in vec2 vFrom;
in vec2 vTo;
out vec4 oFragColor;
void main() {
// Unpack.
vec2 from = vFrom, to = vTo;
// Determine winding, and sort into a consistent order so we only need to find one root below.
bool winding = from.x < to.x;
vec2 left = winding ? from : to, right = winding ? to : from;
// Shoot a vertical ray toward the curve.
vec2 window = clamp(vec2(from.x, to.x), -0.5, 0.5);
float offset = mix(window.x, window.y, 0.5) - left.x;
float t = offset / (right.x - left.x);
// Compute position and derivative to form a line approximation.
float y = mix(left.y, right.y, t);
float d = (right.y - left.y) / (right.x - left.x);
// Look up area under that line, and scale horizontally to the window size.
float dX = window.x - window.y;
oFragColor = vec4(texture(uAreaLUT, vec2(y + 8.0, abs(d * dX)) / 16.0).r * dX);
}

View File

@ -1,58 +0,0 @@
#version 300 es
// pathfinder/demo2/stencil.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 uint aFromPx;
in uint aToPx;
in vec2 aFromSubpx;
in vec2 aToSubpx;
in uint aTileIndex;
out vec2 vFrom;
out vec2 vTo;
vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x);
uvec2 tileOffset = uvec2(aTileIndex % tilesPerRow, aTileIndex / tilesPerRow);
return vec2(tileOffset) * uTileSize;
}
void main() {
vec2 tileOrigin = computeTileOffset(aTileIndex, uFramebufferSize.x);
vec2 from = vec2(aFromPx & 15u, aFromPx >> 4u) + aFromSubpx;
vec2 to = vec2(aToPx & 15u, aToPx >> 4u) + aToSubpx;
vec2 position;
bool zeroArea = !(abs(from.x - to.x) > 0.1) || !(abs(uTileSize.y - min(from.y, to.y)) > 0.1);
if (aTessCoord.x < 0.5)
position.x = floor(min(from.x, to.x));
else
position.x = ceil(max(from.x, to.x));
if (aTessCoord.y < 0.5)
position.y = floor(min(from.y, to.y));
else
position.y = uTileSize.y;
vFrom = from - position;
vTo = to - position;
if (zeroArea)
gl_Position = vec4(0.0);
else
gl_Position = vec4((tileOrigin + position) / uFramebufferSize * 2.0 - 1.0, 0.0, 1.0);
}

View File

@ -1,710 +0,0 @@
// pathfinder/demo2/tiling.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.
import {Point2D, Rect, Size2D, cross, lerp} from "./geometry";
import {PathSegment} from "./path-utils";
import {panic, staticCast, unwrapNull} from "./util";
export const TILE_SIZE: Size2D = {width: 16.0, height: 16.0};
export interface SVGPath {
abs(): SVGPath;
translate(x: number, y: number): SVGPath;
matrix(m: number[]): SVGPath;
iterate(f: (segment: string[], index: number, x: number, y: number) => string[][] | void):
SVGPath;
unshort(): SVGPath;
}
const SVGPath: (path: string) => SVGPath = require('svgpath');
interface EndpointIndex {
subpathIndex: number;
endpointIndex: number;
};
export class Tiler {
private path: SVGPath;
private endpoints: SubpathEndpoints[];
private sortedEdges: Edge[];
private boundingRect: Rect | null;
private strips: Strip[];
private tileStrips: TileStrip[];
constructor(path: SVGPath) {
this.path = path;
//console.log("tiler: path=", path);
// Accumulate endpoints.
this.endpoints = [];
let currentSubpathEndpoints = new SubpathEndpoints;
path.iterate(segString => {
const segment = new PathSegment(segString);
//console.log("segment", segment);
switch (segment.command) {
case 'M':
if (!currentSubpathEndpoints.isEmpty()) {
this.endpoints.push(currentSubpathEndpoints);
currentSubpathEndpoints = new SubpathEndpoints;
}
currentSubpathEndpoints.controlPoints.push(null);
currentSubpathEndpoints.endpoints.push(segment.points[0]);
break;
case 'L':
case 'S':
// TODO(pcwalton): Canonicalize 'S'.
currentSubpathEndpoints.controlPoints.push(null);
currentSubpathEndpoints.endpoints.push(unwrapNull(segment.to()));
break;
case 'Q':
currentSubpathEndpoints.controlPoints.push(segment.points[0]);
currentSubpathEndpoints.endpoints.push(unwrapNull(segment.to()));
break;
case 'Z':
break;
default:
panic("Unexpected path command: " + segment.command);
break;
}
});
if (!currentSubpathEndpoints.isEmpty())
this.endpoints.push(currentSubpathEndpoints);
// Sort edges, and accumulate bounding rect.
this.sortedEdges = [];
this.boundingRect = null;
for (let subpathIndex = 0; subpathIndex < this.endpoints.length; subpathIndex++) {
const subpathEndpoints = this.endpoints[subpathIndex];
for (let endpointIndex = 0;
endpointIndex < subpathEndpoints.endpoints.length;
endpointIndex++) {
this.sortedEdges.push(this.nextEdgeFromEndpoint({subpathIndex, endpointIndex}));
const endpoint = subpathEndpoints.endpoints[endpointIndex];
if (this.boundingRect == null)
this.boundingRect = new Rect(endpoint, {width: 0, height: 0});
else
this.boundingRect = this.boundingRect.unionWithPoint(endpoint);
}
}
this.sortedEdges.sort((edgeA, edgeB) => {
return Math.min(edgeA.from.y, edgeA.to.y) - Math.min(edgeB.from.y, edgeB.to.y);
});
/*
// Dump endpoints.
const allEndpoints = this.sortedEndpointIndices.map(index => {
return {
index: index,
endpoint: this.endpoints[index.subpathIndex].endpoints[index.endpointIndex],
};
});
//console.log("allEndpoints", allEndpoints);
*/
this.strips = [];
this.tileStrips = [];
}
tile(): void {
if (this.boundingRect == null)
return;
const activeIntervals = new Intervals(this.boundingRect.maxX());;
let activeEdges: Edge[] = [];
let nextEdgeIndex = 0;
this.strips = [];
let tileTop = this.boundingRect.origin.y - this.boundingRect.origin.y % TILE_SIZE.height;
while (tileTop < this.boundingRect.maxY()) {
const strip = new Strip(tileTop);
const tileBottom = tileTop + TILE_SIZE.height;
// Populate tile strip with active intervals.
// TODO(pcwalton): Compress this.
for (const interval of activeIntervals.intervalRanges()) {
if (interval.winding === 0)
continue;
const startPoint = new Point2D(interval.start, tileTop);
const endPoint = new Point2D(interval.end, tileTop);
if (interval.winding < 0)
strip.pushEdge(new Edge(startPoint, null, endPoint));
else
strip.pushEdge(new Edge(endPoint, null, startPoint));
}
// Populate tile strip with active edges.
const oldEdges = activeEdges;
activeEdges = [];
for (const activeEdge of oldEdges)
this.processEdgeY(activeEdge, strip, activeEdges, activeIntervals, tileTop);
while (nextEdgeIndex < this.sortedEdges.length) {
const edge = this.sortedEdges[nextEdgeIndex];
if (edge.from.y > tileBottom && edge.to.y > tileBottom)
break;
this.processEdgeY(edge, strip, activeEdges, activeIntervals, tileTop);
//console.log("new intervals:", JSON.stringify(activeIntervals));
nextEdgeIndex++;
}
this.strips.push(strip);
tileTop = tileBottom;
}
// Cut up tile strips.
this.tileStrips = [];
for (const strip of this.strips) {
const tileStrip = this.divideStrip(strip);
if (!tileStrip.isEmpty())
this.tileStrips.push(tileStrip);
}
}
private divideStrip(strip: Strip): TileStrip {
// Sort edges.
const sortedEdges = strip.edges.slice(0);
sortedEdges.sort((edgeA, edgeB) => {
return Math.min(edgeA.from.x, edgeA.to.x) - Math.min(edgeB.from.x, edgeB.to.x);
});
const tileStrip = new TileStrip(strip.tileTop);
const boundingRect = unwrapNull(this.boundingRect);
let tileLeft = boundingRect.origin.x - boundingRect.origin.x % TILE_SIZE.width;
let activeEdges: Edge[] = [];
let nextEdgeIndex = 0;
while (tileLeft < boundingRect.maxX()) {
const tile = new Tile(tileLeft);
const tileRight = tileLeft + TILE_SIZE.width;
// Populate tile with active edges.
const oldEdges = activeEdges;
activeEdges = [];
for (const activeEdge of oldEdges)
this.processEdgeX(activeEdge, tile, activeEdges);
while (nextEdgeIndex < sortedEdges.length) {
const edge = sortedEdges[nextEdgeIndex];
if (edge.from.x > tileRight && edge.to.x > tileRight)
break;
this.processEdgeX(edge, tile, activeEdges);
nextEdgeIndex++;
}
if (!tile.isEmpty())
tileStrip.pushTile(tile);
tileLeft = tileRight;
}
return tileStrip;
}
getStrips(): Strip[] {
return this.strips;
}
getTileStrips(): TileStrip[] {
return this.tileStrips;
}
getBoundingRect(): Rect {
if (this.boundingRect == null)
return new Rect(new Point2D(0, 0), {width: 0, height: 0});
const tileLeft = this.boundingRect.origin.x - this.boundingRect.origin.x % TILE_SIZE.width;
const tileRight = Math.ceil(this.boundingRect.maxX() / TILE_SIZE.width) * TILE_SIZE.width;
const tileTop = this.boundingRect.origin.y - this.boundingRect.origin.y % TILE_SIZE.height;
const tileBottom = Math.ceil(this.boundingRect.maxY() / TILE_SIZE.height) *
TILE_SIZE.height;
return new Rect(new Point2D(tileLeft, tileTop),
{width: tileRight - tileLeft, height: tileBottom - tileTop});
}
private processEdgeX(edge: Edge, tile: Tile, activeEdges: Edge[]): void {
const tileRight = tile.tileLeft + TILE_SIZE.width;
const clipped = this.clipEdgeX(edge, tileRight);
if (clipped.left != null)
tile.pushEdge(clipped.left);
if (clipped.right != null)
activeEdges.push(clipped.right);
}
private processEdgeY(edge: Edge,
tileStrip: Strip,
activeEdges: Edge[],
intervals: Intervals,
tileTop: number):
void {
const tileBottom = tileTop + TILE_SIZE.height;
const clipped = this.clipEdgeY(edge, tileBottom);
if (clipped.upper != null) {
//console.log("pushing clipped upper edge:", JSON.stringify(clipped.upper));
tileStrip.pushEdge(clipped.upper);
if (clipped.upper.from.x <= clipped.upper.to.x)
intervals.add(new IntervalRange(clipped.upper.from.x, clipped.upper.to.x, -1));
else
intervals.add(new IntervalRange(clipped.upper.to.x, clipped.upper.from.x, 1));
}
if (clipped.lower != null)
activeEdges.push(clipped.lower);
}
private clipEdgeX(edge: Edge, x: number): ClippedEdgesX {
const EPSILON: number = 0.00001;
if (edge.from.x < x && edge.to.x < x)
return {left: edge, right: null};
if (edge.from.x > x && edge.to.x > x)
return {left: null, right: edge};
let minT = 0.0, maxT = 1.0;
while (maxT - minT > EPSILON) {
const midT = lerp(minT, maxT, 0.5);
const edges = edge.subdivideAt(midT);
if ((edges.prev.from.x < x && edges.prev.to.x > x) ||
(edges.prev.from.x > x && edges.prev.to.x < x)) {
maxT = midT;
} else {
minT = midT;
}
}
const midT = lerp(minT, maxT, 0.5);
const edges = edge.subdivideAt(midT);
if (edge.from.x < x)
return {left: edges.prev, right: edges.next};
return {left: edges.next, right: edges.prev};
}
private clipEdgeY(edge: Edge, y: number): ClippedEdgesY {
const EPSILON: number = 0.00001;
if (edge.from.y < y && edge.to.y < y)
return {upper: edge, lower: null};
if (edge.from.y > y && edge.to.y > y)
return {upper: null, lower: edge};
let minT = 0.0, maxT = 1.0;
while (maxT - minT > EPSILON) {
const midT = lerp(minT, maxT, 0.5);
const edges = edge.subdivideAt(midT);
if ((edges.prev.from.y < y && edges.prev.to.y > y) ||
(edges.prev.from.y > y && edges.prev.to.y < y)) {
maxT = midT;
} else {
minT = midT;
}
}
const midT = lerp(minT, maxT, 0.5);
const edges = edge.subdivideAt(midT);
if (edge.from.y < y)
return {upper: edges.prev, lower: edges.next};
return {upper: edges.next, lower: edges.prev};
}
private nextEdgeFromEndpoint(endpointIndex: EndpointIndex): Edge {
const subpathEndpoints = this.endpoints[endpointIndex.subpathIndex];
const nextEndpointIndex =
subpathEndpoints.nextEndpointIndexOf(endpointIndex.endpointIndex);
return new Edge(subpathEndpoints.endpoints[endpointIndex.endpointIndex],
subpathEndpoints.controlPoints[nextEndpointIndex],
subpathEndpoints.endpoints[nextEndpointIndex]);
}
}
class SubpathEndpoints {
endpoints: Point2D[];
controlPoints: (Point2D | null)[];
constructor() {
this.endpoints = [];
this.controlPoints = [];
}
isEmpty(): boolean {
return this.endpoints.length < 2;
}
prevEndpointIndexOf(index: number): number {
const prevIndex = index - 1;
return prevIndex < 0 ? this.endpoints.length - 1 : prevIndex;
}
nextEndpointIndexOf(index: number): number {
const nextIndex = index + 1;
return nextIndex >= this.endpoints.length ? 0 : nextIndex;
}
prevEndpointOf(index: number): Point2D {
return this.endpoints[this.prevEndpointIndexOf(index)];
}
nextEndpointOf(index: number): Point2D {
return this.endpoints[this.nextEndpointIndexOf(index)];
}
}
export class Edge {
from: Point2D;
ctrl: Point2D | null;
to: Point2D;
constructor(from: Point2D, ctrl: Point2D | null, to: Point2D) {
this.from = from;
this.ctrl = ctrl;
this.to = to;
Object.freeze(this);
}
subdivideAt(t: number): SubdividedEdges {
if (this.ctrl == null) {
const mid = this.from.lerp(this.to, t);
return {
prev: new Edge(this.from, null, mid),
next: new Edge(mid, null, this.to),
};
}
const ctrlA = this.from.lerp(this.ctrl, t);
const ctrlB = this.ctrl.lerp(this.to, t);
const mid = ctrlA.lerp(ctrlB, t);
return {
prev: new Edge(this.from, ctrlA, mid),
next: new Edge(mid, ctrlB, this.to),
}
}
toSVGPieces(): string[] {
if (this.ctrl == null)
return ['L', "" + this.to.x, "" + this.to.y];
return ['Q', "" + this.ctrl.x, "" + this.ctrl.y, "" + this.to.x, "" + this.to.y];
}
}
interface SubdividedEdges {
prev: Edge;
next: Edge;
}
class Strip {
edges: Edge[];
tileTop: number;
constructor(tileTop: number) {
this.edges = [];
this.tileTop = tileTop;
}
pushEdge(edge: Edge): void {
this.edges.push(new Edge(edge.from.translate(0, -this.tileTop),
edge.ctrl == null ? null : edge.ctrl.translate(0, -this.tileTop),
edge.to.translate(0, -this.tileTop)));
}
tileBottom(): number {
return this.tileTop + TILE_SIZE.height;
}
}
export class TileStrip {
tiles: Tile[];
tileTop: number;
constructor(tileTop: number) {
this.tiles = [];
this.tileTop = tileTop;
}
pushTile(tile: Tile): void {
this.tiles.push(tile);
}
tileBottom(): number {
return this.tileTop + TILE_SIZE.height;
}
isEmpty(): boolean {
return this.tiles.length === 0;
}
}
export class Tile {
edges: Edge[];
tileLeft: number;
constructor(tileLeft: number) {
this.edges = [];
this.tileLeft = tileLeft;
}
pushEdge(edge: Edge): void {
this.edges.push(new Edge(edge.from.translate(-this.tileLeft, 0),
edge.ctrl == null ? null : edge.ctrl.translate(-this.tileLeft, 0),
edge.to.translate(-this.tileLeft, 0)));
}
isEmpty(): boolean {
return this.edges.length === 0;
}
isFilled(): boolean {
if (this.edges.length !== 1)
return false;
const edge = this.edges[0];
if (edge.ctrl != null)
return false;
//console.log("single edge:", JSON.stringify(edge));
const left = edge.from.x < edge.to.x ? edge.from : edge.to;
const right = edge.from.x < edge.to.x ? edge.to : edge.from;
return left.approxEq(new Point2D(0, 0), 0.1) &&
right.approxEq(new Point2D(TILE_SIZE.width, 0), 0.1);
}
}
interface ClippedEdgesX {
left: Edge | null;
right: Edge | null;
}
interface ClippedEdgesY {
upper: Edge | null;
lower: Edge | null;
}
class Intervals {
private ranges: IntervalRange[];
constructor(width: number) {
this.ranges = [new IntervalRange(0, width, 0)];
}
intervalRanges(): IntervalRange[] {
return this.ranges;
}
add(range: IntervalRange): void {
//console.log("IntervalRange.add(", range, ")");
//console.log("... before ...", JSON.stringify(this));
this.splitAt(range.start);
this.splitAt(range.end);
let startIndex = this.ranges.length, endIndex = this.ranges.length;
for (let i = 0; i < this.ranges.length; i++) {
if (range.start === this.ranges[i].start)
startIndex = i;
if (range.end === this.ranges[i].end)
endIndex = i + 1;
}
// Adjust winding numbers.
for (let i = startIndex; i < endIndex; i++)
this.ranges[i].winding += range.winding;
this.mergeAdjacent();
//console.log("... after ...", JSON.stringify(this));
}
clear(): void {
this.ranges = [new IntervalRange(0, this.ranges[this.ranges.length - 1].end, 0)];
}
private splitAt(value: number): void {
for (let i = 0; i < this.ranges.length; i++) {
if (this.ranges[i].start < value && value < this.ranges[i].end) {
const oldRange = this.ranges[i];
const range0 = new IntervalRange(oldRange.start, value, oldRange.winding);
const range1 = new IntervalRange(value, oldRange.end, oldRange.winding);
this.ranges.splice(i, 1, range0, range1);
break;
}
}
}
private mergeAdjacent(): void {
let i = 0;
while (i + 1 < this.ranges.length) {
if (this.ranges[i].end === this.ranges[i + 1].start &&
this.ranges[i].winding === this.ranges[i + 1].winding) {
this.ranges[i].end = this.ranges[i + 1].end;
this.ranges.splice(i + 1, 1);
continue;
}
i++;
}
}
}
class IntervalRange {
start: number;
end: number;
winding: number;
constructor(start: number, end: number, winding: number) {
this.start = start;
this.end = end;
this.winding = winding;
}
contains(value: number): boolean {
return value >= this.start && value < this.end;
}
}
// Debugging
const SVG_NS: string = "http://www.w3.org/2000/svg";
export class TileDebugger {
svg: SVGElement;
size: Size2D;
constructor(document: HTMLDocument) {
this.svg = staticCast(document.createElementNS(SVG_NS, 'svg'), SVGElement);
this.size = {width: 0, height: 0};
this.svg.style.position = 'absolute';
this.svg.style.left = "0";
this.svg.style.top = "0";
this.updateSVGSize();
}
addTiler(tiler: Tiler, fillColor: string, id: string): void {
const boundingRect = tiler.getBoundingRect();
this.size.width = Math.max(this.size.width, boundingRect.maxX());
this.size.height = Math.max(this.size.height, boundingRect.maxY());
const tileStrips = tiler.getTileStrips();
for (let tileStripIndex = 0; tileStripIndex < tileStrips.length; tileStripIndex++) {
const tileStrip = tileStrips[tileStripIndex];
for (let tileIndex = 0; tileIndex < tileStrip.tiles.length; tileIndex++) {
const tile = tileStrip.tiles[tileIndex];
let path = "";
for (const edge of tile.edges) {
path += "M " + edge.from.x + " " + edge.from.y + " ";
path += "L " + edge.to.x + " " + edge.to.y + " ";
path += "L " + edge.to.x + " " + TILE_SIZE.height + " ";
path += "L " + edge.from.x + " " + TILE_SIZE.height + " ";
path += "Z ";
}
const pathElement = staticCast(document.createElementNS(SVG_NS, 'path'),
SVGPathElement);
pathElement.setAttribute('d', path);
pathElement.setAttribute('fill', fillColor);
//pathElement.setAttribute('stroke', "rgb(0, 128.0, 0)");
pathElement.setAttribute('data-tile-id', id);
pathElement.setAttribute('data-tile-index', "" + tileIndex);
pathElement.setAttribute('data-tile-strip-index', "" + tileStripIndex);
pathElement.setAttribute('transform',
"translate(" + tile.tileLeft + " " + tileStrip.tileTop + ")");
this.svg.appendChild(pathElement);
}
}
this.updateSVGSize();
}
private updateSVGSize(): void {
this.svg.style.width = this.size.width + "px";
this.svg.style.height = this.size.height + "px";
}
}
function assertEq<T>(actual: T, expected: T): void {
if (JSON.stringify(expected) !== JSON.stringify(actual)) {
console.error("expected", expected, "but found", actual);
throw new Error("Assertion failed!");
}
}
export function testIntervals(): void {
const intervals = new Intervals(7);
intervals.add(new IntervalRange(1, 2, 1));
intervals.add(new IntervalRange(3, 4, 1));
intervals.add(new IntervalRange(5, 6, 1));
assertEq(intervals.intervalRanges(), [
new IntervalRange(0, 1, 0),
new IntervalRange(1, 2, 1),
new IntervalRange(2, 3, 0),
new IntervalRange(3, 4, 1),
new IntervalRange(4, 5, 0),
new IntervalRange(5, 6, 1),
new IntervalRange(6, 7, 0),
]);
intervals.clear();
intervals.add(new IntervalRange(1, 2, 1));
intervals.add(new IntervalRange(2, 3, 1));
assertEq(intervals.intervalRanges(), [
new IntervalRange(0, 1, 0),
new IntervalRange(1, 3, 1),
new IntervalRange(3, 7, 0),
]);
intervals.clear();
intervals.add(new IntervalRange(1, 4, 1));
intervals.add(new IntervalRange(3, 5, 1));
assertEq(intervals.intervalRanges(), [
new IntervalRange(0, 1, 0),
new IntervalRange(1, 3, 1),
new IntervalRange(3, 4, 2),
new IntervalRange(4, 5, 1),
new IntervalRange(5, 7, 0),
]);
intervals.clear();
intervals.add(new IntervalRange(2, 3.5, 1));
intervals.add(new IntervalRange(3, 5, 1));
intervals.add(new IntervalRange(6, 7, 1));
assertEq(intervals.intervalRanges(), [
new IntervalRange(0, 2, 0),
new IntervalRange(2, 3, 1),
new IntervalRange(3, 3.5, 2),
new IntervalRange(3.5, 5, 1),
new IntervalRange(5, 6, 0),
new IntervalRange(6, 7, 1),
]);
intervals.clear();
intervals.add(new IntervalRange(2, 5, 1));
intervals.add(new IntervalRange(3, 3.5, -1));
assertEq(intervals.intervalRanges(), [
new IntervalRange(0, 2, 0),
new IntervalRange(2, 3, 1),
new IntervalRange(3, 3.5, 0),
new IntervalRange(3.5, 5, 1),
new IntervalRange(5, 7, 0),
]);
intervals.clear();
intervals.add(new IntervalRange(2, 5, 1));
intervals.add(new IntervalRange(3, 3.5, -1));
intervals.add(new IntervalRange(3, 3.5, 1));
assertEq(intervals.intervalRanges(), [
new IntervalRange(0, 2, 0),
new IntervalRange(2, 5, 1),
new IntervalRange(5, 7, 0),
]);
}

View File

@ -1,6 +0,0 @@
{
"compilerOptions": {
"strict": true,
"target": "es6"
},
}

View File

@ -1,31 +0,0 @@
// pathfinder/demo2/util.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.
export function panic(msg: string): never {
throw new Error(msg);
}
export function staticCast<T>(value: any, constructor: { new(...args: any[]): T }): T {
if (!(value instanceof constructor))
panic("Invalid dynamic cast");
return value;
}
export function unwrapNull<T>(value: T | null): T {
if (value == null)
throw new Error("Unexpected null");
return value;
}
export function unwrapUndef<T>(value: T | undefined): T {
if (value == null)
throw new Error("Unexpected undefined");
return value;
}