add mode key sohrtcuts, face selection re-triangulation
This commit is contained in:
parent
f221c44be6
commit
c960501a87
8 changed files with 95 additions and 22 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -110,6 +110,10 @@ class Polygon {
|
|||
return this;
|
||||
}
|
||||
|
||||
fromVectors(array) {
|
||||
return this.addVerts(array.map(v => [ ...v ]).flat());
|
||||
}
|
||||
|
||||
toObject() {
|
||||
return {
|
||||
points: this.toArray(),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
])
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue