grid-apps-cmms/src/kiri/mode/cam/work/op-shadow.js

103 lines
3.4 KiB
JavaScript

/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { CamOp } from '../core/op.js';
import { newSlice } from '../../../core/slice.js';
import { newPolygon } from '../../../../geo/polygon.js';
/**
* Computes the Part "shadow" and attaches relevant data to the "state" object
*
* The part shadow consists of top-cown layers at which the polygon shadow changes
* shape. For curved or sloped surfaces, this is approximated and paths that clip
* to it should use the next lower layer from current Z to ensure no part collisions.
*
* The shadow at each layer is computed by top-down unioning the part outline with
* the shadow from the layer above.
*
* This operation is injected at the start of the operation chain before processing.
*/
class OpShadow extends CamOp {
constructor(state, op) {
super(state, op);
}
async slice(progress) {
let state = this.state;
let { addSlices, settings, shadowAt, unsafe, widget, workarea } = state;
let { devel } = settings.controller;
let bounds = widget.getBoundingBox();
let slices = [];
let shadowBase;
let skipTerrain = unsafe;
let terrain = [];
let tzindex = [];
let minZ = Math.floor(bounds.min.z);
let maxZ = Math.floor(bounds.max.z);
for (let z = maxZ; z >= minZ; z--) {
tzindex.push(z);
}
if (skipTerrain) {
let { stock } = settings;
let { center } = stock;
console.log("skipping terrain generation");
shadowBase = [ newPolygon().centerRectangle(center, stock.x, stock.y) ];
tzindex = [ ];
state.shadow = {
base: shadowBase, // computed shadow union at base of part
holes: [],
skip: skipTerrain,
slices: [], // legacy / transitional
stack: [], // stack of shadow slices
};
return;
}
// distributed pre-fill shadow layer cache
await widget.computeShadowStack(tzindex, prog => progress(prog / 2, 'shadow'));
// terrain is the "shadow stack" where index 0 = top of part
// thus array.length -1 = bottom of part
for (let i=0; i<tzindex.length; i++) {
let z = tzindex[i];
let shadow = shadowBase = await shadowAt(z);
let slice = newSlice(z);
slice.shadow = shadow;
slice.addTops(shadow);
slices.push(slice);
terrain.push({ slice, tops: shadow });
if (devel) {
slice.output()
.setLayer("shadow", {line: 0x888800, thin: true })
.addPolys(shadow, { thin: true });
}
progress(0.5 + (i / tzindex.length) * 0.5, 'shadow');
}
if (devel && slices.length) {
addSlices(slices);
}
if (terrain.length === 0) {
throw `invalid widget shadow`;
}
// TODO: refactor ops to use a unified shadow object
state.shadow = {
base: shadowBase, // computed shadow union at base of part
holes: shadowBase.map(p => p.inner || []).flat(),
skip: skipTerrain,
slices: slices, // legacy / transitional
stack: terrain, // stack of shadow slices
};
}
}
export { OpShadow };