WIP
This commit is contained in:
parent
26bbf2c3d5
commit
c1407c3970
|
@ -17,6 +17,7 @@ export class Point2D {
|
|||
constructor(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
approxEq(other: Point2D): boolean {
|
||||
|
@ -33,9 +34,55 @@ export interface Size2D {
|
|||
height: number;
|
||||
}
|
||||
|
||||
export interface Rect {
|
||||
export class Rect {
|
||||
origin: Point2D;
|
||||
size: Size2D;
|
||||
|
||||
constructor(origin: Point2D, size: Size2D) {
|
||||
this.origin = origin;
|
||||
this.size = size;
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
unionWithPoint(point: Point2D): Rect {
|
||||
let newOrigin = this.origin, newSize = this.size;
|
||||
|
||||
if (point.x < this.origin.x) {
|
||||
newSize = {
|
||||
width: newSize.width + newOrigin.x - point.x,
|
||||
height: newSize.height,
|
||||
};
|
||||
newOrigin = new Point2D(point.x, newOrigin.y);
|
||||
} else if (point.x > this.maxX()) {
|
||||
newSize = {
|
||||
width: newSize.width + point.x - this.maxX(),
|
||||
height: newSize.height,
|
||||
};
|
||||
}
|
||||
|
||||
if (point.y < this.origin.y) {
|
||||
newSize = {
|
||||
width: newSize.width,
|
||||
height: newSize.height + newOrigin.y - point.y,
|
||||
};
|
||||
newOrigin = new Point2D(newOrigin.x, point.y);
|
||||
} else if (point.y > this.maxY()) {
|
||||
newSize = {
|
||||
width: newSize.width,
|
||||
height: newSize.height + point.y - this.maxY(),
|
||||
};
|
||||
}
|
||||
|
||||
return new Rect(newOrigin, newSize);
|
||||
}
|
||||
|
||||
maxX(): number {
|
||||
return this.origin.x + this.size.width;
|
||||
}
|
||||
|
||||
maxY(): number {
|
||||
return this.origin.y + this.size.height;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Vector3D {
|
||||
|
@ -63,3 +110,11 @@ export function approxEq(a: number, b: number): boolean {
|
|||
export function lerp(a: number, b: number, t: number): number {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
export function cross(a: Vector3D, b: Vector3D): Vector3D {
|
||||
return {
|
||||
x: a.y*b.z - a.z*b.y,
|
||||
y: a.z*b.x - a.x*b.z,
|
||||
z: a.x*b.y - a.y*b.x,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ 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 {Matrix2D, Point2D, Rect, Size2D, Vector3D, approxEq, cross, lerp} from "./geometry";
|
||||
import {SVGPath, TILE_SIZE, TileDebugger, Tiler, testIntervals} from "./tiling";
|
||||
import {staticCast, unwrapNull} from "./util";
|
||||
|
||||
const SVGPath: (path: string) => SVGPath = require('svgpath');
|
||||
|
@ -23,7 +23,6 @@ const parseColor: (color: string) => any = require('parse-color');
|
|||
|
||||
const SVG_NS: string = "http://www.w3.org/2000/svg";
|
||||
|
||||
const TILE_SIZE: Size2D = {width: 32.0, height: 32.0};
|
||||
const STENCIL_FRAMEBUFFER_SIZE: Size2D = {
|
||||
width: TILE_SIZE.width * 256,
|
||||
height: TILE_SIZE.height * 256,
|
||||
|
@ -417,7 +416,9 @@ class Scene {
|
|||
const pathElements = Array.from(document.getElementsByTagName('path'));
|
||||
const tiles: Tile[] = [], pathColors = [];
|
||||
|
||||
for (let pathElementIndex = 0;
|
||||
const tileDebugger = new TileDebugger(document);
|
||||
|
||||
for (let pathElementIndex = 0, realPathIndex = 0;
|
||||
pathElementIndex < pathElements.length;
|
||||
pathElementIndex++) {
|
||||
const pathElement = pathElements[pathElementIndex];
|
||||
|
@ -450,7 +451,15 @@ class Scene {
|
|||
path = flattenPath(path);
|
||||
path = canonicalizePath(path);
|
||||
|
||||
realPathIndex++;
|
||||
|
||||
//if (realPathIndex === 73) {
|
||||
//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());
|
||||
//}
|
||||
|
||||
const boundingRect = this.boundingRectOfPath(path);
|
||||
|
||||
|
@ -464,7 +473,7 @@ class Scene {
|
|||
while (true) {
|
||||
let x = boundingRect.origin.x - boundingRect.origin.x % TILE_SIZE.width;
|
||||
while (true) {
|
||||
const tileBounds = {origin: new Point2D(x, y), size: TILE_SIZE};
|
||||
const tileBounds = new Rect(new Point2D(x, y), TILE_SIZE);
|
||||
const tilePath = this.clipPathToRect(path, tileBounds);
|
||||
|
||||
if (tilePath.toString().length > 0) {
|
||||
|
@ -518,6 +527,13 @@ class Scene {
|
|||
|
||||
document.body.removeChild(svgElement);
|
||||
|
||||
const svgContainer = document.createElement('div');
|
||||
svgContainer.style.position = 'relative';
|
||||
svgContainer.style.width = "2000px";
|
||||
svgContainer.style.height = "2000px";
|
||||
svgContainer.appendChild(tileDebugger.svg);
|
||||
document.body.appendChild(svgContainer);
|
||||
|
||||
console.log(tiles);
|
||||
this.tiles = tiles;
|
||||
this.pathColors = pathColors;
|
||||
|
@ -537,8 +553,8 @@ class Scene {
|
|||
}
|
||||
});
|
||||
if (minX == null || minY == null || maxX == null || maxY == null)
|
||||
return {origin: new Point2D(0, 0), size: {width: 0, height: 0}};
|
||||
return {origin: new Point2D(minX, minY), size: {width: maxX - minX, height: maxY - minY}};
|
||||
return new Rect(new Point2D(0, 0), {width: 0, height: 0});
|
||||
return new Rect(new Point2D(minX, minY), {width: maxX - minX, height: maxY - minY});
|
||||
}
|
||||
|
||||
private clipPathToRect(path: SVGPath, tileBounds: Rect): SVGPath {
|
||||
|
@ -772,14 +788,6 @@ function canonicalizePath(path: SVGPath): SVGPath {
|
|||
});
|
||||
}
|
||||
|
||||
function cross(a: Vector3D, b: Vector3D): Vector3D {
|
||||
return {
|
||||
x: a.y*b.z - a.z*b.y,
|
||||
y: a.z*b.x - a.x*b.z,
|
||||
z: a.x*b.y - a.y*b.x,
|
||||
};
|
||||
}
|
||||
|
||||
function waitForQuery(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, query: WebGLQuery):
|
||||
void {
|
||||
const queryResultAvailable = disjointTimerQueryExt.QUERY_RESULT_AVAILABLE_EXT;
|
||||
|
@ -812,6 +820,8 @@ function sampleBezier(from: Point2D, ctrl: Point2D, to: Point2D, t: number): Poi
|
|||
function main(): void {
|
||||
window.fetch(SVG).then(svg => {
|
||||
svg.text().then(svgText => {
|
||||
testIntervals();
|
||||
|
||||
const svg = staticCast((new DOMParser).parseFromString(svgText, 'image/svg+xml'),
|
||||
XMLDocument);
|
||||
const image = new Image;
|
||||
|
|
396
demo2/tiling.ts
396
demo2/tiling.ts
|
@ -8,8 +8,10 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
import {Point2D} from "./geometry";
|
||||
import {panic, unwrapNull} from "./util";
|
||||
import {Point2D, Rect, Size2D, cross} from "./geometry";
|
||||
import {panic, staticCast, unwrapNull} from "./util";
|
||||
|
||||
export const TILE_SIZE: Size2D = {width: 32.0, height: 32.0};
|
||||
|
||||
export interface SVGPath {
|
||||
abs(): SVGPath;
|
||||
|
@ -29,16 +31,20 @@ interface EndpointIndex {
|
|||
export class Tiler {
|
||||
private path: SVGPath;
|
||||
private endpoints: SubpathEndpoints[];
|
||||
private sortedEndpointIndices: EndpointIndex[];
|
||||
private sortedEdges: Edge[];
|
||||
private boundingRect: Rect | null;
|
||||
private tileStrips: TileStrip[];
|
||||
|
||||
constructor(path: SVGPath) {
|
||||
this.path = path;
|
||||
//console.log("tiler: path=", path);
|
||||
|
||||
// Accumulate endpoints.
|
||||
this.endpoints = [];
|
||||
let currentSubpathEndpoints = new SubpathEndpoints;
|
||||
path.iterate(segString => {
|
||||
const segment = new PathSegment(segString);
|
||||
//console.log("segment", segment);
|
||||
switch (segment.command) {
|
||||
case 'M':
|
||||
if (!currentSubpathEndpoints.isEmpty()) {
|
||||
|
@ -64,23 +70,157 @@ export class Tiler {
|
|||
if (!currentSubpathEndpoints.isEmpty())
|
||||
this.endpoints.push(currentSubpathEndpoints);
|
||||
|
||||
// Sort endpoints.
|
||||
this.sortedEndpointIndices = [];
|
||||
// Sort edges, and accumulate bounding rect.
|
||||
this.sortedEdges = [];
|
||||
this.boundingRect = null;
|
||||
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});
|
||||
this.sortedEdges.push(this.nextEdgeFromEndpoint({subpathIndex, endpointIndex}));
|
||||
|
||||
const endpoint = subpathEndpoints.endpoints[endpointIndex];
|
||||
if (this.boundingRect == null)
|
||||
this.boundingRect = new Rect(endpoint, {width: 0, height: 0});
|
||||
else
|
||||
this.boundingRect.unionWithPoint(endpoint);
|
||||
}
|
||||
}
|
||||
this.sortedEdges.sort((edgeA, edgeB) => {
|
||||
return Math.min(edgeA.from.y, edgeA.to.y) - Math.min(edgeB.from.y, edgeB.to.y);
|
||||
});
|
||||
|
||||
/*
|
||||
// Dump endpoints.
|
||||
const allEndpoints = this.sortedEndpointIndices.map(index => {
|
||||
return {
|
||||
index: index,
|
||||
endpoint: this.endpoints[index.subpathIndex].endpoints[index.endpointIndex],
|
||||
};
|
||||
});
|
||||
//console.log("allEndpoints", allEndpoints);
|
||||
*/
|
||||
|
||||
this.tileStrips = [];
|
||||
}
|
||||
|
||||
tile(): void {
|
||||
const activeEdges = [];
|
||||
for (const endpointIndex of this.sortedEndpointIndices) {
|
||||
if (this.boundingRect == null)
|
||||
return;
|
||||
|
||||
const activeIntervals = new Intervals(this.boundingRect.maxY());;
|
||||
let activeEdges: Edge[] = [];
|
||||
let nextEdgeIndex = 0;
|
||||
this.tileStrips = [];
|
||||
|
||||
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);
|
||||
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));
|
||||
else
|
||||
tileStrip.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);
|
||||
|
||||
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);
|
||||
nextEdgeIndex++;
|
||||
}
|
||||
|
||||
this.tileStrips.push(tileStrip);
|
||||
tileTop = tileBottom;
|
||||
}
|
||||
}
|
||||
|
||||
getTileStrips(): TileStrip[] {
|
||||
return this.tileStrips;
|
||||
}
|
||||
|
||||
getBoundingRect(): Rect {
|
||||
if (this.boundingRect == null)
|
||||
return new Rect(new Point2D(0, 0), {width: 0, height: 0});
|
||||
|
||||
const tileLeft = this.boundingRect.origin.x - this.boundingRect.origin.x % TILE_SIZE.width;
|
||||
const tileRight = Math.ceil(this.boundingRect.maxX() / TILE_SIZE.width) * TILE_SIZE.width;
|
||||
const tileTop = this.boundingRect.origin.y - this.boundingRect.origin.y % TILE_SIZE.height;
|
||||
const tileBottom = Math.ceil(this.boundingRect.maxY() / TILE_SIZE.height) *
|
||||
TILE_SIZE.height;
|
||||
return new Rect(new Point2D(tileLeft, tileTop),
|
||||
{width: tileRight - tileLeft, height: tileBottom - tileTop});
|
||||
}
|
||||
|
||||
private processEdge(edge: Edge,
|
||||
tileStrip: TileStrip,
|
||||
activeEdges: Edge[],
|
||||
intervals: Intervals,
|
||||
tileTop: number):
|
||||
void {
|
||||
const tileBottom = tileTop + TILE_SIZE.height;
|
||||
const clipped = this.clipEdgeY(edge, tileBottom);
|
||||
|
||||
if (clipped.upper != null) {
|
||||
tileStrip.pushEdge(clipped.upper);
|
||||
|
||||
if (edge.from.x <= edge.to.x)
|
||||
intervals.add(new IntervalRange(edge.from.x, edge.to.x, 1));
|
||||
else
|
||||
intervals.add(new IntervalRange(edge.to.x, edge.from.x, -1));
|
||||
}
|
||||
|
||||
if (clipped.lower != null)
|
||||
activeEdges.push(clipped.lower);
|
||||
}
|
||||
|
||||
private clipEdgeY(edge: Edge, y: number): ClippedEdgesY {
|
||||
if (edge.from.y < y && edge.to.y < y)
|
||||
return {upper: edge, lower: null};
|
||||
if (edge.from.y > y && edge.to.y > y)
|
||||
return {upper: null, lower: 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: 0.0, y: 1.0, z: -y };
|
||||
|
||||
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.y < y)
|
||||
return {upper: fromEdge, lower: toEdge};
|
||||
return {upper: toEdge, lower: fromEdge};
|
||||
}
|
||||
|
||||
private prevEdgeFromEndpoint(endpointIndex: EndpointIndex): Edge {
|
||||
const subpathEndpoints = this.endpoints[endpointIndex.subpathIndex];
|
||||
return new Edge(subpathEndpoints.prevEndpointOf(endpointIndex.endpointIndex),
|
||||
subpathEndpoints.endpoints[endpointIndex.endpointIndex]);
|
||||
}
|
||||
|
||||
private nextEdgeFromEndpoint(endpointIndex: EndpointIndex): Edge {
|
||||
const subpathEndpoints = this.endpoints[endpointIndex.subpathIndex];
|
||||
return new Edge(subpathEndpoints.endpoints[endpointIndex.endpointIndex],
|
||||
subpathEndpoints.nextEndpointOf(endpointIndex.endpointIndex));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,6 +263,7 @@ export class PathSegment {
|
|||
for (let i = 1; i < segment.length; i += 2)
|
||||
points.push(new Point2D(parseFloat(segment[i]), parseFloat(segment[i + 1])));
|
||||
this.points = points;
|
||||
//console.log("PathSegment, segment=", segment, "points=", points);
|
||||
this.command = segment[0];
|
||||
}
|
||||
|
||||
|
@ -130,3 +271,242 @@ export class PathSegment {
|
|||
return this.points[this.points.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
class Edge {
|
||||
from: Point2D;
|
||||
to: Point2D;
|
||||
|
||||
constructor(from: Point2D, to: Point2D) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
Object.freeze(this);
|
||||
}
|
||||
}
|
||||
|
||||
class TileStrip {
|
||||
edges: Edge[];
|
||||
tileTop: number;
|
||||
|
||||
constructor(tileTop: number) {
|
||||
this.edges = [];
|
||||
this.tileTop = tileTop;
|
||||
}
|
||||
|
||||
pushEdge(edge: Edge): void {
|
||||
this.edges.push(edge);
|
||||
}
|
||||
|
||||
tileBottom(): number {
|
||||
return this.tileTop + TILE_SIZE.height;
|
||||
}
|
||||
}
|
||||
|
||||
interface ClippedEdgesY {
|
||||
upper: Edge | null;
|
||||
lower: Edge | null;
|
||||
}
|
||||
|
||||
class Intervals {
|
||||
private ranges: IntervalRange[];
|
||||
|
||||
constructor(width: number) {
|
||||
this.ranges = [new IntervalRange(0, width, 0)];
|
||||
}
|
||||
|
||||
intervalRanges(): IntervalRange[] {
|
||||
return this.ranges;
|
||||
}
|
||||
|
||||
add(range: IntervalRange): void {
|
||||
this.splitAt(range.start);
|
||||
this.splitAt(range.end);
|
||||
|
||||
let startIndex = this.ranges.length, endIndex = this.ranges.length;
|
||||
for (let i = 0; i < this.ranges.length; i++) {
|
||||
if (range.start === this.ranges[i].start)
|
||||
startIndex = i;
|
||||
if (range.end === this.ranges[i].end)
|
||||
endIndex = i + 1;
|
||||
}
|
||||
|
||||
// Adjust winding numbers.
|
||||
for (let i = startIndex; i < endIndex; i++)
|
||||
this.ranges[i].winding += range.winding;
|
||||
|
||||
this.mergeAdjacent();
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.ranges = [new IntervalRange(0, this.ranges[this.ranges.length - 1].end, 0)];
|
||||
}
|
||||
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private mergeAdjacent(): void {
|
||||
let i = 0;
|
||||
while (i + 1 < this.ranges.length) {
|
||||
if (this.ranges[i].end === this.ranges[i + 1].start &&
|
||||
this.ranges[i].winding === this.ranges[i + 1].winding) {
|
||||
this.ranges[i].end = this.ranges[i + 1].end;
|
||||
this.ranges.splice(i + 1, 1);
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IntervalRange {
|
||||
start: number;
|
||||
end: number;
|
||||
winding: number;
|
||||
|
||||
constructor(start: number, end: number, winding: number) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.winding = winding;
|
||||
}
|
||||
|
||||
contains(value: number): boolean {
|
||||
return value >= this.start && value < this.end;
|
||||
}
|
||||
}
|
||||
|
||||
// Debugging
|
||||
|
||||
const SVG_NS: string = "http://www.w3.org/2000/svg";
|
||||
|
||||
export class TileDebugger {
|
||||
svg: SVGElement;
|
||||
size: Size2D;
|
||||
|
||||
constructor(document: HTMLDocument) {
|
||||
this.svg = staticCast(document.createElementNS(SVG_NS, 'svg'), SVGElement);
|
||||
|
||||
this.size = {width: 0, height: 0};
|
||||
|
||||
this.svg.style.position = 'absolute';
|
||||
this.svg.style.left = "0";
|
||||
this.svg.style.top = "0";
|
||||
this.updateSVGSize();
|
||||
}
|
||||
|
||||
addTiler(tiler: Tiler, fillColor: 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 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);
|
||||
}
|
||||
|
||||
this.updateSVGSize();
|
||||
}
|
||||
|
||||
private updateSVGSize(): void {
|
||||
this.svg.style.width = this.size.width + "px";
|
||||
this.svg.style.height = this.size.height + "px";
|
||||
}
|
||||
}
|
||||
|
||||
function assertEq<T>(actual: T, expected: T): void {
|
||||
if (JSON.stringify(expected) !== JSON.stringify(actual)) {
|
||||
console.error("expected", expected, "but found", actual);
|
||||
throw new Error("Assertion failed!");
|
||||
}
|
||||
}
|
||||
|
||||
export function testIntervals(): void {
|
||||
const intervals = new Intervals(7);
|
||||
intervals.add(new IntervalRange(1, 2, 1));
|
||||
intervals.add(new IntervalRange(3, 4, 1));
|
||||
intervals.add(new IntervalRange(5, 6, 1));
|
||||
assertEq(intervals.intervalRanges(), [
|
||||
new IntervalRange(0, 1, 0),
|
||||
new IntervalRange(1, 2, 1),
|
||||
new IntervalRange(2, 3, 0),
|
||||
new IntervalRange(3, 4, 1),
|
||||
new IntervalRange(4, 5, 0),
|
||||
new IntervalRange(5, 6, 1),
|
||||
new IntervalRange(6, 7, 0),
|
||||
]);
|
||||
|
||||
intervals.clear();
|
||||
intervals.add(new IntervalRange(1, 2, 1));
|
||||
intervals.add(new IntervalRange(2, 3, 1));
|
||||
assertEq(intervals.intervalRanges(), [
|
||||
new IntervalRange(0, 1, 0),
|
||||
new IntervalRange(1, 3, 1),
|
||||
new IntervalRange(3, 7, 0),
|
||||
]);
|
||||
|
||||
intervals.clear();
|
||||
intervals.add(new IntervalRange(1, 4, 1));
|
||||
intervals.add(new IntervalRange(3, 5, 1));
|
||||
assertEq(intervals.intervalRanges(), [
|
||||
new IntervalRange(0, 1, 0),
|
||||
new IntervalRange(1, 3, 1),
|
||||
new IntervalRange(3, 4, 2),
|
||||
new IntervalRange(4, 5, 1),
|
||||
new IntervalRange(5, 7, 0),
|
||||
]);
|
||||
|
||||
intervals.clear();
|
||||
intervals.add(new IntervalRange(2, 3.5, 1));
|
||||
intervals.add(new IntervalRange(3, 5, 1));
|
||||
intervals.add(new IntervalRange(6, 7, 1));
|
||||
assertEq(intervals.intervalRanges(), [
|
||||
new IntervalRange(0, 2, 0),
|
||||
new IntervalRange(2, 3, 1),
|
||||
new IntervalRange(3, 3.5, 2),
|
||||
new IntervalRange(3.5, 5, 1),
|
||||
new IntervalRange(5, 6, 0),
|
||||
new IntervalRange(6, 7, 1),
|
||||
]);
|
||||
|
||||
intervals.clear();
|
||||
intervals.add(new IntervalRange(2, 5, 1));
|
||||
intervals.add(new IntervalRange(3, 3.5, -1));
|
||||
assertEq(intervals.intervalRanges(), [
|
||||
new IntervalRange(0, 2, 0),
|
||||
new IntervalRange(2, 3, 1),
|
||||
new IntervalRange(3, 3.5, 0),
|
||||
new IntervalRange(3.5, 5, 1),
|
||||
new IntervalRange(5, 7, 0),
|
||||
]);
|
||||
|
||||
intervals.clear();
|
||||
intervals.add(new IntervalRange(2, 5, 1));
|
||||
intervals.add(new IntervalRange(3, 3.5, -1));
|
||||
intervals.add(new IntervalRange(3, 3.5, 1));
|
||||
assertEq(intervals.intervalRanges(), [
|
||||
new IntervalRange(0, 2, 0),
|
||||
new IntervalRange(2, 5, 1),
|
||||
new IntervalRange(5, 7, 0),
|
||||
]);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue