UNPKG

fernandez-polygon-decomposition

Version:

An algorithm to decompose polygons with holes from "A practical algorithm for decomposing polygonal domains into convex polygons by diagonals" by J Fernández

96 lines (83 loc) 4.01 kB
import { vertexEqualityAfterAbsorption, orientation, isConvex } from './utils.js'; function rotateRight (arr, n) { const length = arr.length; return [...arr.slice(length - n), ...arr.slice(0, length - n)]; } /** * Merges two polygons. * polygon1 and polygon2 should be convex and share an edge (= two consecutive vertices) * * @param {{ x: number, y: number, id: number }[]} polygon1 * @param {{ x: number, y: number, id: number }[]} polygon2 * @returns {{ x: number, y: number, id: number }[]} */ export function mergePolygons (polygon1, polygon2) { // const sharedVertices = polygon1.map((v1, index) => [index, polygon2.findIndex(v2 => pointEquality(v1, v2))]).filter(([_, v2Index]) => v2Index > -1); // can be problematic when a point corresponds to several vertices const sharedVertices = polygon1.map((v1, index) => [index, polygon2.findIndex(v2 => vertexEqualityAfterAbsorption(v1, v2))]).filter(([_, v2Index]) => v2Index > -1); const polygon1Length = polygon1.length; if (sharedVertices.length !== 2) { throw new Error(`sharedVertices length should be 2 : ${JSON.stringify(sharedVertices)}`); } if ((sharedVertices[0][0] + 1) % polygon1Length === sharedVertices[1][0]) { return [ ...rotateRight(polygon1, polygon1Length - sharedVertices[1][0]), ...rotateRight(polygon2, polygon2.length - sharedVertices[0][1]).slice(1, -1), ]; } else { return [ ...rotateRight(polygon1, polygon1Length - sharedVertices[0][0]), ...rotateRight(polygon2, polygon2.length - sharedVertices[1][1]).slice(1, -1), ]; } } /** * Procedure to remove inessential diagonals from the partition of convex polygons. * * @params {{ x: number, y: number, id: number }[][]} polygons * @params {{ i2: { x: number, y: number, id: number }, j2: { x: number, y: number, id: number }, rightPolygon: { x: number, y: number, id: number }[][], leftPolygon: { x: number, y: number, id: number }[][] }[]} * @returns {{ x: number, y: number, id: number }[][]} */ export function mergingAlgorithm (polygons, LLE) { if (polygons.length < 2) { return polygons; } // LDP[poly] = true means that the polygon `poly` is one of the definitive polygons of the partition after the merging process. const LDP = new Map(); // LUP[poly1] = poly2 means that the polygon `poly1` is part of the polygon `poly2`. const LUP = new Map(); polygons.forEach((poly) => { LDP.set(poly, true); LUP.set(poly, poly); }); if (LLE.length + 1 !== polygons.length) { throw new Error('wtf ? LLE + 1 !== polygons.length (' + (LLE.length + 1) + ', ' + polygons.length + ')'); } for (let j = 0; j < LLE.length; j++) { const { i2, j2, rightPolygon, leftPolygon } = LLE[j]; const Pj = LUP.get(leftPolygon); const Pu = LUP.get(rightPolygon); const PjLength = Pj.length; const PuLength = Pu.length; // custom nextVertex & previousVertex to take into account the originalId (for absHol) const i1 = Pu[(Pu.findIndex(v => vertexEqualityAfterAbsorption(v, i2)) + PuLength - 1) % PuLength]; // previousVertex(i2, Pu); const i3 = Pj[(Pj.findIndex(v => vertexEqualityAfterAbsorption(v, i2)) + 1) % PjLength]; // nextVertex(i2, Pj); const j1 = Pj[(Pj.findIndex(v => vertexEqualityAfterAbsorption(v, j2)) + PjLength - 1) % PjLength]; // previousVertex(j2, Pj) const j3 = Pu[(Pu.findIndex(v => vertexEqualityAfterAbsorption(v, j2)) + 1) % PuLength]; // nextVertex(j2, Pu); if (orientation(i1, i2, i3) >= 0 && orientation(j1, j2, j3) >= 0) { const P = mergePolygons(Pj, Pu); if (!isConvex(P)) { throw new Error('mergePolygons is not convex !'); } LDP.set(Pj, false); LDP.set(Pu, false); LDP.set(P, true); LUP.set(P, P); for (const poly of LUP.keys()) { if (LUP.get(poly) === Pj || LUP.get(poly) === Pu) { LUP.set(poly, P); } } } } return [...LDP.entries()].filter(([_, inPartition]) => inPartition).map(([polygon]) => polygon); }