move mesh.split into module. start edge mode
This commit is contained in:
parent
5fac736ea9
commit
3541c76ccb
4 changed files with 147 additions and 109 deletions
113
src/main/mesh.js
113
src/main/mesh.js
|
|
@ -9,13 +9,14 @@
|
||||||
// dep: moto.space
|
// dep: moto.space
|
||||||
// dep: data.index
|
// dep: data.index
|
||||||
// dep: mesh.api
|
// dep: mesh.api
|
||||||
|
// dep: mesh.split
|
||||||
// dep: mesh.model
|
// dep: mesh.model
|
||||||
// dep: mesh.build
|
// dep: mesh.build
|
||||||
// dep: load.file
|
// dep: load.file
|
||||||
// use: geo.polygons
|
// use: geo.polygons
|
||||||
gapp.main("main.mesh", [], (root) => {
|
gapp.main("main.mesh", [], (root) => {
|
||||||
|
|
||||||
const { Quaternion, Mesh, MeshPhongMaterial, PlaneGeometry, DoubleSide, Vector3 } = THREE;
|
const { Quaternion } = THREE;
|
||||||
const { broker } = gapp;
|
const { broker } = gapp;
|
||||||
const { moto } = root;
|
const { moto } = root;
|
||||||
const { space } = moto;
|
const { space } = moto;
|
||||||
|
|
@ -170,108 +171,11 @@ async function restore_space() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// toggle edit/split temporary mode (present plane on hover)
|
|
||||||
let temp_mode;
|
|
||||||
|
|
||||||
// split functions
|
|
||||||
let split = {
|
|
||||||
start() {
|
|
||||||
let space = moto.space;
|
|
||||||
let { api, util } = mesh;
|
|
||||||
// highlight button
|
|
||||||
let button = event.target;
|
|
||||||
button.classList.add('selected');
|
|
||||||
// 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 = { button, 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;
|
|
||||||
|
|
||||||
mat.color.set(0x5555aa);
|
|
||||||
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 };
|
|
||||||
obj.scale.set(dim.x + 2, dim.y + 2, 1);
|
|
||||||
obj.position.set(mid.x, y, -mid.y);
|
|
||||||
});
|
|
||||||
temp_mode = split;
|
|
||||||
},
|
|
||||||
|
|
||||||
select() {
|
|
||||||
let { log } = mesh.api;
|
|
||||||
let { models, plane } = split.state;
|
|
||||||
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);
|
|
||||||
log.emit('split complete').unpin();
|
|
||||||
split.end();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
end() {
|
|
||||||
let space = moto.space;
|
|
||||||
let { button, obj } = split.state;
|
|
||||||
button.classList.remove('selected');
|
|
||||||
space.scene.remove(obj);
|
|
||||||
space.mouse.onHover(undefined);
|
|
||||||
temp_mode = 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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function edit_split(event) {
|
|
||||||
if (temp_mode) {
|
|
||||||
temp_mode.end();
|
|
||||||
} else {
|
|
||||||
split.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add space event bindings
|
// add space event bindings
|
||||||
function space_init(data) {
|
function space_init(data) {
|
||||||
let { space, platform } = data;
|
|
||||||
let platcolor = 0x00ff00;
|
let platcolor = 0x00ff00;
|
||||||
let api = mesh.api;
|
let { space, platform } = data;
|
||||||
|
let { api } = mesh;
|
||||||
let { selection } = api;
|
let { selection } = api;
|
||||||
|
|
||||||
// add file drop handler
|
// add file drop handler
|
||||||
|
|
@ -337,7 +241,7 @@ function space_init(data) {
|
||||||
case 'KeyB':
|
case 'KeyB':
|
||||||
return selection.boundsBox({toggle:true});
|
return selection.boundsBox({toggle:true});
|
||||||
case 'KeyH':
|
case 'KeyH':
|
||||||
return space.view.home();
|
return shiftKey ? selection.hide() : space.view.home();
|
||||||
case 'KeyT':
|
case 'KeyT':
|
||||||
return space.view.top();
|
return space.view.top();
|
||||||
case 'KeyZ':
|
case 'KeyZ':
|
||||||
|
|
@ -363,7 +267,7 @@ function space_init(data) {
|
||||||
break;
|
break;
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
selection.clear();
|
selection.clear();
|
||||||
temp_mode && temp_mode.end();
|
mesh.split.active() && mesh.split.end();
|
||||||
estop(evt);
|
estop(evt);
|
||||||
break;
|
break;
|
||||||
case 'Backspace':
|
case 'Backspace':
|
||||||
|
|
@ -417,8 +321,8 @@ function space_init(data) {
|
||||||
space.mouse.upSelect((int, event) => {
|
space.mouse.upSelect((int, event) => {
|
||||||
if (event && event.target.nodeName === "CANVAS") {
|
if (event && event.target.nodeName === "CANVAS") {
|
||||||
const model = int && int.object.model ? int.object.model : undefined;
|
const model = int && int.object.model ? int.object.model : undefined;
|
||||||
if (temp_mode) {
|
if (mesh.split.active()) {
|
||||||
return temp_mode.select(model);
|
return mesh.split.select(model);
|
||||||
}
|
}
|
||||||
if (model) {
|
if (model) {
|
||||||
const group = model.group;
|
const group = model.group;
|
||||||
|
|
@ -666,7 +570,6 @@ function set_wireframe_fog(fogx) {
|
||||||
|
|
||||||
// bind functions to topics
|
// bind functions to topics
|
||||||
broker.listeners({
|
broker.listeners({
|
||||||
edit_split,
|
|
||||||
load_files,
|
load_files,
|
||||||
object_matrix,
|
object_matrix,
|
||||||
object_destroy,
|
object_destroy,
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,12 @@ const selection = {
|
||||||
for (let m of selection.models()) {
|
for (let m of selection.models()) {
|
||||||
m.visible(true);
|
m.visible(true);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
for (let m of selection.models()) {
|
||||||
|
m.visible(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -835,10 +841,11 @@ const tool = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const modes = {
|
const modes = {
|
||||||
object: "object",
|
object: "object", // mesh or group
|
||||||
tool: "tool",
|
tool: "tool", // mesh or group for subtraction
|
||||||
face: "face",
|
face: "face", // facet / triangle
|
||||||
surface: "surface"
|
surface: "surface", // normal aligned & adjacent faces
|
||||||
|
edge: "edge", // edge of a face
|
||||||
};
|
};
|
||||||
|
|
||||||
// edit mode
|
// edit mode
|
||||||
|
|
@ -895,6 +902,13 @@ const mode = {
|
||||||
selection.clear();
|
selection.clear();
|
||||||
}
|
}
|
||||||
mode.set(modes.surface);
|
mode.set(modes.surface);
|
||||||
|
},
|
||||||
|
|
||||||
|
edge() {
|
||||||
|
if (!mode.is([ modes.edge ])) {
|
||||||
|
selection.clear();
|
||||||
|
}
|
||||||
|
mode.set(modes.edge);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -351,6 +351,7 @@ function ui_build() {
|
||||||
]), h.div([
|
]), h.div([
|
||||||
h.button({ _: 'surface', id: "mode-surface", onclick() { mode.surface() } }),
|
h.button({ _: 'surface', id: "mode-surface", onclick() { mode.surface() } }),
|
||||||
h.button({ _: 'face', id: "mode-face", onclick() { mode.face() } }),
|
h.button({ _: 'face', id: "mode-face", onclick() { mode.face() } }),
|
||||||
|
h.button({ _: 'edge', id: "mode-edge", onclick() { mode.edge() } }),
|
||||||
]) ]);
|
]) ]);
|
||||||
|
|
||||||
// create hotkey/action menu (top/left)
|
// create hotkey/action menu (top/left)
|
||||||
|
|
|
||||||
120
src/mesh/split.js
Normal file
120
src/mesh/split.js
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// dep: moto.space
|
||||||
|
gapp.register("mesh.split", [], (root, exports) => {
|
||||||
|
|
||||||
|
const { Mesh, MeshPhongMaterial, PlaneGeometry, DoubleSide, Vector3 } = THREE;
|
||||||
|
const { broker } = gapp;
|
||||||
|
const { moto } = root;
|
||||||
|
const { space } = moto;
|
||||||
|
|
||||||
|
// toggle edit/split temporary mode (present plane on hover)
|
||||||
|
let temp_mode;
|
||||||
|
|
||||||
|
// split functions
|
||||||
|
let split = {
|
||||||
|
active() {
|
||||||
|
return temp_mode ? true : false;
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
let { api, util } = mesh;
|
||||||
|
// highlight button
|
||||||
|
let button = event.target;
|
||||||
|
button.classList.add('selected');
|
||||||
|
// 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 = { button, 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;
|
||||||
|
|
||||||
|
mat.color.set(0x5555aa);
|
||||||
|
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 };
|
||||||
|
obj.scale.set(dim.x + 2, dim.y + 2, 1);
|
||||||
|
obj.position.set(mid.x, y, -mid.y);
|
||||||
|
});
|
||||||
|
temp_mode = split;
|
||||||
|
},
|
||||||
|
|
||||||
|
select() {
|
||||||
|
let { log } = mesh.api;
|
||||||
|
let { models, plane } = split.state;
|
||||||
|
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);
|
||||||
|
log.emit('split complete').unpin();
|
||||||
|
split.end();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
end() {
|
||||||
|
let space = moto.space;
|
||||||
|
let { button, obj } = split.state;
|
||||||
|
button.classList.remove('selected');
|
||||||
|
space.scene.remove(obj);
|
||||||
|
space.mouse.onHover(undefined);
|
||||||
|
temp_mode = 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function edit_split(event) {
|
||||||
|
if (temp_mode) {
|
||||||
|
temp_mode.end();
|
||||||
|
} else {
|
||||||
|
split.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports(split);
|
||||||
|
|
||||||
|
// bind functions to topics
|
||||||
|
broker.listeners({
|
||||||
|
edit_split,
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue