diff --git a/src/geo/csg.js b/src/geo/csg.js index 2433fdf7..2a098c26 100644 --- a/src/geo/csg.js +++ b/src/geo/csg.js @@ -8,6 +8,8 @@ gapp.register("geo.csg", [], (root, exports) => { const { base, ext } = root; const debug = true; +const precision = 0.001; +const factor = 1/precision; function log() { if (root?.mesh?.log) { @@ -34,6 +36,11 @@ const CSG = { return CSG.moduleOp('manifold.intersect', 'intersection', ...arguments); }, + // accepts 2 or more arguments with threejs vertex position arrays + difference() { + return CSG.moduleOp('manifold.difference', 'difference', ...arguments); + }, + moduleOp(name, op) { log(`${name} ${arguments.length} meshes`); const args = [...arguments].slice(2).map(a => new Instance.Manifold(a)); @@ -63,7 +70,7 @@ const CSG = { vertices[p++] = vertPos[vi++]; vertices[p++] = vertPos[vi++]; } - return vertices; + return vertices.map(v => v/factor); } throw "mesh missing required fields"; }, @@ -71,7 +78,7 @@ const CSG = { fromPositionArray(pos) { const { index, vertices } = indexVertices(pos); let mesh = new Instance.Mesh({ - vertProperties: vertices.toFloat32(), + vertProperties: vertices.map(v => (v*factor)|0).toFloat32(), triVerts: index.toUint32() }); return mesh; diff --git a/src/mesh/api.js b/src/mesh/api.js index 65d4b2ff..5d42d127 100644 --- a/src/mesh/api.js +++ b/src/mesh/api.js @@ -235,10 +235,10 @@ const pattern = { * @param {float[]} center [x,y,z] */ circle(count, center) { - async function doit(count, center = [0,0,0]) { + async function doit(count, center = [0,0]) { api.modal.hide(); let models = selection.models(); - let [ cx, cy, cz ] = center; + let [ cx, cy ] = center; for (let model of models) { await api.tool.regroup([ model ]); let pos = model.group.position().clone(); @@ -260,8 +260,8 @@ const pattern = { body: [ h.div({ class: "addgear" }, [ h.label('total count'), h.input({ value: 3, size: 5, id: "_count" }), - h.label('center x,y,z'), - h.input({ value: "0,0,0", size: 5, id: "_center" }), + h.label('center x,y'), + h.input({ value: "0,0", size: 5, id: "_center" }), h.button({ _: "create", onclick() { doit( parseInt(api.modal.bound._count.value) || 3, @@ -489,6 +489,7 @@ const tool = { file: `merged`, mesh: data })]).promote(); + api.selection.visible(false); api.selection.set([group]); api.log.emit('merge complete').unpin(); }); @@ -508,6 +509,7 @@ const tool = { file: `union`, mesh: data })]).promote(); + api.selection.visible(false); api.selection.set([group]); }) .catch(error => { @@ -518,6 +520,31 @@ const tool = { }); }, + difference(models) { + models = fallback(models); + if (models.length < 2) { + return api.log.emit('nothing to diff'); + } + api.log.emit(`diff ${models.length} models`).pin(); + worker.model_difference(models.map(m => { + return { id: m.id, matrix: m.matrix } + })) + .then(data => { + let group = api.group.new([new mesh.model({ + file: `diff`, + mesh: data + })]).promote(); + api.selection.visible(false); + api.selection.set([group]); + }) + .catch(error => { + api.log.emit(`diff error: ${error}`); + }) + .finally(() => { + api.log.emit('diff complete').unpin(); + }); + }, + intersect(models) { models = fallback(models); if (models.length < 2) { @@ -532,6 +559,7 @@ const tool = { file: `intersect`, mesh: data })]).promote(); + api.selection.visible(false); api.selection.set([group]); }) .catch(error => { @@ -556,6 +584,7 @@ const tool = { file: `subtract`, mesh: data })]).promote(); + api.selection.visible(false); api.selection.set([group]); }) .catch(error => { diff --git a/src/mesh/build.js b/src/mesh/build.js index 2e0756bf..db6c2223 100644 --- a/src/mesh/build.js +++ b/src/mesh/build.js @@ -396,6 +396,7 @@ function ui_build() { h.button({ _: 'union', onclick: tool.union }), h.button({ _: 'subtract', onclick: tool.subtract }), h.button({ _: 'intersect', onclick: tool.intersect }), + h.button({ _: 'difference', onclick: tool.difference }), ]) ]), h.div([ diff --git a/src/mesh/model.js b/src/mesh/model.js index 69908604..0084d65e 100644 --- a/src/mesh/model.js +++ b/src/mesh/model.js @@ -153,13 +153,17 @@ mesh.model = class MeshModel extends mesh.object { return this.duplicate({ mirror: true }); } - // return new group containing just this model in world coordinates + // return a model containing just this model + // translated into world coordinates (rebuilt from rotation matrix) + // defaults to returning a new model in a new group + // options to mirror, re-use a group, or update model in-place duplicate(opt = {}) { return worker.model_duplicate({ matrix: this.matrix, id: this.id, opt: { mirror: opt.mirror} }).then(data => { + if (opt.reload) return this.reload(data); let model = new mesh.model({ file: `${this.file}`, mesh: data }); let group = opt.group || mesh.api.group.new(); group.add(model); diff --git a/src/mesh/work.js b/src/mesh/work.js index 96a3ced3..ae4f75fb 100644 --- a/src/mesh/work.js +++ b/src/mesh/work.js @@ -24,7 +24,7 @@ const { Matrix4, Vector3, BufferGeometry, BufferAttribute, computeFaceNormal } = const { base, mesh, moto } = root; const { client, worker } = moto; const { util } = mesh; -const { newPolygon } = base; +const { CSG, newPolygon } = base; const cache = {}; // add scoped access to cache @@ -146,29 +146,36 @@ let model = { union(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.union(...solids); - return base.CSG.toPositionArray(union); + let solids = arrays.map(a => CSG.fromPositionArray(a)); + let union = CSG.union(...solids); + return CSG.toPositionArray(union); + }, + + difference(recs) { + let arrays = recs.map(rec => translate_encode(rec.id, rec.matrix)); + let solids = arrays.map(a => CSG.fromPositionArray(a)); + let diff = CSG.difference(...solids); + return CSG.toPositionArray(diff); }, 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); + let solids = arrays.map(a => CSG.fromPositionArray(a)); + let union = CSG.intersect(...solids); + return 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)); + .map(a => CSG.fromPositionArray(a)); let tools = recs.filter(rec => rec.tool) .map(rec => translate_encode(rec.id, rec.matrix)) - .map(a => base.CSG.fromPositionArray(a)); + .map(a => CSG.fromPositionArray(a)); let subs = []; for (let obj of bases) { - let sub = base.CSG.subtract(obj, ...tools); - subs.appendAll(base.CSG.toPositionArray(sub)); + let sub = CSG.subtract(obj, ...tools); + subs.appendAll(CSG.toPositionArray(sub)); } return subs.toFloat32(); },