2020-11-05 14:47:18 -05:00
|
|
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
2022-02-22 00:05:29 -05:00
|
|
|
// dep: geo.base
|
|
|
|
|
// dep: geo.line
|
|
|
|
|
// dep: geo.point
|
|
|
|
|
// dep: geo.polygon
|
|
|
|
|
// dep: geo.polygons
|
|
|
|
|
// dep: kiri.slice
|
|
|
|
|
// dep: kiri-mode.cam.driver
|
2023-01-09 19:56:58 -05:00
|
|
|
// dep: kiri-mode.cam.slicer2
|
|
|
|
|
// dep: moto.broker
|
|
|
|
|
gapp.register("kiri-mode.cam.topo", [], (root, exports) => {
|
2020-12-02 10:00:41 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
const { base, kiri } = root;
|
|
|
|
|
const { driver, newSlice } = kiri;
|
|
|
|
|
const { CAM } = driver;
|
|
|
|
|
const { polygons, newLine, newSlope, newPoint, newPolygon } = base;
|
2020-12-16 23:08:52 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
const PRO = CAM.process;
|
|
|
|
|
const POLY = polygons;
|
|
|
|
|
const RAD2DEG = 180 / Math.PI;
|
2020-11-25 22:50:44 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
class Topo {
|
2023-01-08 14:04:02 -05:00
|
|
|
constructor() { }
|
|
|
|
|
|
|
|
|
|
async generate(opt = {}) {
|
2022-02-21 10:01:19 -05:00
|
|
|
let { state, contour, onupdate, ondone } = opt;
|
|
|
|
|
let { widget, settings, tshadow, center, tabs, color } = opt.state;
|
2020-11-14 19:13:17 -05:00
|
|
|
|
2023-01-08 14:04:02 -05:00
|
|
|
let { controller, process } = settings,
|
|
|
|
|
density = parseInt(controller.animesh || 100) * 2500,
|
2022-02-21 10:01:19 -05:00
|
|
|
axis = contour.axis.toLowerCase(),
|
|
|
|
|
contourX = axis === "x",
|
|
|
|
|
contourY = axis === "y",
|
|
|
|
|
bounds = widget.getBoundingBox().clone(),
|
|
|
|
|
tolerance = contour.tolerance,
|
2022-07-12 16:57:46 -04:00
|
|
|
flatness = contour.flatness || 0.005,
|
2022-02-21 10:01:19 -05:00
|
|
|
shadow = tshadow,
|
|
|
|
|
minX = bounds.min.x,
|
|
|
|
|
maxX = bounds.max.x,
|
|
|
|
|
minY = bounds.min.y,
|
|
|
|
|
maxY = bounds.max.y,
|
2022-10-05 12:04:00 -04:00
|
|
|
zBottom = contour.bottom ? state.zBottom : 0,
|
2022-02-21 10:01:19 -05:00
|
|
|
zMin = Math.max(bounds.min.z, zBottom) + 0.0001,
|
|
|
|
|
boundsX = maxX - minX,
|
|
|
|
|
boundsY = maxY - minY,
|
|
|
|
|
inside = contour.inside,
|
|
|
|
|
resolution = tolerance ? tolerance : 1/Math.sqrt(density/(boundsX * boundsY)),
|
|
|
|
|
tool = new CAM.Tool(settings, contour.tool),
|
|
|
|
|
toolOffset = tool.generateProfile(resolution).profile,
|
|
|
|
|
toolDiameter = tool.fluteDiameter(),
|
|
|
|
|
toolStep = toolDiameter * contour.step,
|
|
|
|
|
traceJoin = toolDiameter / 2,
|
|
|
|
|
maxangle = contour.angle,
|
|
|
|
|
curvesOnly = contour.curves,
|
2023-01-08 22:30:32 -05:00
|
|
|
bridge = contour.bridging || 0,
|
2023-01-12 22:30:10 -05:00
|
|
|
// R2A = 180 / Math.PI,
|
2023-01-11 19:23:46 -05:00
|
|
|
stepsX = Math.ceil(boundsX / resolution),
|
|
|
|
|
stepsY = Math.ceil(boundsY / resolution),
|
2022-10-10 00:51:03 -04:00
|
|
|
widtopo = widget.topo,
|
|
|
|
|
topoCache = widtopo && widtopo.resolution === resolution ? widtopo : undefined,
|
|
|
|
|
topo = widget.topo = topoCache || {
|
2023-01-11 19:23:46 -05:00
|
|
|
data: new Float32Array(new SharedArrayBuffer(stepsX * stepsY * 4)),
|
|
|
|
|
stepsX: stepsX,
|
|
|
|
|
stepsY: stepsY,
|
2022-02-21 10:01:19 -05:00
|
|
|
bounds: bounds,
|
|
|
|
|
diameter: toolDiameter,
|
|
|
|
|
resolution: resolution,
|
|
|
|
|
profile: toolOffset,
|
|
|
|
|
widget: widget,
|
|
|
|
|
raster: true,
|
|
|
|
|
slices: null
|
|
|
|
|
},
|
|
|
|
|
data = topo.data,
|
|
|
|
|
newslices = [],
|
|
|
|
|
tabsMax = tabs ? Math.max(...tabs.map(tab => tab.dim.z/2 + tab.pos.z)) : 0,
|
|
|
|
|
tabsOn = tabs,
|
2023-01-08 14:04:02 -05:00
|
|
|
tabHeight = Math.max(process.camTabsHeight + zBottom, tabsMax),
|
2022-02-21 10:01:19 -05:00
|
|
|
clipTab = tabsOn ? [] : null,
|
|
|
|
|
clipTo = inside ? shadow : POLY.expand(shadow, toolDiameter/2 + resolution * 3),
|
|
|
|
|
partOff = inside ? 0 : toolDiameter / 2 + resolution,
|
|
|
|
|
gridDelta = Math.floor(partOff / resolution),
|
|
|
|
|
debug = false,
|
|
|
|
|
debug_clips = debug && true,
|
|
|
|
|
debug_topo = debug && true,
|
|
|
|
|
debug_topo_lines = debug && true,
|
|
|
|
|
debug_topo_shells = debug && true,
|
|
|
|
|
time = Date.now;
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2022-10-10 00:51:03 -04:00
|
|
|
if (tolerance === 0 && !topoCache) {
|
|
|
|
|
console.log(widget.id, 'topo auto tolerance', resolution.round(4));
|
2022-02-21 10:01:19 -05:00
|
|
|
}
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
this.tolerance = resolution;
|
|
|
|
|
|
|
|
|
|
if (tabs) {
|
|
|
|
|
clipTab.appendAll(tabs.map(tab => {
|
|
|
|
|
let ctab = POLY.expand([tab.poly], toolDiameter/2);
|
|
|
|
|
ctab.forEach(ct => ct.z = tab.dim.z/2 + tab.pos.z);
|
|
|
|
|
return ctab;
|
|
|
|
|
}).flat());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// debug clipTab and clipTo
|
|
|
|
|
if (debug_clips) {
|
|
|
|
|
const debug = newSlice(-1);
|
|
|
|
|
const output = debug.output();
|
|
|
|
|
// if (clipTab) output.setLayer("clip.tab", { line: 0xff0000 }).addPolys(clipTab);
|
|
|
|
|
if (clipTo) output.setLayer("clip.to", { line: 0x00dd00 }).addPolys(clipTo);
|
|
|
|
|
newslices.push(debug);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-14 11:35:07 -05:00
|
|
|
const probe = new Probe({
|
|
|
|
|
profile: toolOffset,
|
|
|
|
|
data,
|
|
|
|
|
stepsX,
|
|
|
|
|
stepsY,
|
|
|
|
|
boundsX,
|
|
|
|
|
boundsY,
|
|
|
|
|
zMin
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { toolAtZ, toolAtXY, zAtXY } = probe;
|
|
|
|
|
|
|
|
|
|
this.toolAtZ = toolAtZ;
|
|
|
|
|
this.toolAtXY = toolAtXY;
|
|
|
|
|
this.zAtXY = zAtXY;
|
|
|
|
|
|
|
|
|
|
// const trace = new Trace({
|
|
|
|
|
//
|
|
|
|
|
// });
|
|
|
|
|
//
|
|
|
|
|
// const { push_point, end_poly, newtrace } = trace;
|
|
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
let newtrace,
|
|
|
|
|
sliceout,
|
|
|
|
|
latent,
|
|
|
|
|
lastP,
|
2023-01-12 22:30:10 -05:00
|
|
|
slice;
|
2022-02-21 10:01:19 -05:00
|
|
|
|
2023-01-12 22:30:10 -05:00
|
|
|
function push_point(x, y, z) {
|
|
|
|
|
const newP = newPoint(x, y, z);
|
2022-02-21 10:01:19 -05:00
|
|
|
// todo: merge co-linear, not just co-planar
|
2022-07-12 16:57:46 -04:00
|
|
|
if (lastP && Math.abs(lastP.z - z) < flatness) {
|
2022-12-27 12:50:16 -05:00
|
|
|
if (curvesOnly) {
|
2022-10-04 22:14:30 -04:00
|
|
|
if ((newtrace.last() || lastP).distTo2D(newP) >= bridge) {
|
|
|
|
|
end_poly();
|
|
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
} else {
|
|
|
|
|
latent = newP;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2020-11-05 14:47:18 -05:00
|
|
|
if (latent) {
|
|
|
|
|
newtrace.push(latent);
|
2022-02-21 10:01:19 -05:00
|
|
|
latent = null;
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
2022-12-27 13:51:24 -05:00
|
|
|
if (curvesOnly && lastP) {
|
2022-02-21 10:01:19 -05:00
|
|
|
// maxangle
|
|
|
|
|
const dz = Math.abs(lastP.z - z);
|
|
|
|
|
const dv = contourX ? Math.abs(lastP.x - x) : Math.abs(lastP.y - y);
|
|
|
|
|
const angle = Math.atan2(dz, dv) * RAD2DEG;
|
|
|
|
|
// if (lastP.z < 0.1) console.log('pp', {dz, dxy, angle});
|
|
|
|
|
if (angle > maxangle) {
|
|
|
|
|
lastP = newP;
|
|
|
|
|
return;
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
|
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
newtrace.push(newP);
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
lastP = newP;
|
|
|
|
|
}
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
function end_poly() {
|
|
|
|
|
if (latent) {
|
|
|
|
|
newtrace.push(latent);
|
|
|
|
|
}
|
|
|
|
|
if (newtrace.length > 0) {
|
|
|
|
|
// add additional constraint on min perimeter()
|
|
|
|
|
if (newtrace.length > 1) {
|
|
|
|
|
sliceout.push(newtrace);
|
2020-12-16 21:25:45 -05:00
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
newtrace = newPolygon().setOpen();
|
|
|
|
|
}
|
|
|
|
|
latent = undefined;
|
|
|
|
|
lastP = undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 20:33:14 -05:00
|
|
|
function contouring() {
|
2022-02-21 10:01:19 -05:00
|
|
|
let gridx = 0,
|
|
|
|
|
gridy,
|
|
|
|
|
gridi, // index
|
|
|
|
|
gridv, // value
|
2023-01-12 22:30:10 -05:00
|
|
|
i, il, j, x, y, tv,
|
|
|
|
|
stepsTaken = 0,
|
|
|
|
|
stepsTotal = 0;
|
2022-02-21 10:01:19 -05:00
|
|
|
|
2023-01-12 22:30:10 -05:00
|
|
|
if (contourX) {
|
|
|
|
|
stepsTotal += ((maxX - minX + partOff * 2) / toolStep) | 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (contourY) {
|
|
|
|
|
stepsTotal += ((maxY - minY + partOff * 2) / toolStep) | 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const box = topo.box.clone().expandByVector(new THREE.Vector3(
|
2023-01-08 22:30:32 -05:00
|
|
|
partOff, partOff, 0
|
|
|
|
|
));
|
2023-01-11 12:48:43 -05:00
|
|
|
|
2023-01-12 22:30:10 -05:00
|
|
|
const inClip = function(clips, checkZ, point) {
|
|
|
|
|
for (let i=0; i<clips.length; i++) {
|
|
|
|
|
let poly = clips[i];
|
2022-02-21 10:01:19 -05:00
|
|
|
let zok = checkZ ? checkZ <= poly.z : true;
|
|
|
|
|
tabZ = poly.z;
|
2023-01-12 22:30:10 -05:00
|
|
|
if (zok && point.isInPolygon(poly)) {
|
2022-02-21 10:01:19 -05:00
|
|
|
return true;
|
2020-11-11 23:27:58 -05:00
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
let tabZ;
|
|
|
|
|
|
2023-01-12 22:30:10 -05:00
|
|
|
function traceY(params) {
|
|
|
|
|
let { from, to, step, box, x, gridx, gridy } = params;
|
|
|
|
|
let { clipTab, tabHeight, clipTo } = params;
|
|
|
|
|
const checkr = newPoint(0,0);
|
|
|
|
|
newtrace = newPolygon().setOpen();
|
|
|
|
|
sliceout = [];
|
|
|
|
|
for (let y = from; y < to; y += step) {
|
|
|
|
|
if (y < box.min.y || y > box.max.y) {
|
|
|
|
|
gridy++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// find tool z at grid point
|
|
|
|
|
let tv = toolAtZ(gridx, gridy);
|
|
|
|
|
checkr.x = x;
|
|
|
|
|
checkr.y = y;
|
|
|
|
|
// when tabs are on and this point is inside the
|
|
|
|
|
// tab polygon, ensure z is at least tabHeight
|
|
|
|
|
if (clipTab && tv < tabHeight && inClip(clipTab, tv, checkr)) {
|
|
|
|
|
tv = tabZ;
|
|
|
|
|
}
|
|
|
|
|
// if the value is on the floor and inside the clip
|
|
|
|
|
// poly (usually shadow), end the segment
|
|
|
|
|
if (clipTo && !inClip(clipTo, undefined, checkr)) {
|
|
|
|
|
end_poly();
|
|
|
|
|
gridy++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
push_point(x, y, tv);
|
|
|
|
|
gridy++;
|
|
|
|
|
}
|
|
|
|
|
end_poly();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function traceX(params) {
|
|
|
|
|
let { from, to, step, box, y, gridx, gridy } = params;
|
|
|
|
|
let { clipTab, tabHeight, clipTo } = params;
|
|
|
|
|
const checkr = newPoint(0,0);
|
|
|
|
|
newtrace = newPolygon().setOpen();
|
|
|
|
|
sliceout = [];
|
|
|
|
|
for (let x = from; x < to; x += step) {
|
|
|
|
|
if (x < box.min.x || y > box.max.x) {
|
|
|
|
|
gridx++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// find tool z at grid point
|
|
|
|
|
let tv = toolAtZ(gridx, gridy);
|
|
|
|
|
checkr.x = x;
|
|
|
|
|
checkr.y = y;
|
|
|
|
|
// when tabs are on and this point is inside the
|
|
|
|
|
// tab polygon, ensure z is at least tabHeight
|
|
|
|
|
if (clipTab && tv < tabHeight && inClip(clipTab, tv, checkr)) {
|
|
|
|
|
tv = tabZ;
|
|
|
|
|
}
|
|
|
|
|
// if the value is on the floor and inside the clip
|
|
|
|
|
// poly (usually shadow), end the segment
|
|
|
|
|
if (clipTo && !inClip(clipTo, undefined, checkr)) {
|
|
|
|
|
end_poly();
|
|
|
|
|
gridx++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
push_point(x, y, tv);
|
|
|
|
|
gridx++;
|
|
|
|
|
}
|
|
|
|
|
end_poly();
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
if (contourY) {
|
|
|
|
|
// emit slice per X
|
|
|
|
|
for (x = minX - partOff; x <= maxX + partOff; x += toolStep) {
|
2022-12-27 12:10:58 -05:00
|
|
|
if (x < box.min.x || x > box.max.x) continue;
|
2023-01-11 19:23:46 -05:00
|
|
|
gridx = Math.round(((x - minX) / boundsX) * stepsX);
|
2023-01-12 22:30:10 -05:00
|
|
|
gridy = -gridDelta;
|
|
|
|
|
|
|
|
|
|
traceY({
|
|
|
|
|
from: minY - partOff,
|
|
|
|
|
to: maxY + partOff,
|
|
|
|
|
step: resolution,
|
|
|
|
|
box,
|
|
|
|
|
x,
|
|
|
|
|
gridx,
|
|
|
|
|
gridy,
|
|
|
|
|
clipTo,
|
|
|
|
|
clipTab,
|
|
|
|
|
tabHeight
|
|
|
|
|
});
|
|
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
if (sliceout.length > 0) {
|
2023-01-12 22:30:10 -05:00
|
|
|
slice = newSlice(gridx);
|
2022-02-21 10:01:19 -05:00
|
|
|
slice.camLines = sliceout;
|
|
|
|
|
slice.output()
|
|
|
|
|
.setLayer("contour y", {face: color, line: color})
|
|
|
|
|
.addPolys(sliceout);
|
2023-01-12 22:30:10 -05:00
|
|
|
newslices.push(slice);
|
2022-02-21 10:01:19 -05:00
|
|
|
}
|
|
|
|
|
onupdate(++stepsTaken, stepsTotal, "contour y");
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
}
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
if (contourX) {
|
|
|
|
|
// emit slice per Y
|
|
|
|
|
for (y = minY - partOff; y <= maxY + partOff; y += toolStep) {
|
2022-12-27 12:10:58 -05:00
|
|
|
if (y < box.min.y || y > box.max.y) continue;
|
2023-01-11 19:23:46 -05:00
|
|
|
gridy = Math.round(((y - minY) / boundsY) * stepsY);
|
2023-01-12 22:30:10 -05:00
|
|
|
gridx = -gridDelta;
|
|
|
|
|
|
|
|
|
|
traceX({
|
|
|
|
|
from: minX - partOff,
|
|
|
|
|
to: maxX + partOff,
|
|
|
|
|
step: resolution,
|
|
|
|
|
box,
|
|
|
|
|
y,
|
|
|
|
|
gridx,
|
|
|
|
|
gridy,
|
|
|
|
|
clipTo,
|
|
|
|
|
clipTab,
|
|
|
|
|
tabHeight
|
|
|
|
|
});
|
|
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
if (sliceout.length > 0) {
|
2023-01-12 22:30:10 -05:00
|
|
|
slice = newSlice(gridy);
|
2022-02-21 10:01:19 -05:00
|
|
|
slice.camLines = sliceout;
|
|
|
|
|
slice.output()
|
|
|
|
|
.setLayer("contour x", {face: color, line: color})
|
|
|
|
|
.addPolys(sliceout);
|
2023-01-12 22:30:10 -05:00
|
|
|
newslices.push(slice);
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
onupdate(++stepsTaken, stepsTotal, "contour x");
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
|
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
}
|
2020-11-11 15:38:14 -05:00
|
|
|
|
2023-01-12 22:30:10 -05:00
|
|
|
if (topo.raster) {
|
|
|
|
|
const box = topo.box = new THREE.Box2();
|
|
|
|
|
|
|
|
|
|
const params = {
|
|
|
|
|
resolution,
|
|
|
|
|
curvesOnly,
|
|
|
|
|
flatness,
|
|
|
|
|
stepsY,
|
|
|
|
|
minY,
|
|
|
|
|
maxY,
|
|
|
|
|
zMin,
|
|
|
|
|
data,
|
|
|
|
|
box
|
|
|
|
|
};
|
2023-01-11 19:23:46 -05:00
|
|
|
|
2023-01-12 22:30:10 -05:00
|
|
|
onupdate(0, 1, "raster");
|
|
|
|
|
await this.topo_raster(widget, params);
|
|
|
|
|
topo.raster = false;
|
|
|
|
|
}
|
2020-11-11 15:38:14 -05:00
|
|
|
|
2023-01-12 20:33:14 -05:00
|
|
|
contouring();
|
2023-01-11 12:48:43 -05:00
|
|
|
ondone(newslices);
|
2023-01-08 14:04:02 -05:00
|
|
|
|
|
|
|
|
return this;
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:30:10 -05:00
|
|
|
async topo_raster(widget, params) {
|
2023-01-11 19:23:46 -05:00
|
|
|
const { resolution } = params;
|
|
|
|
|
|
2023-01-12 22:30:10 -05:00
|
|
|
console.log({ topo_raster: widget, resolution });
|
2023-01-09 20:08:57 -05:00
|
|
|
|
|
|
|
|
const dispatch = kiri.worker;
|
|
|
|
|
const minions = kiri.minions;
|
2023-01-09 19:56:58 -05:00
|
|
|
const vertices = widget.getGeoVertices().toShared();
|
|
|
|
|
const range = { min: Infinity, max: -Infinity };
|
2023-01-12 20:33:14 -05:00
|
|
|
const { box } = params;
|
2023-01-09 19:56:58 -05:00
|
|
|
|
|
|
|
|
// swap XZ in shared array
|
|
|
|
|
for (let i=0,l=vertices.length; i<l; i += 3) {
|
|
|
|
|
const x = vertices[i];
|
|
|
|
|
const z = vertices[i + 2];
|
|
|
|
|
vertices[i] = z;
|
|
|
|
|
vertices[i + 2] = x;
|
|
|
|
|
range.min = Math.min(range.min, x);
|
|
|
|
|
range.max = Math.max(range.max, x);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const shards = Math.ceil(Math.min(25, vertices.length / 27000));
|
|
|
|
|
const step = (range.max - range.min) / shards;
|
|
|
|
|
|
|
|
|
|
let slices = [];
|
2023-01-11 19:23:46 -05:00
|
|
|
let index = 0;
|
|
|
|
|
let slice = { min: range.min, max: range.min + step, index };
|
2023-01-09 19:56:58 -05:00
|
|
|
for (let z = range.min; z < range.max; z += resolution) {
|
|
|
|
|
if (z > slice.max) {
|
|
|
|
|
slices.push(slice);
|
2023-01-11 19:23:46 -05:00
|
|
|
slice = { min: z, max: z + step, index };
|
2023-01-09 19:56:58 -05:00
|
|
|
}
|
2023-01-11 19:23:46 -05:00
|
|
|
index++;
|
2023-01-09 19:56:58 -05:00
|
|
|
}
|
|
|
|
|
slices.push(slice);
|
|
|
|
|
console.log({ shards, range, step, slices });
|
|
|
|
|
|
|
|
|
|
// define sharded ranges
|
|
|
|
|
if (minions.running > 1) {
|
|
|
|
|
|
2023-01-12 22:30:10 -05:00
|
|
|
console.time('topo raster minion');
|
2023-01-09 19:56:58 -05:00
|
|
|
dispatch.putCache({ key: widget.id, data: vertices }, { done: data => {
|
|
|
|
|
// console.log({ put_cache_done: data });
|
|
|
|
|
}});
|
|
|
|
|
|
2023-01-12 20:33:14 -05:00
|
|
|
let promises = slices.map((slice, index) => {
|
2023-01-09 19:56:58 -05:00
|
|
|
return new Promise(resolve => {
|
|
|
|
|
minions.queue({
|
2023-01-12 22:30:10 -05:00
|
|
|
cmd: "topo_raster",
|
2023-01-09 19:56:58 -05:00
|
|
|
id: widget.id,
|
2023-01-11 19:48:19 -05:00
|
|
|
params,
|
2023-01-09 19:56:58 -05:00
|
|
|
slice
|
|
|
|
|
}, data => {
|
|
|
|
|
resolve(data);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2023-01-12 20:33:14 -05:00
|
|
|
// merge boxes for all rasters for contouring clipping
|
|
|
|
|
(await Promise.all(promises))
|
|
|
|
|
.map(rec => rec.box)
|
|
|
|
|
.map(box => new THREE.Box2(
|
|
|
|
|
new THREE.Vector2(box.min.x, box.min.y),
|
|
|
|
|
new THREE.Vector2(box.max.x, box.max.y)
|
|
|
|
|
))
|
|
|
|
|
.map(box2 => {
|
|
|
|
|
box.union(box2);
|
|
|
|
|
return box2;
|
2023-01-09 19:56:58 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
dispatch.clearCache({}, { done: data => {
|
|
|
|
|
// console.log({ clear_cache_done: data });
|
|
|
|
|
}});
|
2023-01-12 22:30:10 -05:00
|
|
|
console.timeEnd('topo raster minion');
|
2023-01-09 19:56:58 -05:00
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
2023-01-12 22:30:10 -05:00
|
|
|
console.time('topo raster');
|
2023-01-09 19:56:58 -05:00
|
|
|
// iterate over shards, merge output
|
2023-01-12 20:33:14 -05:00
|
|
|
// const output = [];
|
2023-01-09 19:56:58 -05:00
|
|
|
for (let slice of slices) {
|
2023-01-12 20:33:14 -05:00
|
|
|
new kiri.topo_slicer(slice.index)
|
2023-01-09 19:56:58 -05:00
|
|
|
.setFromArray(vertices, slice)
|
|
|
|
|
.slice(resolution)
|
|
|
|
|
.map(rec => {
|
2023-01-12 20:33:14 -05:00
|
|
|
|
2023-01-09 19:56:58 -05:00
|
|
|
const slice = kiri.newSlice(rec.z);
|
2023-01-11 19:23:46 -05:00
|
|
|
slice.index = rec.index;
|
2023-01-09 19:56:58 -05:00
|
|
|
slice.lines = rec.lines;
|
|
|
|
|
for (let line of rec.lines) {
|
|
|
|
|
const { p1, p2 } = line;
|
|
|
|
|
if (!p1.swapped) { p1.swapXZ(); p1.swapped = true }
|
|
|
|
|
if (!p2.swapped) { p2.swapXZ(); p2.swapped = true }
|
|
|
|
|
}
|
2023-01-12 20:33:14 -05:00
|
|
|
|
|
|
|
|
raster_slice({
|
|
|
|
|
...params,
|
|
|
|
|
box,
|
|
|
|
|
lines: rec.lines,
|
|
|
|
|
gridx: rec.index
|
|
|
|
|
});
|
|
|
|
|
|
2023-01-09 19:56:58 -05:00
|
|
|
return slice;
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-01-12 22:30:10 -05:00
|
|
|
console.timeEnd('topo raster');
|
2023-01-09 19:56:58 -05:00
|
|
|
|
|
|
|
|
}
|
2023-01-09 20:08:57 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-14 11:35:07 -05:00
|
|
|
class Probe {
|
|
|
|
|
constructor(params) {
|
|
|
|
|
const { data, profile } = params;
|
|
|
|
|
const { stepsX, stepsY, boundsX, boundsY, zMin } = params;
|
|
|
|
|
|
|
|
|
|
// return the touching z given topo x,y and a tool profile
|
|
|
|
|
const toolAtZ = this.toolAtZ = function(x,y) {
|
|
|
|
|
let sx = stepsX,
|
|
|
|
|
sy = stepsY,
|
|
|
|
|
xl = sx - 1,
|
|
|
|
|
yl = sy - 1;
|
|
|
|
|
|
|
|
|
|
let tx, ty, tz, gv, i = 0, mz = -1;
|
|
|
|
|
|
|
|
|
|
while (i < profile.length) {
|
|
|
|
|
// tool profile point x, y, and z offsets
|
|
|
|
|
const tx = profile[i++] + x;
|
|
|
|
|
const ty = profile[i++] + y;
|
|
|
|
|
const tz = profile[i++];
|
|
|
|
|
if (tx < 0 || tx > xl || ty < 0 || ty > yl) {
|
|
|
|
|
// if outside max topo steps, use zMin
|
|
|
|
|
gv = zMin;
|
|
|
|
|
} else {
|
|
|
|
|
// lookup grid value @ tx, ty
|
|
|
|
|
gv = data[tx * sy + ty] || zMin;
|
|
|
|
|
}
|
|
|
|
|
// update the rest
|
|
|
|
|
mz = Math.max(tz + gv, mz);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Math.max(mz, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// export z probe function
|
|
|
|
|
const rx = stepsX / boundsX;
|
|
|
|
|
const ry = stepsX / boundsX;
|
|
|
|
|
const toolAtXY = this.toolAtXY = function(px, py) {
|
|
|
|
|
px = Math.round(rx * (px - minX));
|
|
|
|
|
py = Math.round(ry * (py - minY));
|
|
|
|
|
return toolAtZ(px, py);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const zAtXY = this.zAtXY = function(px, py) {
|
|
|
|
|
let ix = Math.round(rx * (px - minX));
|
|
|
|
|
let iy = Math.round(ry * (py - minY));
|
|
|
|
|
return data[ix * stepsY + iy] || zMin;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Trace {
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
|
|
|
|
let trace,
|
|
|
|
|
sliceout,
|
|
|
|
|
latent,
|
|
|
|
|
lastP,
|
|
|
|
|
slice;
|
|
|
|
|
|
|
|
|
|
function push_point(x, y, z) {
|
|
|
|
|
const newP = newPoint(x, y, z);
|
|
|
|
|
// todo: merge co-linear, not just co-planar
|
|
|
|
|
if (lastP && Math.abs(lastP.z - z) < flatness) {
|
|
|
|
|
if (curvesOnly) {
|
|
|
|
|
if ((trace.last() || lastP).distTo2D(newP) >= bridge) {
|
|
|
|
|
end_poly();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
latent = newP;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (latent) {
|
|
|
|
|
trace.push(latent);
|
|
|
|
|
latent = null;
|
|
|
|
|
}
|
|
|
|
|
if (curvesOnly && lastP) {
|
|
|
|
|
// maxangle
|
|
|
|
|
const dz = Math.abs(lastP.z - z);
|
|
|
|
|
const dv = contourX ? Math.abs(lastP.x - x) : Math.abs(lastP.y - y);
|
|
|
|
|
const angle = Math.atan2(dz, dv) * RAD2DEG;
|
|
|
|
|
// if (lastP.z < 0.1) console.log('pp', {dz, dxy, angle});
|
|
|
|
|
if (angle > maxangle) {
|
|
|
|
|
lastP = newP;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
trace.push(newP);
|
|
|
|
|
}
|
|
|
|
|
lastP = newP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function end_poly() {
|
|
|
|
|
if (latent) {
|
|
|
|
|
trace.push(latent);
|
|
|
|
|
}
|
|
|
|
|
if (trace.length > 0) {
|
|
|
|
|
// add additional constraint on min perimeter()
|
|
|
|
|
if (trace.length > 1) {
|
|
|
|
|
sliceout.push(trace);
|
|
|
|
|
}
|
|
|
|
|
newtrace();
|
|
|
|
|
}
|
|
|
|
|
latent = undefined;
|
|
|
|
|
lastP = undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function newtrace() {
|
|
|
|
|
trace = newPolygon().setOpen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-11 12:48:43 -05:00
|
|
|
function raster_slice(inputs) {
|
|
|
|
|
const { lines, data, box, resolution, curvesOnly } = inputs;
|
2023-01-11 19:23:46 -05:00
|
|
|
const { flatness, zMin, minY, maxY, stepsY, gridx } = inputs;
|
2023-01-11 12:48:43 -05:00
|
|
|
const { slice } = inputs;
|
|
|
|
|
|
|
|
|
|
let gridy,
|
|
|
|
|
gridi, // index
|
|
|
|
|
gridv, // value
|
2023-01-12 22:30:10 -05:00
|
|
|
i, il, j, x, y, tv;
|
2023-01-11 12:48:43 -05:00
|
|
|
|
|
|
|
|
// filter lines pairs to only surface "up-facing", "uncovered" lines
|
2023-01-11 21:31:36 -05:00
|
|
|
let points = [];
|
2023-01-11 12:48:43 -05:00
|
|
|
// emit an array of valid line-pairs
|
|
|
|
|
const len = lines.length;
|
|
|
|
|
|
|
|
|
|
outer: for (let i=0; i<len; i++) {
|
|
|
|
|
let l1 = lines[i], p1 = l1.p1, p2 = l1.p2;
|
|
|
|
|
// eliminate vertical
|
|
|
|
|
if (Math.abs(p1.y - p2.y) < flatness) continue;
|
|
|
|
|
// eliminate if both points below cutoff
|
|
|
|
|
if (p1.z < zMin && p2.z < zMin) continue;
|
|
|
|
|
// sort p1,p2 by y for comparison
|
|
|
|
|
if (p1.y > p2.y) { const tp = p1; p1 = p2; p2 = tp };
|
|
|
|
|
// eliminate if points "under" other lines
|
|
|
|
|
for (let j=0; j<len; j++) {
|
|
|
|
|
// skip self and adjacent
|
|
|
|
|
if (j >= i-1 && j <= i+1) continue;
|
|
|
|
|
let l2 = lines[j], p3 = l2.p1, p4 = l2.p2;
|
|
|
|
|
// sort p3,p4 by y for comparison
|
|
|
|
|
if (p3.y > p4.y) { const tp = p3; p3 = p4; p4 = tp };
|
|
|
|
|
// it's under the other line
|
|
|
|
|
if (Math.max(p1.z,p2.z) < Math.min(p3.z,p4.z)) {
|
|
|
|
|
// it's inside the other line, too, so skip
|
|
|
|
|
if (p1.y >= p3.y && p2.y <= p4.y) continue outer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
points.push(p1, p2);
|
|
|
|
|
box.expandByPoint(p1);
|
|
|
|
|
box.expandByPoint(p2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gridy = 0;
|
|
|
|
|
// rasterize one x slice
|
2023-01-11 19:23:46 -05:00
|
|
|
for (y = minY; y < maxY && gridy < stepsY; y += resolution) {
|
|
|
|
|
gridi = gridx * stepsY + gridy;
|
2023-01-11 12:48:43 -05:00
|
|
|
gridv = data[gridi] || 0;
|
|
|
|
|
// strategy using raw lines (faster slice, but more lines)
|
|
|
|
|
for (i=0, il=points.length; i<il; i += 2) {
|
|
|
|
|
const p1 = points[i], p2 = points[i+1];
|
|
|
|
|
// one endpoint above grid
|
|
|
|
|
const crossz = (p1.z > gridv || p2.z > gridv);
|
|
|
|
|
// segment crosses grid y
|
|
|
|
|
const spansy = (p1.y <= y && p2.y >= y);
|
|
|
|
|
if (crossz && spansy) {
|
|
|
|
|
// compute intersection of z ray up
|
|
|
|
|
// and segment at this grid point
|
|
|
|
|
const dy = p1.y - p2.y,
|
|
|
|
|
dz = p1.z - p2.z,
|
|
|
|
|
pct = (p1.y - y) / dy,
|
|
|
|
|
nz = p1.z - (dz * pct);
|
|
|
|
|
// save if point is greater than existing grid point
|
|
|
|
|
if (nz > gridv) {
|
|
|
|
|
gridv = data[gridi] = Math.max(nz, zMin);
|
|
|
|
|
if (slice) slice.output()
|
|
|
|
|
.setLayer("heights", {face: 0, line: 0})
|
|
|
|
|
.addLine(
|
|
|
|
|
newPoint(p1.x, y, 0),
|
|
|
|
|
newPoint(p1.x, y, gridv)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
gridy++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// remove flat lines when curvesOnly
|
|
|
|
|
if (curvesOnly) {
|
|
|
|
|
let nup = [];
|
|
|
|
|
for (let i=0, p=points, l=p.length; i<l; i += 2) {
|
|
|
|
|
const p1 = p[i];
|
|
|
|
|
const p2 = p[i+1];
|
|
|
|
|
if (Math.abs(p1.z - p2.z) >= flatness) {
|
|
|
|
|
nup.push(p1, p2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
points = nup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return points;
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-09 20:08:57 -05:00
|
|
|
CAM.Topo = async function(opt) {
|
|
|
|
|
return new Topo().generate(opt);
|
|
|
|
|
};
|
2023-01-09 19:56:58 -05:00
|
|
|
|
|
|
|
|
moto.broker.subscribe("minion.started", msg => {
|
|
|
|
|
// console.log({ cam_slicer2_minion: msg });
|
|
|
|
|
const { funcs, cache, reply, log } = msg;
|
|
|
|
|
|
2023-01-12 22:30:10 -05:00
|
|
|
funcs.topo_raster = (data, seq) => {
|
2023-01-11 19:48:19 -05:00
|
|
|
const { id, slice, params } = data;
|
|
|
|
|
const { resolution } = params;
|
|
|
|
|
const vertices = cache[id];
|
2023-01-12 20:33:14 -05:00
|
|
|
const box = new THREE.Box2();
|
|
|
|
|
new kiri.topo_slicer(slice.index)
|
2023-01-11 19:48:19 -05:00
|
|
|
.setFromArray(vertices, slice)
|
|
|
|
|
.slice(resolution)
|
2023-01-12 20:33:14 -05:00
|
|
|
.forEach(rec => {
|
2023-01-11 19:48:19 -05:00
|
|
|
const { z, index, lines } = rec;
|
|
|
|
|
|
|
|
|
|
for (let line of lines) {
|
2023-01-09 19:56:58 -05:00
|
|
|
const { p1, p2 } = line;
|
|
|
|
|
if (!p1.swapped) { p1.swapXZ(); p1.swapped = true }
|
|
|
|
|
if (!p2.swapped) { p2.swapXZ(); p2.swapped = true }
|
|
|
|
|
}
|
2023-01-11 19:48:19 -05:00
|
|
|
|
2023-01-12 20:33:14 -05:00
|
|
|
raster_slice({
|
|
|
|
|
...params,
|
|
|
|
|
box,
|
|
|
|
|
lines,
|
|
|
|
|
gridx: index
|
|
|
|
|
});
|
2023-01-09 19:56:58 -05:00
|
|
|
});
|
2023-01-12 20:33:14 -05:00
|
|
|
// only pass back bounds of rasters to be merged
|
|
|
|
|
reply({ seq, box });
|
2023-01-09 19:56:58 -05:00
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
});
|