2021-12-27 16:24:39 -05:00
|
|
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
|
2021-12-29 16:49:15 -05:00
|
|
|
// start worker pool (disabled for now with *0)
|
|
|
|
|
moto.client.start(`/code/mesh_pool?${gapp.version}`, moto.client.max() * 0);
|
2021-12-29 00:19:22 -05:00
|
|
|
|
2021-12-29 16:49:15 -05:00
|
|
|
// dep: ext.three
|
|
|
|
|
// dep: ext.three-bgu
|
|
|
|
|
gapp.finalize("mesh.work", [
|
|
|
|
|
"moto.license", // dep: moto.license
|
2021-12-29 17:20:17 -05:00
|
|
|
"moto.client", // dep: moto.client
|
|
|
|
|
"moto.worker", // dep: moto.worker
|
|
|
|
|
"mesh.tool", // dep: mesh.tool
|
2021-12-31 22:11:20 -05:00
|
|
|
"add.three", // dep: add.three
|
2021-12-29 16:49:15 -05:00
|
|
|
]);
|
2021-12-27 18:13:11 -05:00
|
|
|
|
2022-01-12 11:46:10 -05:00
|
|
|
let { Matrix4, Vector3, BufferGeometry, BufferAttribute, computeFaceNormal } = THREE;
|
|
|
|
|
|
2021-12-31 22:11:20 -05:00
|
|
|
// compensation for space/world/platform rotation
|
2022-01-12 11:46:10 -05:00
|
|
|
let core_matrix = new Matrix4().makeRotationX(Math.PI / 2);
|
2021-12-31 22:11:20 -05:00
|
|
|
|
2022-01-02 00:40:43 -05:00
|
|
|
let { client, worker } = moto;
|
2022-01-01 22:39:49 -05:00
|
|
|
let cache = {};
|
|
|
|
|
|
2022-01-07 18:53:49 -05:00
|
|
|
function log(msg) {
|
|
|
|
|
return worker.publish("mesh.log", msg);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-02 14:25:28 -05:00
|
|
|
function cacheUpdate(id, data) {
|
|
|
|
|
Object.assign(cache[id], data);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-12 23:02:08 -05:00
|
|
|
// translate original mesh vertices into UI world view (PI/2 rotation on X)
|
2022-01-08 21:00:19 -05:00
|
|
|
function translate_encode(id, matrix) {
|
|
|
|
|
let rec = cache[id];
|
|
|
|
|
let geo = rec.geo.clone();
|
2022-01-12 11:46:10 -05:00
|
|
|
geo.applyMatrix4(core_matrix.clone().multiply( new Matrix4().fromArray(matrix) ));
|
2022-01-08 21:00:19 -05:00
|
|
|
return geo.attributes.position.array;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-11 18:41:49 -05:00
|
|
|
function analyze(id, opt = {}) {
|
2022-01-11 14:41:49 -05:00
|
|
|
log(`${id} | indexing...`);
|
|
|
|
|
let geo = cache[id].geo;
|
|
|
|
|
let tool = new mesh.tool({
|
|
|
|
|
vertices: geo.attributes.position.array,
|
|
|
|
|
faces: geo.index ? geo.index.array : undefined,
|
|
|
|
|
debug: false
|
|
|
|
|
});
|
|
|
|
|
log(`${id} | analyzing...`);
|
2022-01-11 18:41:49 -05:00
|
|
|
tool.heal(opt);
|
2022-01-11 14:41:49 -05:00
|
|
|
dbug.log(tool);
|
|
|
|
|
return tool;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-02 14:25:28 -05:00
|
|
|
let model = {
|
|
|
|
|
load(data) {
|
2022-01-04 01:11:13 -05:00
|
|
|
let { vertices, indices, name, id } = data;
|
2022-01-12 11:46:10 -05:00
|
|
|
let geo = new BufferGeometry();
|
|
|
|
|
geo.setAttribute('position', new BufferAttribute(vertices, 3));
|
|
|
|
|
if (indices) geo.setIndex(new BufferAttribute(indices, 1));
|
2022-01-02 16:30:05 -05:00
|
|
|
cacheUpdate(id, { name, geo, matrix: core_matrix.clone() });
|
2022-01-02 14:25:28 -05:00
|
|
|
},
|
2022-01-02 00:40:43 -05:00
|
|
|
|
2022-01-11 14:41:49 -05:00
|
|
|
// return new vertices in world coordinates
|
|
|
|
|
duplicate(data) {
|
2022-01-08 21:00:19 -05:00
|
|
|
let { id, matrix } = data;
|
|
|
|
|
return translate_encode(id, matrix);
|
2022-01-11 16:29:54 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// merge several model vertices into a single array
|
|
|
|
|
merge(recs) {
|
|
|
|
|
let arrays = recs.map(rec => translate_encode(rec.id, rec.matrix));
|
|
|
|
|
let length = arrays.map(a => a.length).reduce((v,a) => v + a);
|
|
|
|
|
let data = new Float32Array(length);
|
|
|
|
|
for (let i=0, l=arrays.length, p=0; i<l; ) {
|
|
|
|
|
data.set(arrays[i], p);
|
|
|
|
|
p += arrays[i++].length;
|
|
|
|
|
}
|
|
|
|
|
return data;
|
2022-01-03 22:57:19 -05:00
|
|
|
},
|
|
|
|
|
|
2022-01-11 14:41:49 -05:00
|
|
|
analyze(id) {
|
2022-01-11 18:41:49 -05:00
|
|
|
let tool = analyze(id, { mapped: true });
|
|
|
|
|
let { stats, mapped } = tool;
|
2022-01-11 14:41:49 -05:00
|
|
|
let { cull, dups, faces } = stats;
|
|
|
|
|
log(`${id} | face count=${faces} bad=${cull} dup=${dups}`);
|
|
|
|
|
log(`${id} | open loops=${tool.loops.length} edges=${tool.edges.length}`);
|
2022-01-11 18:41:49 -05:00
|
|
|
return { stats, mapped };
|
2022-01-11 14:41:49 -05:00
|
|
|
},
|
|
|
|
|
|
2022-01-11 22:42:43 -05:00
|
|
|
heal(data) {
|
|
|
|
|
let { id, opt } = data;
|
|
|
|
|
let tool = analyze(id, opt);
|
2022-01-11 14:41:49 -05:00
|
|
|
log(`${id} | unrolling...`);
|
2022-01-11 18:41:49 -05:00
|
|
|
return {
|
2022-01-05 09:29:27 -05:00
|
|
|
vertices: tool.unrolled().toFloat32(),
|
2022-01-11 18:41:49 -05:00
|
|
|
};
|
2022-01-12 23:02:08 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// given model and point, locate matching vertices, lines, and faces
|
2022-01-12 23:19:24 -05:00
|
|
|
select(data) {
|
|
|
|
|
let { id, x, y, z, a, b, c, matrix } = data;
|
2022-01-13 00:45:20 -05:00
|
|
|
// translate point into mesh matrix space
|
|
|
|
|
let v3 = new Vector3(x,y,z).applyMatrix4(
|
|
|
|
|
core_matrix.clone().multiply(new Matrix4().fromArray(matrix)).invert()
|
|
|
|
|
);
|
|
|
|
|
x = v3.x; y = v3.y; z = v3.z;
|
|
|
|
|
let arr = cache[id].geo.attributes.position.array;
|
|
|
|
|
// distance tolerance for click to vertex (rough distance)
|
2022-01-13 21:17:35 -05:00
|
|
|
let eps = 0.25;
|
2022-01-13 00:45:20 -05:00
|
|
|
let faces = [];
|
2022-01-13 11:59:26 -05:00
|
|
|
let verts = [];
|
|
|
|
|
let edges = [];
|
2022-01-13 20:23:48 -05:00
|
|
|
let point;
|
2022-01-12 23:02:08 -05:00
|
|
|
for (let i=0, l=arr.length; i<l; ) {
|
2022-01-12 23:19:24 -05:00
|
|
|
// matches here are within radius of a vertex
|
|
|
|
|
// select all faces that share a matched vertex
|
2022-01-13 11:59:26 -05:00
|
|
|
let vert = i/3; // vertex index
|
|
|
|
|
let face = (i/9) | 0; // face index
|
2022-01-12 23:02:08 -05:00
|
|
|
let ax = arr[i++];
|
|
|
|
|
let ay = arr[i++];
|
|
|
|
|
let az = arr[i++];
|
|
|
|
|
let dx = Math.abs(ax - x);
|
|
|
|
|
let dy = Math.abs(ay - y);
|
|
|
|
|
let dz = Math.abs(az - z);
|
2022-01-13 00:45:20 -05:00
|
|
|
if (dx < eps && dy < eps && dz < eps) {
|
|
|
|
|
faces.addOnce(face);
|
2022-01-13 11:59:26 -05:00
|
|
|
verts.push(vert);
|
2022-01-13 21:17:35 -05:00
|
|
|
// console.log(`match @ ${i-3} = ${face}`, ax, ay, az);
|
2022-01-12 23:02:08 -05:00
|
|
|
}
|
|
|
|
|
}
|
2022-01-12 23:19:24 -05:00
|
|
|
// no matches and we look at the line segments from the provided face
|
|
|
|
|
// to see if x,y,z point was on or near that line. then select the
|
|
|
|
|
// two faces shared by that line
|
2022-01-13 00:45:20 -05:00
|
|
|
if (faces.length === 0) {
|
|
|
|
|
// todo or not todo
|
|
|
|
|
}
|
|
|
|
|
// if no lines match, select the provided face (from min vertex index)
|
|
|
|
|
if (faces.length === 0) {
|
|
|
|
|
faces.push(Math.min(a,b,c) / 3);
|
|
|
|
|
}
|
2022-01-13 20:23:48 -05:00
|
|
|
return { faces, edges, verts, point };
|
2022-01-02 14:25:28 -05:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let group = {
|
|
|
|
|
add(data) {
|
|
|
|
|
let { id, model } = data;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
remove(data) {
|
|
|
|
|
let { id, model } = data;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let object = {
|
|
|
|
|
create(data) {
|
|
|
|
|
let { id, type } = data;
|
|
|
|
|
cache[id] = { id, type };
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
destroy(data) {
|
|
|
|
|
delete cache[data.id];
|
|
|
|
|
}
|
|
|
|
|
};
|
2022-01-02 00:40:43 -05:00
|
|
|
|
2022-01-08 21:00:19 -05:00
|
|
|
let file = {
|
|
|
|
|
export(data, send) {
|
2022-01-11 10:04:07 -05:00
|
|
|
let header = `# Generated By Mesh:Tool @ https://grid.space/mesh (units = millimeters)`;
|
2022-01-08 21:00:19 -05:00
|
|
|
let { format, recs } = data;
|
2022-01-10 21:06:38 -05:00
|
|
|
let vtot = 0;
|
|
|
|
|
for (let rec of recs) {
|
|
|
|
|
let { id, matrix, file } = rec;
|
|
|
|
|
let vs = rec.varr = Array.from(translate_encode(id, matrix)).map(v => v.round(5));
|
|
|
|
|
vtot += (vs.length / 3);
|
|
|
|
|
}
|
2022-01-08 21:00:19 -05:00
|
|
|
switch (format) {
|
|
|
|
|
case "obj":
|
|
|
|
|
let p = 1;
|
2022-01-11 10:04:07 -05:00
|
|
|
let obj = [header];
|
2022-01-08 21:00:19 -05:00
|
|
|
for (let rec of recs) {
|
2022-01-10 21:06:38 -05:00
|
|
|
let { id, matrix, file, varr } = rec;
|
2022-01-08 21:00:19 -05:00
|
|
|
obj.push(`g ${file}`);
|
2022-01-10 21:06:38 -05:00
|
|
|
for (let i=0; i<varr.length; p += 3) {
|
|
|
|
|
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
|
|
|
|
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
|
|
|
|
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
2022-01-08 21:00:19 -05:00
|
|
|
obj.push(`f ${p} ${p+1} ${p+2}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return obj.join('\n');
|
2022-01-10 21:06:38 -05:00
|
|
|
case "stl":
|
|
|
|
|
let stl = new Uint8Array(80 + 4 + vtot/3 * 50);
|
|
|
|
|
let dat = new DataView(stl.buffer);
|
|
|
|
|
let pos = 84;
|
2022-01-11 10:04:07 -05:00
|
|
|
header.split('').forEach((c,i) => {
|
|
|
|
|
dat.setUint8(i, c.charCodeAt(0));
|
|
|
|
|
});
|
2022-01-10 21:06:38 -05:00
|
|
|
// todo put Kiri:Moto info in header
|
|
|
|
|
dat.setInt32(80, vtot/3, true);
|
|
|
|
|
for (let rec of recs) {
|
|
|
|
|
let { id, matrix, file, varr } = rec;
|
|
|
|
|
for (let i=0, l=varr.length; i<l;) {
|
2022-01-12 11:46:10 -05:00
|
|
|
let p0 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
|
|
|
|
let p1 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
|
|
|
|
let p2 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
|
|
|
|
let norm = computeFaceNormal(p0, p1, p2);
|
2022-01-10 21:06:38 -05:00
|
|
|
dat.setFloat32(pos + 0, norm.x, true);
|
|
|
|
|
dat.setFloat32(pos + 4, norm.y, true);
|
|
|
|
|
dat.setFloat32(pos + 8, norm.z, true);
|
|
|
|
|
dat.setFloat32(pos + 12, p0.x, true);
|
|
|
|
|
dat.setFloat32(pos + 16, p0.y, true);
|
|
|
|
|
dat.setFloat32(pos + 20, p0.z, true);
|
|
|
|
|
dat.setFloat32(pos + 24, p1.x, true);
|
|
|
|
|
dat.setFloat32(pos + 28, p1.y, true);
|
|
|
|
|
dat.setFloat32(pos + 32, p1.z, true);
|
|
|
|
|
dat.setFloat32(pos + 36, p2.x, true);
|
|
|
|
|
dat.setFloat32(pos + 40, p2.y, true);
|
|
|
|
|
dat.setFloat32(pos + 44, p2.z, true);
|
|
|
|
|
pos += 50;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return stl;
|
2022-01-08 21:00:19 -05:00
|
|
|
default:
|
|
|
|
|
throw `invalid format "${format}"`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function debug() {
|
2022-01-03 17:13:50 -05:00
|
|
|
console.log({work_cache: cache});
|
2022-01-08 21:00:19 -05:00
|
|
|
}
|
2022-01-03 17:13:50 -05:00
|
|
|
|
2022-01-02 14:25:28 -05:00
|
|
|
worker.bindObject({
|
2022-01-08 21:00:19 -05:00
|
|
|
debug,
|
2022-01-02 14:25:28 -05:00
|
|
|
model,
|
|
|
|
|
group,
|
2022-01-08 21:00:19 -05:00
|
|
|
object,
|
|
|
|
|
file
|
2021-12-31 22:11:20 -05:00
|
|
|
});
|
|
|
|
|
|
2022-01-03 17:13:50 -05:00
|
|
|
worker.ready();
|
|
|
|
|
|
2021-12-27 18:13:11 -05:00
|
|
|
})();
|