@flatten-js/boolean-op
Version:
Boolean operations on polygons
525 lines (451 loc) • 21.7 kB
JavaScript
/**
* Created by alexbol on 1/21/2018.
*/
'use strict';
import {expect} from 'chai';
import Flatten from '@flatten-js/core';
import {Polygon} from '@flatten-js/core';
import {point, segment, arc, circle} from '@flatten-js/core';
import {unify, subtract, intersect} from '../index.js';
describe('#Algorithms.Boolean Operations', function () {
describe('#Algorithms.Boolean Union', function () {
it('Function unify defined', function () {
expect(unify).to.exist;
expect(unify).to.be.a('function');
});
// it('Function arrange defined', function () {
// expect(arrange).to.exist;
// expect(arrange).to.be.a('function');
// });
// it('Can arrange two polygons and add vertices', function () {
// "use strict";
// let poly1 = new Polygon();
// poly1.addFace([point(0, 0), point(150, 0), point(150, 30), point(0, 30)]);
// let poly2 = new Polygon();
// poly2.addFace([point(100, 20), point(200, 20), point(200, 40), point(100, 40)]);
// arrange(poly1, poly2);
// expect(poly1.edges.size).to.equal(6);
// expect(poly2.edges.size).to.equal(6);
// for (let face of poly1.faces) {
// expect(face.size).to.equal(6);
// }
// for (let face of poly2.faces) {
// expect(face.size).to.equal(6);
// }
// });
it('Can perform unify. 2 polygons, intersect', function () {
"use strict";
let poly1 = new Polygon();
poly1.addFace([point(0, 0), point(150, 0), point(150, 30), point(0, 30)]);
let poly2 = new Polygon();
poly2.addFace([point(100, 20), point(200, 20), point(200, 40), point(100, 40)]);
let poly = unify(poly1, poly2);
expect(poly.faces.size).to.equal(1);
for (let face of poly.faces) {
expect(face.size).to.equal(8);
}
let vertices = poly.vertices;
expect(vertices.find((pt) => pt.equalTo(point(0, 0)))).not.to.be.undefined;
expect(vertices.find((pt) => pt.equalTo(point(150, 0)))).not.to.be.undefined;
expect(vertices.find((pt) => pt.equalTo(point(150, 30)))).to.be.undefined;
expect(vertices.find((pt) => pt.equalTo(point(0, 30)))).not.to.be.undefined;
expect(vertices.find((pt) => pt.equalTo(point(100, 20)))).to.be.undefined;
expect(vertices.find((pt) => pt.equalTo(point(200, 20)))).not.to.be.undefined;
expect(vertices.find((pt) => pt.equalTo(point(200, 40)))).not.to.be.undefined;
expect(vertices.find((pt) => pt.equalTo(point(100, 40)))).not.to.be.undefined;
});
it('Can perform unify. 2 polygons, disjoint', function () {
"use strict";
let poly1 = new Polygon();
poly1.addFace([point(0, 0), point(50, 0), point(50, 30), point(0, 30)]);
let poly2 = new Polygon();
poly2.addFace([point(100, 50), point(200, 50), point(200, 100), point(100, 100)]);
let poly = unify(poly1, poly2);
expect(poly.faces.size).to.equal(2);
for (let face of poly.faces) {
expect(face.size).to.equal(4);
}
});
it('Can perform unify. 2 polygons, 1 in 2', function () {
"use strict";
let poly1 = new Polygon();
poly1.addFace([point(0, 0), point(50, 0), point(50, 30), point(0, 30)]);
let poly2 = new Polygon();
poly2.addFace([point(-100, -50), point(200, -50), point(200, 100), point(-100, 100)]);
let poly = unify(poly1, poly2);
expect(poly.faces.size).to.equal(1);
for (let face of poly.faces) {
expect(face.size).to.equal(4);
}
});
it('Can perform unify. 2 polygons, 2 in 1', function () {
"use strict";
let poly1 = new Polygon();
poly1.addFace([point(0, 0), point(50, 0), point(50, 30), point(0, 30)]);
let poly2 = new Polygon();
poly2.addFace([point(-100, -50), point(200, -50), point(200, 100), point(-100, 100)]);
let poly = unify(poly2, poly1);
expect(poly.faces.size).to.equal(1);
expect(poly.edges.size).to.equal(4);
for (let face of poly.faces) {
expect(face.size).to.equal(4);
}
});
it('Can perform unify. 2 polygons, 2 in 1 touching from inside, overlapping same', function () {
"use strict";
let poly1 = new Polygon();
poly1.addFace([point(0, 0), point(50, 0), point(50, 30), point(0, 30)]);
expect([...poly1.edges][0].shape instanceof Flatten.Segment).to.be.true;
let poly2 = new Polygon();
poly2.addFace([point(25, 0), point(50, 0), point(50, 15), point(25, 15)]);
let poly = unify(poly1, poly2);
expect(poly.faces.size).to.equal(1);
expect(poly.edges.size).to.equal(6);
});
it('Can perform unify. 2 polygons, 2 in 1 touching from outside, overlapping opposite', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([
segment(-50, -50, 50, -50),
segment(50, -50, 50, 50),
segment(50, 50, -50, 50),
segment(-50, 50, -50, -50)
]);
let polygon2 = new Polygon();
polygon2.addFace([
segment(0, 50, 100, 50),
segment(100, 50, 100, 100),
segment(100, 100, 0, 100),
segment(0, 100, 0, 50)
]);
let poly = unify(polygon1, polygon2);
expect(poly.faces.size).to.equal(1);
expect(poly.edges.size).to.equal(8);
});
it('Can perform unify. 2 polygons form cross-shape', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([
point(-10, 0),
point(10, 0),
point(10, 80),
point(-10, 80)
]);
let polygon2 = new Polygon();
polygon2.addFace([
point(-40, 30),
point(40, 30),
point(40, 50),
point(-40, 50)
]);
let poly = unify(polygon1, polygon2);
expect(poly.faces.size).to.equal(1);
expect(poly.edges.size).to.equal(12);
});
it('Can perform unify. 2 disjoint polygons', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([
point(-10, 0),
point(10, 0),
point(10, 20),
point(-10, 20)
]);
let polygon2 = new Polygon();
polygon2.addFace([
point(-40, 30),
point(40, 30),
point(40, 50),
point(-40, 50)
]);
let poly = unify(polygon1, polygon2);
expect(poly.faces.size).to.equal(2);
expect(poly.edges.size).to.equal(8);
});
it('Can perform unify. 1st polygon with one round hole, 2nd polygon partially intersect hole ', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([
point(-10, 0),
point(-10, 20),
point(10, 20),
point(10, 0)
]);
polygon1.addFace(
[circle(point(0, 10), 5).toArc(true)]
);
let polygon2 = new Polygon();
polygon2.addFace([
point(-40, 13),
point(-40, 50),
point(40, 50),
point(40, 13)
]);
let poly = unify(polygon1, polygon2);
expect(poly.faces.size).to.equal(2);
let faces = [...poly.faces];
expect(faces[0].size).to.equal(8);
expect(faces[1].size).to.equal(3);
expect(poly.edges.size).to.equal(11);
});
it('Can perform unify. 1st polygon with one round hole, 2nd polygon fully cover hole ', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([
point(-10, 0),
point(-10, 20),
point(10, 20),
point(10, 0)
]);
polygon1.addFace(
[circle(point(0, 10), 5).toArc(true)]
);
let polygon2 = new Polygon();
polygon2.addFace([
point(-8, 2),
point(-8, 18),
point(8, 18),
point(8, 2)
]);
let poly = unify(polygon1, polygon2);
expect(poly.faces.size).to.equal(1);
expect(poly.edges.size).to.equal(4);
});
it('Can perform unify. 2 polygons create one triangular hole after unify', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([point(100,10), point(100, 300), point(350, 300),point(150, 150), point(350, 10)]);
let polygon2 = new Polygon();
polygon2.addFace([point(400, 10), point(300, 10), point(300,300), point(400, 300)]);
let poly = unify(polygon1, polygon2);
expect(poly.faces.size).to.equal(2);
expect([...poly.faces][0].size).to.equal(8);
expect([...poly.faces][0].orientation()).to.equal(Flatten.ORIENTATION.CW);
expect([...poly.faces][1].size).to.equal(3);
expect([...poly.faces][1].orientation()).to.equal(Flatten.ORIENTATION.CCW);
expect(poly.edges.size).to.equal(11);
});
});
describe('#Algorithms.Boolean Subtraction', function () {
it('Can perform subtract. 2 intersecting polygons', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([
point(-10, 0),
point(-10, 20),
point(10, 20),
point(10, 0)
]);
let polygon2 = new Polygon();
polygon2.addFace([
point(5, 10),
point(5, 30),
point(15, 30),
point(15, 10)
]);
let poly = subtract(polygon1, polygon2);
expect(poly.faces.size).to.equal(1);
expect(poly.edges.size).to.equal(6);
});
it('Can perform subtract. 1-face polygon split with 2nd to 2-faced polygon and vice a verse', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([
point(-20, 0),
point(-20, 20),
point(20, 20),
point(20, 0)
]);
let polygon2 = new Polygon();
polygon2.addFace([
point(-5, -10),
point(-5, 30),
point(5, 30),
point(5, -10)
]);
expect([...polygon1.faces][0].orientation()).to.equal(Flatten.ORIENTATION.CW);
expect([...polygon2.faces][0].orientation()).to.equal(Flatten.ORIENTATION.CW);
let poly = subtract(polygon1, polygon2);
expect(poly.faces.size).to.equal(2);
expect(poly.edges.size).to.equal(8);
poly = subtract(polygon2, polygon1);
expect(poly.faces.size).to.equal(2);
expect(poly.edges.size).to.equal(8);
});
it('Can perform subtract. 2 intersecting polygons produce 2-island result', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([point(100, 10), point(100, 300), point(400, 150), point(250, 10)]);
let polygon2 = new Polygon();
polygon2.addFace([point(450, 10), point(0, 150), point(300, 300), point(600, 300)]);
let poly;
poly = subtract(polygon1, polygon2);
expect(poly.faces.size).to.equal(2);
expect(poly.edges.size).to.equal(7);
poly = subtract(polygon2, polygon1);
expect(poly.faces.size).to.equal(2);
expect(poly.edges.size).to.equal(9);
});
// it('Can perform (boolean) subtraction. First polygon inside the second', function () {
// "use strict";
//
// let {Polygon, point} = Flatten;
//
// let polygon1 = new Polygon();
// polygon1.addFace([point(100, 10), point(100, 300), point(400, 150), point(250, 10)]);
//
// let polygon2 = new Polygon();
// polygon2.addFace([point(50, 0), point(50, 400), point(500, 400), point(500, 0)]);
//
// let poly = subtract(polygon2, polygon1);
// expect(poly.faces.size).to.equal(2);
// expect(poly.edges.size).to.equal(8);
//
// });
it('Can perform subtract big polygon from smaller polygon and get empty result (Issue #4)', function() {
let polygon1 = new Polygon();
polygon1.addFace([point(0,0), point(100, 0), point(100, 100), point(0, 100)]);
let polygon2 = new Polygon();
polygon2.addFace([point(10, 10), point(90, 10), point(90,90), point(10, 90)]);
// polygon2 is completely inside polygon1, I expect the result polygon to be empty
let polygon = subtract(polygon2, polygon1);
expect(polygon.isEmpty()).to.be.true;
expect(polygon.faces.size).to.equal(0);
expect(polygon.edges.size).to.equal(0);
});
it("Can subtract one polygon from another and create a hole (Issue #7)", function() {
const baseZ0Surface = [[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0]]];
const z0Surface = [[[1, 1, 0], [9, 1, 0], [9, 9, 0], [1, 9, 0]]]; // where is the hole?
// const z0Surface = [[[1, 1, 0], [9, 1, 0], [9, 9, 0], [1, 11, 0]]]; // subtraction works when not producing holes
const a = new Polygon();
for (const polygon of baseZ0Surface) {
let face = a.addFace(polygon.map(([x, y]) => point(x, y)));
if (face.orientation() !== Flatten.ORIENTATION.CCW) {
face.reverse();
}
}
const b = new Polygon();
for (const polygon of z0Surface) {
let face = b.addFace(polygon.map(([x, y]) => point(x, y)));
if (face.orientation() !== Flatten.ORIENTATION.CCW) {
face.reverse();
}
}
const myPoly = subtract(a, b);
expect(myPoly.faces.size).to.equal(2);
expect(myPoly.edges.size).to.equal(8);
expect([...myPoly.faces][0].orientation()).to.equal(Flatten.ORIENTATION.CCW);
expect([...myPoly.faces][1].orientation()).to.equal(Flatten.ORIENTATION.CW);
});
});
describe('#Algorithms.Boolean Intersection', function () {
it('Can perform (boolean) intersection. 2 intersecting polygons', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([
point(-10, 0),
point(-10, 20),
point(10, 20),
point(10, 0)
]);
let polygon2 = new Polygon();
polygon2.addFace([
point(5, 10),
point(5, 30),
point(15, 30),
point(15, 10)
]);
let poly = intersect(polygon1, polygon2);
expect(poly.faces.size).to.equal(1);
expect(poly.edges.size).to.equal(4);
expect([...poly.faces][0].size).to.equal(4);
});
it('Can perform (boolean) intersection. Other 2 intersecting polygons', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([point(100, 10), point(100, 300), point(400, 150), point(250, 10)]);
let polygon2 = new Polygon();
polygon2.addFace([point(450, 10), point(0, 150), point(300, 300), point(600, 300)]);
let poly = intersect(polygon1, polygon2);
expect(poly.faces.size).to.equal(1);
expect(poly.edges.size).to.equal(5);
expect([...poly.faces][0].size).to.equal(5);
});
it('Can perform (boolean) intersection. First polygon inside the second', function () {
"use strict";
let polygon1 = new Polygon();
polygon1.addFace([point(100, 10), point(100, 300), point(400, 150), point(250, 10)]);
let polygon2 = new Polygon();
polygon2.addFace([point(50, 0), point(50, 400), point(500, 400), point(500, 0)]);
let poly = intersect(polygon1, polygon2);
expect(poly.faces.size).to.equal(1);
expect(poly.edges.size).to.equal(4);
expect([...poly.faces][0].size).to.equal(4);
});
it("Issue #2 with intersection of circle and box", function() {
"use strict"
let myPoly = new Polygon();
myPoly.addFace([point(50, 50), point(50,950), point(950, 950), point(950, 50)]);
// myPoly.addFace([point(50, 50), point(950, 50), point(950, 950), point(50,950)]);
let myCircle = new Polygon();
myCircle.addFace([arc(point(0,1000),980, 0, 2*Math.PI, Flatten.CW)]);
myPoly = intersect(myPoly, myCircle);
myCircle = new Polygon();
myCircle.addFace([arc(point(0,1000),780, 0, 2*Math.PI, Flatten.CW)]);
myPoly = subtract(myPoly, myCircle);
myCircle = new Polygon();
myCircle.addFace([arc(point(1000,1000),1330, 0, 2*Math.PI, Flatten.CW)]);
myPoly = intersect(myPoly,myCircle);
myCircle = new Polygon();
myCircle.addFace([arc(point(1000,1000),1130, 0, 2*Math.PI, Flatten.CW)]);
myPoly = subtract(myPoly,myCircle);
myCircle = new Polygon();
myCircle.addFace([arc(point(1000,0),980, 0, 2*Math.PI, Flatten.CW)]);
myPoly = intersect(myPoly, myCircle);
expect(myPoly.faces.size).to.equal(1);
expect(myPoly.edges.size).to.equal(6);
expect([...myPoly.faces][0].size).to.equal(6);
expect([...myPoly.faces][0].orientation()).to.equal(Flatten.ORIENTATION.CW);
});
it("Issue #3", function() {
"use strict";
let myPoly = new Polygon();
myPoly.addFace([point(6, 6), point(6,114), point(114, 114), point(114, 6)]);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myPoly); // 0
let myCircle = new Polygon();
myCircle.addFace([arc(point(0,0),84.5779281026111, 0, 2*Math.PI, Flatten.CW)]);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myCircle); // 1
myPoly = intersect(myPoly,myCircle);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myPoly); // 2
myCircle = new Polygon();
myCircle.addFace([arc(point(0,0),84.49938828627135, 0, 2*Math.PI, Flatten.CW)]);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myCircle); // 3
myPoly = subtract(myPoly,myCircle);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myPoly); // 4
myCircle = new Polygon();
myCircle.addFace([arc(point(0,120),84.8710637077582, 0, 2*Math.PI, Flatten.CW)]);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myCircle); // 5
myPoly = intersect(myPoly,myCircle);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myPoly); // 6
myCircle = new Polygon();
myCircle.addFace([arc(point(0,120),84.79252389141845, 0, 2*Math.PI, Flatten.CW)]);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myCircle); // 7
myPoly = subtract(myPoly,myCircle);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myPoly); // 8
myCircle = new Polygon();
myCircle.addFace([arc(point(120,120),85.20624291591454, 0, 2*Math.PI, Flatten.CW)]);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myCircle); // 9
myPoly = intersect(myPoly,myCircle);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myPoly); // 10
myCircle = new Polygon();
myCircle.addFace([arc(point(120,120), 85.1277030995748, 0, 2*Math.PI, Flatten.CW)]);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myCircle); // 11
myPoly = subtract(myPoly,myCircle);
// state.layers[state.layers.length] = Layers.newLayer(stage, layers).add(myPoly); // 12
expect(myPoly.faces.size).to.equal(1);
expect(myPoly.edges.size).to.equal(7);
expect([...myPoly.faces][0].size).to.equal(7);
expect([...myPoly.faces][0].orientation()).to.equal(Flatten.ORIENTATION.CW);
});
});
});