import { assert } from 'chai'; import { fuzzyEqual, DOUBLE_EPSILON } from '../../../../lib/math/float'; import { radians } from '../../../../lib/math/units'; import { Vector2D } from '../../../../lib/graphics/Vector2D'; import { Vector2DArray } from '../../../../lib/graphics/Vector2DArray'; import { Matrix } from '../../../../lib/graphics/Matrix'; import { Shape } from '../../../../lib/graphics/shapes/Shape'; import { Point } from '../../../../lib/graphics/shapes/Point'; import { Line } from '../../../../lib/graphics/shapes/Line'; import { Circle } from '../../../../lib/graphics/shapes/Circle'; import { Rectangle } from '../../../../lib/graphics/shapes/Rectangle'; import { Polygon } from '../../../../lib/graphics/shapes/Polygon'; import { Polyline } from '../../../../lib/graphics/shapes/Polyline'; import { canvasRenderingContext2DSpy } from '../mock/CanvasRenderingContext2D'; const matrix = Matrix.create; const vector2d = Vector2D.create; const fuzzyDeepEqual = Vector2DArray.fuzzyDeepEqual; const point = Point.create; const line = Line.create; const circle = Circle.create; const rectangle = Rectangle.create; const polygon = Polygon.create; const polyline = Polyline.create; // Square root of two const SQRT2 = 1.4142135623731; // Length of unit vector projected to x from 45 degrees const UNITX_45 = 0.707106781186547; describe('graphics.shapes.Line', () => { describe('constructor', () => { it('default constructor', () => { const l = new Line(); assert.strictEqual(l.p1.x, 0); assert.strictEqual(l.p1.y, 0); assert.strictEqual(l.p2.x, 0); assert.strictEqual(l.p2.y, 0); }); it('p1 constructor', () => { const l = new Line(new Vector2D(1, 1)); assert.strictEqual(l.p1.x, 1); assert.strictEqual(l.p1.y, 1); assert.strictEqual(l.p2.x, 0); assert.strictEqual(l.p2.y, 0); }); it('p1, p2 constructor', () => { const l = new Line(new Vector2D(1, 1), new Vector2D(-1, -1)); assert.strictEqual(l.p1.x, 1); assert.strictEqual(l.p1.y, 1); assert.strictEqual(l.p2.x, -1); assert.strictEqual(l.p2.y, -1); }); it('instanceof', () => { const l = new Line(); assert.isTrue(l instanceof Shape); assert.isTrue(l instanceof Line); }); }); describe('clone', function () { it('clone', function () { const l1 = line(vector2d(1, 2), vector2d(3, 4)); const l2 = l1.clone(); l1.p1.x = 5; l1.p1.y = 6; l1.p2.x = 7; l1.p2.y = 8; assert.isTrue(l2 instanceof Line); assert.isTrue(l2.equals(line(vector2d(1, 2), vector2d(3, 4)))); }); }); describe('getters/setters', () => { it('getters/setters', () => { const l = line(vector2d(1, 2), vector2d(3, 4)); assert.strictEqual(l.x1, 1); assert.strictEqual(l.y1, 2); assert.strictEqual(l.x2, 3); assert.strictEqual(l.y2, 4); l.x1 = -1; l.y1 = -2; l.x2 = -3; l.y2 = -4; assert.strictEqual(l.x1, -1); assert.strictEqual(l.y1, -2); assert.strictEqual(l.x2, -3); assert.strictEqual(l.y2, -4); }); }); describe('isNull', () => { test.each([ [line(), true], [line(vector2d()), true], [line(vector2d(), vector2d()), true], [line(vector2d(1)), false], [line(vector2d(), vector2d(1)), false], [line(vector2d(1, 1), vector2d(1, 1)), true], [line(vector2d(1, 1), vector2d(1, -1)), false], ])('isNull#%#', (line: Line, result: boolean) => { assert.isTrue(line.isNull() === result); }); }); describe('fuzzyIsNull', () => { test.each([ [line(), true, , undefined], [line(vector2d()), true, , undefined], [line(vector2d(), vector2d()), true, , undefined], [line(vector2d(1)), false, , undefined], [line(vector2d(), vector2d(1)), false, , undefined], [line(vector2d(1, 1), vector2d(1, 1)), true, , undefined], [line(vector2d(1, 1), vector2d(1, -1)), false, , undefined], [line(vector2d(0.999999, 1), vector2d(1, 1.000001)), true, , undefined], [line(vector2d(0.000001, 0), vector2d()), true, , undefined], [line(vector2d(0.999999, 1), vector2d(1, 1.000001)), false, DOUBLE_EPSILON], ])('fuzzyIsNull#%#', (line: Line, result: boolean, epsilon: number) => { assert.isTrue(line.fuzzyIsNull(epsilon) === result); }); }); describe('equals', () => { test.each([ [line(), line(), true], [line(vector2d(0, 0)), line(), true], [line(vector2d(0, 0), vector2d(0, 0)), line(), true], [line(undefined, vector2d(0, 0)), line(), true], [line(vector2d(1, 1)), line(), false], [line(vector2d(1, 1)), line(vector2d(1, 1)), true], [line(vector2d(1, 1)), line(vector2d(1, 2)), false], [line(vector2d(1, 2)), line(vector2d(1, 2)), true], [line(vector2d(0, 0), vector2d(1, 1)), line(), false], [line(vector2d(0, 0), vector2d(1, 1)), line(vector2d(0, 0), vector2d(1, 1)), true], [line(vector2d(0, 0), vector2d(1, 1)), line(vector2d(0, 0), vector2d(1, 2)), false], [line(vector2d(0, 1), vector2d(1, 1)), line(vector2d(0, 0), vector2d(1, 1)), false], [line(vector2d(0, 1), vector2d(1, 2)), line(vector2d(0, 1), vector2d(1, 2)), true], ])('equals#%#', (line1: Line, line2: Line, result: boolean) => { assert.isTrue(line1.equals(line2) === result); }); }); describe('fuzzyEquals', () => { test.each([ [line(), line(), true, undefined], [line(vector2d(0, 0)), line(), true, undefined], [line(vector2d(0, 0), vector2d(0, 0)), line(), true, undefined], [line(undefined, vector2d(0, 0)), line(), true, undefined], [line(vector2d(1, 1)), line(), false, undefined], [line(vector2d(1, 1)), line(vector2d(1, 1)), true, undefined], [line(vector2d(1, 1)), line(vector2d(1, 2)), false, undefined], [line(vector2d(1, 2)), line(vector2d(1, 2)), true, undefined], [line(vector2d(0, 0), vector2d(1, 1)), line(), false, undefined], [line(vector2d(0, 0), vector2d(1, 1)), line(vector2d(0, 0), vector2d(1, 1)), true, undefined], [line(vector2d(0, 0), vector2d(1, 1)), line(vector2d(0, 0), vector2d(1, 2)), false, undefined], [line(vector2d(0, 1), vector2d(1, 1)), line(vector2d(0, 0), vector2d(1, 1)), false, undefined], [line(vector2d(0, 1), vector2d(1, 2)), line(vector2d(0, 1), vector2d(1, 2)), true, undefined], [line(vector2d(1)), line(vector2d(1.000001)), true, undefined], [line(vector2d(), vector2d(1, -1)), line(vector2d(), vector2d(1, -1.000001)), true, undefined], [line(vector2d(1)), line(vector2d(1.000001)), false, DOUBLE_EPSILON], ])('fuzzyEquals#%#', (line1: Line, line2: Line, result: boolean, epsilon: number) => { assert.isTrue(line1.fuzzyEquals(line2, epsilon) === result); }); }); describe('translate', () => { test.each([ [line(), vector2d(1, 1), line(vector2d(1, 1), vector2d(1, 1))], [line(vector2d(1, 2), vector2d(3, 4)), vector2d(), line(vector2d(1, 2), vector2d(3, 4))], [line(vector2d(1, 2), vector2d(3, 4)), vector2d(1, 1), line(vector2d(2, 3), vector2d(4, 5))], ])('translate#%#', (line: Line, offset: Vector2D, result: Line) => { assert.isTrue(line.translate(offset.x, offset.y).equals(result)); }); }); describe('translated', () => { test.each([ [line(), vector2d(1, 1), line(vector2d(1, 1), vector2d(1, 1))], [line(vector2d(1, 2), vector2d(3, 4)), vector2d(), line(vector2d(1, 2), vector2d(3, 4))], [line(vector2d(1, 2), vector2d(3, 4)), vector2d(1, 1), line(vector2d(2, 3), vector2d(4, 5))], ])('translated#%#', (line: Line, offset: Vector2D, result: Line) => { assert.isTrue(line.translated(offset.x, offset.y).equals(result)); }); }); describe('transform', () => { it('transform', () => { const l1 = line(vector2d(2, 0), vector2d(4, 0)); const m = matrix().translate(2, 2).scale(2, 2).rotate(radians(90)); assert.isTrue(l1.transform(m) === l1); assert.isTrue(l1.fuzzyEquals(line(vector2d(2, 6), vector2d(2, 10)))); }); }); describe('transformed', () => { it('transformed', () => { const l1 = line(vector2d(2, 0), vector2d(4, 0)); const m = matrix().translate(2, 2).scale(2, 2).rotate(radians(90)); const l2 = l1.transformed(m); assert.isTrue(l2 instanceof Line); assert.isTrue(l2.fuzzyEquals(line(vector2d(2, 6), vector2d(2, 10)))); }); }); describe('center', () => { test.each([ ['[0, 0]', 0, 0, 0, 0, 0, 0], ['top', 0, 0, 2, 0, 1, 0], ['right', 0, 0, 0, 2, 0, 1], ['bottom', 0, 0, -2, 0, -1, 0], ['left', 0, 0, 0, -2, 0, -1], ['precision+', 0, 0, 1, 1, 0.5, 0.5], ['precision-', -1, -1, 0, 0, -0.5, -0.5], ])('center#%#', (tag: string, x1: number, y1: number, x2: number, y2: number, cx: number, cy: number) => { assert.isTrue(line(vector2d(x1, y1), vector2d(x2, y2)).center().equals(vector2d(cx, cy))); }); }); describe('length', () => { test.each([ ['[1,0]*2', 0.0, 0.0, 1.0, 0.0, 1.0, 2.0, 2.0, 0.0], ['[0,1]*2', 0.0, 0.0, 0.0, 1.0, 1.0, 2.0, 0.0, 2.0], ['[-1,0]*2', 0.0, 0.0, -1.0, 0.0, 1.0, 2.0, -2.0, 0.0], ['[0,-1]*2', 0.0, 0.0, 0.0, -1.0, 1.0, 2.0, 0.0, -2.0], ['[1,1]->|1|', 0.0, 0.0, 1.0, 1.0, SQRT2, 1.0, UNITX_45, UNITX_45], ['[-1,1]->|1|', 0.0, 0.0, -1.0, 1.0, SQRT2, 1.0, -UNITX_45, UNITX_45], ['[1,-1]->|1|', 0.0, 0.0, 1.0, -1.0, SQRT2, 1.0, UNITX_45, -UNITX_45], ['[-1,-1]->|1|', 0.0, 0.0, -1.0, -1.0, SQRT2, 1.0, -UNITX_45, -UNITX_45], ['[1,0]*2 (2,2)', 2.0, 2.0, 3.0, 2.0, 1.0, 2.0, 2.0, 0.0], ['[0,1]*2 (2,2)', 2.0, 2.0, 2.0, 3.0, 1.0, 2.0, 0.0, 2.0], ['[-1,0]*2 (2,2)', 2.0, 2.0, 1.0, 2.0, 1.0, 2.0, -2.0, 0.0], ['[0,-1]*2 (2,2)', 2.0, 2.0, 2.0, 1.0, 1.0, 2.0, 0.0, -2.0], ['[1,1]->|1| (2,2)', 2.0, 2.0, 3.0, 3.0, SQRT2, 1.0, UNITX_45, UNITX_45], ['[-1,1]->|1| (2,2)', 2.0, 2.0, 1.0, 3.0, SQRT2, 1.0, -UNITX_45, UNITX_45], ['[1,-1]->|1| (2,2)', 2.0, 2.0, 3.0, 1.0, SQRT2, 1.0, UNITX_45, -UNITX_45], ['[-1,-1]->|1| (2,2)', 2.0, 2.0, 1.0, 1.0, SQRT2, 1.0, -UNITX_45, -UNITX_45] ])('length#%#', (tag: string, x1: number, y1: number, x2: number, y2: number, length: number, lengthToSet: number, vx: number, vy: number) => { const l = line(vector2d(x1, y1), vector2d(x2, y2)); assert.isTrue(fuzzyEqual(l.length(), length, DOUBLE_EPSILON)); l.setLength(lengthToSet); assert.isTrue(fuzzyEqual(l.length(), lengthToSet, DOUBLE_EPSILON)); assert.isTrue(fuzzyEqual(l.dx(), vx, DOUBLE_EPSILON)); assert.isTrue(fuzzyEqual(l.dy(), vy, DOUBLE_EPSILON)); }); }); describe('pointAt', () => { test.each([ [line(vector2d(1, 1), vector2d(3, 3)), 0, vector2d(1, 1)], [line(vector2d(1, 1), vector2d(3, 3)), .5, vector2d(2, 2)], [line(vector2d(1, 1), vector2d(3, 3)), 1, vector2d(3, 3)], [line(vector2d(1, 1), vector2d(3, 3)), 2, vector2d(5, 5)], ])('pointAt#%#', (l: Line, t: number, result: Vector2D) => { assert.isTrue(l.pointAt(t).fuzzyEquals(result, DOUBLE_EPSILON)); }); }); describe('normalVector', () => { test.each([ ['[1, 0]', 0.0, 0.0, 1.0, 0.0, 0.0, -1.0], ['[-1, 0]', 0.0, 0.0, -1.0, 0.0, 0.0, 1.0], ['[0, 1]', 0.0, 0.0, 0.0, 1.0, 1.0, 0.0], ['[0, -1]', 0.0, 0.0, 0.0, -1.0, -1.0, 0.0], ['[2, 3]', 2.0, 3.0, 4.0, 6.0, 3.0, -2.0] ])('normalVector#%#', (tag: string, x1: number, y1: number, x2: number, y2: number, nvx: number, nvy: number) => { const n = line(vector2d(x1, y1), vector2d(x2, y2)).normalVector(); assert.strictEqual(n.x, nvx); assert.strictEqual(n.y, nvy); }); }); describe('angle', () => { test.each([ ['right', 0.0, 0.0, 10.0, 0.0, 0.0], ['left', 0.0, 0.0, -10.0, 0.0, 180.0], ['up', 0.0, 0.0, 0.0, -10.0, 90.0], ['down', 0.0, 0.0, 0.0, 10.0, 270.0], ['diag a', 0.0, 0.0, 10.0, -10.0, 45.0], ['diag b', 0.0, 0.0, -10.0, -10.0, 135.0], ['diag c', 0.0, 0.0, -10.0, 10.0, 225.0], ['diag d', 0.0, 0.0, 10.0, 10.0, 315.0] ])('angle#%#', (tag: string, x1: number, y1: number, x2: number, y2: number, degrees: number) => { const angle = radians(degrees); const l = line(vector2d(x1, y1), vector2d(x2, y2)); assert.strictEqual(l.angle(), angle); const p = Line.fromPolar(l.length(), angle); assert.isTrue(fuzzyEqual(l.p1.x, p.p1.x)); assert.isTrue(fuzzyEqual(l.p1.y, p.p1.y)); assert.isTrue(fuzzyEqual(l.p2.x, p.p2.x)); assert.isTrue(fuzzyEqual(l.p2.y, p.p2.y)); }); it('-720->720', () => { for (let i = -720; i <= 720; ++i) { const l = line(vector2d(0, 0), vector2d(100, 0)); l.setAngle(radians(i)); const expected = (i + 720) % 360; assert.isTrue(fuzzyEqual(l.angle(), radians(expected))); assert.isTrue(fuzzyEqual(l.length(), 100.0)); assert.isTrue(Line.fromPolar(100.0, radians(i)).fuzzyEquals(l)); } }); }); describe('angleTo', () => { test.each([ ['parallel', 1.0, 1.0, 3.0, 4.0, 5.0, 6.0, 7.0, 9.0, 0.0], ['[4,4]-[4,0]', 1.0, 1.0, 5.0, 5.0, 0.0, 4.0, 3.0, 4.0, 45.0], ['[4,4]-[-4,0]', 1.0, 1.0, 5.0, 5.0, 3.0, 4.0, 0.0, 4.0, 225.0], ['[0,0]-[0,0]', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ['[1,1]-[0,0]', 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], ['[0,1]-[1,0]', 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 90.0], ].concat((() => { const ret = []; for (let i = 0; i < 360; ++i) { const l = Line.fromPolar(1, radians(i)); ret.push([`angle:${i}`, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, l.p2.x, l.p2.y, i]); } return ret; })()))('angleTo#%#', (tag: string, xa1: number, ya1: number, xa2: number, ya2: number, xb1: number, yb1: number, xb2: number, yb2: number, degrees: number) => { const angle = radians(degrees); let a = line(vector2d(xa1, ya1), vector2d(xa2, ya2)); let b = line(vector2d(xb1, yb1), vector2d(xb2, yb2)); const resultAngle = a.angleTo(b); assert.isTrue(fuzzyEqual(resultAngle, angle)); const offset = b.p1.minus(a.p1); a = a.transformed(matrix().translate(offset.x, offset.y)); a.setAngle(a.angle() + resultAngle); if (!a.fuzzyIsNull()) a.setLength(b.length()); assert.isTrue(a.fuzzyEquals(b)); }); }); describe('map', () => { it('map', () => { const l = line(vector2d(2.3, 3.6), vector2d(-1.1, 0.5)); const bind2nd = (fn: (arg1: T1, arg2: T2) => R, boundArg: T2) => (arg: T1) => fn(arg, boundArg); assert.isTrue(l.map(p => p.map(Math.ceil)).equals(line(l.p1.map(Math.ceil), l.p2.map(Math.ceil)))); assert.isTrue(l.map(p => p.map(bind2nd(Math.pow, 2))).equals(line(l.p1.map(bind2nd(Math.pow, 2)), l.p2.map(bind2nd(Math.pow, 2))))); }); }); describe('boundingRectangle', () => { test.each([ [line(), rectangle()], [line(vector2d(1, 2), vector2d(1, 2)), rectangle(vector2d(1, 2))], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(1, 1), vector2d(3, 3))], [line(vector2d(4, 4), vector2d(1, 1)), rectangle(vector2d(1, 1), vector2d(3, 3))], [line(vector2d(1, 4), vector2d(4, 1)), rectangle(vector2d(1, 1), vector2d(3, 3))], [line(vector2d(4, 1), vector2d(1, 4)), rectangle(vector2d(1, 1), vector2d(3, 3))], [line(vector2d(1, 1), vector2d(4, 1)), rectangle(vector2d(1, 1), vector2d(3, 0))], [line(vector2d(4, 1), vector2d(1, 1)), rectangle(vector2d(1, 1), vector2d(3, 0))], [line(vector2d(1, 1), vector2d(1, 4)), rectangle(vector2d(1, 1), vector2d(0, 3))], [line(vector2d(1, 4), vector2d(1, 1)), rectangle(vector2d(1, 1), vector2d(0, 3))], ])('boundingRectangle#%#', (line: Line, result: Rectangle) => { assert.isTrue(line.boundingRectangle().equals(result)); }); }); describe('containsPoint', () => { test.each([ [line(vector2d(1, 1), vector2d(4, 4)), point(1, 1), false], [line(vector2d(1, 1), vector2d(4, 4)), point(2, 2), false], [line(vector2d(1, 1), vector2d(4, 4)), point(4, 4), false], [line(vector2d(1, 1), vector2d(4, 4)), point(1, 2), false], [line(vector2d(1, 1), vector2d(4, 4)), point(0, 0), false], [line(), point(1, 1), false], ])('containsPoint#%#', (line: Line, point: Point, result: boolean) => { assert.isTrue(line.containsPoint(point) === result); }); }); describe('containsLine', () => { test.each([ [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 1), vector2d(4, 4)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(4, 4)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 1), vector2d(2, 2)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(3, 3)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(0, 0), vector2d(2, 2)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(5, 5)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(4, 1), vector2d(1, 4)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(5, 5), vector2d(7, 7)), false], ])('containsLine#%#', (line1: Line, line2: Line, result: boolean) => { assert.isTrue(line1.containsLine(line2) === result); }); }); describe('containsCircle', () => { test.each([ [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(1, 1), 0), false], [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(2, 2), 0), false], [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(4, 4), 0), false], [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(5, 5), 0), false], [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(2, 2), 1), false], [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(1, 2), 0), false], ])('containsCircle#%#', (line: Line, circle: Circle, result: boolean) => { assert.isTrue(line.containsCircle(circle) === result); }); }); describe('containsRectangle', () => { test.each([ [line(), rectangle(), false], [line(), rectangle(vector2d(0, 0), vector2d(1, 1)), false], [line(), rectangle(vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(1, 1)), rectangle(), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(2, 2)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(4, 4)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(1, 1), vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(2, 2), vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(4, 4), vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(1, 2)), false], [line(vector2d(1, 1), vector2d(1, 4)), rectangle(vector2d(1, 1), vector2d(0, 3)), false], [line(vector2d(1, 1), vector2d(1, 4)), rectangle(vector2d(1, 1), vector2d(0, 2)), false], [line(vector2d(1, 1), vector2d(1, 4)), rectangle(vector2d(1, 1), vector2d(0, 4)), false], [line(vector2d(1, 1), vector2d(1, 4)), rectangle(vector2d(1, 1), vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(4, 1)), rectangle(vector2d(1, 1), vector2d(3, 0)), false], [line(vector2d(1, 1), vector2d(4, 1)), rectangle(vector2d(1, 1), vector2d(2, 0)), false], [line(vector2d(1, 1), vector2d(4, 1)), rectangle(vector2d(1, 1), vector2d(4, 0)), false], [line(vector2d(1, 1), vector2d(4, 1)), rectangle(vector2d(1, 1), vector2d(1, 1)), false], ])('containsRectangle#%#', (line: Line, rect: Rectangle, result: boolean) => { assert.isTrue(line.containsRectangle(rect) === result); }); }); describe('containsPolygon', () => { test.each([ [line(), polygon(), false], [line(vector2d(1, 1), vector2d(4, 4)), polygon(), false], [line(vector2d(1, 1), vector2d(4, 4)), polygon([vector2d(2, 2)]), false], [line(vector2d(1, 1), vector2d(4, 4)), polygon([vector2d(2, 2), vector2d(3, 3)]), false], [line(vector2d(1, 1), vector2d(4, 4)), polygon([vector2d(2, 2), vector2d(3, 3), vector2d(4, 4)]), false], [line(vector2d(1, 1), vector2d(4, 4)), polygon([vector2d(2, 2), vector2d(3, 3), vector2d(1, 4)]), false], ])('containsPolygon#%#', (line: Line, polygon: Polygon, result: boolean) => { assert.isTrue(line.containsPolygon(polygon) === result); }); }); describe('containsPolyline', () => { test.each([ [line(), polygon(), false], [line(vector2d(1, 1), vector2d(4, 4)), polyline(), false], [line(vector2d(1, 1), vector2d(4, 4)), polyline([vector2d(2, 2)]), false], [line(vector2d(1, 1), vector2d(4, 4)), polyline([vector2d(2, 2), vector2d(3, 3)]), false], [line(vector2d(1, 1), vector2d(4, 4)), polyline([vector2d(2, 2), vector2d(3, 3), vector2d(4, 4)]), false], [line(vector2d(1, 1), vector2d(4, 4)), polyline([vector2d(2, 2), vector2d(3, 3), vector2d(1, 4)]), false], ])('containsPolyline#%#', (line: Line, polyline: Polyline, result: boolean) => { assert.isTrue(line.containsPolyline(polyline) === result); }); }); describe('contains', () => { test.each([ [line(vector2d(1, 1), vector2d(4, 4)), point(1, 1), false], [line(vector2d(1, 1), vector2d(4, 4)), point(2, 2), false], [line(vector2d(1, 1), vector2d(4, 4)), point(4, 4), false], [line(vector2d(1, 1), vector2d(4, 4)), point(1, 2), false], [line(vector2d(1, 1), vector2d(4, 4)), point(0, 0), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 1), vector2d(4, 4)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(4, 4)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 1), vector2d(2, 2)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(3, 3)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(0, 0), vector2d(2, 2)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(5, 5)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(4, 1), vector2d(1, 4)), false], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(5, 5), vector2d(7, 7)), false], [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(1, 1), 0), false], [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(2, 2), 0), false], [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(4, 4), 0), false], [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(5, 5), 0), false], [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(2, 2), 1), false], [line(vector2d(1, 1), vector2d(4, 4)), circle(vector2d(1, 2), 0), false], [line(), rectangle(), false], [line(), rectangle(vector2d(0, 0), vector2d(1, 1)), false], [line(), rectangle(vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(1, 1)), rectangle(), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(2, 2)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(4, 4)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(1, 1), vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(2, 2), vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(4, 4), vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(4, 4)), rectangle(vector2d(1, 2)), false], [line(vector2d(1, 1), vector2d(1, 4)), rectangle(vector2d(1, 1), vector2d(0, 3)), false], [line(vector2d(1, 1), vector2d(1, 4)), rectangle(vector2d(1, 1), vector2d(0, 2)), false], [line(vector2d(1, 1), vector2d(1, 4)), rectangle(vector2d(1, 1), vector2d(0, 4)), false], [line(vector2d(1, 1), vector2d(1, 4)), rectangle(vector2d(1, 1), vector2d(1, 1)), false], [line(vector2d(1, 1), vector2d(4, 1)), rectangle(vector2d(1, 1), vector2d(3, 0)), false], [line(vector2d(1, 1), vector2d(4, 1)), rectangle(vector2d(1, 1), vector2d(2, 0)), false], [line(vector2d(1, 1), vector2d(4, 1)), rectangle(vector2d(1, 1), vector2d(4, 0)), false], [line(vector2d(1, 1), vector2d(4, 1)), rectangle(vector2d(1, 1), vector2d(1, 1)), false], [line(), polygon(), false], [line(vector2d(1, 1), vector2d(4, 4)), polygon(), false], [line(vector2d(1, 1), vector2d(4, 4)), polygon([vector2d(2, 2)]), false], [line(vector2d(1, 1), vector2d(4, 4)), polygon([vector2d(2, 2), vector2d(3, 3)]), false], [line(vector2d(1, 1), vector2d(4, 4)), polygon([vector2d(2, 2), vector2d(3, 3), vector2d(4, 4)]), false], [line(vector2d(1, 1), vector2d(4, 4)), polygon([vector2d(2, 2), vector2d(3, 3), vector2d(1, 4)]), false], ])('contains#%#', (shape1: Shape, shape2: Shape, result: boolean) => { assert.isTrue(shape2.contains(shape1) === result); }); }); describe('intersectsPoint', () => { test.each([ [line(vector2d(1, 1), vector2d(4, 4)), point(1, 1), true, [vector2d(1, 1)]], [line(vector2d(1, 1), vector2d(4, 4)), point(2, 2), true, [vector2d(2, 2)]], [line(vector2d(1, 1), vector2d(4, 4)), point(4, 4), true, [vector2d(4, 4)]], [line(vector2d(1, 1), vector2d(4, 4)), point(2, 1), false, undefined], [line(vector2d(1, 1), vector2d(4, 4)), point(0, 0), false, undefined], [line(vector2d(1, 1), vector2d(4, 4)), point(5, 5), false, undefined], ])('intersectsPoint#%#', (line: Line, point: Point, result: boolean, points: Vector2D[] | undefined) => { assert.isTrue(line.intersectsPoint(point) === result); const intersection: any = {}; line.intersectsPoint(point, function (this: any, points, thisShape, otherShape) { assert.isTrue(line === thisShape); assert.isTrue(point === otherShape); this.points = this.points === undefined ? points : this.points.concat(points); return false; }, intersection); assert.isTrue(fuzzyDeepEqual(intersection.points, points), `${intersection.points} !== ${points}`); }); }); describe('intersectsLine', () => { test.each([ [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 1), vector2d(4, 4)), true, [vector2d(1, 1), vector2d(4, 4)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(4, 4)), true, [vector2d(2, 2), vector2d(4, 4)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 1), vector2d(2, 2)), true, [vector2d(1, 1), vector2d(2, 2)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(3, 3)), true, [vector2d(2, 2), vector2d(3, 3)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(0, 0), vector2d(2, 2)), true, [vector2d(1, 1), vector2d(2, 2)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(5, 5)), true, [vector2d(2, 2), vector2d(4, 4)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 3), vector2d(3, 1)), true, [vector2d(2, 2)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(5, 5), vector2d(7, 7)), false, undefined], [line(vector2d(3, 1), vector2d(6, 4)), line(vector2d(5, 5), vector2d(7, 7)), false, undefined], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 4), vector2d(2, 3)), false, undefined], ])('intersectsLine#%#', (line1: Line, line2: Line, result: boolean, points: Vector2D[] | undefined) => { assert.isTrue(line1.intersectsLine(line2) === result); const intersection: any = {}; line1.intersectsLine(line2, function (this: any, points, thisShape, otherShape) { assert.isTrue(line1 === thisShape); assert.isTrue(line2 === otherShape); this.points = this.points === undefined ? points : this.points.concat(points); return false; }, intersection); assert.isTrue(fuzzyDeepEqual(intersection.points, points), `${intersection.points} !== ${points}`); }); }); describe('intersects', () => { test.each([ [line(vector2d(1, 1), vector2d(4, 4)), point(1, 1), true, [vector2d(1, 1)]], [line(vector2d(1, 1), vector2d(4, 4)), point(2, 2), true, [vector2d(2, 2)]], [line(vector2d(1, 1), vector2d(4, 4)), point(4, 4), true, [vector2d(4, 4)]], [line(vector2d(1, 1), vector2d(4, 4)), point(2, 1), false, undefined], [line(vector2d(1, 1), vector2d(4, 4)), point(0, 0), false, undefined], [line(vector2d(1, 1), vector2d(4, 4)), point(5, 5), false, undefined], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 1), vector2d(4, 4)), true, [vector2d(1, 1), vector2d(4, 4)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(4, 4)), true, [vector2d(2, 2), vector2d(4, 4)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 1), vector2d(2, 2)), true, [vector2d(1, 1), vector2d(2, 2)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(3, 3)), true, [vector2d(2, 2), vector2d(3, 3)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(0, 0), vector2d(2, 2)), true, [vector2d(1, 1), vector2d(2, 2)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(2, 2), vector2d(5, 5)), true, [vector2d(2, 2), vector2d(4, 4)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 3), vector2d(3, 1)), true, [vector2d(2, 2)]], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(5, 5), vector2d(7, 7)), false, undefined], [line(vector2d(3, 1), vector2d(6, 4)), line(vector2d(5, 5), vector2d(7, 7)), false, undefined], [line(vector2d(1, 1), vector2d(4, 4)), line(vector2d(1, 4), vector2d(2, 3)), false, undefined], [line(vector2d(3, 1), vector2d(1, 4)), circle(vector2d(5, 5), 3), false, undefined], [line(vector2d(2, 5), vector2d(2, 5)), circle(vector2d(5, 5), 3), true, [vector2d(2, 5)]], [line(vector2d(6, 6), vector2d(0, 6)), rectangle(vector2d(3, 4), vector2d(6, 4)), true, [vector2d(3, 6)]], [line(vector2d(5, 5), vector2d(7, 7)), rectangle(vector2d(3, 4), vector2d(6, 4)), false, undefined], [line(vector2d(4, 1), vector2d(8, 1)), polygon([vector2d(2, 7), vector2d(6, 1), vector2d(10, 7)]), true, [vector2d(6, 1), vector2d(6, 1)]], [line(vector2d(5, 6), vector2d(7, 6)), polygon([vector2d(2, 7), vector2d(6, 1), vector2d(10, 7)]), false, undefined], [line(vector2d(4, 1), vector2d(8, 1)), polyline([vector2d(2, 7), vector2d(6, 1), vector2d(10, 7)]), true, [vector2d(6, 1), vector2d(6, 1)]], [line(vector2d(5, 6), vector2d(7, 6)), polyline([vector2d(2, 7), vector2d(6, 1), vector2d(10, 7)]), false, undefined], ])('intersects#%#', (s1: Shape, s2: Shape, result: boolean, points: Vector2D[] | undefined) => { assert.isTrue(s2.intersects(s1) === result); const intersection: any = {}; s2.intersects(s1, function (this: any, points, thisShape, otherShape) { assert.isTrue(intersection === this); assert.isTrue(s1 === thisShape); assert.isTrue(s2 === otherShape); this.points = this.points === undefined ? points : this.points.concat(points); return false; }, intersection); assert.isTrue(fuzzyDeepEqual(intersection.points, points), `${intersection.points} !== ${points}`); }); }); describe('intersectsSelf', () => { it('intersectsSelf', () => { assert.isFalse(line().intersectsSelf()); assert.isFalse(line(vector2d(1, 2), vector2d(1, 2)).intersectsSelf()); }); }); describe('render', () => { const begin = [ { apply: 'beginPath', args: [] } ]; const end = [ { apply: 'stroke', args: [] }, { set: 'strokeStyle', value: undefined, }, { set: 'fillStyle', value: undefined, } ]; test.each([ [ line(), undefined, undefined, [ ...begin, { apply: 'moveTo', args: [0, 0] }, { apply: 'lineTo', args: [0, 0] }, ...end ] ], [ line(vector2d(1, 2), vector2d(3, 4)), undefined, undefined, [ ...begin, { apply: 'moveTo', args: [1, 2] }, { apply: 'lineTo', args: [3, 4] }, ...end ] ], [ line(vector2d(1, 2), vector2d(3, 4)), 'yellow', 'black', [ { set: 'strokeStyle', value: 'yellow', }, { set: 'fillStyle', value: 'black', }, ...begin, { apply: 'moveTo', args: [1, 2] }, { apply: 'lineTo', args: [3, 4] }, ...end ] ], ])('render#%#', (l: Line, strokeStyle: string | undefined, fillStyle: string | undefined, result: any[]) => { const interceptions: any[] = []; const context = canvasRenderingContext2DSpy(interceptions); l.render(context, strokeStyle, fillStyle); assert.deepEqual(interceptions, result); }); }); describe('Line.create', () => { it('default create', () => { const l = Line.create(); assert.strictEqual(l.p1.x, 0); assert.strictEqual(l.p1.y, 0); assert.strictEqual(l.p2.x, 0); assert.strictEqual(l.p2.y, 0); }); it('p1 create', () => { const l = Line.create(new Vector2D(1, 1)); assert.strictEqual(l.p1.x, 1); assert.strictEqual(l.p1.y, 1); assert.strictEqual(l.p2.x, 0); assert.strictEqual(l.p2.y, 0); }); it('p1, p2 create', () => { const l = Line.create(new Vector2D(1, 1), new Vector2D(-1, -1)); assert.strictEqual(l.p1.x, 1); assert.strictEqual(l.p1.y, 1); assert.strictEqual(l.p2.x, -1); assert.strictEqual(l.p2.y, -1); }); }); describe('Line.fromPolar', () => { it('fromPolar', () => { const l = Line.fromPolar(Math.sqrt(3 * 3 + 3 * 3), radians(-45)); assert.isTrue(l.fuzzyEquals(line(vector2d(0, 0), vector2d(3, 3)))); }); }); });