parallelize widget shadow generation 2x faster
This commit is contained in:
parent
dda5107810
commit
f81e002766
5 changed files with 74 additions and 16 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { decode } from '../core/codec.js';
|
||||||
import { util as mesh_util } from '../../mesh/util.js';
|
import { util as mesh_util } from '../../mesh/util.js';
|
||||||
import { tool as mesh_tool } from '../../mesh/tool.js';
|
import { tool as mesh_tool } from '../../mesh/tool.js';
|
||||||
import { verticesToPoints } from '../../geo/points.js';
|
import { verticesToPoints } from '../../geo/points.js';
|
||||||
|
|
@ -12,6 +13,7 @@ const sharedArrayClass = self.SharedArrayBuffer || undefined;
|
||||||
const hasSharedArrays = sharedArrayClass ? true : false;
|
const hasSharedArrays = sharedArrayClass ? true : false;
|
||||||
const solid_opacity = 1.0;
|
const solid_opacity = 1.0;
|
||||||
const groups = [];
|
const groups = [];
|
||||||
|
const debug_shadow = false;
|
||||||
|
|
||||||
let nextId = 0;
|
let nextId = 0;
|
||||||
|
|
||||||
|
|
@ -739,8 +741,9 @@ class Widget {
|
||||||
// the stacks are then used to produce shadowlines
|
// the stacks are then used to produce shadowlines
|
||||||
#ensureShadowCache() {
|
#ensureShadowCache() {
|
||||||
if (this.cache.shadow) {
|
if (this.cache.shadow) {
|
||||||
return;
|
return this.cache.shadow;
|
||||||
}
|
}
|
||||||
|
if (debug_shadow) console.time('shadow buckets');
|
||||||
const geo = this.getGeoVertices({ unroll: true, translate: true });
|
const geo = this.getGeoVertices({ unroll: true, translate: true });
|
||||||
const length = geo.length;
|
const length = geo.length;
|
||||||
const bounds = this.getBoundingBox();
|
const bounds = this.getBoundingBox();
|
||||||
|
|
@ -762,15 +765,54 @@ class Widget {
|
||||||
stack[z].push(a, b, c);
|
stack[z].push(a, b, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.cache.shadow = stack;
|
if (debug_shadow) console.timeEnd('shadow buckets');
|
||||||
|
return this.cache.shadow = stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
async computeShadowStack(zlist, progress) {
|
||||||
|
let shadow_stack = this.cache.shadow_stack;
|
||||||
|
if (!shadow_stack) {
|
||||||
|
shadow_stack = this.cache.shadow_stack = {};
|
||||||
|
}
|
||||||
|
let work = self.kiri_worker;
|
||||||
|
let stack = this.#ensureShadowCache();
|
||||||
|
let plist = [];
|
||||||
|
if (debug_shadow) console.time('seed minion buckets');
|
||||||
|
work.minions.broadcast('cam_shadow_stack', stack);
|
||||||
|
if (debug_shadow) console.timeEnd('seed minion buckets');
|
||||||
|
let pinc = 1 / zlist.length;
|
||||||
|
let pval = 0;
|
||||||
|
for (let z of zlist) {
|
||||||
|
let p = work.minions.queueAsync({
|
||||||
|
cmd: 'cam_shadow_z',
|
||||||
|
z: z - 0.005,
|
||||||
|
t: z + 1
|
||||||
|
}).then(reply => {
|
||||||
|
shadow_stack[z - 0.005] = decode(reply.data);
|
||||||
|
pval += pinc;
|
||||||
|
progress(pval);
|
||||||
|
});
|
||||||
|
plist.push(p);
|
||||||
|
}
|
||||||
|
await Promise.all(plist);
|
||||||
|
work.minions.broadcast('cam_shadow_stack', { clear: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
computeShadowAtZ(z, ztop, cached) {
|
||||||
|
return this.#computeShadowAt(z, ztop, cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
// union triangles > z (opt cap < ztop) into polygon(s)
|
// union triangles > z (opt cap < ztop) into polygon(s)
|
||||||
// slice the triangle stack matchingg z then union the results
|
// slice the triangle stack matchingg z then union the results
|
||||||
#computeShadowAt(z, ztop) {
|
#computeShadowAt(z, ztop, cached) {
|
||||||
this.#ensureShadowCache();
|
let shadow_stack = this.cache.shadow_stack;
|
||||||
|
if (shadow_stack && shadow_stack[z]) {
|
||||||
|
return shadow_stack[z];
|
||||||
|
}
|
||||||
|
let label = `compute shadow ${z.round(2)}`;
|
||||||
|
if (debug_shadow) console.time(label);
|
||||||
const found = [];
|
const found = [];
|
||||||
const stack = this.cache.shadow;
|
const stack = cached ?? this.#ensureShadowCache();
|
||||||
let minZ = Math.floor(z);
|
let minZ = Math.floor(z);
|
||||||
let maxZ = Math.ceil(z);
|
let maxZ = Math.ceil(z);
|
||||||
let slices = [];
|
let slices = [];
|
||||||
|
|
@ -832,6 +874,7 @@ class Widget {
|
||||||
polys = POLY.offset(polys, 0.01);
|
polys = POLY.offset(polys, 0.01);
|
||||||
polys = POLY.offset(polys, -0.01);
|
polys = POLY.offset(polys, -0.01);
|
||||||
|
|
||||||
|
if (debug_shadow) console.timeEnd(label);
|
||||||
return polys;
|
return polys;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ export class OpIndex extends CamOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
weight() {
|
weight() {
|
||||||
return 0.1;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
async slice() {
|
async slice(progress) {
|
||||||
let { op, state } = this;
|
let { op, state } = this;
|
||||||
|
|
||||||
if (!state.isIndexed) {
|
if (!state.isIndexed) {
|
||||||
|
|
@ -37,7 +37,7 @@ export class OpIndex extends CamOp {
|
||||||
updateSlicer();
|
updateSlicer();
|
||||||
|
|
||||||
// recompute shadow from new widget geometry
|
// recompute shadow from new widget geometry
|
||||||
await computeShadows();
|
await computeShadows(progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare(ops, progress) {
|
prepare(ops, progress) {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class OpShadow extends CamOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
weight() {
|
weight() {
|
||||||
return 0.1;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
async slice(progress) {
|
async slice(progress) {
|
||||||
|
|
@ -50,6 +50,9 @@ class OpShadow extends CamOp {
|
||||||
tzindex = [ ];
|
tzindex = [ ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// terrain is the "shadow stack" where index 0 = top of part
|
||||||
// thus array.length -1 = bottom of part
|
// thus array.length -1 = bottom of part
|
||||||
for (let i=0; i<tzindex.length; i++) {
|
for (let i=0; i<tzindex.length; i++) {
|
||||||
|
|
@ -65,7 +68,7 @@ class OpShadow extends CamOp {
|
||||||
.setLayer("shadow", {line: 0x888800, thin: true })
|
.setLayer("shadow", {line: 0x888800, thin: true })
|
||||||
.addPolys(shadow, { thin: true });
|
.addPolys(shadow, { thin: true });
|
||||||
}
|
}
|
||||||
progress(i / tzindex.length);
|
progress(0.5 + i / tzindex.length, 'shadow');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (devel && slices.length) {
|
if (devel && slices.length) {
|
||||||
|
|
|
||||||
|
|
@ -261,12 +261,10 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
|
||||||
return slicer = state.slicer = new cam_slicer(widget, opts);
|
return slicer = state.slicer = new cam_slicer(widget, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function computeShadows() {
|
async function computeShadows(progress) {
|
||||||
console.time('computeShadows');
|
console.time('compute shadow');
|
||||||
await new OPS.shadow(state, { type: "shadow", silent: true }).slice(progress => {
|
await new OPS.shadow(state, { type: "shadow", xsilent: true }).slice(progress);
|
||||||
// console.log('reshadowing', progress.round(3));
|
console.timeEnd('compute shadow');
|
||||||
});
|
|
||||||
console.timeEnd('computeShadows');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setToolDiam(toolDiam) {
|
function setToolDiam(toolDiam) {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { base } from '../../geo/base.js';
|
||||||
import { codec, encode, encodePointArray } from '../core/codec.js';
|
import { codec, encode, encodePointArray } from '../core/codec.js';
|
||||||
import { doTopShells } from '../mode/fdm/post.js';
|
import { doTopShells } from '../mode/fdm/post.js';
|
||||||
import { newPoint } from '../../geo/point.js';
|
import { newPoint } from '../../geo/point.js';
|
||||||
|
import { newWidget } from '../core/widget.js';
|
||||||
import { polygons as POLY } from '../../geo/polygons.js';
|
import { polygons as POLY } from '../../geo/polygons.js';
|
||||||
import { sliceZ, sliceConnect } from '../../geo/slicer.js';
|
import { sliceZ, sliceConnect } from '../../geo/slicer.js';
|
||||||
import { Slicer as cam_slicer } from '../mode/cam/slicer_cam.js';
|
import { Slicer as cam_slicer } from '../mode/cam/slicer_cam.js';
|
||||||
|
|
@ -317,5 +318,18 @@ const funcs = self.minion = {
|
||||||
const topo4 = Object.assign(new Topo4(), cache.lathe);
|
const topo4 = Object.assign(new Topo4(), cache.lathe);
|
||||||
const heights = topo4.lathePath(stmp, tool);
|
const heights = topo4.lathePath(stmp, tool);
|
||||||
reply({ seq, heights });
|
reply({ seq, heights });
|
||||||
|
},
|
||||||
|
|
||||||
|
// cam widget shadow
|
||||||
|
|
||||||
|
cam_shadow_stack(data, seq) {
|
||||||
|
cache.shadow_stack = data.clear ? undefined : data;
|
||||||
|
},
|
||||||
|
|
||||||
|
cam_shadow_z(data, seq) {
|
||||||
|
let polys = newWidget().computeShadowAtZ(data.z, data.t, cache.shadow_stack);
|
||||||
|
let coded = codec.encode(polys);
|
||||||
|
reply({ seq, data: coded });
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue