From 4c4adf2cc58163e704d7e170df221d6ed19e3e7d Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 15 Dec 2025 15:29:08 -0500 Subject: [PATCH] speed up shadow with grid bucketed recursive union. could be moved to workers --- src/geo/polygons.js | 2 +- src/kiri/core/conf.js | 4 +-- src/kiri/core/widget.js | 71 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/geo/polygons.js b/src/geo/polygons.js index 7eadfb10..7f9c35f2 100644 --- a/src/geo/polygons.js +++ b/src/geo/polygons.js @@ -442,7 +442,7 @@ export function union(polys, minarea, all, opt = {}) { if (opt.wasm && geo.wasm) { try { - console.log({ wasm_union: polys, minarea }); + // console.log({ wasm_union: polys, minarea }); if (minarea) polys = polys.filter(p => p.area() >= minarea); let out = geo.wasm.js.union(polys, polys[0].getZ()); opt.changes = length(out) - lpre; diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index 7e64df04..0df72e1a 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -455,7 +455,7 @@ export const conf = { camContourTool: 1000, camContourXOn: true, camContourYOn: true, - camDepthFirst: false, + camDepthFirst: true, camDrillDown: 5, camDrillDownSpeed: 250, camDrillDwell: 250, @@ -472,7 +472,7 @@ export const conf = { camExpertFast: false, camFastFeed: 6000, camFastFeedZ: 300, - camFirstZMax: false, + camFirstZMax: true, camFlatness: 0.001, camFlipAxis: "X", camFlipOther: "", diff --git a/src/kiri/core/widget.js b/src/kiri/core/widget.js index cd7ee0a0..a3758bc6 100644 --- a/src/kiri/core/widget.js +++ b/src/kiri/core/widget.js @@ -959,7 +959,9 @@ class Widget { .add(a[1].x, a[1].y, a[1].z) .add(a[2].x, a[2].y, a[2].z); }); - polys = POLY.union(polys, 0, true, { wasm: false }); + + // recursively merge grid constrained subsets of triangles + polys = unionTris(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 @@ -970,6 +972,73 @@ 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() {