diff --git a/src/main/mesh.js b/src/main/mesh.js index d97f8d06..0bb37b31 100644 --- a/src/main/mesh.js +++ b/src/main/mesh.js @@ -5,6 +5,7 @@ (function() { let broker = gapp.broker; +let call = broker.send; function init() { let moto = self.moto, @@ -36,22 +37,78 @@ function init() { }); space.view.setZoom(zoomrev, zoomspd); - // trigger space event binding - broker.send.space_init({ space, platform }); - // start worker moto.client.start(`/code/mesh_work?${gapp.version}`); - // set app version - $h('app-name', "Mesh:Tool"); - $h('app-vers', gapp.version); + // trigger space event binding + call.space_init({ space, platform }); + + // trigger ui building + call.ui_build(); // hide loading curtain $d('curtain','none'); } +function ui_build() { + // set app version + $h('app-name', "Mesh:Tool"); + $h('app-vers', gapp.version); + + let bound = h.bind($('app-body'), [ + h.div({id: 'grouplist'}) + ]); + + let { grouplist } = bound; + let api = mesh.api; + + let maker = { + update() { + clearTimeout(maker.timer); + maker.timer = setTimeout(maker.do, 10); + }, + do() { + let groups = api.group.list() + // map groups to divs + .map(g => h.div([ + h.button({ + _: g.id, + class: [ + "group", + api.selection.contains(g) ? 'selected' : undefined + ], + onclick() { api.selection.toggle(g); } + }), + h.div({ class: "vsep" }), + h.div({ class: "models"}, + // map models to buttons + g.models.map(m => h.button({ + _: m.id, + class: api.selection.contains(m) ? [ 'selected' ] : [], + onclick() { api.selection.toggle(m); } + })) + ) + ])); + h.bind(grouplist, groups); + } + }; + + function update(object, topic) { + console.log({update: object, topic}); + } + + // listen for api calls + broker.listeners({ + model_add: maker.update, + group_add: maker.update, + model_remove: maker.update, + group_remove: maker.update, + selection_update: maker.update, + }) +} + // add space event bindings -broker.subscribe('space_init', data => { +function space_init(data) { let { space, platform } = data; let platcolor = 0x00ff00; let api = mesh.api; @@ -64,7 +121,7 @@ broker.subscribe('space_init', data => { platform.setColor(platcolor); load.File.load([...evt.dataTransfer.files]) .then(data => { - broker.send.space_load(data); + call.space_load(data); }) .catch(error => { dbug.error(error); @@ -127,7 +184,7 @@ broker.subscribe('space_init', data => { m.debug(); } } else { - broker.send.space_debug(); + call.space_debug(); } break; case 'Escape': @@ -199,20 +256,19 @@ broker.subscribe('space_init', data => { return api.objects().length > 0; } }); - -}); +} // add object loader -broker.subscribe('space_load', data => { +function space_load(data) { mesh.api.group.new(data.flat().map(el => new mesh.model(el))) .centerModels() .centerXY() .floor() .focus(); -}); +} // on debug key press -broker.subscribe('space_debug', () => { +function space_debug() { for (let g of mesh.api.group.list()) { let { center, size } = g.bounds(); console.group(g.id); @@ -230,6 +286,14 @@ broker.subscribe('space_debug', () => { console.groupEnd(); } moto.client.fn.debug(); +} + +// bind functions to topics +broker.listeners({ + ui_build, + space_init, + space_load, + space_debug, }); // remove version cache bust from url diff --git a/src/mesh/api.js b/src/mesh/api.js index 2a0db761..f3b856ea 100644 --- a/src/mesh/api.js +++ b/src/mesh/api.js @@ -50,6 +50,10 @@ let selection = { return mdl; }, + contains(object) { + return selected.contains(object); + }, + // @param group {MeshObject[]} set(objects) { selected = objects; @@ -82,6 +86,9 @@ let selection = { for (let group of groups) { group.material(mesh.material.unselected); } + // prevent selection of model and its group + let mgsel = selected.filter(s => s instanceof mesh.model).map(m => m.group); + selected = selected.filter(sel => !mgsel.contains(sel)); for (let object of selected) { object.material(mesh.material.selected); } diff --git a/src/mesh/group.js b/src/mesh/group.js index 30fa25e6..1b879097 100644 --- a/src/mesh/group.js +++ b/src/mesh/group.js @@ -8,6 +8,7 @@ gapp.register("mesh.group", [ "add.array", // dep: add.array "add.three", // dep: add.three "moto.license", // dep: moto.license + "moto.broker", // dep: moto.broker "mesh.object", // dep: mesh.object "mesh.model", // dep: mesh.model "mesh.api", // dep: mesh.api @@ -16,12 +17,15 @@ gapp.register("mesh.group", [ let mesh = self.mesh = self.mesh || {}; if (mesh.group) return; +let broker = gapp.broker; +let call = broker.send; + let space = moto.Space; let worker = moto.client.fn; mesh.group = class MeshGroup extends mesh.object { - // @param group {mesh.model[]} + // @param group {MeshModel[]} constructor(models = []) { super(); this.group = new THREE.Group(); @@ -45,6 +49,7 @@ mesh.group = class MeshGroup extends mesh.object { this.models.addOnce(model); this.group.add(model.mesh); space.update(); + call.model_add({model, group:this}); worker.group_add({id: this.id, model:model.id}); return this; } @@ -61,6 +66,8 @@ mesh.group = class MeshGroup extends mesh.object { model.group = undefined; this.models.remove(model); this.group.remove(model.mesh); + // create message for listeners + call.model_remove({model, group:this}); // trigger sync with worker worker.group_remove({id: this.id, model:model.id}); model.remove(true); diff --git a/src/mesh/work.js b/src/mesh/work.js index 59ef1477..4a8c97fd 100644 --- a/src/mesh/work.js +++ b/src/mesh/work.js @@ -18,7 +18,7 @@ gapp.finalize("mesh.work", [ ]); // compensation for space/world/platform rotation -let space_rotation = new THREE.Matrix4().makeRotationX(Math.PI / 2); +let core_matrix = new THREE.Matrix4().makeRotationX(Math.PI / 2); let { client, worker } = moto; let cache = {}; @@ -38,14 +38,14 @@ let model = { let geo = new THREE.BufferGeometry(); geo.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); geo.computeVertexNormals(); - cacheUpdate(id, { name, geo }); + cacheUpdate(id, { name, geo, matrix: core_matrix.clone() }); }, debug(data) { let { matrix, id } = data; let rec = cache[id]; let geo = rec.geo.clone(); - let m4 = space_rotation.clone().multiply( new THREE.Matrix4().fromArray(matrix) ); + let m4 = core_matrix.clone().multiply( new THREE.Matrix4().fromArray(matrix) ); geo.applyMatrix4(m4); // for debugging state / matrix ops return geo.attributes.position.array; @@ -59,11 +59,6 @@ let group = { remove(data) { let { id, model } = data; - }, - - create(data) { - let { id, type } = data; - cache[id] = { id, type }; } }; @@ -78,23 +73,23 @@ let object = { }, move(data) { - let { id, type, x, y, z } = data; + let { id, x, y, z } = data; }, position(data) { - let { id, type, x, y, z } = data; + let { id, x, y, z } = data; }, rotate(data) { - let { id, type, x, y, z } = data; + let { id, x, y, z } = data; }, rotation(data) { - let { id, type, x, y, z } = data; + let { id, x, y, z } = data; }, qrotation(data) { - let { id, type, w, x, y, z } = data; + let { id, w, x, y, z } = data; } }; diff --git a/src/moto/broker.js b/src/moto/broker.js index ff58eef3..8abd2c29 100644 --- a/src/moto/broker.js +++ b/src/moto/broker.js @@ -23,6 +23,20 @@ gapp.broker = new class Broker { return Object.keys(this.topics); } + // create subscriptions for all functions in an object + // a complement wrapObject on the subscription side + listeners(object, root) { + for (let [key, fn] of Object.entries(object)) { + if (typeof fn === 'function') { + key = root ? `${root}_${key}` : key; + this.subscribe(key, fn); + } + } + } + + // attach function to a topic + // creates topic if new topic + // creates a send function if new topic subscribe(topic, listener) { if (Array.isArray(topic)) { for (let t of topic) { diff --git a/src/moto/webui.js b/src/moto/webui.js index 4c9671f7..9f92ad5b 100644 --- a/src/moto/webui.js +++ b/src/moto/webui.js @@ -58,8 +58,9 @@ function build(data, context) { func[key] = val; elid = elid || `_${nextid++}`; } else if (tov === 'object') { - html.push(` ${key}="${val.join(' ')}"`); - } else { + if (val.length) + html.push(` ${key}="${val.filter(v => v !== undefined).join(' ')}"`); + } else if (tov !== 'undefined') { html.push(` ${key}="${val}"`); } } diff --git a/web/mesh/index.css b/web/mesh/index.css index 07b87bc6..02a2a349 100644 --- a/web/mesh/index.css +++ b/web/mesh/index.css @@ -140,3 +140,35 @@ body, div { margin-right: -14px !important; margin-bottom: -14px !important; } + +#grouplist { + position: absolute; + margin: 3px 3px 0 0; + right: 0; + top: 0; +} + +#grouplist, #grouplist div { + flex-direction: column; + background-color: transparent; +} + +#grouplist > div { + border: 1px solid gray; + border-radius: 3px; + padding: 3px; +} + +#grouplist .models button { + margin-top: 1px; +} + +#grouplist .selected { + background-color: rgba(0,255,0,0.75) +} + +#grouplist .vsep { + height: 1px; + margin: 2px 0 1px 0; + background-color: gray; +}