add vertext deletion. move mode menu to top

This commit is contained in:
Stewart Allen 2022-01-13 20:23:48 -05:00
commit 94311917e7
6 changed files with 133 additions and 27 deletions

View file

@ -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);

View file

@ -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,

View file

@ -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() } }),
]), ]),
]); ]);

View file

@ -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();
}); });
} }

View file

@ -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 };
} }
}; };

View file

@ -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 {