diff --git a/src/mesh/api.js b/src/mesh/api.js index 8a6bed41..ff73dd5d 100644 --- a/src/mesh/api.js +++ b/src/mesh/api.js @@ -13,6 +13,7 @@ gapp.register("mesh.api", [ "moto.space", // dep: moto.space "data.index", // dep: data.index "mesh.tool", // dep: mesh.tool + "mesh.util", // dep: mesh.util "add.array", // dep: add.array ]); @@ -21,6 +22,8 @@ if (mesh.api) return; let space = moto.Space; let worker = moto.client.fn; +let util = mesh.util; + let groups = []; let selected = []; @@ -427,142 +430,6 @@ let api = mesh.api = { } }; -let deferFn = []; -let boundsCache = {}; - -// util functions augmented in build (download) -let util = mesh.util = { - uuid(segs = 1) { - let uid = []; - while (segs-- > 0) { - uid.push(Math.round(Math.random() * 0xffffffff).toString(36)); - } - return uid.join('-'); - }, - - // merge repeated function calls like updates - // that importantly take no arguments - defer(fn, time = 50) { - for (let rec of deferFn) { - if (rec.fn === fn) { - clearTimeout(rec.timer); - deferFn.remove(rec); - break; - } - } - let rec = { fn }; - rec.timer = setTimeout(() => { - deferFn.remove(rec); - fn(); - }); - deferFn.push(rec); - }, - - // return a function wrapper which can be re-used - deferWrap(fn, time) { - return function() { - util.defer(fn, time); - } - }, - - // @param object {THREE.Object3D | THREE.Object3D[] | MeshObject | MeshObject[]} - // @returns bounds modified for moto.Space - bounds(object) { - let box = new THREE.Box3(); - if (Array.isArray(object)) { - for (let o of object) { - util.box3expand(box, o instanceof mesh.object ? o.object : o); - } - } else if (object) { - util.box3expand(box, object instanceof mesh.object ? object.object : object); - } else { - return box; - } - let bnd = { - min: { - x: box.min.x, - y: box.min.z, - z: box.min.y - }, - max: { - x: box.max.x, - y: box.max.z, - z: box.max.y - } - }; - bnd.size = bnd.dim = { - x: bnd.max.x - bnd.min.x, - y: bnd.max.y - bnd.min.y, - z: bnd.max.z - bnd.min.z - }; - bnd.center = bnd.mid = { - x: (bnd.max.x + bnd.min.x) / 2, - y: -(bnd.max.y + bnd.min.y) / 2, - z: (bnd.max.z + bnd.min.z) / 2 - }; - return bnd; - }, - - // bounding box workaround adapted from: - // https://discourse.threejs.org/t/bounding-box-bigger-than-concave-object-extrudegeometry/26073/2 - // https://discourse.threejs.org/t/invalid-bounds-generated-for-some-orientations/33205 - box3expand(box3, object) { - let geometry = object.geometry; - object.updateWorldMatrix(geometry ? true : false, false); - - if (geometry) { - let matrix = object.matrixWorld; - let bkey = [matrix.elements.map(v => v.round(5))].join(',') - let cached = boundsCache[object.id]; - if (!cached || cached.bkey !== bkey) { - let position = geometry.attributes.position.clone(); - position.applyMatrix4(new THREE.Matrix4().extractRotation(matrix)); - let bounds = new THREE.Box3().setFromBufferAttribute(position); - // let scale = new THREE.Vector3().setFromMatrixScale(matrix); - // bounds.min.multiply(scale); - // bounds.max.multiply(scale); - cached = boundsCache[object.id] = { bkey, bounds }; - } - let bt = cached.bounds.clone(); - let m4 = new THREE.Matrix4(); - m4.setPosition(new THREE.Vector3().setFromMatrixPosition(object.matrixWorld)); - bt.applyMatrix4(m4); - box3.union(bt); - } - - let children = object.children; - for (let i = 0, l = children.length; i < l; i++) { - util.box3expand(box3, children[i]); - } - }, - - extract(object, opt = {}) { - let field = opt.fields || ['x', 'y', 'z']; - let array = []; - for (let k of field) { - let v = object[k] || 0; - if (opt.round !== undefined) v = v.round(opt.round); - if (opt.fixed !== undefined) v = v.toFixed(opt.fixed); - array.push(v); - } - return array; - }, - - average(array, opt = {}) { - let fields = opt.fields || ['x', 'y', 'z']; - let avg = {}; - for (let e of array) { - for (let f of fields) { - avg[f] = (avg[f] || 0) + e[f]; - } - } - for (let f of fields) { - avg[f] = (avg[f] || 0) / array.length; - } - return avg; - } -}; - let broker = gapp.broker; // publish messages when api functions are called broker.wrapObject(selection, 'selection'); diff --git a/src/mesh/build.js b/src/mesh/build.js index 3dd83c16..f7e1877c 100644 --- a/src/mesh/build.js +++ b/src/mesh/build.js @@ -6,6 +6,7 @@ gapp.register("mesh.build",[ "moto.broker", // dep: moto.broker + "mesh.util", // dep: mesh.util "mesh.api", // dep: mesh.api ]); diff --git a/src/mesh/model.js b/src/mesh/model.js index d8e36fc2..a6e6d82f 100644 --- a/src/mesh/model.js +++ b/src/mesh/model.js @@ -10,6 +10,7 @@ gapp.register("mesh.model", [ "moto.license", // dep: moto.license "mesh.object", // dep: mesh.object "mesh.group", // dep: mesh.group + "mesh.util", // dep: mesh.util "mesh.api", // dep: mesh.api ]); diff --git a/src/mesh/object.js b/src/mesh/object.js index da53de0c..3972a8b0 100644 --- a/src/mesh/object.js +++ b/src/mesh/object.js @@ -8,6 +8,7 @@ gapp.register("mesh.object", [ "add.three", // dep: add.three "moto.license", // dep: moto.license "moto.broker", // dep: moto.broker + "mesh.util", // dep: mesh.util "mesh.api", // dep: mesh.api ]); diff --git a/src/mesh/util.js b/src/mesh/util.js new file mode 100644 index 00000000..2148c991 --- /dev/null +++ b/src/mesh/util.js @@ -0,0 +1,153 @@ +/** Copyright Stewart Allen -- All Rights Reserved */ + +"use strict"; + +(function() { + +// dep: ext.three +// dep: ext.three-bgu +gapp.register("mesh.util", [ + "moto.license", // dep: moto.license + "add.array", // dep: add.array +]); + +let mesh = self.mesh = self.mesh || {}; +if (mesh.util) return; + +let deferFn = []; +let boundsCache = {}; + +// util functions augmented in build (download) +let util = mesh.util = { + uuid(segs = 1) { + let uid = []; + while (segs-- > 0) { + uid.push(Math.round(Math.random() * 0xffffffff).toString(36)); + } + return uid.join('-'); + }, + + // merge repeated function calls like updates + // that importantly take no arguments + defer(fn, time = 50) { + for (let rec of deferFn) { + if (rec.fn === fn) { + clearTimeout(rec.timer); + deferFn.remove(rec); + break; + } + } + let rec = { fn }; + rec.timer = setTimeout(() => { + deferFn.remove(rec); + fn(); + }); + deferFn.push(rec); + }, + + // return a function wrapper which can be re-used + deferWrap(fn, time) { + return function() { + util.defer(fn, time); + } + }, + + // @param object {THREE.Object3D | THREE.Object3D[] | MeshObject | MeshObject[]} + // @returns bounds modified for moto.Space + bounds(object) { + let box = new THREE.Box3(); + if (Array.isArray(object)) { + for (let o of object) { + util.box3expand(box, o instanceof mesh.object ? o.object : o); + } + } else if (object) { + util.box3expand(box, object instanceof mesh.object ? object.object : object); + } else { + return box; + } + let bnd = { + min: { + x: box.min.x, + y: box.min.z, + z: box.min.y + }, + max: { + x: box.max.x, + y: box.max.z, + z: box.max.y + } + }; + bnd.size = bnd.dim = { + x: bnd.max.x - bnd.min.x, + y: bnd.max.y - bnd.min.y, + z: bnd.max.z - bnd.min.z + }; + bnd.center = bnd.mid = { + x: (bnd.max.x + bnd.min.x) / 2, + y: -(bnd.max.y + bnd.min.y) / 2, + z: (bnd.max.z + bnd.min.z) / 2 + }; + return bnd; + }, + + // bounding box workaround adapted from: + // https://discourse.threejs.org/t/bounding-box-bigger-than-concave-object-extrudegeometry/26073/2 + // https://discourse.threejs.org/t/invalid-bounds-generated-for-some-orientations/33205 + box3expand(box3, object) { + let geometry = object.geometry; + object.updateWorldMatrix(geometry ? true : false, false); + + if (geometry) { + let matrix = object.matrixWorld; + let bkey = [matrix.elements.map(v => v.round(5))].join(',') + let cached = boundsCache[object.id]; + if (!cached || cached.bkey !== bkey) { + let position = geometry.attributes.position.clone(); + position.applyMatrix4(new THREE.Matrix4().extractRotation(matrix)); + let bounds = new THREE.Box3().setFromBufferAttribute(position); + // let scale = new THREE.Vector3().setFromMatrixScale(matrix); + // bounds.min.multiply(scale); + // bounds.max.multiply(scale); + cached = boundsCache[object.id] = { bkey, bounds }; + } + let bt = cached.bounds.clone(); + let m4 = new THREE.Matrix4(); + m4.setPosition(new THREE.Vector3().setFromMatrixPosition(object.matrixWorld)); + bt.applyMatrix4(m4); + box3.union(bt); + } + + let children = object.children; + for (let i = 0, l = children.length; i < l; i++) { + util.box3expand(box3, children[i]); + } + }, + + extract(object, opt = {}) { + let field = opt.fields || ['x', 'y', 'z']; + let array = []; + for (let k of field) { + let v = object[k] || 0; + if (opt.round !== undefined) v = v.round(opt.round); + if (opt.fixed !== undefined) v = v.toFixed(opt.fixed); + array.push(v); + } + return array; + }, + + average(array, opt = {}) { + let fields = opt.fields || ['x', 'y', 'z']; + let avg = {}; + for (let e of array) { + for (let f of fields) { + avg[f] = (avg[f] || 0) + e[f]; + } + } + for (let f of fields) { + avg[f] = (avg[f] || 0) / array.length; + } + return avg; + } +}; + +})(); \ No newline at end of file