This commit is contained in:
Patrick Walton 2018-11-26 18:30:04 -08:00
parent e9d1d3c322
commit eda11577c6
4 changed files with 241 additions and 104 deletions

65
demo2/geometry.ts Normal file
View File

@ -0,0 +1,65 @@
// 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;
}
approxEq(other: Point2D): boolean {
return approxEq(this.x, other.x) && approxEq(this.y, other.y);
}
lerp(other: Point2D, t: number): Point2D {
return new Point2D(lerp(this.x, other.x, t), lerp(this.y, other.y, t));
}
}
export interface Size2D {
width: number;
height: number;
}
export interface Rect {
origin: Point2D;
size: Size2D;
}
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): boolean {
return Math.abs(a - b) <= EPSILON;
}
export function lerp(a: number, b: number, t: number): number {
return a + (b - a) * t;
}

View File

@ -14,6 +14,9 @@ import STENCIL_VERTEX_SHADER_SOURCE from "./stencil.vs.glsl";
import STENCIL_FRAGMENT_SHADER_SOURCE from "./stencil.fs.glsl";
import SVG from "../resources/svg/Ghostscript_Tiger.svg";
import AREA_LUT from "../resources/textures/area-lut.png";
import {Matrix2D, Point2D, Rect, Size2D, Vector3D, approxEq, lerp} from "./geometry";
import {SVGPath, Tiler} from "./tiling";
import {staticCast, unwrapNull} from "./util";
const SVGPath: (path: string) => SVGPath = require('svgpath');
const parseColor: (color: string) => any = require('parse-color');
@ -33,62 +36,6 @@ const QUAD_VERTEX_POSITIONS: Uint8Array = new Uint8Array([
0, 1,
]);
const EPSILON: number = 1e-6;
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;
}
class Point2D {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
approxEq(other: Point2D): boolean {
return approxEq(this.x, other.x) && approxEq(this.y, other.y);
}
lerp(other: Point2D, t: number): Point2D {
return new Point2D(lerp(this.x, other.x, t), lerp(this.y, other.y, t));
}
}
interface Size2D {
width: number;
height: number;
}
interface Rect {
origin: Point2D;
size: Size2D;
}
interface Vector3D {
x: number;
y: number;
z: number;
}
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);
interface Color {
@ -299,34 +246,32 @@ class App {
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, unwrapNull(this.primitiveCount));
gl.disable(gl.BLEND);
/*
// Read back stencil and dump it.
const stencilData =
new Float32Array(STENCIL_FRAMEBUFFER_SIZE * STENCIL_FRAMEBUFFER_SIZE * 4);
const totalStencilFramebufferSize = STENCIL_FRAMEBUFFER_SIZE.width *
STENCIL_FRAMEBUFFER_SIZE.height * 4;
const stencilData = new Float32Array(totalStencilFramebufferSize);
gl.readPixels(0, 0,
STENCIL_FRAMEBUFFER_SIZE, STENCIL_FRAMEBUFFER_SIZE,
STENCIL_FRAMEBUFFER_SIZE.width, STENCIL_FRAMEBUFFER_SIZE.height,
gl.RGBA,
gl.FLOAT,
stencilData);
const stencilDumpData = new
Uint8ClampedArray(STENCIL_FRAMEBUFFER_SIZE * STENCIL_FRAMEBUFFER_SIZE * 4);
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;
stencilDumpCanvas.height = STENCIL_FRAMEBUFFER_SIZE;
stencilDumpCanvas.style.width = (STENCIL_FRAMEBUFFER_SIZE / window.devicePixelRatio) +
"px";
stencilDumpCanvas.style.height = (STENCIL_FRAMEBUFFER_SIZE / window.devicePixelRatio) +
"px";
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,
STENCIL_FRAMEBUFFER_SIZE);
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
stencilDumpCanvasContext.putImageData(stencilDumpImageData, 0, 0);
document.body.appendChild(stencilDumpCanvas);
//console.log(stencilData);
*/
// Cover.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
@ -502,6 +447,9 @@ class Scene {
path = flattenPath(path);
path = canonicalizePath(path);
const tiler = new Tiler(path);
const boundingRect = this.boundingRectOfPath(path);
/*console.log("path " + pathElementIndex, path.toString(), ":",
@ -796,19 +744,6 @@ class Program<U extends string, A extends string> {
}
}
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] === 'C') {
@ -855,10 +790,6 @@ function waitForQuery(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, qu
console.log(elapsed + "ms elapsed");
}
function approxEq(a: number, b: number): boolean {
return Math.abs(a - b) <= EPSILON;
}
function pathIsRect(path: SVGPath, rectSize: Size2D): boolean {
let result = true;
path.iterate((segment, index) => {
@ -872,10 +803,6 @@ function pathIsRect(path: SVGPath, rectSize: Size2D): boolean {
return result;
}
function lerp(a: number, b: number, t: number): number {
return a + (b - a) * t;
}
function sampleBezier(from: Point2D, ctrl: Point2D, to: Point2D, t: number): Point2D {
return from.lerp(ctrl, t).lerp(ctrl.lerp(to, t), t);
}
@ -902,15 +829,3 @@ function main(): void {
}
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;
}

132
demo2/tiling.ts Normal file
View File

@ -0,0 +1,132 @@
// 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} from "./geometry";
import {panic, unwrapNull} from "./util";
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;
}
const SVGPath: (path: string) => SVGPath = require('svgpath');
interface EndpointIndex {
subpathIndex: number;
endpointIndex: number;
};
export class Tiler {
private path: SVGPath;
private endpoints: SubpathEndpoints[];
private sortedEndpointIndices: EndpointIndex[];
constructor(path: SVGPath) {
this.path = path;
// Accumulate endpoints.
this.endpoints = [];
let currentSubpathEndpoints = new SubpathEndpoints;
path.iterate(segString => {
const segment = new PathSegment(segString);
switch (segment.command) {
case 'M':
if (!currentSubpathEndpoints.isEmpty()) {
this.endpoints.push(currentSubpathEndpoints);
currentSubpathEndpoints = new SubpathEndpoints;
}
currentSubpathEndpoints.endpoints.push(segment.points[0]);
break;
case 'L':
case 'S':
case 'Q':
case 'C':
// TODO(pcwalton): Canonicalize 'S'.
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 endpoints.
this.sortedEndpointIndices = [];
for (let subpathIndex = 0; subpathIndex < this.endpoints.length; subpathIndex++) {
const subpathEndpoints = this.endpoints[subpathIndex];
for (let endpointIndex = 0;
endpointIndex < subpathEndpoints.endpoints.length;
endpointIndex++) {
this.sortedEndpointIndices.push({subpathIndex, endpointIndex});
}
}
}
tile(): void {
const activeEdges = [];
for (const endpointIndex of this.sortedEndpointIndices) {
}
}
}
class SubpathEndpoints {
endpoints: Point2D[];
constructor() {
this.endpoints = [];
}
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 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];
}
}

25
demo2/util.ts Normal file
View File

@ -0,0 +1,25 @@
// 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;
}