2021-12-29 21:52:24 -05:00
|
|
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
|
|
|
|
|
// dep: ext.three
|
|
|
|
|
// dep: ext.three-bgu
|
2022-01-01 22:39:49 -05:00
|
|
|
gapp.register("mesh.api", [
|
2021-12-29 21:52:24 -05:00
|
|
|
"moto.license", // dep: moto.license
|
|
|
|
|
"moto.client", // dep: moto.client
|
2022-01-01 12:12:57 -05:00
|
|
|
"moto.broker", // dep: moto.broker
|
2021-12-29 21:52:24 -05:00
|
|
|
"moto.space", // dep: moto.space
|
|
|
|
|
"mesh.tool", // dep: mesh.tool
|
|
|
|
|
"add.array", // dep: add.array
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
let mesh = self.mesh = self.mesh || {};
|
|
|
|
|
if (mesh.api) return;
|
|
|
|
|
|
|
|
|
|
let space = moto.Space;
|
2021-12-30 22:59:46 -05:00
|
|
|
let groups = [];
|
2021-12-31 14:12:42 -05:00
|
|
|
let selected = [];
|
2021-12-29 21:52:24 -05:00
|
|
|
|
2021-12-31 14:12:42 -05:00
|
|
|
let selection = {
|
2022-01-05 17:58:37 -05:00
|
|
|
// @returns {MeshObject[]} or all groups if not strict and no selection
|
|
|
|
|
list(strict = false) {
|
|
|
|
|
return selected.length || strict ? selected.slice() : groups.slice();
|
2021-12-31 14:12:42 -05:00
|
|
|
},
|
2021-12-29 21:52:24 -05:00
|
|
|
|
2022-01-05 17:58:37 -05:00
|
|
|
groups(strict) {
|
|
|
|
|
let all = selection.list(strict);
|
2022-01-01 13:43:35 -05:00
|
|
|
let grp = all.filter(s => s instanceof mesh.group);
|
|
|
|
|
let mdl = all.filter(s => s instanceof mesh.model);
|
|
|
|
|
for (let m of mdl) {
|
|
|
|
|
grp.addOnce(m.group);
|
|
|
|
|
}
|
|
|
|
|
return grp;
|
|
|
|
|
},
|
|
|
|
|
|
2022-01-05 17:58:37 -05:00
|
|
|
models(strict) {
|
|
|
|
|
let all = selection.list(strict);
|
2022-01-01 13:43:35 -05:00
|
|
|
let grp = all.filter(s => s instanceof mesh.group);
|
|
|
|
|
let mdl = all.filter(s => s instanceof mesh.model);
|
|
|
|
|
for (let g of grp) {
|
|
|
|
|
for (let m of g.models) {
|
|
|
|
|
mdl.addOnce(m);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return mdl;
|
|
|
|
|
},
|
|
|
|
|
|
2022-01-02 16:30:05 -05:00
|
|
|
contains(object) {
|
|
|
|
|
return selected.contains(object);
|
|
|
|
|
},
|
|
|
|
|
|
2021-12-31 17:14:55 -05:00
|
|
|
// @param group {MeshObject[]}
|
2021-12-31 14:12:42 -05:00
|
|
|
set(objects) {
|
|
|
|
|
selected = objects;
|
2022-01-02 23:47:28 -05:00
|
|
|
util.defer(selection.update);
|
2021-12-31 14:12:42 -05:00
|
|
|
},
|
|
|
|
|
|
2021-12-31 17:14:55 -05:00
|
|
|
// @param group {MeshObject}
|
2021-12-31 14:12:42 -05:00
|
|
|
add(object) {
|
2022-01-02 19:52:56 -05:00
|
|
|
// pendantic code necessary to minimize re-entrant api calls
|
|
|
|
|
if (object.models) {
|
|
|
|
|
// if group, remove discrete selected members
|
|
|
|
|
for (let m of object.models) {
|
|
|
|
|
if (selected.contains(m)) {
|
|
|
|
|
selection.remove(m);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// if model, remove selcted group
|
|
|
|
|
if (selected.contains(object.group)) {
|
|
|
|
|
selection.remove(object.group);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-31 14:12:42 -05:00
|
|
|
selected.addOnce(object);
|
2022-01-02 23:47:28 -05:00
|
|
|
util.defer(selection.update);
|
2021-12-31 14:12:42 -05:00
|
|
|
},
|
|
|
|
|
|
2021-12-31 17:14:55 -05:00
|
|
|
// @param group {MeshObject}
|
2021-12-31 14:12:42 -05:00
|
|
|
remove(object) {
|
|
|
|
|
selected.remove(object);
|
2022-01-02 23:47:28 -05:00
|
|
|
util.defer(selection.update);
|
2021-12-31 14:12:42 -05:00
|
|
|
},
|
|
|
|
|
|
2022-01-02 19:52:56 -05:00
|
|
|
// @param group {MeshObject}
|
|
|
|
|
toggle(object) {
|
|
|
|
|
if (selected.contains(object)) {
|
|
|
|
|
selection.remove(object);
|
|
|
|
|
} else {
|
|
|
|
|
selection.add(object);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2021-12-31 14:12:42 -05:00
|
|
|
clear() {
|
2022-01-05 21:16:18 -05:00
|
|
|
for (let s of selection.list()) {
|
2022-01-02 23:47:28 -05:00
|
|
|
selection.remove(s);
|
|
|
|
|
}
|
2021-12-31 17:14:55 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
update() {
|
|
|
|
|
for (let group of groups) {
|
|
|
|
|
group.material(mesh.material.unselected);
|
|
|
|
|
}
|
2022-01-02 16:30:05 -05:00
|
|
|
// 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));
|
2021-12-31 17:14:55 -05:00
|
|
|
for (let object of selected) {
|
|
|
|
|
object.material(mesh.material.selected);
|
|
|
|
|
}
|
2022-01-01 12:12:57 -05:00
|
|
|
return selection;
|
2021-12-31 14:12:42 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
move(dx = 0, dy = 0, dz = 0) {
|
|
|
|
|
for (let s of selected) {
|
|
|
|
|
s.move(dx, dy, dz);
|
2021-12-30 22:59:46 -05:00
|
|
|
}
|
2021-12-31 14:12:42 -05:00
|
|
|
return selection;
|
2021-12-30 22:59:46 -05:00
|
|
|
},
|
|
|
|
|
|
2021-12-31 14:12:42 -05:00
|
|
|
rotate(dx = 0, dy = 0, dz = 0) {
|
|
|
|
|
for (let s of selected) {
|
2021-12-31 16:12:37 -05:00
|
|
|
s.rotate(dx, dy, dz);
|
2021-12-31 14:12:42 -05:00
|
|
|
}
|
|
|
|
|
return selection;
|
|
|
|
|
},
|
|
|
|
|
|
2022-01-01 10:44:06 -05:00
|
|
|
qrotate(q) {
|
|
|
|
|
for (let s of selected) {
|
|
|
|
|
s.qrotate(q);
|
|
|
|
|
}
|
|
|
|
|
return selection;
|
|
|
|
|
},
|
|
|
|
|
|
2021-12-31 14:12:42 -05:00
|
|
|
scale(dx = 0, dy = 0, dz = 0) {
|
|
|
|
|
for (let s of selected) {
|
|
|
|
|
let { x, y, z } = s.scale();
|
|
|
|
|
s.scale(x + dx, y + dy, z + dz);
|
|
|
|
|
}
|
|
|
|
|
return selection;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
floor() {
|
|
|
|
|
for (let s of selected) {
|
|
|
|
|
s.floor(...arguments);
|
|
|
|
|
}
|
|
|
|
|
return selection;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
centerXY() {
|
|
|
|
|
for (let s of selected) {
|
2021-12-31 16:59:38 -05:00
|
|
|
s.centerXY(...arguments);
|
2021-12-31 14:12:42 -05:00
|
|
|
}
|
|
|
|
|
return selection;
|
|
|
|
|
},
|
|
|
|
|
|
2022-01-01 13:43:35 -05:00
|
|
|
boundsBox() {
|
|
|
|
|
for (let m of selection.groups()) {
|
|
|
|
|
m.showBounds(...arguments);
|
|
|
|
|
}
|
|
|
|
|
return selection;
|
|
|
|
|
},
|
|
|
|
|
|
2021-12-31 14:12:42 -05:00
|
|
|
home() {
|
|
|
|
|
return selection.centerXY().floor();
|
2021-12-31 16:59:38 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
focus() {
|
|
|
|
|
api.focus(selected);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
bounds() {
|
2022-01-05 17:58:37 -05:00
|
|
|
return util.bounds(selected.map(s => s.object));
|
2021-12-31 14:12:42 -05:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let group = {
|
|
|
|
|
// @returns {MeshGroup[]}
|
|
|
|
|
list() {
|
|
|
|
|
return groups.slice();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// @param group {MeshModel[]}
|
2022-01-03 17:13:50 -05:00
|
|
|
new(models, id) {
|
|
|
|
|
return group.add(new mesh.group(models, id));
|
2021-12-31 14:12:42 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// @param group {MeshGroup}
|
2022-01-01 23:48:36 -05:00
|
|
|
add(group) {
|
2021-12-31 14:12:42 -05:00
|
|
|
groups.addOnce(group);
|
|
|
|
|
space.world.add(group.group);
|
|
|
|
|
space.update();
|
|
|
|
|
return group;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// @param group {MeshGroup}
|
2022-01-01 23:48:36 -05:00
|
|
|
remove(group) {
|
2021-12-31 14:12:42 -05:00
|
|
|
groups.remove(group);
|
|
|
|
|
space.world.remove(group.group);
|
|
|
|
|
space.update();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-01 22:39:49 -05:00
|
|
|
let model = {
|
|
|
|
|
// @returns {MeshModel[]}
|
|
|
|
|
list() {
|
|
|
|
|
return groups.map(g => g.models).flat();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-12-31 14:12:42 -05:00
|
|
|
let api = mesh.api = {
|
2022-01-01 23:48:36 -05:00
|
|
|
clear() {
|
2021-12-31 14:12:42 -05:00
|
|
|
for (let group of group.list()) {
|
|
|
|
|
group.remove(group);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2022-01-03 00:43:35 -05:00
|
|
|
// @param object {THREE.Object3D | THREE.Object3D[] | Point}
|
2022-01-01 23:48:36 -05:00
|
|
|
focus(object) {
|
2022-01-03 00:43:35 -05:00
|
|
|
let { center } = object.center ? object : util.bounds(object);
|
2021-12-31 17:20:41 -05:00
|
|
|
// when no valid objects supplied, set origin
|
2021-12-31 16:59:38 -05:00
|
|
|
if (isNaN(center.x * center.y * center.z)) {
|
2021-12-31 17:20:41 -05:00
|
|
|
center = { x: 0, y: 0, z: 0 };
|
2021-12-31 16:59:38 -05:00
|
|
|
}
|
2021-12-31 17:20:41 -05:00
|
|
|
// sets "home" views (front, back, home, reset)
|
2022-01-01 13:43:35 -05:00
|
|
|
space.platform.setCenter(center.x, -center.y, center.z);
|
2021-12-31 17:20:41 -05:00
|
|
|
// sets camera focus
|
2021-12-31 16:59:38 -05:00
|
|
|
space.view.setFocus(new THREE.Vector3(
|
2022-01-01 13:43:35 -05:00
|
|
|
center.x, center.z, -center.y
|
2021-12-31 16:59:38 -05:00
|
|
|
));
|
|
|
|
|
},
|
|
|
|
|
|
2022-01-06 09:38:04 -05:00
|
|
|
wireframe() {
|
|
|
|
|
for (let m of api.model.list()) {
|
|
|
|
|
m.wireframe(...arguments);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2021-12-31 14:12:42 -05:00
|
|
|
selection,
|
|
|
|
|
|
|
|
|
|
group,
|
|
|
|
|
|
2022-01-01 22:39:49 -05:00
|
|
|
model,
|
2022-01-01 00:04:35 -05:00
|
|
|
|
2022-01-01 23:48:36 -05:00
|
|
|
objects() {
|
2022-01-02 14:25:28 -05:00
|
|
|
// return model objects suitable for finding ray intersections
|
2022-01-05 17:58:37 -05:00
|
|
|
return group.list().map(o => o.models).flat().map(o => o.object);
|
2021-12-29 21:52:24 -05:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-02 23:47:28 -05:00
|
|
|
let deferFn = [];
|
|
|
|
|
|
2021-12-31 00:35:45 -05:00
|
|
|
let util = mesh.util = {
|
|
|
|
|
|
2022-01-01 23:48:36 -05:00
|
|
|
uuid(segs = 1) {
|
2022-01-01 22:39:49 -05:00
|
|
|
let uid = [];
|
|
|
|
|
while (segs-- > 0) {
|
|
|
|
|
uid.push(Math.round(Math.random() * 0xffffffff).toString(36));
|
|
|
|
|
}
|
|
|
|
|
return uid.join('-');
|
|
|
|
|
},
|
|
|
|
|
|
2022-01-02 23:47:28 -05:00
|
|
|
// 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);
|
|
|
|
|
},
|
|
|
|
|
|
2022-01-03 10:50:59 -05:00
|
|
|
// return a function wrapper which can be re-used
|
|
|
|
|
deferWrap(fn, time) {
|
|
|
|
|
return function() {
|
|
|
|
|
util.defer(fn, time);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2021-12-31 17:14:55 -05:00
|
|
|
// @param object {THREE.Object3D | THREE.Object3D[] | MeshObject | MeshObject[]}
|
2021-12-31 00:35:45 -05:00
|
|
|
// @returns bounds modified for moto.Space
|
2022-01-01 23:48:36 -05:00
|
|
|
bounds(object) {
|
2021-12-31 16:59:38 -05:00
|
|
|
let box = new THREE.Box3();
|
|
|
|
|
if (Array.isArray(object)) {
|
|
|
|
|
for (let o of object) {
|
2022-01-05 17:58:37 -05:00
|
|
|
util.box3expand(box, o instanceof mesh.object ? o.object : o);
|
2021-12-31 16:59:38 -05:00
|
|
|
}
|
|
|
|
|
} else if (object) {
|
2022-01-05 17:58:37 -05:00
|
|
|
util.box3expand(box, object instanceof mesh.object ? object.object : object);
|
2021-12-31 16:59:38 -05:00
|
|
|
} else {
|
|
|
|
|
return box;
|
|
|
|
|
}
|
2021-12-31 00:35:45 -05:00
|
|
|
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 = {
|
|
|
|
|
x: bnd.max.x - bnd.min.x,
|
|
|
|
|
y: bnd.max.y - bnd.min.y,
|
|
|
|
|
z: bnd.max.z - bnd.min.z
|
|
|
|
|
};
|
|
|
|
|
bnd.center = {
|
|
|
|
|
x: (bnd.max.x + bnd.min.x) / 2,
|
2022-01-01 12:12:57 -05:00
|
|
|
y: -(bnd.max.y + bnd.min.y) / 2,
|
2021-12-31 00:35:45 -05:00
|
|
|
z: (bnd.max.z + bnd.min.z) / 2
|
|
|
|
|
};
|
|
|
|
|
return bnd;
|
2022-01-01 20:07:05 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// bounding box workaround adapted from:
|
|
|
|
|
// https://discourse.threejs.org/t/bounding-box-bigger-than-concave-object-extrudegeometry/26073/2
|
2022-01-05 09:29:27 -05:00
|
|
|
// https://discourse.threejs.org/t/invalid-bounds-generated-for-some-orientations/33205
|
2022-01-01 23:48:36 -05:00
|
|
|
box3expand(box3, object) {
|
2022-01-05 00:15:44 -05:00
|
|
|
let geometry = object.geometry;
|
2022-01-01 20:07:05 -05:00
|
|
|
object.updateWorldMatrix(geometry ? true : false, false);
|
|
|
|
|
|
|
|
|
|
if (geometry) {
|
2022-01-05 00:15:44 -05:00
|
|
|
let matrix = object.matrixWorld;
|
|
|
|
|
let position = geometry.attributes.position.clone();
|
|
|
|
|
position.applyMatrix4(new THREE.Matrix4().extractRotation(matrix));
|
|
|
|
|
let bounds = new THREE.Box3().setFromBufferAttribute(position);
|
2022-01-01 20:19:45 -05:00
|
|
|
let bt = new THREE.Box3().copy(bounds);
|
|
|
|
|
let m4 = new THREE.Matrix4();
|
|
|
|
|
m4.setPosition(new THREE.Vector3().setFromMatrixPosition(object.matrixWorld));
|
|
|
|
|
bt.applyMatrix4(m4);
|
|
|
|
|
box3.union(bt);
|
2022-01-01 20:07:05 -05:00
|
|
|
}
|
|
|
|
|
|
2022-01-05 00:15:44 -05:00
|
|
|
let children = object.children;
|
2022-01-01 20:07:05 -05:00
|
|
|
for (let i = 0, l = children.length; i < l; i++) {
|
|
|
|
|
util.box3expand(box3, children[i]);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2022-01-05 20:32:41 -05:00
|
|
|
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;
|
2022-01-05 21:16:18 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
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;
|
2022-01-05 20:32:41 -05:00
|
|
|
}
|
2021-12-31 00:35:45 -05:00
|
|
|
};
|
|
|
|
|
|
2022-01-01 22:39:49 -05:00
|
|
|
let broker = gapp.broker;
|
2022-01-01 23:48:36 -05:00
|
|
|
// publish messages when api functions are called
|
|
|
|
|
broker.wrapObject(selection, 'selection');
|
|
|
|
|
broker.wrapObject(model, 'model');
|
|
|
|
|
broker.wrapObject(group, 'group');
|
2022-01-01 22:39:49 -05:00
|
|
|
|
2021-12-29 21:52:24 -05:00
|
|
|
})();
|