/** Copyright Stewart Allen -- All Rights Reserved */ "use strict"; // dep: moto.license // dep: ext.clip2 // dep: add.three // dep: add.array // dep: moto.client // dep: moto.worker // dep: mesh.util // dep: geo.base // dep: geo.line // dep: geo.point // dep: geo.polygon // dep: geo.polygons // dep: geo.bounds // dep: geo.slicer // dep: geo.csg // dep: mesh.tool gapp.main("mesh.work", [], (root) => { const { Matrix4, Vector3, BufferGeometry, BufferAttribute, computeFaceNormal } = THREE; const { base, mesh, moto } = root; const { client, worker } = moto; const { util } = mesh; const { newPolygon } = base; const cache = {}; // add scoped access to cache mesh.work = { cache }; // compensation for space/world/platform rotation const core_matrix = new Matrix4().makeRotationX(Math.PI / 2); // start worker pool (disabled for now with *0) client.start(`/code/mesh_pool?${gapp.version}`, client.max() * 0); function log(msg) { return worker.publish("mesh.log", msg); } mesh.log = log; function cacheUpdate(id, data) { Object.assign(cache[id], data); } // translate original mesh vertices into UI world view (PI/2 rotation on X) function translate_encode(id, matrix) { let rec = cache[id]; let mkey = matrix.map(v => v.round(6)).join('-'); if (!rec || !rec.trans || rec.mkey !== mkey) { let geo = rec.geo.clone(); let mat = core_matrix.clone().multiply(new Matrix4().fromArray(matrix)); geo.applyMatrix4(mat); rec.mkey = mkey; rec.trans = geo.attributes.position.array } return rec.trans; } function analyze(id, opt = {}) { let rec = cache[id]; let { geo, tool } = rec; if (!tool) { tool = rec.tool = new mesh.tool(); } if (tool.faces) { log(`${id} | analysis cached`); } else { log(`${id} | analyzing...`); tool.generateFaces(geo.attributes.position.array); log(`${id} | patching...`); tool.patch(opt); } return tool; } function isolateBodies(id) { let tool = indexFaces(id); log(`${id} | isolating bodies`); return tool.isolateBodies(); } function indexFaces(id) { let rec = cache[id]; let { geo, tool } = rec; if (!tool) { tool = rec.tool = new mesh.tool(); } if (!tool.indexed) { log(`${id} | indexing mesh`); tool.index(geo.attributes.position.array); } return tool; } let model = { load(data) { let { vertices, name, id } = data; let geo = new BufferGeometry(); geo.setAttribute('position', new BufferAttribute(vertices, 3)); cacheUpdate(id, { name, geo, xmatrix: core_matrix.clone(), trans: undefined, tool: undefined }); }, // return new vertices in world coordinates duplicate(data) { let { id, matrix, opt } = data; let array = translate_encode(id, matrix); if (opt.mirror) { array = array.slice(); // find max z and invert z let maxz = -Infinity; for (let i=2, l=array.length; i translate_encode(rec.id, rec.matrix)); let length = arrays.map(a => a.length).reduce((v,a) => v + a); let data = new Float32Array(length); for (let i=0, l=arrays.length, p=0; i translate_encode(rec.id, rec.matrix)); let solids = arrays.map(a => base.CSG.fromPositionArray(a)); let union = base.CSG.union(...solids); return base.CSG.toPositionArray(union); }, intersect(recs) { let arrays = recs.map(rec => translate_encode(rec.id, rec.matrix)); let solids = arrays.map(a => base.CSG.fromPositionArray(a)); let union = base.CSG.intersect(...solids); return base.CSG.toPositionArray(union); }, subtract(recs) { let bases = recs.filter(rec => !rec.tool) .map(rec => translate_encode(rec.id, rec.matrix)) .map(a => base.CSG.fromPositionArray(a)); let tools = recs.filter(rec => rec.tool) .map(rec => translate_encode(rec.id, rec.matrix)) .map(a => base.CSG.fromPositionArray(a)); let subs = []; for (let obj of bases) { let sub = base.CSG.subtract(obj, ...tools); subs.appendAll(base.CSG.toPositionArray(sub)); } return subs.toFloat32(); }, // split a model along an axis at a given point // return two arrays of vertices for each resulting object split(data) { let { id, matrix, z } = data; let o1 = []; // new bottom let o2 = []; // new top let pos = translate_encode(id, matrix); let split = []; let on = []; let over = []; let under = []; function sort(v) { if (v.z < z) return under.push(v); if (v.z > z) return over.push(v); on.push(v); } function lerp(v1, v2) { let zd = Math.abs(v1.z - v2.z); let z1 = Math.abs(v1.z - z); return v1.clone().lerp(v2, z1/zd); } for (let i=0, l=pos.length; i { let { points, slices } = output; log(`${id} | ${slices.length} slices Z`); for (let slice of slices) { for (let line of slice.lines) { layers.appendAll(util.extract(line.p1)); layers.appendAll(util.extract(line.p2)); } } for (let p of points) p.swapXZ(); return base.slice(points, { autoDim: true, both: true, debug: true, minstep: 0.25, }).then(output => { let { points, slices } = output; log(`${id} | ${slices.length} slices X`); for (let slice of slices) { for (let line of slice.lines) { if (!line.p1.swapped) { line.p1.swapXZ().swapped = true } if (!line.p2.swapped) { line.p2.swapXZ().swapped = true } layers.appendAll(util.extract(line.p1)); layers.appendAll(util.extract(line.p2)); } } }); }).finally(() => { log(`${id} | rebuild complete`); send.done({ lines: layers }); });; }, gen_gear(data, send) { const { teeth, module, angle, twist, shaft, z } = data; let points = new mesh.tool() .generateGear(teeth, module, angle) .map(v => [...v, 0]).flat(); let poly = newPolygon().addVerts(points); if (shaft) { poly.addInner( newPolygon().centerCircle({ x:0, y:0, z:0 }, shaft / 2, Math.min(20 + shaft,100)) ); } let verts = poly.extrude(z || 15); if (twist) { let rad = base.util.toRadians(twist); for (let i=0; i v.round(5)); vtot += (vs.length / 3); } switch (format) { case "obj": let p = 1; let obj = [header]; for (let rec of recs) { let { id, matrix, file, varr } = rec; obj.push(`g ${file}`); for (let i=0; i { dat.setUint8(i, c.charCodeAt(0)); }); // todo put Kiri:Moto info in header dat.setInt32(80, vtot/3, true); for (let rec of recs) { let { id, matrix, file, varr } = rec; for (let i=0, l=varr.length; i