update jscad/modeling pkg, add surfaces menu, normals generator in tool
This commit is contained in:
parent
8309a7491a
commit
52388e704e
5 changed files with 80 additions and 35 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 }),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue