add vertext deletion. move mode menu to top
This commit is contained in:
parent
6ca4551fb5
commit
94311917e7
6 changed files with 133 additions and 27 deletions
|
|
@ -220,10 +220,17 @@ function space_init(data) {
|
||||||
break;
|
break;
|
||||||
case 'Backspace':
|
case 'Backspace':
|
||||||
case 'Delete':
|
case 'Delete':
|
||||||
for (let s of selection.list(true)) {
|
let mode = api.mode.get();
|
||||||
selection.remove(s);
|
if (mode !== api.modes.object) {
|
||||||
s.showBounds(false);
|
for (let m of selection.models()) {
|
||||||
s.remove();
|
m.deleteSelections(mode);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let s of selection.list(true)) {
|
||||||
|
selection.remove(s);
|
||||||
|
s.showBounds(false);
|
||||||
|
s.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
estop(evt);
|
estop(evt);
|
||||||
break;
|
break;
|
||||||
|
|
@ -275,7 +282,7 @@ function space_init(data) {
|
||||||
// rotate selected face towawrd z "floor"
|
// rotate selected face towawrd z "floor"
|
||||||
group.faceDown(int.face.normal);
|
group.faceDown(int.face.normal);
|
||||||
} else {
|
} else {
|
||||||
let modes = api.mode.modes;
|
let { modes } = api;
|
||||||
switch(api.mode.get()) {
|
switch(api.mode.get()) {
|
||||||
case modes.object:
|
case modes.object:
|
||||||
selection.toggle(shiftKey ? model : model.group);
|
selection.toggle(shiftKey ? model : model.group);
|
||||||
|
|
|
||||||
|
|
@ -408,8 +408,6 @@ let mode = {
|
||||||
vertex() {
|
vertex() {
|
||||||
mode.set(modes.vertex);
|
mode.set(modes.vertex);
|
||||||
},
|
},
|
||||||
|
|
||||||
modes
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// persisted preference map
|
// persisted preference map
|
||||||
|
|
@ -498,6 +496,8 @@ let api = mesh.api = {
|
||||||
|
|
||||||
mode,
|
mode,
|
||||||
|
|
||||||
|
modes,
|
||||||
|
|
||||||
selection,
|
selection,
|
||||||
|
|
||||||
group,
|
group,
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,7 @@ function ui_build() {
|
||||||
h.div({ id: 'spinner', class: 'spin' })
|
h.div({ id: 'spinner', class: 'spin' })
|
||||||
]),
|
]),
|
||||||
// display and action areas
|
// display and action areas
|
||||||
|
h.div({ id: 'modes' }),
|
||||||
h.div({ id: 'actions' }),
|
h.div({ id: 'actions' }),
|
||||||
h.div({ id: 'grouplist'}),
|
h.div({ id: 'grouplist'}),
|
||||||
h.div({ id: 'selectlist'}),
|
h.div({ id: 'selectlist'}),
|
||||||
|
|
@ -205,7 +206,7 @@ function ui_build() {
|
||||||
h.div({ id: 'download', class: "hide" })
|
h.div({ id: 'download', class: "hide" })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let { actions, grouplist, selectlist, logger } = bound;
|
let { modes, actions, grouplist, selectlist, logger } = bound;
|
||||||
|
|
||||||
// create slid in/out logging window
|
// create slid in/out logging window
|
||||||
h.bind(logger, [ h.div({ id: 'logtext' }) ]);
|
h.bind(logger, [ h.div({ id: 'logtext' }) ]);
|
||||||
|
|
@ -213,6 +214,14 @@ function ui_build() {
|
||||||
// a few shortcuts to api calls
|
// a few shortcuts to api calls
|
||||||
let { file, selection, mode, tool, prefs } = api;
|
let { file, selection, mode, tool, prefs } = api;
|
||||||
|
|
||||||
|
// top/center mode selector
|
||||||
|
h.bind(modes, [
|
||||||
|
h.button({ _: 'object', id: "mode-object", onclick() { mode.object() } }),
|
||||||
|
h.button({ _: 'face', id: "mode-face", onclick() { mode.face() } }),
|
||||||
|
h.button({ _: 'line', id: "mode-line", onclick() { mode.line() } }),
|
||||||
|
h.button({ _: 'vertex', id: "mode-vertex", onclick() { mode.vertex() } }),
|
||||||
|
]);
|
||||||
|
|
||||||
// create hotkey/action menu (top/left)
|
// create hotkey/action menu (top/left)
|
||||||
h.bind(actions, [
|
h.bind(actions, [
|
||||||
h.div([
|
h.div([
|
||||||
|
|
@ -236,11 +245,6 @@ function ui_build() {
|
||||||
h.button({ _: 'analyze', onclick: tool.analyze }),
|
h.button({ _: 'analyze', onclick: tool.analyze }),
|
||||||
h.button({ _: 'repair', onclick: tool.repair }),
|
h.button({ _: 'repair', onclick: tool.repair }),
|
||||||
h.button({ _: 'clean', onclick: tool.clean }),
|
h.button({ _: 'clean', onclick: tool.clean }),
|
||||||
h.div({ class: "vsep" }),
|
|
||||||
h.button({ _: 'object', id: "mode-object", onclick() { mode.object() } }),
|
|
||||||
h.button({ _: 'face', id: "mode-face", onclick() { mode.face() } }),
|
|
||||||
h.button({ _: 'line', id: "mode-line", onclick() { mode.line() } }),
|
|
||||||
h.button({ _: 'vertex', id: "mode-vertex", onclick() { mode.vertex() } }),
|
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,14 @@ mesh.model = class MeshModel extends mesh.object {
|
||||||
face: materials.face.clone()
|
face: materials.face.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// information about selected faces, lines, and vertices
|
||||||
|
// vertex compound keys are sorted least index to greatest
|
||||||
|
this.sel = {
|
||||||
|
faces: [], // first index into vertices
|
||||||
|
lines: {}, // key = vertex-vertex, val = [ faces ]
|
||||||
|
verts: {}, // key = x-y-z, val = { faces:[], sphere }
|
||||||
|
};
|
||||||
|
|
||||||
this.file = file || 'unnamed';
|
this.file = file || 'unnamed';
|
||||||
this.load(mesh || vertices, indices, normals);
|
this.load(mesh || vertices, indices, normals);
|
||||||
// persist in db so it can be restored on page load
|
// persist in db so it can be restored on page load
|
||||||
|
|
@ -289,8 +297,52 @@ mesh.model = class MeshModel extends mesh.object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectedFaces() {
|
clearSelections() {
|
||||||
let faces = this._faces = this._faces || [];
|
console.log('todo: clearSelections');
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSelections(mode) {
|
||||||
|
let { geometry } = this.mesh;
|
||||||
|
let { groups } = geometry;
|
||||||
|
let { array } = geometry.attributes.position;
|
||||||
|
let newtot = 0;
|
||||||
|
// filter to unselected groups
|
||||||
|
groups = groups.filter(g => g.materialIndex === 0);
|
||||||
|
for (let group of groups) {
|
||||||
|
let start = group.start * 3;
|
||||||
|
let count = group.count * 3;
|
||||||
|
newtot += group.count < Infinity ? count : array.length - start;
|
||||||
|
}
|
||||||
|
// nothing to do if new length is the same
|
||||||
|
if (newtot === array.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let newverts = new Float32Array(newtot);
|
||||||
|
let pos = 0;
|
||||||
|
// copy back unselected faces
|
||||||
|
for (let group of groups) {
|
||||||
|
let start = group.start * 3;
|
||||||
|
let count = group.count * 3;
|
||||||
|
if (count === Infinity) count = array.length - start;
|
||||||
|
let slice = array.slice(start, start + count);
|
||||||
|
newverts.set(slice, pos);
|
||||||
|
pos += count;
|
||||||
|
}
|
||||||
|
this.reload(newverts);
|
||||||
|
// clear face selections (since they've been deleted);
|
||||||
|
this.sel.faces = [];
|
||||||
|
this.updateSelections();
|
||||||
|
|
||||||
|
// let { modes } = mesh.api;
|
||||||
|
// switch (mode) {
|
||||||
|
// case modes.face:
|
||||||
|
// console.log('delete selected faces');
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelections() {
|
||||||
|
let faces = this.sel.faces;
|
||||||
let groups = [];
|
let groups = [];
|
||||||
if (faces && faces.length) {
|
if (faces && faces.length) {
|
||||||
faces = faces.sort((a,b) => a - b).slice();
|
faces = faces.sort((a,b) => a - b).slice();
|
||||||
|
|
@ -320,27 +372,51 @@ mesh.model = class MeshModel extends mesh.object {
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSelectedFaces(toggle = []) {
|
toggleSelectedFaces(toggle = []) {
|
||||||
let faces = this._faces = this._faces || [];
|
let faces = this.sel.faces;
|
||||||
for (let t of toggle) {
|
for (let t of toggle) {
|
||||||
faces.remove(t) || faces.addOnce(t);
|
faces.remove(t) || faces.addOnce(t);
|
||||||
}
|
}
|
||||||
this.updateSelectedFaces();
|
}
|
||||||
|
|
||||||
|
toggleSelectedVertices(vert) {
|
||||||
|
if (Array.isArray(vert)) {
|
||||||
|
for (let e of vert) {
|
||||||
|
this.toggleSelectedVertices(e);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let sel = this.sel;
|
||||||
|
let key = [x,y,z].map(v => v.round(5)).join('-');
|
||||||
|
let rec = sel.verts[key];
|
||||||
|
if (rec) {
|
||||||
|
delete sel.verts[key];
|
||||||
|
space.scene.remove(rec.sphere);
|
||||||
|
} else {
|
||||||
|
let geometry = new THREE.SphereGeometry( 0.5, 16, 16 );
|
||||||
|
let material = new MeshPhongMaterial( { color: 0x777777, transparent: true, opacity: 0.25 } );
|
||||||
|
let sphere = new Mesh( geometry, material );
|
||||||
|
sphere.position.set(x, y, z);
|
||||||
|
space.scene.add(sphere);
|
||||||
|
sel.verts[key] = {
|
||||||
|
sphere,
|
||||||
|
faces,
|
||||||
|
verts
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// find adjacent faces to clicked point/line on a face
|
// find adjacent faces to clicked point/line on a face
|
||||||
find(point, face) {
|
find(point, face) {
|
||||||
// let geometry = new THREE.SphereGeometry( 0.5, 16, 16 );
|
|
||||||
// let material = new MeshPhongMaterial( { color: 0x777777, transparent: true, opacity: 0.25 } );
|
|
||||||
// let sphere = new Mesh( geometry, material );
|
|
||||||
let { x, y, z } = point;
|
let { x, y, z } = point;
|
||||||
let { a, b, c } = face;
|
let { a, b, c } = face;
|
||||||
// sphere.position.set(x,y,z);
|
|
||||||
// space.scene.add(sphere);
|
|
||||||
worker.model_select({
|
worker.model_select({
|
||||||
id: this.id, x, y:-z, z:y, a, b, c, matrix: this.matrix
|
id: this.id, x, y:-z, z:y, a, b, c, matrix: this.matrix
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
let { faces, edges, verts } = data;
|
let { faces, edges, verts, point } = data;
|
||||||
|
console.log({data});
|
||||||
|
// this.toggleSelectedVertices(verts);
|
||||||
this.toggleSelectedFaces(faces);
|
this.toggleSelectedFaces(faces);
|
||||||
|
this.updateSelections();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ let model = {
|
||||||
let faces = [];
|
let faces = [];
|
||||||
let verts = [];
|
let verts = [];
|
||||||
let edges = [];
|
let edges = [];
|
||||||
|
let point;
|
||||||
for (let i=0, l=arr.length; i<l; ) {
|
for (let i=0, l=arr.length; i<l; ) {
|
||||||
// matches here are within radius of a vertex
|
// matches here are within radius of a vertex
|
||||||
// select all faces that share a matched vertex
|
// select all faces that share a matched vertex
|
||||||
|
|
@ -141,7 +142,7 @@ let model = {
|
||||||
if (faces.length === 0) {
|
if (faces.length === 0) {
|
||||||
faces.push(Math.min(a,b,c) / 3);
|
faces.push(Math.min(a,b,c) / 3);
|
||||||
}
|
}
|
||||||
return { faces, edges, verts };
|
return { faces, edges, verts, point };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -280,16 +280,34 @@ button:hover {
|
||||||
border-bottom: 1px dashed gray;
|
border-bottom: 1px dashed gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** actions menu top/left **/
|
/** modes menu top/center **/
|
||||||
|
|
||||||
#actions button.selected {
|
#modes {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid rgba(0,0,0,0.5);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 3px;
|
||||||
|
padding: 3px;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modes button.selected {
|
||||||
background-color: rgba(0,255,0,0.5);
|
background-color: rgba(0,255,0,0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions button.selected:hover {
|
#modes button.selected:hover {
|
||||||
background-color: rgba(0,255,0,1);
|
background-color: rgba(0,255,0,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mode-line, #mode-vertex {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/** slide in/out logging window **/
|
/** slide in/out logging window **/
|
||||||
|
|
||||||
#logger {
|
#logger {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue