WIP
This commit is contained in:
parent
726c3c013c
commit
19d17afdb8
|
@ -52,6 +52,10 @@ class Point2D {
|
||||||
approxEq(other: Point2D): boolean {
|
approxEq(other: Point2D): boolean {
|
||||||
return approxEq(this.x, other.x) && approxEq(this.y, other.y);
|
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 {
|
interface Size2D {
|
||||||
|
@ -104,7 +108,7 @@ class App {
|
||||||
private stencilTexture: WebGLTexture;
|
private stencilTexture: WebGLTexture;
|
||||||
private stencilFramebuffer: WebGLFramebuffer;
|
private stencilFramebuffer: WebGLFramebuffer;
|
||||||
private stencilProgram: Program<'FramebufferSize' | 'TileSize' | 'AreaLUT',
|
private stencilProgram: Program<'FramebufferSize' | 'TileSize' | 'AreaLUT',
|
||||||
'TessCoord' | 'From' | 'To' | 'TileIndex'>;
|
'TessCoord' | 'From' | 'Ctrl' | 'To' | 'TileIndex'>;
|
||||||
private coverProgram:
|
private coverProgram:
|
||||||
Program<'FramebufferSize' | 'TileSize' | 'StencilTexture' | 'StencilTextureSize',
|
Program<'FramebufferSize' | 'TileSize' | 'StencilTexture' | 'StencilTextureSize',
|
||||||
'TessCoord' | 'TileOrigin' | 'TileIndex' | 'Color'>;
|
'TessCoord' | 'TileOrigin' | 'TileIndex' | 'Color'>;
|
||||||
|
@ -185,7 +189,7 @@ class App {
|
||||||
STENCIL_VERTEX_SHADER_SOURCE,
|
STENCIL_VERTEX_SHADER_SOURCE,
|
||||||
STENCIL_FRAGMENT_SHADER_SOURCE,
|
STENCIL_FRAGMENT_SHADER_SOURCE,
|
||||||
['FramebufferSize', 'TileSize', 'AreaLUT'],
|
['FramebufferSize', 'TileSize', 'AreaLUT'],
|
||||||
['TessCoord', 'From', 'To', 'TileIndex']);
|
['TessCoord', 'From', 'Ctrl', 'To', 'TileIndex']);
|
||||||
this.stencilProgram = stencilProgram;
|
this.stencilProgram = stencilProgram;
|
||||||
|
|
||||||
// Initialize quad VBO.
|
// Initialize quad VBO.
|
||||||
|
@ -209,9 +213,11 @@ class App {
|
||||||
0,
|
0,
|
||||||
0);
|
0);
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.stencilVertexPositionsBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.stencilVertexPositionsBuffer);
|
||||||
gl.vertexAttribPointer(stencilProgram.attributes.From, 2, gl.FLOAT, false, 16, 0);
|
gl.vertexAttribPointer(stencilProgram.attributes.From, 2, gl.FLOAT, false, 24, 0);
|
||||||
gl.vertexAttribDivisor(stencilProgram.attributes.From, 1);
|
gl.vertexAttribDivisor(stencilProgram.attributes.From, 1);
|
||||||
gl.vertexAttribPointer(stencilProgram.attributes.To, 2, gl.FLOAT, false, 16, 8);
|
gl.vertexAttribPointer(stencilProgram.attributes.Ctrl, 2, gl.FLOAT, false, 24, 8);
|
||||||
|
gl.vertexAttribDivisor(stencilProgram.attributes.Ctrl, 1);
|
||||||
|
gl.vertexAttribPointer(stencilProgram.attributes.To, 2, gl.FLOAT, false, 24, 16);
|
||||||
gl.vertexAttribDivisor(stencilProgram.attributes.To, 1);
|
gl.vertexAttribDivisor(stencilProgram.attributes.To, 1);
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.stencilVertexTileIndicesBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.stencilVertexTileIndicesBuffer);
|
||||||
gl.vertexAttribIPointer(stencilProgram.attributes.TileIndex,
|
gl.vertexAttribIPointer(stencilProgram.attributes.TileIndex,
|
||||||
|
@ -222,6 +228,7 @@ class App {
|
||||||
gl.vertexAttribDivisor(stencilProgram.attributes.TileIndex, 1);
|
gl.vertexAttribDivisor(stencilProgram.attributes.TileIndex, 1);
|
||||||
gl.enableVertexAttribArray(stencilProgram.attributes.TessCoord);
|
gl.enableVertexAttribArray(stencilProgram.attributes.TessCoord);
|
||||||
gl.enableVertexAttribArray(stencilProgram.attributes.From);
|
gl.enableVertexAttribArray(stencilProgram.attributes.From);
|
||||||
|
gl.enableVertexAttribArray(stencilProgram.attributes.Ctrl);
|
||||||
gl.enableVertexAttribArray(stencilProgram.attributes.To);
|
gl.enableVertexAttribArray(stencilProgram.attributes.To);
|
||||||
gl.enableVertexAttribArray(stencilProgram.attributes.TileIndex);
|
gl.enableVertexAttribArray(stencilProgram.attributes.TileIndex);
|
||||||
|
|
||||||
|
@ -364,7 +371,7 @@ class App {
|
||||||
const primitiveCountHistogram: number[] = [];
|
const primitiveCountHistogram: number[] = [];
|
||||||
for (let tileIndex = 0; tileIndex < scene.tiles.length; tileIndex++) {
|
for (let tileIndex = 0; tileIndex < scene.tiles.length; tileIndex++) {
|
||||||
const tile = scene.tiles[tileIndex];
|
const tile = scene.tiles[tileIndex];
|
||||||
let firstPoint = {x: 0.0, y: 0.0}, lastPoint = {x: 0.0, y: 0.0};
|
let firstPoint = new Point2D(0.0, 0.0), lastPoint = new Point2D(0.0, 0.0);
|
||||||
let primitiveCountForThisTile = 0;
|
let primitiveCountForThisTile = 0;
|
||||||
tile.path.iterate(segment => {
|
tile.path.iterate(segment => {
|
||||||
/*if (primitiveCountForThisTile > 0)
|
/*if (primitiveCountForThisTile > 0)
|
||||||
|
@ -374,10 +381,8 @@ class App {
|
||||||
if (segment[0] === 'Z') {
|
if (segment[0] === 'Z') {
|
||||||
point = firstPoint;
|
point = firstPoint;
|
||||||
} else {
|
} else {
|
||||||
point = {
|
point = new Point2D(parseFloat(segment[segment.length - 2]),
|
||||||
x: parseFloat(segment[segment.length - 2]),
|
parseFloat(segment[segment.length - 1]));
|
||||||
y: parseFloat(segment[segment.length - 1]),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -393,8 +398,20 @@ class App {
|
||||||
|
|
||||||
if (segment[0] === 'M') {
|
if (segment[0] === 'M') {
|
||||||
firstPoint = point;
|
firstPoint = point;
|
||||||
|
} else if (segment[0] === 'Q') {
|
||||||
|
const ctrl = new Point2D(parseFloat(segment[segment.length - 4]),
|
||||||
|
parseFloat(segment[segment.length - 3]));
|
||||||
|
stencilVertexPositions.push(lastPoint.x, lastPoint.y,
|
||||||
|
ctrl.x, ctrl.y,
|
||||||
|
point.x, point.y);
|
||||||
|
stencilVertexTileIndices.push(tileIndex);
|
||||||
|
primitiveCount++;
|
||||||
|
primitiveCountForThisTile++;
|
||||||
} else {
|
} else {
|
||||||
stencilVertexPositions.push(lastPoint.x, lastPoint.y, point.x, point.y);
|
const ctrl = lastPoint.lerp(point, 0.5);
|
||||||
|
stencilVertexPositions.push(lastPoint.x, lastPoint.y,
|
||||||
|
ctrl.x, ctrl.y,
|
||||||
|
point.x, point.y);
|
||||||
stencilVertexTileIndices.push(tileIndex);
|
stencilVertexTileIndices.push(tileIndex);
|
||||||
primitiveCount++;
|
primitiveCount++;
|
||||||
primitiveCountForThisTile++;
|
primitiveCountForThisTile++;
|
||||||
|
@ -587,7 +604,7 @@ class Scene {
|
||||||
let output: string[][] = [];
|
let output: string[][] = [];
|
||||||
input.iterate(segment => {
|
input.iterate(segment => {
|
||||||
const event = segment[0];
|
const event = segment[0];
|
||||||
let to;
|
let ctrl: Point2D | null = null, to;
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case 'M':
|
case 'M':
|
||||||
from = new Point2D(parseFloat(segment[segment.length - 2]),
|
from = new Point2D(parseFloat(segment[segment.length - 2]),
|
||||||
|
@ -600,6 +617,10 @@ class Scene {
|
||||||
return;
|
return;
|
||||||
to = pathStart;
|
to = pathStart;
|
||||||
break;
|
break;
|
||||||
|
case 'Q':
|
||||||
|
ctrl = new Point2D(parseFloat(segment[segment.length - 4]),
|
||||||
|
parseFloat(segment[segment.length - 3]));
|
||||||
|
// fallthrough
|
||||||
default:
|
default:
|
||||||
to = new Point2D(parseFloat(segment[segment.length - 2]),
|
to = new Point2D(parseFloat(segment[segment.length - 2]),
|
||||||
parseFloat(segment[segment.length - 1]));
|
parseFloat(segment[segment.length - 1]));
|
||||||
|
@ -608,17 +629,14 @@ class Scene {
|
||||||
|
|
||||||
if (this.pointIsInside(edge, edgePos, to)) {
|
if (this.pointIsInside(edge, edgePos, to)) {
|
||||||
if (!this.pointIsInside(edge, edgePos, from)) {
|
if (!this.pointIsInside(edge, edgePos, from)) {
|
||||||
this.addLine(this.computeLineIntersection(edge, edgePos, from, to),
|
this.addClippedLine(from, ctrl, to, edge, edgePos, output, firstPoint);
|
||||||
output,
|
|
||||||
firstPoint);
|
|
||||||
firstPoint = false;
|
firstPoint = false;
|
||||||
}
|
}
|
||||||
|
// FIXME(pcwalton): Is this right?
|
||||||
this.addLine(to, output, firstPoint);
|
this.addLine(to, output, firstPoint);
|
||||||
firstPoint = false;
|
firstPoint = false;
|
||||||
} else if (this.pointIsInside(edge, edgePos, from)) {
|
} else if (this.pointIsInside(edge, edgePos, from)) {
|
||||||
this.addLine(this.computeLineIntersection(edge, edgePos, from, to),
|
this.addClippedLine(from, ctrl, to, edge, edgePos, output, firstPoint);
|
||||||
output,
|
|
||||||
firstPoint);
|
|
||||||
firstPoint = false;
|
firstPoint = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,6 +651,47 @@ class Scene {
|
||||||
return SVGPath(output.map(segment => segment.join(" ")).join(" "));
|
return SVGPath(output.map(segment => segment.join(" ")).join(" "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addClippedLine(from: Point2D,
|
||||||
|
ctrl: Point2D | null,
|
||||||
|
to: Point2D,
|
||||||
|
edge: Edge,
|
||||||
|
edgePos: number,
|
||||||
|
output: string[][],
|
||||||
|
firstPoint: boolean):
|
||||||
|
void {
|
||||||
|
if (ctrl == null) {
|
||||||
|
if (edge === 'left' || edge === 'right')
|
||||||
|
to = this.computeLineIntersectionX(edgePos, from, to);
|
||||||
|
else
|
||||||
|
to = this.computeLineIntersectionY(edgePos, from, to);
|
||||||
|
} else {
|
||||||
|
let minT = 0.0, maxT = 1.0;
|
||||||
|
while (maxT - minT > 1e-3) {
|
||||||
|
const midT = lerp(minT, maxT, 0.5);
|
||||||
|
const point = sampleBezier(from, ctrl, to, midT);
|
||||||
|
const diff = ((edge === 'left' || edge === 'right') ? point.x : point.y) - edgePos;
|
||||||
|
if (diff < 0.0)
|
||||||
|
maxT = midT;
|
||||||
|
else if (diff > 0.0)
|
||||||
|
minT = midT;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const midT = lerp(minT, maxT, 0.5);
|
||||||
|
const newCtrl = from.lerp(ctrl, midT);
|
||||||
|
to = sampleBezier(from, ctrl, to, midT);
|
||||||
|
ctrl = newCtrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstPoint)
|
||||||
|
output.push(['M', "" + to.x, "" + to.y]);
|
||||||
|
else if (ctrl == null)
|
||||||
|
output.push(['L', "" + to.x, "" + to.y]);
|
||||||
|
else
|
||||||
|
output.push(['Q', "" + ctrl.x, "" + ctrl.y, "" + to.x, "" + to.y]);
|
||||||
|
}
|
||||||
|
|
||||||
private addLine(to: Point2D, output: string[][], firstPoint: boolean) {
|
private addLine(to: Point2D, output: string[][], firstPoint: boolean) {
|
||||||
if (firstPoint)
|
if (firstPoint)
|
||||||
output.push(['M', "" + to.x, "" + to.y]);
|
output.push(['M', "" + to.x, "" + to.y]);
|
||||||
|
@ -649,29 +708,27 @@ class Scene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeLineIntersection(edge: Edge,
|
private computeLineIntersectionX(x: number,
|
||||||
edgePos: number,
|
startPoint: Point2D,
|
||||||
startPoint: Point2D,
|
endpoint: Point2D):
|
||||||
endpoint: Point2D):
|
Point2D {
|
||||||
Point2D {
|
|
||||||
const start = {x: startPoint.x, y: startPoint.y, z: 1.0};
|
const start = {x: startPoint.x, y: startPoint.y, z: 1.0};
|
||||||
const end = {x: endpoint.x, y: endpoint.y, z: 1.0};
|
const end = {x: endpoint.x, y: endpoint.y, z: 1.0};
|
||||||
|
let edgeVector: Vector3D = {x: 1.0, y: 0.0, z: -x};
|
||||||
let edgeVector: Vector3D;
|
|
||||||
switch (edge) {
|
|
||||||
case 'left':
|
|
||||||
case 'right':
|
|
||||||
edgeVector = {x: 1.0, y: 0.0, z: -edgePos};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
edgeVector = {x: 0.0, y: 1.0, z: -edgePos};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const intersection = cross(cross(start, end), edgeVector);
|
const intersection = cross(cross(start, end), edgeVector);
|
||||||
return new Point2D(intersection.x / intersection.z, intersection.y / intersection.z);
|
return new Point2D(intersection.x / intersection.z, intersection.y / intersection.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private computeLineIntersectionY(y: number,
|
||||||
|
startPoint: Point2D,
|
||||||
|
endpoint: Point2D):
|
||||||
|
Point2D {
|
||||||
|
const start = {x: startPoint.x, y: startPoint.y, z: 1.0};
|
||||||
|
const end = {x: endpoint.x, y: endpoint.y, z: 1.0};
|
||||||
|
let edgeVector: Vector3D = {x: 0.0, y: 1.0, z: -y};
|
||||||
|
const intersection = cross(cross(start, end), edgeVector);
|
||||||
|
return new Point2D(intersection.x / intersection.z, intersection.y / intersection.z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tile {
|
class Tile {
|
||||||
|
@ -753,14 +810,15 @@ class PathSegment {
|
||||||
|
|
||||||
function flattenPath(path: SVGPath): SVGPath {
|
function flattenPath(path: SVGPath): SVGPath {
|
||||||
return path.abs().iterate(segment => {
|
return path.abs().iterate(segment => {
|
||||||
if (segment[0] === 'Q')
|
|
||||||
return [['L', segment[1], segment[2]], ['L', segment[3], segment[4]]];
|
|
||||||
if (segment[0] === 'C') {
|
if (segment[0] === 'C') {
|
||||||
return [
|
const ctrl0 = new Point2D(parseFloat(segment[segment.length - 6]),
|
||||||
['L', segment[1], segment[2]],
|
parseFloat(segment[segment.length - 5]));
|
||||||
['L', segment[3], segment[4]],
|
const ctrl1 = new Point2D(parseFloat(segment[segment.length - 4]),
|
||||||
['L', segment[5], segment[6]],
|
parseFloat(segment[segment.length - 3]));
|
||||||
];
|
const to = new Point2D(parseFloat(segment[segment.length - 2]),
|
||||||
|
parseFloat(segment[segment.length - 1]));
|
||||||
|
const ctrl = new Point2D(0.5 * (ctrl0.x + ctrl1.x), 0.5 * (ctrl0.y + ctrl1.y));
|
||||||
|
return [['Q', "" + ctrl.x, "" + ctrl.y, "" + to.x, "" + to.y]];
|
||||||
}
|
}
|
||||||
return [segment];
|
return [segment];
|
||||||
});
|
});
|
||||||
|
@ -813,6 +871,14 @@ function pathIsSquare(path: SVGPath, squareLength: number): 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 {
|
||||||
|
return from.lerp(ctrl, t).lerp(ctrl.lerp(to, t), t);
|
||||||
|
}
|
||||||
|
|
||||||
function main(): void {
|
function main(): void {
|
||||||
window.fetch(SVG).then(svg => {
|
window.fetch(SVG).then(svg => {
|
||||||
svg.text().then(svgText => {
|
svg.text().then(svgText => {
|
||||||
|
|
|
@ -17,6 +17,7 @@ uniform vec2 uTileSize;
|
||||||
|
|
||||||
in vec2 aTessCoord;
|
in vec2 aTessCoord;
|
||||||
in vec2 aFrom;
|
in vec2 aFrom;
|
||||||
|
in vec2 aCtrl;
|
||||||
in vec2 aTo;
|
in vec2 aTo;
|
||||||
in uint aTileIndex;
|
in uint aTileIndex;
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
|
||||||
void main() {
|
void main() {
|
||||||
vec2 tileOrigin = computeTileOffset(aTileIndex, uFramebufferSize.x);
|
vec2 tileOrigin = computeTileOffset(aTileIndex, uFramebufferSize.x);
|
||||||
|
|
||||||
vec2 from = aFrom, ctrl = mix(aFrom, aTo, 0.5), to = aTo;
|
vec2 from = aFrom, ctrl = aCtrl, to = aTo;
|
||||||
|
|
||||||
vec2 dilation, position;
|
vec2 dilation, position;
|
||||||
bool zeroArea = abs(from.x - to.x) < 0.01;
|
bool zeroArea = abs(from.x - to.x) < 0.01;
|
||||||
|
@ -53,9 +54,9 @@ void main() {
|
||||||
}
|
}
|
||||||
position += dilation;
|
position += dilation;
|
||||||
|
|
||||||
vFrom = aFrom - position;
|
vFrom = from - position;
|
||||||
vCtrl = mix(aFrom, aTo, 0.5) - position;
|
vCtrl = ctrl - position;
|
||||||
vTo = aTo - position;
|
vTo = to - position;
|
||||||
|
|
||||||
gl_Position = vec4((tileOrigin + position) / uFramebufferSize * 2.0 - 1.0, 0.0, 1.0);
|
gl_Position = vec4((tileOrigin + position) / uFramebufferSize * 2.0 - 1.0, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue