/** Copyright Stewart Allen -- All Rights Reserved */ import { THREE } from '../ext/three.js'; import { api } from './api.js'; import { broker as motoBroker } from '../moto/broker.js'; import { client as motoClient } from '../moto/client.js'; import { space as motoSpace } from '../moto/space.js'; import { util as meshUtil } from './util.js'; const { Matrix4, Vector3, Box3, Box3Helper, Quaternion } = THREE; const worker = motoClient.fn; const lookUp = new Vector3(0,0,-1); // broker updates generated by objects const publish = { meta: motoBroker.bind("object_meta"), destroy: motoBroker.bind("object_destroy"), visible: motoBroker.bind("object_visible"), }; class MeshObject { constructor(id) { this.id = id || meshUtil.uuid(); this.meta = { pos: [0,0,0] }; worker.object_create({ id: this.id, type: this.type }); worker.object_meta({ id: this.id, meta: this.meta }); this.log('NEW'); } log() { api.log.emit(this.id, this.type, ...arguments); } get type() { throw "type() requires implementation"; } // @returns {THREE.Object3D} get object() { throw "object() requires implementation"; } get bounds() { throw "bounds() requires implementation"; } // manage lifecycle with worker, mesh app caches, etc destroy() { // update worker state worker.object_destroy({id: this.id}); // update object store api.db.space.remove(this.id); // main app cache workspace updates publish.destroy(this.id); } // used during state restoration applyMeta(meta = {}) { this.log('apply-meta', meta); if (meta.pos) this.position(...meta.pos); this.visible(meta.visible ?? true); this.metaChanged(meta); return this; } // used during state restoration applyMatrix(elements) { if (elements) { this.log('apply-matrix'); this.object.applyMatrix4(new Matrix4().fromArray(elements)); } return this; } // used during model splitting applyMatrix4(matrix) { return this.applyMatrix(matrix.elements); } select() { throw "select() requires implementation"; } remove() { throw "remove() requires implementation"; } focus() { api.focus(this); return this; } visible(opt) { if (opt === undefined) { return this.object.visible; } if (opt.toggle) { this.visible(!this.visible()); } else { this.object.visible = opt; } publish.visible({ id: this.id, visible: this.object.visible }); return this; } floor() { return this.move(0, 0, -this.bounds.min.z); } center() { throw "center() requires implementation"; } centerXY() { throw "centerXY() requires implementation"; } move() { throw "move() requires implementation"; } scale() { throw "scale() requires implementation"; } qrotate(quaternion) { throw "qrotate() requires implementation"; } rotate(x = 0, y = 0, z = 0) { this.log('object-rotate', ...arguments); let m = new Matrix4(); if (x) m.multiply(new Matrix4().makeRotationX(x)); if (y) m.multiply(new Matrix4().makeRotationY(y)); if (z) m.multiply(new Matrix4().makeRotationZ(z)); this.qrotate(new Quaternion().setFromRotationMatrix(m)); return this; } rotation() { throw "rotation() requires implementation"; } // rotate object in the directon of normal (place face on Z plane) rotateTowardZ(normal) { let q = new Quaternion().setFromUnitVectors(normal, lookUp); this.qrotate(q); this.floor(); } position() { throw "position() requires implementation"; } metaChanged(values = {}) { this.object.updateMatrix(); motoSpace.update(); Object.assign(this.meta, values, { // matrix: this.object.matrix.elements }); worker.object_meta({ id: this.id, meta: this.meta }); publish.meta({ id: this.id, meta: this.meta }); return this; } } export { MeshObject as object };