grid-apps-cmms/src.old/mesh/split.js

154 lines
4.8 KiB
JavaScript

/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
/**
* toggle edit/split on Z tool
* presents a Z plane on hover
* click to split the selected mesh(es)
*/
"use strict";
// dep: moto.space
// dep: moto.broker
gapp.register("mesh.split", [], (root, exports) => {
const { broker } = gapp;
const { Mesh, MeshPhongMaterial, PlaneGeometry, DoubleSide, Vector3 } = THREE;
const { mesh, moto } = root;
const { space } = moto;
let isActive;
const key_once = broker.bind('key_once');
const key_cancel = broker.bind('key_once_cancel');
// split functions
let split = {
active() {
return isActive ? true : false;
},
start() {
if (isActive) {
return;
}
let { api, util } = mesh;
let { log } = api;
if (api.selection.models().length === 0) {
log.emit('no models selected for splitting');
return;
}
// create split plane visual
let geo, mat, obj = new Mesh(
geo = new PlaneGeometry(1,1),
mat = new MeshPhongMaterial({
side: DoubleSide,
color: 0x5555aa,
transparent: false,
opacity: 0.5
})
);
space.scene.add(obj);
// hide until first hover
obj.visible = false;
// enable temp mode
let state = split.state = { obj };
let models = state.models = api.selection.models();
let meshes = models.map(m => m.mesh);
// for split and lay flat modes
space.mouse.onHover((int, event, ints) => {
if (!event) {
return meshes;
}
obj.visible = false;
let { button, buttons } = event;
if (buttons) {
return;
}
let { dim, mid } = util.bounds(meshes);
let { point, face, object } = int;
let { x, y, z } = point;
// set appearance of split plane
mat.color.set(api.prefs.map.space.dark ? 0x0059bb : 0x0079ff);
obj.visible = true;
if (event.shiftKey) {
y = split.closestZ(y, object, face).y;
}
// y is z in model space for the purposes of a split
state.plane = { z: y };
// ensure split plane exceeds bounds of split target
obj.scale.set(dim.x + 2, dim.y + 2, 1);
obj.position.set(mid.x, y, -mid.y);
});
isActive = true;
key_once({ code: 'KeyS', fn(evt) {
split.select();
}});
key_once({ code: 'KeyV', fn(evt) {
evt.preventDefault();
let state = split.state;
split.end();
function doit(z) {
state.plane = { z };
split.select(state);
}
api.modal.dialog({
title: "split object Z",
body: [ h.div({ class: "additem" }, [
h.label('Z value'),
h.input({ value: 0, size: 5, id: "_value" }),
h.button({ _: "split", onclick() {
const { _value } = api.modal.bound;
doit(parseFloat(_value.value));
api.modal.hide();
} })
]) ]
});
api.modal.bound._value.focus();
} });
},
select(state) {
let { log } = mesh.api;
let { models, plane } = split.state || state;
if (!(models && plane)) {
return split.end();
}
log.emit(`splitting ${models.length} model(s) at ${plane.z.round(3)}`).pin();
Promise.all(models.map(m => m.split(plane))).then(models => {
mesh.api.selection.set(models.filter(m => m));
log.emit('split complete').unpin();
split.end();
});
},
end() {
if (!isActive) {
return;
}
key_cancel('KeyS');
key_cancel('KeyV');
let space = moto.space;
let { obj } = split.state;
space.scene.remove(obj);
space.mouse.onHover(undefined);
isActive = split.state = undefined;
mesh.api.selection.update();
},
closestZ(z, object, face) {
let { position } = object.geometry.attributes;
let matrix = object.matrixWorld;
let v0 = new Vector3(position.getX(face.a), position.getY(face.a), position.getZ(face.a)).applyMatrix4(matrix);
let v1 = new Vector3(position.getX(face.b), position.getY(face.b), position.getZ(face.b)).applyMatrix4(matrix);
let v2 = new Vector3(position.getX(face.c), position.getY(face.c), position.getZ(face.c)).applyMatrix4(matrix);
v0._d = Math.abs(v0.y - z);
v1._d = Math.abs(v1.y - z);
v2._d = Math.abs(v2.y - z);
return [ v0, v1, v2 ].sort((a,b) => a._d - b._d)[0];
}
}
exports(split);
});