/** Copyright Stewart Allen -- All Rights Reserved */ import { THREE } from '../ext/three.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 { api as meshApi } from './api.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() { // meshApi.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 meshApi.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() { meshApi.focus(this.object); 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() { let b = this.bounds; return this.move(0, 0, -b.min.z); } center() { throw "center() requires implementation"; let b = this.bounds; return this.move(-b.mid.x, -b.mid.y, -b.mid.z); } centerXY() { throw "centerXY() requires implementation"; let b = this.bounds; return this.move(-b.mid.x, -b.mid.y, 0); } 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"; } showBounds(bool) { let was = this._showBounds; if (bool && bool.toggle) { bool = !this._showBounds; } if (was === bool) { return; } this._showBounds = bool; this.updateBoundsBox(); } updateBoundsBox() { this.log('update-bounds-box', this._showBounds); let helper = this._boundsBox; let world = motoSpace.world; if (helper) { world.remove(helper); } if (this._showBounds) { let { mid, dim } = this.bounds; let b3 = new Box3().setFromCenterAndSize( new Vector3(mid.x, mid.y, mid.z), new Vector3(dim.x, dim.y, dim.z) ); let helper = this._boundsBox = new Box3Helper(b3, 0x555555); world.add(helper); return true; } } metaChanged(values = {}) { this.object.updateMatrix(); this.updateBoundsBox(); 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 };