122 lines
2.8 KiB
TypeScript
122 lines
2.8 KiB
TypeScript
// @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
|
|
// depend on @types/bun so the import type is unresolved at compile time.
|
|
import { describe, expect, test } from 'bun:test'
|
|
import { type Point2D, subtractPolygonsFromPolygon, unionPolygons } from './polygon-union'
|
|
|
|
function polygonArea(points: Point2D[]) {
|
|
let area = 0
|
|
for (let i = 0; i < points.length; i++) {
|
|
const current = points[i]!
|
|
const next = points[(i + 1) % points.length]!
|
|
area += current[0] * next[1] - next[0] * current[1]
|
|
}
|
|
return Math.abs(area / 2)
|
|
}
|
|
|
|
describe('unionPolygons', () => {
|
|
test('collapses a contained polygon into the containing polygon', () => {
|
|
const small: Point2D[] = [
|
|
[0, 0],
|
|
[1, 0],
|
|
[1, 1],
|
|
[0, 1],
|
|
]
|
|
const large: Point2D[] = [
|
|
[-1, -1],
|
|
[2, -1],
|
|
[2, 2],
|
|
[-1, 2],
|
|
]
|
|
|
|
const result = unionPolygons([small, large])
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(polygonArea(result[0]!)).toBeCloseTo(9)
|
|
})
|
|
|
|
test('combines overlapping rectangles into one boundary', () => {
|
|
const left: Point2D[] = [
|
|
[0, 0],
|
|
[2, 0],
|
|
[2, 2],
|
|
[0, 2],
|
|
]
|
|
const right: Point2D[] = [
|
|
[1, 1],
|
|
[3, 1],
|
|
[3, 3],
|
|
[1, 3],
|
|
]
|
|
|
|
const result = unionPolygons([left, right])
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0]).toHaveLength(8)
|
|
expect(polygonArea(result[0]!)).toBeCloseTo(7)
|
|
})
|
|
|
|
test('keeps disjoint polygons as separate boundaries', () => {
|
|
const left: Point2D[] = [
|
|
[0, 0],
|
|
[1, 0],
|
|
[1, 1],
|
|
[0, 1],
|
|
]
|
|
const right: Point2D[] = [
|
|
[2, 0],
|
|
[3, 0],
|
|
[3, 1],
|
|
[2, 1],
|
|
]
|
|
|
|
const result = unionPolygons([left, right])
|
|
|
|
expect(result).toHaveLength(2)
|
|
expect(result.map(polygonArea)).toEqual([1, 1])
|
|
})
|
|
})
|
|
|
|
describe('subtractPolygonsFromPolygon', () => {
|
|
test('turns a boundary-overlapping cutter into an indentation', () => {
|
|
const slab: Point2D[] = [
|
|
[0, 0],
|
|
[4, 0],
|
|
[4, 3],
|
|
[0, 3],
|
|
]
|
|
const cutout: Point2D[] = [
|
|
[1, -0.5],
|
|
[3, -0.5],
|
|
[3, 1],
|
|
[1, 1],
|
|
]
|
|
|
|
const result = subtractPolygonsFromPolygon(slab, [cutout])
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0]).toContainEqual([1, 1])
|
|
expect(result[0]).toContainEqual([3, 1])
|
|
expect(polygonArea(result[0]!)).toBeCloseTo(10)
|
|
})
|
|
|
|
test('returns separate contours when a cutter splits the subject', () => {
|
|
const slab: Point2D[] = [
|
|
[0, 0],
|
|
[4, 0],
|
|
[4, 3],
|
|
[0, 3],
|
|
]
|
|
const cutout: Point2D[] = [
|
|
[1.5, -1],
|
|
[2.5, -1],
|
|
[2.5, 4],
|
|
[1.5, 4],
|
|
]
|
|
|
|
const result = subtractPolygonsFromPolygon(slab, [cutout])
|
|
|
|
expect(result).toHaveLength(2)
|
|
expect(result.map(polygonArea).sort((a, b) => a - b)).toEqual([4.5, 4.5])
|
|
})
|
|
})
|