plotboilerplate
Version:
A simple javascript plotting boilerplate for 2d stuff.
125 lines (108 loc) • 4.23 kB
text/typescript
/**
* @requires Line
* @requires Vertex
*
* @author Ikaros Kappler
* @date 2020-12-04
* @modified 2020-12-07 Ported from vanilla JS to typescript.
* @modified 2024-11-22 Added the `insideBoundsOnly` param to the `splitPolygonToNonIntersecting` algorithm.
* @version 1.1.0
*/
import { Line } from "../../Line";
import { Vertex } from "../../Vertex";
/**
* Splitting a polygon at a self intersection results in two new polygons.
*/
type ISplit = [Array<Vertex>, Array<Vertex>];
/**
* This function cuts a given self-intersecting polygon into non-self-intersecting
* sub polygons.
*
* The algorithm only works for non-self-overlapping polygons:
* Please note that the union set of the sub polygons themselves will _not_ be disjunct if
* the input polyon overlaps with itself!
*
* See this illustration for details:
* ./demos/27-polygon-intersection-greinerhormann/splitting-polygon-types.png
*
* @name splitPolygonToNonIntersecting
* @param {Array<Vertex>} vertices - The polygon as an array of vertices.
* @param {number=10} maxDepth - Number of max recursive steps (default is 10).
* @return {Array<Array<Vertex>>} A sequence of non-self-intersecting sub polygons.
*/
export const splitPolygonToNonIntersecting = (() => {
/**
* @param {Array<Vertex>} vertices
* @param {number=10} maxDepth
*/
const splitPolygonToNonIntersecting = (
vertices: Array<Vertex>,
maxDepth?: number,
insideBoundsOnly?: boolean
): Array<Array<Vertex>> => {
if (typeof maxDepth === "undefined") maxDepth = 10;
return _splitPolygonToNonIntersecting(vertices, maxDepth, insideBoundsOnly);
};
const _splitPolygonToNonIntersecting = (
vertices: Array<Vertex>,
maxDepth: number,
insideBoundsOnly?: boolean
): Array<Array<Vertex>> => {
if (maxDepth <= 0) {
// aborting at max depth
return [vertices];
}
if (vertices.length <= 3) {
// No intersections possible
return [vertices];
}
const n: number = vertices.length;
const lineA: Line = new Line(new Vertex(), new Vertex());
const lineB: Line = new Line(new Vertex(), new Vertex());
for (var a = 0; a < vertices.length; a++) {
lineA.a.set(vertices[a]);
lineA.b.set(vertices[(a + 1) % n]);
for (var b = 0; b < vertices.length; b++) {
// Equal edges or neighbour edges intersect by definition.
// We ignore them.
if (a == b || a + 1 == b || a == b + 1 || (a == 0 && b + 1 == vertices.length) || (b == 0 && a + 1 == vertices.length))
continue;
lineB.a.set(vertices[b]);
lineB.b.set(vertices[(b + 1) % n]);
const intersectionPoint: Vertex | null = lineA.intersection(lineB);
if (
intersectionPoint &&
lineA.hasPoint(intersectionPoint, insideBoundsOnly) &&
lineB.hasPoint(intersectionPoint, insideBoundsOnly)
) {
// Cut polygon into two here
const split: ISplit = splitPolygonAt(vertices, a, b, intersectionPoint);
// Split has 2 elements.
const leftCleaned: Array<Array<Vertex>> = _splitPolygonToNonIntersecting(split[0], maxDepth - 1, insideBoundsOnly);
const rightCleaned: Array<Array<Vertex>> = _splitPolygonToNonIntersecting(split[1], maxDepth - 1, insideBoundsOnly);
return leftCleaned.concat(rightCleaned);
}
}
}
// No intersection found:
// just return a list with the original
// polygon as its only element.
return [vertices];
};
// Pre: edgeIndexA < edgeIndexB && vertices.length >= 4
const splitPolygonAt = (vertices: Array<Vertex>, edgeIndexA: number, edgeIndexB: number, intersectionPoint: Vertex): ISplit => {
const first: Array<Vertex> = [];
var second: Array<Vertex> = [];
for (var i = 0; i < vertices.length; i++) {
if (i <= edgeIndexA) first.push(vertices[i]);
if (i == edgeIndexA) {
first.push(intersectionPoint);
second.push(intersectionPoint); // clone???
}
if (i > edgeIndexA && i <= edgeIndexB) second.push(vertices[i]);
if (i > edgeIndexB) first.push(vertices[i]);
}
return [first, second];
};
return splitPolygonToNonIntersecting;
})();