update jscad/modeling pkg, add surfaces menu, normals generator in tool

This commit is contained in:
Stewart Allen 2022-03-31 11:22:45 -04:00
commit 52388e704e
5 changed files with 80 additions and 35 deletions

View file

@ -29,7 +29,7 @@
"connect": "^3.7.0", "connect": "^3.7.0",
"earcut": "^2.2.3", "earcut": "^2.2.3",
"express-useragent": "^1.0.13", "express-useragent": "^1.0.13",
"@jscad/modeling": "^2.8.0", "@jscad/modeling": "^2.9.0",
"jszip": "^3.7.1", "jszip": "^3.7.1",
"moment": "^2.24.0", "moment": "^2.24.0",
"pixi.js": "^4.8.9", "pixi.js": "^4.8.9",

View file

@ -446,7 +446,13 @@ const tool = {
} }
}; };
const modes = { object: "object", face: "face", line: "line", vertex: "vertex" }; const modes = {
object: "object",
face: "face",
line: "line",
vertex: "vertex",
surface: "surface"
};
// edit mode // edit mode
const mode = { const mode = {
@ -477,6 +483,10 @@ const mode = {
vertex() { vertex() {
mode.set(modes.vertex); mode.set(modes.vertex);
}, },
surface() {
mode.set(modes.surface);
}
}; };
// cache pref signature so we know when it changes // cache pref signature so we know when it changes
@ -631,7 +641,9 @@ const api = exports({
objects() { objects() {
// return model objects suitable for finding ray intersections // return model objects suitable for finding ray intersections
return group.list().map(o => o.models).flat().map(o => o.mesh); return group.list().map(o => o.models).flat().map(o => o.mesh);
} },
isDebug: self.debug === true
}); });
const { broker } = gapp; const { broker } = gapp;

View file

@ -131,6 +131,9 @@ const log = api.log = {
text: `${dbug.since()} | ${msg}`, text: `${dbug.since()} | ${msg}`,
time: now time: now
}); });
if (api.isDebug) {
console.log(data.peek().text);
}
while (!log.pinned && data.length && (data.length > lines || now - data[0].time > age)) { while (!log.pinned && data.length && (data.length > lines || now - data[0].time > age)) {
data.shift(); data.shift();
} }
@ -308,6 +311,7 @@ function ui_build() {
h.button({ _: 'face', id: "mode-face", onclick() { mode.face() } }), h.button({ _: 'face', id: "mode-face", onclick() { mode.face() } }),
h.button({ _: 'line', id: "mode-line", onclick() { mode.line() } }), h.button({ _: 'line', id: "mode-line", onclick() { mode.line() } }),
h.button({ _: 'vertex', id: "mode-vertex", onclick() { mode.vertex() } }), h.button({ _: 'vertex', id: "mode-vertex", onclick() { mode.vertex() } }),
h.button({ _: 'surface', id: "mode-surface", onclick() { mode.surface() } }),
]); ]);
// create hotkey/action menu (top/left) // create hotkey/action menu (top/left)
@ -334,7 +338,7 @@ function ui_build() {
h.button({ _: 'wireframe', onclick() { api.wireframe() } }), h.button({ _: 'wireframe', onclick() { api.wireframe() } }),
]), ]),
h.div([ h.div([
h.div({ _: "edit", class: "head" }), h.div({ _: "models", class: "head" }),
// h.div({ class: "vsep" }), // h.div({ class: "vsep" }),
h.button({ _: 'duplicate', onclick: tool.duplicate }), h.button({ _: 'duplicate', onclick: tool.duplicate }),
h.button({ _: 'regroup', onclick: tool.regroup }), h.button({ _: 'regroup', onclick: tool.regroup }),
@ -344,12 +348,15 @@ function ui_build() {
h.button({ _: 'split', onclick: call.edit_split }), h.button({ _: 'split', onclick: call.edit_split }),
]), ]),
h.div([ h.div([
h.div({ _: "fix", class: "head" }), h.div({ _: "normal", class: "head" }),
// h.div({ class: "vsep" }),
h.button({ _: 'automatic', onclick: tool.repair }),
h.button({ _: 'analyze', onclick: tool.analyze }),
// h.button({ _: 'rebuild', onclick: tool.rebuild }),
h.button({ _: 'invert', onclick: tool.invert }), h.button({ _: 'invert', onclick: tool.invert }),
]),
h.div([
h.div({ _: "repair", class: "head" }),
// h.div({ class: "vsep" }),
h.button({ _: 'analyze', onclick: tool.analyze }),
h.button({ _: 'patch', onclick: tool.repair }),
// h.button({ _: 'rebuild', onclick: tool.rebuild }),
h.button({ _: 'clean', onclick: tool.clean }), h.button({ _: 'clean', onclick: tool.clean }),
]), ]),
]); ]);

View file

