move unionTris into geo/polygons as unionFaces

This commit is contained in:
Stewart Allen 2025-12-23 21:35:30 -05:00
commit 146c8d93b7
2 changed files with 74 additions and 68 deletions

View file

@ -71,6 +71,7 @@ const POLYS = {
toClipper,
trimTo,
union,
unionFaces,
xor,
};
@ -487,6 +488,78 @@ export function union(polys, minarea, all, opt = {}) {
return uset;
}
/**
* Recursively process triangle soup from mesh by bucketing them
* by grid cell locality with max depth. This minimizes the number
* of progressive small unions. Results in a few unions of larger polys.
* Optimized for unioning 1000+ triangular faces from mesh slicing.
*
* @param {Polygon[]} polys - array of polygons (typically triangular faces)
* @param {number} depth - recursion depth (internal, default 0)
* @returns {Polygon[]} unioned polygons
*/
export function unionFaces(polys, depth = 0) {
if (depth < 5 && polys.length > 1000) {
// Calculate bounding box of all triangles
let minX = Infinity, maxX = -Infinity;
let minY = Infinity, maxY = -Infinity;
for (let tri of polys) {
for (let pt of tri.points) {
minX = Math.min(minX, pt.x);
maxX = Math.max(maxX, pt.x);
minY = Math.min(minY, pt.y);
maxY = Math.max(maxY, pt.y);
}
}
// Create NxN grid
const gridSize = 3;
const cellWidth = (maxX - minX) / gridSize;
const cellHeight = (maxY - minY) / gridSize;
// Partition polygons into grid cells
const grid = Array(gridSize).fill(0).map(() => Array(gridSize).fill(0).map(() => []));
for (let i = 0; i < polys.length; i++) {
const poly = polys[i];
// Use polygon centroid to assign to cell
const bounds = poly.bounds;
const cx = (bounds.minx + bounds.maxx) / 2;
const cy = (bounds.miny + bounds.maxy) / 2;
// Clamp to grid bounds
let gx = Math.floor((cx - minX) / cellWidth);
let gy = Math.floor((cy - minY) / cellHeight);
gx = Math.max(0, Math.min(gridSize - 1, gx));
gy = Math.max(0, Math.min(gridSize - 1, gy));
grid[gx][gy].push(poly);
}
// Union within each cell
const cellResults = [];
let totalInCell = 0;
for (let gx = 0; gx < gridSize; gx++) {
for (let gy = 0; gy < gridSize; gy++) {
const cellPolys = grid[gx][gy];
if (cellPolys.length > 0) {
totalInCell += cellPolys.length;
const cellUnion = unionFaces(cellPolys, depth + 1);
cellResults.push(...cellUnion);
}
}
}
// Union the cell results
polys = unionFaces(cellResults, depth + 1);
} else {
polys = union(polys, 0, true, { wasm: false });
}
return polys;
}
/**
* @param {Polygon} poly clipping mask
* @returns {?Polygon[]}

View file

@ -965,7 +965,7 @@ class Widget {
});
// recursively merge grid constrained subsets of polygons
polys = unionTris(polys);
polys = POLY.unionFaces(polys);
// for a more perfect union, pump shadows to merge very close lines
// todo: create clipper only version that avoids round trip thru geo classes
@ -976,73 +976,6 @@ class Widget {
}
}
/*
* recursively process triangle soup from mesh by bucketing them
* by grid cell locality with max depth. this minimizes the number
* of progressive small unions. results in a few unions of larger polys.
*/
function unionTris(polys, depth = 0) {
if (depth < 5 && polys.length > 1000) {
// Calculate bounding box of all triangles
let minX = Infinity, maxX = -Infinity;
let minY = Infinity, maxY = -Infinity;
for (let tri of polys) {
for (let pt of tri.points) {
minX = Math.min(minX, pt.x);
maxX = Math.max(maxX, pt.x);
minY = Math.min(minY, pt.y);
maxY = Math.max(maxY, pt.y);
}
}
// Create NxN grid
const gridSize = 3;
const cellWidth = (maxX - minX) / gridSize;
const cellHeight = (maxY - minY) / gridSize;
// Partition polygons into grid cells
const grid = Array(gridSize).fill(0).map(() => Array(gridSize).fill(0).map(() => []));
for (let i = 0; i < polys.length; i++) {
const poly = polys[i];
// Use polygon centroid to assign to cell
const bounds = poly.bounds;
const cx = (bounds.minx + bounds.maxx) / 2;
const cy = (bounds.miny + bounds.maxy) / 2;
// Clamp to grid bounds
let gx = Math.floor((cx - minX) / cellWidth);
let gy = Math.floor((cy - minY) / cellHeight);
gx = Math.max(0, Math.min(gridSize - 1, gx));
gy = Math.max(0, Math.min(gridSize - 1, gy));
grid[gx][gy].push(poly);
}
// Union within each cell
const cellResults = [];
let totalInCell = 0;
for (let gx = 0; gx < gridSize; gx++) {
for (let gy = 0; gy < gridSize; gy++) {
const cellPolys = grid[gx][gy];
if (cellPolys.length > 0) {
totalInCell += cellPolys.length;
const cellUnion = unionTris(cellPolys, depth + 1);
cellResults.push(...cellUnion);
}
}
}
// Union the cell results
polys = unionTris(cellResults, depth + 1);
} else {
polys = POLY.union(polys, 0, true, { wasm: false });
}
return polys;
}
// Widget Grouping API
const Group = Widget.Groups = {
list() {