Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Boolean fix #179

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions src/boolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<Event>();
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;
Expand All @@ -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);
16 changes: 8 additions & 8 deletions src/polygon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand All @@ -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));
}

Expand Down
20 changes: 20 additions & 0 deletions test/boolean-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]));
Expand Down Expand Up @@ -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();
});
Loading