@ -16,10 +16,10 @@ const { geom } = mesh;
* to base.util.triagulate * to base.util.triagulate
*/ */
mesh.tool = class MeshTool { mesh.tool = class MeshTool {
constructor(params = {}) { constructor(params = {}, opt = { clean: true }) {
this.precision = Math.pow(10, params.precision || 6); this.precision = Math.pow(10, params.precision || 6);
if (params.vertices) { if (params.vertices) {
this.setVertices(params.vertices); this.setVertices(params.vertices, opt);
} }
} }
@ -27,12 +27,8 @@ mesh.tool = class MeshTool {
* @param {number[]} vertices * @param {number[]} vertices
*/ */
setVertices(vertices, opt = {}) { setVertices(vertices, opt = {}) {
if (!vertices) { this.checkVertices(vertices);
throw "missing vertices"; let doClean = opt.clean !== false;
}
if (vertices.length % 3 !== 0) {
throw "invalid vertices";
}
let faces = []; let faces = [];
let fcac = {}; // seen face hash let fcac = {}; // seen face hash
let fnew = []; // accumulate vertex triplets let fnew = []; // accumulate vertex triplets
@ -58,25 +54,29 @@ mesh.tool = class MeshTool {
nuvt.push(y); nuvt.push(y);
nuvt.push(z); nuvt.push(z);
} }
// add vertex position to face accumulator if (doClean) {
fnew.push(vpos); // add vertex position to face accumulator
if (fnew.length === 3) { fnew.push(vpos);
// check and emit face if it's unique if (fnew.length === 3) {
// cull invalid faces (has 2 or more shared vertices) // check and emit face if it's unique
if (fnew[0] === fnew[1] || fnew[0] === fnew[2] || fnew[1] === fnew[2]) { // cull invalid faces (has 2 or more shared vertices)
if (fnew[0] === fnew[1] || fnew[0] === fnew[2] || fnew[1] === fnew[2]) {
fnew = [];
cull++;
continue;
}
let key = fnew.slice().sort().join('-');
// drop duplicate faces (sort handles reverse order)
if (!fcac[key]) {
faces.appendAll(fnew);
fcac[key] = key;
} else {
dups++;
}
fnew = []; fnew = [];
cull++;
continue;
} }
let key = fnew.slice().sort().join('-'); } else {
// drop duplicate faces (sort handles reverse order) faces.push(vpos);
if (!fcac[key]) {
faces.appendAll(fnew);
fcac[key] = key;
} else {
dups++;
}
fnew = [];
} }
} }
vertices = nuvt; vertices = nuvt;
@ -88,13 +88,40 @@ mesh.tool = class MeshTool {
return this; return this;
} }
checkVertices(v) {
const vertices = v || this.vertices;
if (!vertices) {
throw "missing vertices";
}
if (vertices.length % 3 !== 0) {
throw "invalid vertices";
}
return vertices;
}
generateFaceNormals() {
const vertices = this.checkVertices(this.vertices);
const normals = this.normals = [];
const _va = new THREE.Vector3();
const _vb = new THREE.Vector3();
const _vc = new THREE.Vector3();
for (let i=0, l=vertices.length; i<l; ) {
_va.set(vertices[i++], vertices[i++], vertices[i++]);
_vb.set(vertices[i++], vertices[i++], vertices[i++]);
_vc.set(vertices[i++], vertices[i++], vertices[i++]);
let vn = THREE.computeFaceNormal(_va, _vb, _vc);
normals.push(vn.x, vn.y, vn.z);
}
return normals;
}
/** /**
* finds edge lines which are line segments on a single face. * finds edge lines which are line segments on a single face.
* construct ordered line maps with array of connected edges. * construct ordered line maps with array of connected edges.
* connect lines into polys. earcut polys into new faces. * connect lines into polys. earcut polys into new faces.
*/ */
patch(opt = { merge: true }) { patch(opt = { merge: true }) {
let vertices = this.vertices; let vertices = this.checkVertices(this.vertices);
let faces = this.faces; let faces = this.faces;
let hash = {}; // key to line map let hash = {}; // key to line map
let vmap = {}; // vertext to line map let vmap = {}; // vertext to line map
@ -457,7 +484,6 @@ mesh.tool = class MeshTool {
// return non-indexed vertex list (for compatibility) // return non-indexed vertex list (for compatibility)
unrolled() { unrolled() {
let out = []; let out = [];
let vertices = this.vertices;
for (let face of this.faces) { for (let face of this.faces) {
this.appendVertex(face, out); this.appendVertex(face, out);
} }

View file

@ -238,7 +238,7 @@ let model = {
let tool = analyze(id, { mapped: true, ...opt }); let tool = analyze(id, { mapped: true, ...opt });
let { stats, mapped } = tool; let { stats, mapped } = tool;
let { cull, dups, faces } = stats; let { cull, dups, faces } = stats;
log(`${id} | face count=${faces} bad=${cull} dup=${dups}`); log(`${id} | face count=${faces} cull=${cull} dup=${dups}`);
log(`${id} | open loops=${tool.loops.length} edges=${tool.edges.length}`); log(`${id} | open loops=${tool.loops.length} edges=${tool.edges.length}`);
return { stats, mapped }; return { stats, mapped };
}, },