wip
This commit is contained in:
parent
e9d1d3c322
commit
eda11577c6
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue