diff --git a/demo2/pathfinder.ts b/demo2/pathfinder.ts index 9abd0cba..7164af9a 100644 --- a/demo2/pathfinder.ts +++ b/demo2/pathfinder.ts @@ -457,8 +457,8 @@ class Scene { //console.log("path", pathElementIndex, "svg path", path); const tiler = new Tiler(path); tiler.tile(); - tileDebugger.addTiler(tiler, paint); - console.log("path", pathElementIndex, "tiles", tiler.getTileStrips()); + tileDebugger.addTiler(tiler, paint, "" + realPathIndex); + console.log("path", pathElementIndex, "tiles", tiler.getStrips()); //} const boundingRect = this.boundingRectOfPath(path); @@ -820,7 +820,7 @@ function sampleBezier(from: Point2D, ctrl: Point2D, to: Point2D, t: number): Poi function main(): void { window.fetch(SVG).then(svg => { svg.text().then(svgText => { - testIntervals(); + //testIntervals(); const svg = staticCast((new DOMParser).parseFromString(svgText, 'image/svg+xml'), XMLDocument); diff --git a/demo2/tiling.ts b/demo2/tiling.ts index 622e6207..df70b458 100644 --- a/demo2/tiling.ts +++ b/demo2/tiling.ts @@ -33,6 +33,7 @@ export class Tiler { private endpoints: SubpathEndpoints[]; private sortedEdges: Edge[]; private boundingRect: Rect | null; + private strips: Strip[]; private tileStrips: TileStrip[]; constructor(path: SVGPath) { @@ -84,7 +85,7 @@ export class Tiler { if (this.boundingRect == null) this.boundingRect = new Rect(endpoint, {width: 0, height: 0}); else - this.boundingRect.unionWithPoint(endpoint); + this.boundingRect = this.boundingRect.unionWithPoint(endpoint); } } this.sortedEdges.sort((edgeA, edgeB) => { @@ -102,6 +103,7 @@ export class Tiler { //console.log("allEndpoints", allEndpoints); */ + this.strips = []; this.tileStrips = []; } @@ -109,46 +111,96 @@ export class Tiler { if (this.boundingRect == null) return; - const activeIntervals = new Intervals(this.boundingRect.maxY());; + const activeIntervals = new Intervals(this.boundingRect.maxX());; let activeEdges: Edge[] = []; let nextEdgeIndex = 0; - this.tileStrips = []; + this.strips = []; let tileTop = this.boundingRect.origin.y - this.boundingRect.origin.y % TILE_SIZE.height; while (tileTop < this.boundingRect.maxY()) { const tileBottom = tileTop + TILE_SIZE.height; // Populate tile strip with active intervals. - const tileStrip = new TileStrip(tileTop); + const strip = new Strip(tileTop); + /*console.log("tileTop", tileTop, + "intervals", JSON.stringify(activeIntervals.intervalRanges()));*/ 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) - tileStrip.pushEdge(new Edge(startPoint, endPoint)); + strip.pushEdge(new Edge(startPoint, endPoint)); else - tileStrip.pushEdge(new Edge(endPoint, startPoint)); + strip.pushEdge(new Edge(endPoint, startPoint)); } // Populate tile strip with active edges. const oldEdges = activeEdges; activeEdges = []; for (const activeEdge of oldEdges) - this.processEdge(activeEdge, tileStrip, activeEdges, activeIntervals, tileTop); + 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.processEdge(edge, tileStrip, activeEdges, activeIntervals, tileTop); + this.processEdgeY(edge, strip, activeEdges, activeIntervals, tileTop); + //console.log("new intervals:", JSON.stringify(activeIntervals)); nextEdgeIndex++; } - this.tileStrips.push(tileStrip); + this.strips.push(strip); tileTop = tileBottom; } + + // Cut up tile strips. + //console.log("strips count:", this.strips.length); + this.tileStrips = this.strips.map(strip => this.divideStrip(strip)); + } + + 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++; + } + + tileStrip.pushTile(tile); + tileLeft = tileRight; + } + + return tileStrip; + } + + getStrips(): Strip[] { + return this.strips; } getTileStrips(): TileStrip[] { @@ -168,28 +220,61 @@ export class Tiler { {width: tileRight - tileLeft, height: tileBottom - tileTop}); } - private processEdge(edge: Edge, - tileStrip: TileStrip, - activeEdges: Edge[], - intervals: Intervals, - tileTop: number): - void { + 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 (edge.from.x <= edge.to.x) - intervals.add(new IntervalRange(edge.from.x, edge.to.x, 1)); + 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(edge.to.x, edge.from.x, -1)); + 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 { + 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}; + + const from = {x: edge.from.x, y: edge.from.y, z: 1.0}; + const to = {x: edge.to.x, y: edge.to.y, z: 1.0}; + const clipLine = {x: 1.0, y: 0.0, z: -x }; + + const intersectionHC = cross(cross(from, to), clipLine); + const intersection = new Point2D(intersectionHC.x / intersectionHC.z, + intersectionHC.y / intersectionHC.z); + const fromEdge = new Edge(edge.from, intersection); + const toEdge = new Edge(intersection, edge.to); + + if (edge.from.x < x) + return {left: fromEdge, right: toEdge}; + return {left: toEdge, right: fromEdge}; + } + private clipEdgeY(edge: Edge, y: number): ClippedEdgesY { if (edge.from.y < y && edge.to.y < y) return {upper: edge, lower: null}; @@ -283,7 +368,7 @@ class Edge { } } -class TileStrip { +class Strip { edges: Edge[]; tileTop: number; @@ -301,6 +386,43 @@ class TileStrip { } } +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; + } +} + +class Tile { + edges: Edge[]; + tileLeft: number; + + constructor(tileLeft: number) { + this.edges = []; + this.tileLeft = tileLeft; + } + + pushEdge(edge: Edge): void { + this.edges.push(edge); + } +} + +interface ClippedEdgesX { + left: Edge | null; + right: Edge | null; +} + interface ClippedEdgesY { upper: Edge | null; lower: Edge | null; @@ -318,6 +440,9 @@ class Intervals { } add(range: IntervalRange): void { + //console.log("IntervalRange.add(", range, ")"); + //console.log("... before ...", JSON.stringify(this)); + this.splitAt(range.start); this.splitAt(range.end); @@ -334,6 +459,8 @@ class Intervals { this.ranges[i].winding += range.winding; this.mergeAdjacent(); + + //console.log("... after ...", JSON.stringify(this)); } clear(): void { @@ -343,10 +470,10 @@ class Intervals { 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 firstRange = this.ranges[i]; - const secondRange = new IntervalRange(value, firstRange.end, firstRange.winding); - this.ranges.splice(i + 1, 0, secondRange); - firstRange.end = value; + 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; } } @@ -401,28 +528,38 @@ export class TileDebugger { this.updateSVGSize(); } - addTiler(tiler: Tiler, fillColor: string): void { + 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()); - for (const tileStrip of tiler.getTileStrips()) { + const tileStrips = tiler.getTileStrips(); + for (let tileStripIndex = 0; tileStripIndex < tileStrips.length; tileStripIndex++) { + const tileStrip = tileStrips[tileStripIndex]; const tileBottom = tileStrip.tileBottom(); - let path = ""; - for (const edge of tileStrip.edges) { - path += "M " + edge.from.x + " " + edge.from.y + " "; - path += "L " + edge.to.x + " " + edge.to.y + " "; - path += "L " + edge.to.x + " " + tileBottom + " "; - path += "L " + edge.from.x + " " + tileBottom + " "; - 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)"); - this.svg.appendChild(pathElement); + 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 + " " + tileBottom + " "; + path += "L " + edge.from.x + " " + tileBottom + " "; + 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); + this.svg.appendChild(pathElement); + } } this.updateSVGSize(); diff --git a/shaders/gles2/stencil-aaa.vs.glsl b/shaders/gles2/stencil-aaa.vs.glsl index b9ad03bb..1cd43e34 100644 --- a/shaders/gles2/stencil-aaa.vs.glsl +++ b/shaders/gles2/stencil-aaa.vs.glsl @@ -92,11 +92,11 @@ void main() { vec2 dilation, position; bool zeroArea = abs(from.x - to.x) < 0.00001; if (aTessCoord.x < 0.5) { - position.x = min(min(from.x, to.x), ctrl.x); - dilation.x = zeroArea ? 0.0 : -1.0; + position.x = from.x; + dilation.x = zeroArea ? 0.0 : (from.x < to.x ? -1.0 : 1.0); } else { - position.x = max(max(from.x, to.x), ctrl.x); - dilation.x = zeroArea ? 0.0 : 1.0; + position.x = to.x; + dilation.x = zeroArea ? 0.0 : (from.x < to.x ? 1.0 : -1.0); } if (aTessCoord.y < 0.5) { position.y = min(min(from.y, to.y), ctrl.y); @@ -116,7 +116,12 @@ void main() { // Finish up. gl_Position = vec4(offsetPosition, depth, 1.0); - vFrom = (from - position) * framebufferSizeVector; vCtrl = (ctrl - position) * framebufferSizeVector; - vTo = (to - position) * framebufferSizeVector; + if (from.x < to.x) { + vFrom = (from - position) * framebufferSizeVector; + vTo = (to - position) * framebufferSizeVector; + } else { + vFrom = (to - position) * framebufferSizeVector; + vTo = (from - position) * framebufferSizeVector; + } }