add mode key sohrtcuts, face selection re-triangulation

This commit is contained in:
Stewart Allen 2024-08-12 15:54:49 -04:00
commit c960501a87
8 changed files with 95 additions and 22 deletions

View file

@ -436,6 +436,7 @@ function orderClosest(array, fn, from) {
// wrapper for earcut that handles higher order dimensions and finds the
// two with the greatest delta to pass to the earcut algorith, then returns
// an unwrapped array in the original dimensions
// at present, only used by load.obj.parse()
function triangulate(array, holes, dims, pos) {
let narray;
let info;

View file

@ -37,6 +37,14 @@ class Point {
};
}
toArray() {
return [ this.x, this.y, this.z ];
}
toVector3() {
return new THREE.Vector3(this.x, this.y, this.z);
}
set(x, y, z) {
this.x = x;
this.y = y;

View file

@ -110,6 +110,10 @@ class Polygon {
return this;
}
fromVectors(array) {
return this.addVerts(array.map(v => [ ...v ]).flat());
}
toObject() {
return {
points: this.toArray(),

View file

@ -216,6 +216,18 @@ function space_init(data) {
}
let { shiftKey, metaKey, ctrlKey, code } = evt;
switch (code) {
case 'Digit1':
return api.mode.sketch();
case 'Digit2':
return api.mode.object();
case 'Digit3':
return api.mode.tool();
case 'Digit4':
return api.mode.surface();
case 'Digit5':
return api.mode.face();
case 'Digit6':
return api.mode.edge();
case 'KeyQ':
return api.settings();
case 'KeyI':
@ -261,7 +273,7 @@ function space_init(data) {
case 'KeyH':
return shiftKey ? selection.hide() : space.view.home();
case 'KeyT':
return space.view.top();
return shiftKey ? api.tool.triangulate() : space.view.top();
case 'KeyZ':
return space.view.reset();
}
@ -300,7 +312,7 @@ function space_init(data) {
selection.delete();
} else {
for (let m of selection.models()) {
m.deleteSelections(mode);
m.deleteSelections();
space.refresh()
}
}

View file

@ -1065,6 +1065,12 @@ const tool = {
}
},
triangulate() {
for (let m of selection.models()) {
m.triangulateSelections();
}
},
rename(models, type = 'model') {
models = fallback(models, true);
let model = models[0];

View file

@ -353,14 +353,14 @@ function facon(name, sub) {
return h.i({ class: [ sub || "fa", name ] });
}
function menu_item(text, fn, short) {
function menu_item(text, fn, short, id) {
if (short) {
short = Array.isArray(short) ? short : [ short ];
short = div({ class: "short" }, short.map(s => s.startsWith('bi-') ?
bicon(s) : div(s)
));
}
return div({ onclick: fn }, [
return div({ id, onclick: fn }, [
div({ _: text, class: "grow" }),
short
].filter(o => o));
@ -432,14 +432,14 @@ function ui_build() {
div({ class: "menu" }, [
div('Mode'),
div({ class: "menu-items" }, [
div({ _: 'Sketch', id: "mode-sketch", onclick: mode.sketch }),
menu_item('Sketch', mode.sketch, '1', 'mode-sketch'),
hr(),
div({ _: 'Object', id: "mode-object", onclick: mode.object }),
div({ _: 'Tool', id: "mode-tool", onclick: mode.tool }),
menu_item('Object', mode.object, '2', 'mode-object'),
menu_item('Tool', mode.tool, '3', 'mode-tool'),
hr(),
div({ _: 'Surface', id: "mode-surface", onclick: mode.surface }),
div({ _: 'Face', id: "mode-face", onclick: mode.face }),
div({ _: 'Edge', id: "mode-edge", onclick: mode.edge }),
menu_item('Surface', mode.surface, '4', 'mode-surface'),
menu_item('Face', mode.face, '5', 'mode-face'),
menu_item('Edge', mode.edge, '6', 'mode-edge'),
])
]),
div({ class: "menu sketch-on" }, [
@ -484,6 +484,7 @@ function ui_build() {
div('Faces'),
div({ class: "menu-items" }, [
menu_item('Flip Normals', tool.invert),
menu_item('Triangulate', tool.triangulate),
menu_item('Analyze', tool.analyze),
menu_item('Clean', tool.clean),
])

View file

@ -4,6 +4,8 @@
// dep: add.array
// dep: add.three
// dep: geo.polygon
// dep: geo.polygons
// dep: moto.license
// dep: moto.client
// dep: mesh.object
@ -13,10 +15,11 @@
gapp.register("mesh.model", [], (root, exports) => {
const { MeshPhongMaterial, MeshBasicMaterial, LineBasicMaterial } = THREE;
const { BufferGeometry, BufferAttribute, DoubleSide, Mesh, Vector3 } = THREE;
const { mesh, moto } = root;
const { BufferGeometry, BufferAttribute, DoubleSide, Mesh, Vector3, Triangle } = THREE;
const { base, mesh, moto } = root;
const { space } = moto;
const { api } = mesh;
const { newPolygon, polygons } = base;
const mapp = mesh;
const worker = moto.client.fn;
@ -598,19 +601,13 @@ mesh.model = class MeshModel extends mesh.object {
}
}
clearSelections() {
// clear face selections (since they've been deleted);
this.sel.faces = [];
this.updateSelections();
}
deleteSelections(mode) {
collectFacesByMaterialIndex(index) {
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);
groups = groups.filter(g => g.materialIndex === index);
for (let group of groups) {
let start = group.start * 3;
let count = group.count * 3;
@ -631,12 +628,56 @@ mesh.model = class MeshModel extends mesh.object {
newverts.set(slice, pos);
pos += count;
}
this.reload(newverts);
return newverts;
}
clearSelections() {
// clear face selections (since they've been deleted);
this.sel.faces = [];
this.updateSelections();
}
triangulateSelections() {
let selverts = this.collectFacesByMaterialIndex(1);
if (selverts?.length) {
let points = [...selverts].group(3).map(a => new Vector3().fromArray(a));
let tris = points.group(3).map(a => new Triangle(...a));
let norms = tris.map(t => t.getNormal(new Vector3()));
let norm = norms.reduce((a,b) => a.add(b)).normalize();
// compute quaternion and rotate triangles to face Z up
const targetNorm = new THREE.Vector3(0, 0, 1);
const rotato = new THREE.Quaternion().setFromUnitVectors(norm, targetNorm);
points.forEach(p => p.applyQuaternion(rotato));
// union and earcut the result
let polys = tris.map(t => newPolygon().fromVectors([ t.a, t.b, t.c ]));
let union = polygons.union(polys,0,true);
let ears = union.map(p => p.earcut()).flat();
// unrotate using the same quaternion
rotato.invert();
let nupoints = ears.map(p => p.points).flat().map(p => p.toVector3());
nupoints.forEach(p => p.applyQuaternion(rotato));
let nuverts = nupoints.map(p => p.toArray()).flat().toFloat32();
// remove selection and append nuverts
this.deleteSelections(nuverts);
}
}
deleteSelections(append) {
let newverts = this.collectFacesByMaterialIndex(0);
if (newverts) {
if (append) {
let all = new Float32Array(newverts.length + append.length);
all.set(newverts);
all.set(append, newverts.length);
newverts = all;
}
this.reload(newverts);
// clear face selections (since they've been deleted);
this.sel.faces = [];
this.updateSelections();
}
}
updateSelections() {
let groups = mesh.util.facesToGroups(this.sel.faces);
let geo = this.mesh.geometry;

View file

@ -164,7 +164,7 @@ let model = {
let { vertices, name, id } = data;
let geo = new BufferGeometry();
geo.setAttribute('position', new BufferAttribute(vertices, 3));
cacheUpdate(id, { name, geo, mkey: '' });
cacheUpdate(id, { name, geo, mkey: undefined, tool: undefined });
},
// merge several model vertices into a single array