2025-07-05 10:49:09 -04:00
|
|
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
|
|
|
|
|
|
|
|
import { CamOp } from './op.js';
|
2025-12-06 00:09:17 -05:00
|
|
|
import { newSlice } from '../../core/slice.js';
|
|
|
|
|
import { newPolygon } from '../../../geo/polygon.js';
|
2025-07-05 10:49:09 -04:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 18:51:26 -04:00
|
|
|
weight() {
|
|
|
|
|
return 0.1;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-05 10:49:09 -04:00
|
|
|
async slice(progress) {
|
|
|
|
|
let state = this.state;
|
2026-01-03 16:28:18 -05:00
|
|
|
|
2025-12-08 10:22:52 -05:00
|
|
|
let { addSlices, settings, shadowAt, unsafe, widget, workarea } = state;
|
2025-12-06 00:09:17 -05:00
|
|
|
let { devel } = settings.controller;
|
2025-07-05 10:49:09 -04:00
|
|
|
|
2025-12-06 00:09:17 -05:00
|
|
|
let bounds = widget.getBoundingBox();
|
|
|
|
|
let slices = [];
|
|
|
|
|
let shadowBase;
|
2025-07-05 10:49:09 -04:00
|
|
|
let skipTerrain = unsafe;
|
2025-12-06 00:09:17 -05:00
|
|
|
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);
|
|
|
|
|
}
|
2025-07-05 10:49:09 -04:00
|
|
|
|
|
|
|
|
if (skipTerrain) {
|
|
|
|
|
console.log("skipping terrain generation");
|
2025-12-06 00:09:17 -05:00
|
|
|
shadowBase = [ newPolygon() ];
|
|
|
|
|
tzindex = [ ];
|
2025-07-05 10:49:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// terrain is the "shadow stack" where index 0 = top of part
|
|
|
|
|
// thus array.length -1 = bottom of part
|
2025-12-06 00:09:17 -05:00
|
|
|
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) {
|
2025-07-05 10:49:09 -04:00
|
|
|
slice.output()
|
|
|
|
|
.setLayer("shadow", {line: 0x888800, thin: true })
|
2025-12-06 00:09:17 -05:00
|
|
|
.addPolys(shadow, { thin: true });
|
2025-07-05 10:49:09 -04:00
|
|
|
}
|
2025-12-06 00:09:17 -05:00
|
|
|
progress(i / tzindex.length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (devel && slices.length) {
|
|
|
|
|
addSlices(slices);
|
|
|
|
|
}
|
2025-07-05 10:49:09 -04:00
|
|
|
|
|
|
|
|
if (terrain.length === 0) {
|
|
|
|
|
throw `invalid widget shadow`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: refactor ops to use a unified shadow object
|
|
|
|
|
state.shadow = {
|
2025-12-06 00:09:17 -05:00
|
|
|
base: shadowBase, // computed shadow union at base of part
|
|
|
|
|
holes: shadowBase.map(p => p.inner || []).flat(),
|
|
|
|
|
skip: skipTerrain,
|
|
|
|
|
slices: slices, // legacy / transitional
|
2025-07-05 10:49:09 -04:00
|
|
|
stack: terrain, // stack of shadow slices
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export { OpShadow };
|