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 STENCIL_FRAGMENT_SHADER_SOURCE from "./stencil.fs.glsl";
|
||||||
import SVG from "../resources/svg/Ghostscript_Tiger.svg";
|
import SVG from "../resources/svg/Ghostscript_Tiger.svg";
|
||||||
import AREA_LUT from "../resources/textures/area-lut.png";
|
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 SVGPath: (path: string) => SVGPath = require('svgpath');
|
||||||
const parseColor: (color: string) => any = require('parse-color');
|
const parseColor: (color: string) => any = require('parse-color');
|
||||||
|
@ -33,62 +36,6 @@ const QUAD_VERTEX_POSITIONS: Uint8Array = new Uint8Array([
|
||||||
0, 1,
|
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);
|
const GLOBAL_TRANSFORM: Matrix2D = new Matrix2D(3.0, 0.0, 0.0, 3.0, 800.0, 550.0);
|
||||||
|
|
||||||
interface Color {
|
interface Color {
|
||||||
|
@ -299,34 +246,32 @@ class App {
|
||||||
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, unwrapNull(this.primitiveCount));
|
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, unwrapNull(this.primitiveCount));
|
||||||
gl.disable(gl.BLEND);
|
gl.disable(gl.BLEND);
|
||||||
|
|
||||||
/*
|
|
||||||
// Read back stencil and dump it.
|
// Read back stencil and dump it.
|
||||||
const stencilData =
|
const totalStencilFramebufferSize = STENCIL_FRAMEBUFFER_SIZE.width *
|
||||||
new Float32Array(STENCIL_FRAMEBUFFER_SIZE * STENCIL_FRAMEBUFFER_SIZE * 4);
|
STENCIL_FRAMEBUFFER_SIZE.height * 4;
|
||||||
|
const stencilData = new Float32Array(totalStencilFramebufferSize);
|
||||||
gl.readPixels(0, 0,
|
gl.readPixels(0, 0,
|
||||||
STENCIL_FRAMEBUFFER_SIZE, STENCIL_FRAMEBUFFER_SIZE,
|
STENCIL_FRAMEBUFFER_SIZE.width, STENCIL_FRAMEBUFFER_SIZE.height,
|
||||||
gl.RGBA,
|
gl.RGBA,
|
||||||
gl.FLOAT,
|
gl.FLOAT,
|
||||||
stencilData);
|
stencilData);
|
||||||
const stencilDumpData = new
|
const stencilDumpData = new Uint8ClampedArray(totalStencilFramebufferSize);
|
||||||
Uint8ClampedArray(STENCIL_FRAMEBUFFER_SIZE * STENCIL_FRAMEBUFFER_SIZE * 4);
|
|
||||||
for (let i = 0; i < stencilData.length; i++)
|
for (let i = 0; i < stencilData.length; i++)
|
||||||
stencilDumpData[i] = stencilData[i] * 255.0;
|
stencilDumpData[i] = stencilData[i] * 255.0;
|
||||||
const stencilDumpCanvas = document.createElement('canvas');
|
const stencilDumpCanvas = document.createElement('canvas');
|
||||||
stencilDumpCanvas.width = STENCIL_FRAMEBUFFER_SIZE;
|
stencilDumpCanvas.width = STENCIL_FRAMEBUFFER_SIZE.width;
|
||||||
stencilDumpCanvas.height = STENCIL_FRAMEBUFFER_SIZE;
|
stencilDumpCanvas.height = STENCIL_FRAMEBUFFER_SIZE.height;
|
||||||
stencilDumpCanvas.style.width = (STENCIL_FRAMEBUFFER_SIZE / window.devicePixelRatio) +
|
stencilDumpCanvas.style.width =
|
||||||
"px";
|
(STENCIL_FRAMEBUFFER_SIZE.width / window.devicePixelRatio) + "px";
|
||||||
stencilDumpCanvas.style.height = (STENCIL_FRAMEBUFFER_SIZE / window.devicePixelRatio) +
|
stencilDumpCanvas.style.height =
|
||||||
"px";
|
(STENCIL_FRAMEBUFFER_SIZE.height / window.devicePixelRatio) + "px";
|
||||||
const stencilDumpCanvasContext = unwrapNull(stencilDumpCanvas.getContext('2d'));
|
const stencilDumpCanvasContext = unwrapNull(stencilDumpCanvas.getContext('2d'));
|
||||||
const stencilDumpImageData = new ImageData(stencilDumpData,
|
const stencilDumpImageData = new ImageData(stencilDumpData,
|
||||||
STENCIL_FRAMEBUFFER_SIZE,
|
STENCIL_FRAMEBUFFER_SIZE.width,
|
||||||
STENCIL_FRAMEBUFFER_SIZE);
|
STENCIL_FRAMEBUFFER_SIZE.height);
|
||||||
stencilDumpCanvasContext.putImageData(stencilDumpImageData, 0, 0);
|
stencilDumpCanvasContext.putImageData(stencilDumpImageData, 0, 0);
|
||||||
document.body.appendChild(stencilDumpCanvas);
|
document.body.appendChild(stencilDumpCanvas);
|
||||||
//console.log(stencilData);
|
//console.log(stencilData);
|
||||||
*/
|
|
||||||
|
|
||||||
// Cover.
|
// Cover.
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
|
@ -502,6 +447,9 @@ class Scene {
|
||||||
|
|
||||||
path = flattenPath(path);
|
path = flattenPath(path);
|
||||||
path = canonicalizePath(path);
|
path = canonicalizePath(path);
|
||||||
|
|
||||||
|
const tiler = new Tiler(path);
|
||||||
|
|
||||||
const boundingRect = this.boundingRectOfPath(path);
|
const boundingRect = this.boundingRectOfPath(path);
|
||||||
|
|
||||||
/*console.log("path " + pathElementIndex, path.toString(), ":",
|
/*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 {
|
function flattenPath(path: SVGPath): SVGPath {
|
||||||
return path.abs().iterate(segment => {
|
return path.abs().iterate(segment => {
|
||||||
if (segment[0] === 'C') {
|
if (segment[0] === 'C') {
|
||||||
|
@ -855,10 +790,6 @@ function waitForQuery(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, qu
|
||||||
console.log(elapsed + "ms elapsed");
|
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 {
|
function pathIsRect(path: SVGPath, rectSize: Size2D): boolean {
|
||||||
let result = true;
|
let result = true;
|
||||||
path.iterate((segment, index) => {
|
path.iterate((segment, index) => {
|
||||||
|
@ -872,10 +803,6 @@ function pathIsRect(path: SVGPath, rectSize: Size2D): boolean {
|
||||||
return result;
|
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 {
|
function sampleBezier(from: Point2D, ctrl: Point2D, to: Point2D, t: number): Point2D {
|
||||||
return from.lerp(ctrl, t).lerp(ctrl.lerp(to, t), t);
|
return from.lerp(ctrl, t).lerp(ctrl.lerp(to, t), t);
|
||||||
}
|
}
|
||||||
|
@ -902,15 +829,3 @@ function main(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => main(), false);
|
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