'use strict';
const PolyBool = require('polybooljs');
const Line = require('./line');
const Vector2 = require('./vector2');
/**
* Polygon Class
* @property {Vector2} first The first point of the polygon, follow next on first to iterate
*/
class Polygon {
/**
* Create a new Polygon
* @param {Array<Vector2|BezierCurve>} [elements] Vertices, Curves of the polygon
*/
constructor(elements = []) {
this.children = [];
this.first = null;
if (elements && elements.length) {
for (const element of elements) {
this.addElement(element);
}
}
}
/**
* Wrapper for checking if point is inside a polygon
* @param {Vector2} point Point to check
* @returns {boolean}
*/
pointIsInside(point) {
return this.pointIsInsidePolygon(point);
}
/**
* Checking if point is inside a polygon
* @param {Vector2} point Point to check
* @returns {boolean}
*/
pointIsInsidePolygon(point) {
let oddNodes = false;
let vertex = this.first;
if (vertex.isCurve) {
vertex = vertex.next;
}
let next = vertex.next;
if (next.isCurve) {
next = next.next;
}
const x = point.x;
const y = point.y;
do {
if (
(
(vertex.y < y && next.y >= y) ||
(next.y < y && vertex.y >= y)
) &&
(vertex.x <= x || next.x <= x)
) {
oddNodes ^= (vertex.x + ((y - vertex.y) /
(next.y - vertex.y) * (next.x - vertex.x)) < x);
}
vertex = vertex.next;
if (vertex.isCurve) {
vertex = vertex.next;
}
next = vertex.next || this.first;
if (next.isCurve) {
next = next.next;
}
} while (!vertex.equals(this.first));
return oddNodes;
}
/**
* Setting all children polygons (for holes)
* @param {Polygon[]} polygons Children to set
* @returns {Polygon}
*/
setChildren(polygons) {
this.children = polygons;
return this;
}
/**
* Adding a child polygon (for hole)
* @param {Polygon} polygon Child polygon to add
* @returns {Polygon}
*/
addChild(polygon) {
this.children.push(polygon);
return this;
}
/**
* Setting vertices of the polygon
* @param {Vector2[]} vertices Vertices to set
* @deprecated
* @returns {Polygon}
*/
setVertexes(vertices) {
return this.setElements(vertices);
}
/**
* Setting elements of the polygon
* @param {Array<Vector2|BezierCurve>} vertices Vertices to set
* @returns {Polygon}
*/
setElements(elements) {
this.first = null;
for (const v of elements) {
this.addElement(v);
}
return this;
}
/**
* Add a new vertices to the end
* @param {Vector2} vertice the vertice to add
* @deprecated
* @returns {Polygon}
*/
addVertex(vertice) {
return this.addElement(vertice);
}
/**
* Add a new vertices to the end
* @param {Vector2|BezierCurve} element the vertice or curve to add
* @returns {Polygon}
*/
addElement(element) {
if (this.first === null) {
this.first = element;
this.first.next = element;
this.first.prev = element;
} else {
const next = this.first;
const prev = next.prev;
next.prev = element;
element.next = next;
element.prev = prev;
prev.next = element;
}
return this;
}
/**
* Is the current polygon colliding with the given polygon
* @param {Polygon} polygon the second polygon for the collision
* @returns {Boolean}
*/
colliding(polygon) {
return Polygon.Colliding(this, polygon);
}
/**
* Intersect boolean operation on this polygon with the given polygon
* @param {Polygon} polygon the second polygon for the operation
* @returns {Polygon[]}
*/
intersect(polygon) {
return Polygon.Intersect(this, polygon);
}
/**
* Difference boolean operation on this polygon with the given polygon
* @param {Polygon} polygon the second polygon for the operation
* @returns {Polygon[]}
*/
difference(p) {
return Polygon.Difference(this, p);
}
/**
* Union boolean operation on this polygon with the given polygon
* @param {Polygon} polygon the second polygon for the operation
* @returns {Polygon[]}
*/
union(p) {
return Polygon.Union(this, p);
}
/**
* Xor boolean operation on this polygon with the given polygon
* @param {Polygon} polygon the second polygon for the operation
* @returns {Polygon[]}
*/
xor(p) {
return Polygon.Xor(this, p);
}
/**
* Move all vertices of the polygon by a Vector2
* @param {Vector2} value The value to move by
* @returns {Vector2}
*/
moveBy(value) {
let v = this.first;
if (v.isCurve) {
v = v.next;
}
if (v.isCurve) {
v = v.next;
}
do {
v.add(value);
let next = v.next;
if (next.isCurve) {
next = next.next;
}
v = next;
} while (!v.equals(this.first));
return this;
}
/* GETTERS */
/**
* Get region for polybooljs plugin, can be also be used for GeoJson.
* Return a double level array ([[x, y], [x, y], ...])
* @type {Array[]}
* @readonly
*/
get region() {
return this.points.map((p) => {
return [p.x, p.y];
});
}
/**
* Get points of the Polygon
* @type {Vector2[]}
* @readonly
*/
get points() {
const points = [];
let v = this.first;
if (v.isCurve) {
v = v.next;
}
do {
let next = v.next;
if (next.isCurve) {
next = next.next;
}
points.push(v);
v = next;
} while (!v.equals(this.first));
return points;
}
/**
* Get lines of the Polygon
* @type {Line[]}
* @readonly
*/
get lines() {
const lines = [];
let prev = null;
let cur = this.first;
if (cur.isCurve) {
cur = cur.next;
}
do {
let next = cur.next;
if (next.isCurve) {
next = next.next;
}
if (prev !== null) {
lines.push(new Line(prev, cur));
}
prev = cur;
cur = next;
} while (!cur.equals(this.first));
return lines;
}
/**
* Get area of the Polygon
* @type {Number}
* @readonly
*/
get area() {
let total = 0;
let v = this.first;
if (v.isCurve) {
v = v.next;
}
do {
let next = v.next;
if (next.isCurve) {
next = next.next;
}
total += (v.x * next.y) - (v.y * next.x);
v = next;
} while (!v.equals(this.first));
let area = total / 2;
if (this.children && this.children.length) {
for (const child of this.children) {
area -= child.area;
}
}
return Math.abs(area);
}
/**
* Did the polygon is convex
* @readonly
* @see isConcave
* @type {Boolean}
*/
get isConvex() {
return !this.isConcave;
}
/**
* Did the polygon is concave
* @readonly
* @see isConvex
* @type {Boolean}
*/
get isConcave() {
let v = this.first;
if (v.isCurve) {
v = v.next;
}
do {
let next = v.next;
if (next.isCurve) {
next = next.next;
}
const line = new Line(v, next);
if (this.pointIsInsidePolygon(line.alongPointUnclamped(1.01))) {
return true;
}
v = next;
} while (!v.equals(this.first));
return false;
}
/**
* Convert the current polygon to a hull polygon (convert to convex)
* @readonly
* @type {Polygon}
*/
get hull() {
if (this.isConvex) {
return this;
}
let v = this.first;
if (v.isCurve) {
v = v.next;
}
do {
let next = v.next;
if (next.isCurve) {
next = next.next;
}
const line = new Line(v, next);
if (this.pointIsInsidePolygon(line.alongPointUnclamped(1.01))) {
if (next.equals(this.first)) {
this.first = next.next;
}
next = next.next;
}
v = next;
if (v.isCurve) {
v = next;
}
} while (!v.equals(this.first));
return this;
}
/**
* Alias of hull
* @see {@link hull}
* @readonly
* @type {Polygon}
*/
get convex() {
return this.hull;
}
/**
* Clonning the current polygon, usefull to not modify the current polygon
* @readonly
* @type {Polygon}
*/
get clone() {
return new Polygon(this.points);
}
/**
* Is the p1 polygon colliding with the p2 polygon
* @param {Polygon} p1 the first polygon for the collision
* @param {Polygon} p2 the second polygon for the collision
* @returns {Boolean}
*/
static Colliding(p1, p2) {
const p1Points = p1.points;
const p2Points = p2.points;
for (const pt1 of p1Points) {
if (p2.pointIsInside(pt1)) {
return true;
}
}
for (const pt2 of p2Points) {
if (p1.pointIsInside(pt2)) {
return true;
}
}
return false;
}
/**
* Convert region to polygon
* @param {Array[]} region the region to convert
* @returns {Polygon}
*/
static FromRegion(region) {
return new Polygon(region.map((p) => {
return new Vector2(p[0], p[1]);
}));
}
/**
* Intersect boolean operation on the p1 polygon with the p2 polygon
* @param {Polygon} polygon the first polygon for the operation
* @param {Polygon} polygon the second polygon for the operation
* @returns {Polygon[]}
*/
static Intersect(p1, p2) {
const operation = PolyBool.intersect({
regions: [
p1.region
],
inverted: false
}, {
regions: [
p2.region
],
inverted: false
});
return operation.regions.map((r) => {
return Polygon.FromRegion(r);
});
}
/**
* Union boolean operation on the p1 polygon with the p2 polygon
* @param {Polygon} polygon the first polygon for the operation
* @param {Polygon} polygon the second polygon for the operation
* @returns {Polygon[]}
*/
static Union(p1, p2) {
const operation = PolyBool.union({
regions: [
p1.region
],
inverted: false
}, {
regions: [
p2.region
],
inverted: false
});
return operation.regions.map((r) => {
return Polygon.FromRegion(r);
});
}
/**
* Difference boolean operation on the p1 polygon with the p2 polygon
* @param {Polygon} polygon the first polygon for the operation
* @param {Polygon} polygon the second polygon for the operation
* @returns {Polygon[]}
*/
static Difference(p1, p2) {
const operation = PolyBool.difference({
regions: [
p1.region
],
inverted: false
}, {
regions: [
p2.region
],
inverted: false
});
return operation.regions.map((r) => {
return Polygon.FromRegion(r);
});
}
/**
* Xor boolean operation on the p1 polygon with the p2 polygon
* @param {Polygon} polygon the first polygon for the operation
* @param {Polygon} polygon the second polygon for the operation
* @returns {Polygon[]}
*/
static Xor(p1, p2) {
const operation = PolyBool.xor({
regions: [
p1.region
],
inverted: false
}, {
regions: [
p2.region
],
inverted: false
});
return operation.regions.map((r) => {
return Polygon.FromRegion(r);
});
}
}
module.exports = Polygon;