410 lines
12 KiB
JavaScript
410 lines
12 KiB
JavaScript
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
|
|
// WORKER BACK END ANIMATION CODE for 3D (indexed)
|
|
|
|
import { CSG } from '../../../geo/csg.js';
|
|
import { Tool } from './tool.js';
|
|
|
|
let nextMeshID = 1,
|
|
stock,
|
|
path,
|
|
pathIndex,
|
|
tool,
|
|
tools,
|
|
last,
|
|
settings,
|
|
stockZ,
|
|
stockIndexMsg = false,
|
|
stockSlices,
|
|
stockIndex,
|
|
startTime,
|
|
toolID = -1,
|
|
toolMesh,
|
|
toolRadius,
|
|
toolUpdateMsg,
|
|
animateClear = false,
|
|
animating = false,
|
|
renderDone = false,
|
|
renderSteps = 0,
|
|
renderSpeed = 0,
|
|
indexCount = 0,
|
|
updates = 0,
|
|
maxR;
|
|
|
|
export function init(worker) {
|
|
const { dispatch, minions } = worker;
|
|
|
|
dispatch.animate_setup2 = function (data, send) {
|
|
settings = data.settings;
|
|
|
|
const { controller, process } = settings;
|
|
const { print } = worker.current;
|
|
const isIndexed = process.camStockIndexed;
|
|
|
|
pathIndex = 0;
|
|
path = print.output.flat();
|
|
tools = settings.tools;
|
|
stock = settings.stock;
|
|
tool = null;
|
|
last = null;
|
|
animating = false;
|
|
animateClear = false;
|
|
stockIndex = 0;
|
|
indexCount = 0;
|
|
startTime = 0;
|
|
updates = 0;
|
|
stockZ = isIndexed ? 0 : stock.z;
|
|
maxR = Math.hypot(stock.y, stock.z);
|
|
|
|
stockSlices = [];
|
|
const { x, y, z } = stock;
|
|
const sliceCount = parseInt(settings.controller.animesh || 2000) / 100;
|
|
const sliceWidth = stock.x / sliceCount;
|
|
|
|
if (controller.manifold)
|
|
for (let i = 0; i < sliceCount; i++) {
|
|
let xmin = -(x / 2) + (i * sliceWidth) + sliceWidth / 2;
|
|
let slice = new Stock(sliceWidth, y, z).translate(xmin, 0, 0);
|
|
stockSlices.push(slice);
|
|
slice.updateMesh([]);
|
|
slice.send(send);
|
|
// send.data({ mesh_move: { id: slice.id, pos: { x:0, y:0, z: stock.z/2} } });
|
|
}
|
|
|
|
send.done();
|
|
};
|
|
|
|
dispatch.animate2 = function (data, send) {
|
|
renderSpeed = data.speed || 0;
|
|
if (animating) {
|
|
return send.done();
|
|
}
|
|
renderSteps = data.steps || 1;
|
|
renderDone = false;
|
|
animating = renderSpeed > 0;
|
|
startTime = startTime || Date.now();
|
|
renderPath(send);
|
|
};
|
|
|
|
dispatch.animate_cleanup2 = function (data, send) {
|
|
if (animating) {
|
|
animateClear = true;
|
|
}
|
|
};
|
|
}
|
|
|
|
function renderPath(send) {
|
|
if (renderDone) {
|
|
return;
|
|
}
|
|
|
|
if (renderSteps-- === 0) {
|
|
animating = false;
|
|
renderPath(send);
|
|
return;
|
|
}
|
|
|
|
if (animating === false || animateClear || renderSpeed === 0) {
|
|
renderUpdate(send);
|
|
renderDone = true;
|
|
animating = false;
|
|
animateClear = false;
|
|
send.done();
|
|
return;
|
|
}
|
|
|
|
let next = path[pathIndex];
|
|
while (next && next.type === 'laser') {
|
|
last = next;
|
|
next = path[++pathIndex];
|
|
}
|
|
|
|
if (!next) {
|
|
console.log('animation completed in ', ((Date.now() - startTime) / 1000).round(2));
|
|
animating = false;
|
|
renderPath(send);
|
|
return;
|
|
}
|
|
pathIndex++;
|
|
|
|
if (next.tool && (!tool || tool.getID() !== next.tool.getID())) {
|
|
// on real tool change, go to safe Z first
|
|
if (tool && last.point) {
|
|
let pos = last.point = {
|
|
x: last.point.x,
|
|
y: last.point.y,
|
|
z: stock.z
|
|
};
|
|
toolMove(pos);
|
|
send.data(toolUpdateMsg);
|
|
}
|
|
toolUpdate(next.tool.getID(), send);
|
|
}
|
|
|
|
const id = toolID;
|
|
const rezstep = tool ? Math.min(tool.maxDiameter(), 1) / 4 : 1;
|
|
// console.log({ rezstep });
|
|
|
|
if (last) {
|
|
// console.log(last.point, next.point);
|
|
const lp = last.point, np = next.point;
|
|
last = next;
|
|
|
|
// render move line on client
|
|
send.data({ line: [ lp, np ]});
|
|
|
|
// dwell ops have no point
|
|
if (!np || !lp) {
|
|
return renderPath(send);
|
|
}
|
|
|
|
let dx = np.x - lp.x,
|
|
dy = np.y - lp.y,
|
|
dz = np.z - lp.z,
|
|
da = (np.a ?? 0) - (lp.a ?? 0), // delta rotation degrees
|
|
AD = maxR * 2, // arc diameter @ max z
|
|
dr = (Math.abs(da) / 360) * AD, // delta rotation arc
|
|
dist = Math.max(Math.hypot(dx, dy, dz), dr); // motion distance
|
|
|
|
// console.log({ dz, da, AD, dr, dist });
|
|
// sub-divide long moves for animation and better boolean ops
|
|
const md = Math.max(Math.abs(dx), Math.abs(dy), Math.abs(dz), dr);
|
|
const st = Math.ceil(md / rezstep); // steps
|
|
const mx = dx / st, my = dy / st, mz = dz / st, ma = da / st;
|
|
const sd = Math.sqrt(mx * mx + my * my + mz * mz + ma * ma);
|
|
const sp = next.speed;
|
|
const moves = [];
|
|
for (let i = 0, x = lp.x, y = lp.y, z = lp.z, a = lp.a; i < st; i++) {
|
|
moves.push({ x, y, z, a, md: sd, dx, dy, dz, da, sp });
|
|
x += mx;
|
|
y += my;
|
|
z += mz;
|
|
a += ma;
|
|
}
|
|
// console.log({ dist: md, steps: st, speed: next.speed, moves });
|
|
|
|
// push last point
|
|
moves.push({ ...next.point, md: sd, sp });
|
|
// console.log(...moves);
|
|
renderMoves(id, moves, send);
|
|
} else {
|
|
last = next;
|
|
if (tool) {
|
|
toolMove(next.point);
|
|
}
|
|
renderPath(send);
|
|
}
|
|
}
|
|
|
|
function renderMoves(id, moves, send, seed = 0) {
|
|
for (let index = seed; index < moves.length; index++) {
|
|
const pos = moves[index];
|
|
if (!pos) {
|
|
throw `no pos @ ${index} of ${moves.length}`;
|
|
}
|
|
const { dx, dy, dz, da, sp, md } = pos;
|
|
toolMove(pos);
|
|
// console.log('renderMoves', {id, moves, seed});
|
|
let subs = 0;
|
|
if (dx || dy || dz < 0 || da)
|
|
for (let slice of stockSlices) {
|
|
if (slice.bounds.intersectsBox(toolMesh.bounds)) {
|
|
slice.subtractTool(toolMesh);
|
|
subs++;
|
|
}
|
|
}
|
|
// pause renderer at specified offsets
|
|
if (renderSpeed) {
|
|
let time = sp ? (md / sp) * 60000 : 10;
|
|
// console.log({ time, ...pos });
|
|
renderUpdate(send);
|
|
setTimeout(() => {
|
|
renderMoves(id, moves, send, index + 1);
|
|
}, time / renderSpeed);
|
|
return;
|
|
}
|
|
}
|
|
renderPath(send);
|
|
}
|
|
|
|
// send latest tool position and progress bar
|
|
function renderUpdate(send) {
|
|
const updated = []
|
|
for (let slice of stockSlices) {
|
|
slice.updateMesh(updated);
|
|
}
|
|
for (let slice of updated) {
|
|
slice.send(send);
|
|
}
|
|
if (toolUpdateMsg) {
|
|
send.data(toolUpdateMsg);
|
|
}
|
|
if (stockIndexMsg) {
|
|
send.data({ stock_index: stockIndex });
|
|
for (let slice of stockSlices) {
|
|
send.data({ mesh_index: { id: slice.id, index: -stockIndex } });
|
|
}
|
|
stockIndexMsg = false;
|
|
}
|
|
send.data({ progress: pathIndex / path.length });
|
|
updates++;
|
|
}
|
|
|
|
// move tool mesh animation space, update client
|
|
function toolMove(pos) {
|
|
toolUpdateMsg = { mesh_move: { id: toolID, pos: { x: pos.x, y: pos.y, z: pos.z } } };
|
|
if (toolMesh.mesh) {
|
|
toolMesh.mesh.delete();
|
|
}
|
|
toolMesh.mesh = toolMesh.root.translate(pos.x, pos.y, pos.z);
|
|
if (pos.a !== undefined) {
|
|
let tmp = toolMesh.mesh.rotate([pos.a, 0, 0]);
|
|
toolMesh.mesh.delete();
|
|
toolMesh.mesh = tmp;
|
|
if (pos.a !== stockIndex) {
|
|
stockIndexMsg = true;
|
|
stockIndex = pos.a;
|
|
}
|
|
}
|
|
toolMesh.bounds = CSG.toBox3(toolMesh.mesh);
|
|
}
|
|
|
|
// delete old tool mesh, generate tool mesh, send to client
|
|
function toolUpdate(toolid, send) {
|
|
if (tool) {
|
|
send.data({ mesh_del: toolID });
|
|
}
|
|
tool = new Tool(settings, toolid);
|
|
const Instance = CSG.Instance();
|
|
const slen = tool.shaftLength() || 15;
|
|
const srad = tool.shaftDiameter() / 2;
|
|
const flen = tool.fluteLength() || 15;
|
|
const frad = toolRadius = tool.fluteDiameter() / 2;
|
|
const tlen = slen + flen; // total tool length
|
|
let { cylinder, sphere } = Instance.Manifold;
|
|
let mesh;
|
|
if (tool.isBallMill()) {
|
|
mesh = cylinder(tlen - frad * 2, frad, frad, 20, true)
|
|
.add(sphere(frad, 20).translate(0, 0, -(tlen - frad * 2) / 2))
|
|
.add(cylinder(slen, srad, srad, 20, true).translate(0, 0, flen / 2));
|
|
} else if (tool.isTaperBall()) {
|
|
const { b, r } = tool.getBallTaperParams();
|
|
// taper length excludes most of ball
|
|
const taperLen = flen - b;
|
|
// shaft at top
|
|
mesh = cylinder(slen, srad, srad, 20, true).translate(0, 0, slen / 2)
|
|
// taper cone in middle
|
|
.add(cylinder(taperLen, b, frad, 20, true).translate(0, 0, -taperLen / 2))
|
|
// exposed ball at bottom
|
|
.add(sphere(r, 20).translate(0, 0, -taperLen));
|
|
} else if (tool.isTaperMill()) {
|
|
const trad = Math.max(tool.tipDiameter() / 2, 0.001);
|
|
mesh = cylinder(slen, srad, srad, 20, true).translate(0, 0, slen / 2)
|
|
.add(cylinder(flen, trad, frad, 20, true).translate(0, 0, -flen / 2));
|
|
} else {
|
|
mesh = cylinder(tlen, frad, frad, 20, true)
|
|
.add(cylinder(slen, srad, srad, 20, true).translate(0, 0, flen / 2));
|
|
}
|
|
mesh = mesh.translate(0, 0, (tlen - stockZ) / 2);
|
|
const raw = mesh.getMesh();
|
|
const vertex = raw.vertProperties;
|
|
const index = raw.triVerts;
|
|
toolMesh = { root: mesh, index, vertex, bounds: CSG.toBox3(mesh) };
|
|
send.data({ mesh_add: { id: --toolID, ind: index, pos: vertex } });
|
|
}
|
|
|
|
class Stock {
|
|
constructor(x, y, z) {
|
|
this.id = nextMeshID++;
|
|
this.vbuf = undefined;
|
|
this.ibuf = undefined;
|
|
this.ilen = 0;
|
|
this.sends = 0;
|
|
this.newbuf = true;
|
|
this.subtracts = 0;
|
|
this.mesh = CSG.Instance().Manifold.cube([x, y, z], true);
|
|
}
|
|
|
|
send(send) {
|
|
const newbuf = this.newbuf;
|
|
const action = this.sends++ === 0 ? 'mesh_add' : 'mesh_update';
|
|
send.data({
|
|
[action]: {
|
|
id: this.id,
|
|
ind: newbuf ? this.ibuf : undefined,
|
|
pos: newbuf ? this.vbuf : undefined,
|
|
ilen: this.ilen,
|
|
plen: this.plen
|
|
}
|
|
});
|
|
this.newbuf = false;
|
|
this.sends++;
|
|
// console.log({ send: this.id, newbuf, action });
|
|
}
|
|
|
|
translate(x, y, z) {
|
|
// console.log({ translate: this.id, x, y, z });
|
|
const oldmesh = this.mesh;
|
|
this.mesh = this.mesh.translate(x, y, z);
|
|
this.bounds = CSG.toBox3(this.mesh);
|
|
oldmesh.delete();
|
|
return this;
|
|
}
|
|
|
|
updateMesh(updates) {
|
|
if (this.sends > 0 && this.subtracts === 0) {
|
|
return;
|
|
}
|
|
const subs = this.subtracts;
|
|
this.subtracts = 0;
|
|
let start = Date.now();
|
|
let mesh = this.mesh.getMesh();
|
|
this.sharedVertexBuffer(mesh.numVert * 3);
|
|
this.sharedIndexBuffer(mesh.numTri * 3);
|
|
this.vbuf.set(mesh.vertProperties);
|
|
this.ibuf.set(mesh.triVerts);
|
|
let sub = Date.now();
|
|
updates.push(this);
|
|
if (false) console.log({
|
|
update: this.id,
|
|
time: sub - start,
|
|
subs,
|
|
mssub: ((sub - start) / subs).round(3)
|
|
});
|
|
}
|
|
|
|
subtractTool(toolMesh) {
|
|
const oldmesh = this.mesh;
|
|
this.mesh = this.mesh.subtract(toolMesh.mesh);
|
|
oldmesh.delete();
|
|
this.subtracts++;
|
|
}
|
|
|
|
sharedVertexBuffer(size) {
|
|
const old = this.vbuf;
|
|
this.plen = size;
|
|
if (old && old.length >= size) {
|
|
// console.log({svb: this.id, reuse: size, old: old.length});
|
|
return old;
|
|
}
|
|
// let buf = new Float32Array(new SharedArrayBuffer(size * 4));
|
|
let buf = new Float32Array(new SharedArrayBuffer(size * 4 + 1024 * 1024));
|
|
// console.log({new_svb: this.id, size, buf});
|
|
this.newbuf = true;
|
|
return this.vbuf = buf;
|
|
}
|
|
|
|
sharedIndexBuffer(size) {
|
|
const old = this.ibuf;
|
|
this.ilen = size;
|
|
if (old && old.length >= size) {
|
|
// console.log({sib: this.id, reuse: size, old: old.length});
|
|
return old;
|
|
}
|
|
// let buf = new Uint32Array(new SharedArrayBuffer(size * 4));
|
|
let buf = new Uint32Array(new SharedArrayBuffer(size * 4 + 1024 * 1024));
|
|
// console.log({new_sib: this.id, size, buf});
|
|
this.newbuf = true;
|
|
return this.ibuf = buf;
|
|
}
|
|
}
|