diff --git a/src/boolean.ts b/src/boolean.ts index fa14e1e..4ebf12d 100644 --- a/src/boolean.ts +++ b/src/boolean.ts @@ -5,7 +5,7 @@ import {last} from '@mathigon/core'; -import {nearlyEquals} from '@mathigon/fermat'; +import {nearlyEquals, round} from '@mathigon/fermat'; import {Point} from './point'; // Based on https://github.com/velipso/polybooljs (MIT License) @@ -617,11 +617,16 @@ function segments(poly: MultiPolygon) { return calculate(root, true); } -function operate(poly1: MultiPolygon, poly2: MultiPolygon, selection: number[], precision?: number) { +function roundPoint(point: Point, useRound?: boolean, precision = 2) { + if (!useRound) return point; + return new Point(round(point.x, precision), round(point.y, precision)); +} + +function operate(poly1: MultiPolygon, poly2: MultiPolygon, selection: number[], precision?: number, useRound?: boolean) { if (precision !== undefined) PRECISION = precision; const root = new LinkedList(); - for (const s of segments(poly1)) addSegment(root, copy(s.start, s.end, s), true); - for (const s of segments(poly2)) addSegment(root, copy(s.start, s.end, s), false); + for (const s of segments(poly1)) addSegment(root, copy(roundPoint(s.start, useRound), roundPoint(s.end, useRound), s), true); + for (const s of segments(poly2)) addSegment(root, copy(roundPoint(s.start, useRound), roundPoint(s.end, useRound), s), false); const results = segmentChainer(select(calculate(root, false), selection)); PRECISION = DEFAULT_PRECISION; @@ -639,7 +644,7 @@ const INTERSECT = [0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 1, 1, 0, 2, 1, 0]; const DIFFERENCE = [0, 0, 0, 0, 2, 0, 2, 0, 1, 1, 0, 0, 0, 1, 2, 0]; const XOR = [0, 2, 1, 0, 2, 0, 0, 1, 1, 0, 0, 2, 0, 1, 2, 0]; -export const union = (p1: MultiPolygon, p2: MultiPolygon, precision?: number) => operate(p1, p2, UNION, precision); -export const intersect = (p1: MultiPolygon, p2: MultiPolygon, precision?: number) => operate(p1, p2, INTERSECT, precision); -export const difference = (p1: MultiPolygon, p2: MultiPolygon, precision?: number) => operate(p1, p2, DIFFERENCE, precision); -export const xor = (p1: MultiPolygon, p2: MultiPolygon, precision?: number) => operate(p1, p2, XOR, precision); +export const union = (p1: MultiPolygon, p2: MultiPolygon, precision?: number, useRound?: boolean) => operate(p1, p2, UNION, precision, useRound); +export const intersect = (p1: MultiPolygon, p2: MultiPolygon, precision?: number, useRound?: boolean) => operate(p1, p2, INTERSECT, precision, useRound); +export const difference = (p1: MultiPolygon, p2: MultiPolygon, precision?: number, useRound?: boolean) => operate(p1, p2, DIFFERENCE, precision, useRound); +export const xor = (p1: MultiPolygon, p2: MultiPolygon, precision?: number, useRound?: boolean) => operate(p1, p2, XOR, precision, useRound); diff --git a/src/polygon.ts b/src/polygon.ts index ff29240..001358e 100755 --- a/src/polygon.ts +++ b/src/polygon.ts @@ -118,16 +118,16 @@ export class Polygon implements GeoShape { return false; } - static union(polygons: Polygon[], precision?: number): Polygon[] { + static union(polygons: Polygon[], precision?: number, useRound?: boolean): Polygon[] { const [first, ...other] = polygons; if (!other.length) return [first]; const p1 = [first.points]; - const p2 = other.length > 1 ? Polygon.union(other, precision).map(p => p.points) : [polygons[1].points]; - return union(p1, p2, precision).map(p => new Polygon(...p)); + const p2 = other.length > 1 ? Polygon.union(other, precision, useRound).map(p => p.points) : [polygons[1].points]; + return union(p1, p2, precision, useRound).map(p => new Polygon(...p)); } - static intersection(polygons: Polygon[], precision?: number): Polygon[] { + static intersection(polygons: Polygon[], precision?: number, useRound?: boolean): Polygon[] { const [first, ...other] = polygons; if (!other.length) return [first]; @@ -136,16 +136,16 @@ export class Polygon implements GeoShape { for (const poly of other) { const p1 = intersection; const p2 = [poly.points]; - intersection = intersect(p1, p2, precision); + intersection = intersect(p1, p2, precision, useRound); if (!intersection.length) return []; } return intersection.map(p => new Polygon(...p)); } - static difference(p1: Polygon, p2: Polygon, precision?: number): Polygon[] { - const poly12 = difference([p1.points], [p2.points], precision); - const poly21 = difference([p2.points], [p1.points], precision); + static difference(p1: Polygon, p2: Polygon, precision?: number, useRound?: boolean): Polygon[] { + const poly12 = difference([p1.points], [p2.points], precision, useRound); + const poly21 = difference([p2.points], [p1.points], precision, useRound); return poly12.concat(poly21).map(p => new Polygon(...p)); } diff --git a/test/boolean-test.ts b/test/boolean-test.ts index 26c64b5..0efe787 100644 --- a/test/boolean-test.ts +++ b/test/boolean-test.ts @@ -7,6 +7,7 @@ import tape from 'tape'; import {difference, intersect, Polygon, union, xor} from '../src'; import {Point} from '../src'; +import {total} from '@mathigon/core'; const poly = (...p: number[][]) => p.map(q => new Point(q[0], q[1])); @@ -58,5 +59,24 @@ tape('intersections', (test) => { const r2 = Polygon.intersection([p1, p2]); test.equal(r2.length, 1); + // Minimally overlapping triangles + // If useRound is false this produces a zero segment error! + const t1 = new Polygon(new Point(630.64, 783.64), new Point(655.64, 826.941270189222), new Point(680.64, 783.64)); + const t2 = new Polygon(new Point(630.64, 783.6412701892219), new Point(655.64, 740.34), new Point(680.64, 783.6412701892219)); + const i1 = Polygon.intersection([t1, t2], undefined, true); + test.equal(i1.length, 0); + + test.end(); +}); + +tape('unions', (test) => { + // if you change useRound to true on this union it will produce a zero segment error. + const polyList = [ + new Polygon(new Point(1167.2641162274222, 3633.834721294776), new Point(1342.2641162274222, 3330.7258299702225), new Point(1167.2641162274222, 3330.7258299702225), new Point(1079.7641162274222, 3482.2802756324995)), + new Polygon(new Point(1692.26, 3936.95), new Point(1342.26, 3936.94), new Point(1254.76, 4088.49), new Point(1079.76, 4088.49), new Point(992.26, 3936.94), new Point(992.27, 3936.94), new Point(1167.26, 3633.83), new Point(1167.2636603221083, 3633.8336603221082), new Point(1342.26, 3330.74), new Point(1517.2542265184259, 3633.84), new Point(1692.26, 3633.84), new Point(1779.76, 3785.39)) + ]; + const badUnion = Polygon.union(polyList, undefined, false); + test.equal(Math.abs(total(badUnion.map(u => u.area)) - total(polyList.map(u => u.area))) < 1, true); + test.end(); });