diff --git a/src/main/mesh.js b/src/main/mesh.js index 0df9e5ae..d97f8d06 100644 --- a/src/main/mesh.js +++ b/src/main/mesh.js @@ -83,7 +83,8 @@ broker.subscribe('space_init', data => { platform.setColor(platcolor); }, 'keypress', evt => { - switch (evt.code) { + let { shiftKey, metaKey, ctrlKey, code } = evt; + switch (code) { case 'KeyC': selection.centerXY().focus(); break; @@ -96,11 +97,6 @@ broker.subscribe('space_init', data => { case 'KeyB': selection.boundsBox({toggle:true}); break; - case 'KeyS': - for (let m of selection.models()) { - m.sync(); - } - break; case 'KeyH': space.view.home(); break; @@ -126,7 +122,13 @@ broker.subscribe('space_init', data => { } break; case 'KeyD': - broker.send.space_debug(); + if (shiftKey) { + for (let m of selection.models()) { + m.debug(); + } + } else { + broker.send.space_debug(); + } break; case 'Escape': selection.clear(); @@ -213,19 +215,21 @@ broker.subscribe('space_load', data => { broker.subscribe('space_debug', () => { for (let g of mesh.api.group.list()) { let { center, size } = g.bounds(); - console.group('group', { + console.group(g.id); + console.log({ center, size, pos: g.group.position }); for (let m of g.models) { - console.log('model', { + console.log(m.id, { box: m.mesh.getBoundingBox(), pos: m.mesh.position }); } console.groupEnd(); } + moto.client.fn.debug(); }); // remove version cache bust from url diff --git a/src/mesh/api.js b/src/mesh/api.js index 5fc895ba..2a0db761 100644 --- a/src/mesh/api.js +++ b/src/mesh/api.js @@ -221,7 +221,7 @@ let api = mesh.api = { model, objects() { - // return models, not groups + // return model objects suitable for finding ray intersections return group.list().map(o => o.models).flat().map(o => o.object()); } }; diff --git a/src/mesh/group.js b/src/mesh/group.js index a09d67b5..30fa25e6 100644 --- a/src/mesh/group.js +++ b/src/mesh/group.js @@ -16,6 +16,9 @@ gapp.register("mesh.group", [ let mesh = self.mesh = self.mesh || {}; if (mesh.group) return; +let space = moto.Space; +let worker = moto.client.fn; + mesh.group = class MeshGroup extends mesh.object { // @param group {mesh.model[]} @@ -28,6 +31,10 @@ mesh.group = class MeshGroup extends mesh.object { } } + type() { + return "group"; + } + object() { return this.group; } @@ -37,7 +44,8 @@ mesh.group = class MeshGroup extends mesh.object { model.group = this; this.models.addOnce(model); this.group.add(model.mesh); - moto.Space.update(); + space.update(); + worker.group_add({id: this.id, model:model.id}); return this; } @@ -54,12 +62,14 @@ mesh.group = class MeshGroup extends mesh.object { this.models.remove(model); this.group.remove(model.mesh); // trigger sync with worker + worker.group_remove({id: this.id, model:model.id}); model.remove(true); // auto-remove group when empty if (this.group.children.length === 0) { mesh.api.group.remove(this); + worker.object_destroy({id: this.id}); } - moto.Space.update(); + space.update(); return this; } diff --git a/src/mesh/model.js b/src/mesh/model.js index 6cf96b34..ba1ebf6d 100644 --- a/src/mesh/model.js +++ b/src/mesh/model.js @@ -16,6 +16,9 @@ gapp.register("mesh.model", [ let mesh = self.mesh = self.mesh || {}; if (mesh.model) return; +let space = moto.Space; +let worker = moto.client.fn; + /** default materials **/ let materials = mesh.material = { unselected: new THREE.MeshPhongMaterial({ @@ -49,16 +52,19 @@ mesh.model = class MeshModel extends mesh.object { this.mesh = this.load(mesh); } + type() { + return "model"; + } + object() { return this.mesh; } - sync() { - moto.client.fn.model_sync({ + debug() { + worker.model_debug({ matrix: this.mesh.matrixWorld.elements, id: this.id }).then(data => { - if (data && data.length) // for debugging matrix ops return mesh.api.group.new([new mesh.model({ file: `synth-${this.name}`, @@ -80,7 +86,7 @@ mesh.model = class MeshModel extends mesh.object { // this ref allows clicks to be traced to models and groups meh.model = this; // sync data to worker - moto.client.fn.model_load({id: this.id, name: this.name, vertices, indices}); + worker.model_load({id: this.id, name: this.file, vertices, indices}); return meh; } @@ -93,8 +99,8 @@ mesh.model = class MeshModel extends mesh.object { geo.attributes.index.needsUpdate = true; } geo.attributes.position.needsUpdate = true; - geo.computeFaceNormals(); geo.computeVertexNormals(); + worker.model_load({id: this.id, name: this.name, vertices, indices}); } get group() { @@ -122,7 +128,7 @@ mesh.model = class MeshModel extends mesh.object { mat.opacity = ov; mat.visible = true; } - moto.Space.update(); + space.update(); } wireframe(bool, opt = {}) { @@ -151,7 +157,7 @@ mesh.model = class MeshModel extends mesh.object { this.mesh.add(this._wire); this.opacity(opt.opacity || 0); } - moto.Space.update(); + space.update(); } material(mat) { @@ -163,7 +169,7 @@ mesh.model = class MeshModel extends mesh.object { // direct call requires pass through group } else { // called from group - moto.client.fn.model_remove({id: this.id}); + worker.object_destroy({id: this.id}); } } }; diff --git a/src/mesh/object.js b/src/mesh/object.js index 51811a13..5aab2055 100644 --- a/src/mesh/object.js +++ b/src/mesh/object.js @@ -13,10 +13,18 @@ gapp.register("mesh.object", [ let mesh = self.mesh = self.mesh || {}; if (mesh.object) return; +let space = moto.Space; +let worker = moto.client.fn; + mesh.object = class MeshObject { constructor() { this.id = mesh.util.uuid(); + worker.object_create({id: this.id, type: this.type()}); + } + + type() { + throw "type() requires implementation"; } // @returns {THREE.Object3D} @@ -54,7 +62,8 @@ mesh.object = class MeshObject { let pos = this.position(); pos.set(pos.x + x, pos.y + y, pos.z + z); this.updateBoundsBox(); - moto.Space.update(); + space.update(); + moto.client.fn.object_move({id: this.id, x, y, z}); return this; } @@ -65,7 +74,9 @@ mesh.object = class MeshObject { } scale.set(...arguments); this.updateBoundsBox(); - moto.Space.update(); + let [ x, y, z ] = arguments; + moto.client.fn.object_scale({id: this.id, x, y, z}); + space.update(); } rotate(x = 0, y = 0, z = 0) { @@ -73,25 +84,30 @@ mesh.object = class MeshObject { if (y) this.object().rotateOnWorldAxis(new THREE.Vector3(0,1,0), y); if (z) this.object().rotateOnWorldAxis(new THREE.Vector3(0,0,1), z); this.updateBoundsBox(); - moto.Space.update(); + worker.object_rotate({id: this.id, x, y, z}); + space.update(); return this; } rotation() { - let rot = this.object().rotation; + let rotation = this.object().rotation; if (arguments.length === 0) { - return rot; + return rotation; } - rot.set(...arguments); + rotation.set(...arguments); this.updateBoundsBox(); - moto.Space.update(); + let { x, y, z } = rotation; + worker.object_rotation({id: this.id, x, y, z}); + space.update(); return this; } qrotation(quaternion) { this.object().setRotationFromQuaternion(quaternion); this.updateBoundsBox(); - moto.Space.update(); + let { w, x, y, z } = quaternion; + worker.object_qrotation({id: this.id, w, x, y, z}); + space.update(); return this; } @@ -101,7 +117,9 @@ mesh.object = class MeshObject { return pos; } pos.set(...arguments); - moto.Space.update(); + let [ x, y, z ] = arguments; + worker.object_position({id: this.id, x, y, z}); + space.update(); } showBounds(bool) { @@ -114,7 +132,7 @@ mesh.object = class MeshObject { updateBoundsBox() { let helper = this._boundsBox; - let world = moto.Space.world; + let world = space.world; if (helper) { world.remove(helper); } diff --git a/src/mesh/work.js b/src/mesh/work.js index 26bf4db8..59ef1477 100644 --- a/src/mesh/work.js +++ b/src/mesh/work.js @@ -23,33 +23,85 @@ let space_rotation = new THREE.Matrix4().makeRotationX(Math.PI / 2); let { client, worker } = moto; let cache = {}; +function cacheUpdate(id, data) { + Object.assign(cache[id], data); +} + worker.bind("debug", (data, send) => { - console.log({cache}); + console.log({work_cache: cache}); send.done(); }); -worker.bind("model_load", (data, send) => { - let { vertices, name, id } = data; - let geo = new THREE.BufferGeometry(); - geo.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); - geo.computeVertexNormals(); - cache[id] = { id, name, geo }; - send.done(); -}); +let model = { + load(data) { + let { vertices, name, id } = data; + let geo = new THREE.BufferGeometry(); + geo.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); + geo.computeVertexNormals(); + cacheUpdate(id, { name, geo }); + }, -worker.bind("model_remove", (data, send) => { - delete cache[data.id]; - send.done(); -}); + 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) ); + geo.applyMatrix4(m4); + // for debugging state / matrix ops + return geo.attributes.position.array; + } +}; -worker.bind("model_sync", (data, send) => { - let { matrix, id } = data; - let rec = cache[id]; - let geo = rec.geo.clone(); - let m4 = space_rotation.clone().multiply( new THREE.Matrix4().fromArray(matrix) ); - geo.applyMatrix4(m4); - // for debugging matrix ops - send.done(geo.attributes.position.array); +let group = { + add(data) { + let { id, model } = data; + }, + + remove(data) { + let { id, model } = data; + }, + + create(data) { + let { id, type } = data; + cache[id] = { id, type }; + } +}; + +let object = { + create(data) { + let { id, type } = data; + cache[id] = { id, type }; + }, + + destroy(data) { + delete cache[data.id]; + }, + + move(data) { + let { id, type, x, y, z } = data; + }, + + position(data) { + let { id, type, x, y, z } = data; + }, + + rotate(data) { + let { id, type, x, y, z } = data; + }, + + rotation(data) { + let { id, type, x, y, z } = data; + }, + + qrotation(data) { + let { id, type, w, x, y, z } = data; + } +}; + +worker.bindObject({ + model, + group, + object }); })(); diff --git a/src/moto/client.js b/src/moto/client.js index 28d721ef..39de9520 100644 --- a/src/moto/client.js +++ b/src/moto/client.js @@ -120,7 +120,6 @@ let client = moto.client = { data, stream: ondata ? true : false }; - try { worker.postMessage(msg, direct); } catch (error) { diff --git a/src/moto/worker.js b/src/moto/worker.js index fbfb4fa1..48bd3593 100644 --- a/src/moto/worker.js +++ b/src/moto/worker.js @@ -15,32 +15,52 @@ let endpoints = {}; // code is running in the worker / server context const dispatch = moto.worker = { - bind: (name, fn) => { + // bind an endpoint name to a function + bind(name, fn) { endpoints[name] = fn; dispatch.send({bind: name}); }, - send: (msg) => { + // bind all functions in an object + bindObject(object, root, recurse) { + recurse = recurse || root === undefined; + for (let [key, fn] of Object.entries(object)) { + key = root ? `${root}_${key}` : key; + if (typeof fn === 'function') { + dispatch.bind(key, fn); + } else if (recurse) { + dispatch.bindObject(fn, key, recurse); + } + } + }, + + send(msg) { self.postMessage(msg); }, - onmessage: (e) => { + onmessage(e) { let msg = e.data, run = endpoints[msg.task], + async = false, done = false, send = { - data: (data, direct) => { + // puts function in async mode + // prevents auto send.done() on function return + async() { + async = true; + }, + data(data, direct) { if (canSend(data)) { dispatch.send({ done: false, data: data }, direct); } }, - done: (data, direct) => { + done(data, direct) { if (canSend(data)) { dispatch.send({ done: true, data: data }, direct); done = true; } }, - error: (data) => { + error(data) { if (canSend(data)) { done = true; dispatch.send({ error: data }); @@ -57,7 +77,7 @@ const dispatch = moto.worker = { if (run) { try { let data = run(msg.data, send); - if (data) { + if (data || async === false) { send.done(data); } } catch (error) {