diff --git a/src/main/mesh.js b/src/main/mesh.js index 27c88261..2b446998 100644 --- a/src/main/mesh.js +++ b/src/main/mesh.js @@ -85,7 +85,8 @@ broker.subscribe('space_init', data => { // add object loader broker.subscribe('space_load', data => { for (let od of data) { - new mesh.object(od); + let obj = new mesh.object(od); + moto.Space.world.add(obj.mesh); } }); diff --git a/src/mesh/object.js b/src/mesh/object.js index 3d44c470..999d9f76 100644 --- a/src/mesh/object.js +++ b/src/mesh/object.js @@ -7,12 +7,21 @@ gapp.register("mesh.object", [ "add.three", // dep: add.three "moto.license", // dep: moto.license - "moto.worker", // dep: moto.worker ]); let mesh = self.mesh = self.mesh || {}; if (mesh.object) return; +mesh.material = { + solid: new THREE.MeshPhongMaterial({ + transparent: true, + shininess: 100, + specular: 0x181818, + color: 0xffff00, + opacity: 1 + }) +}; + mesh.object = class MeshObject { constructor(data) { let { file, mesh } = data; @@ -22,7 +31,23 @@ mesh.object = class MeshObject { return; } - console.log({new_mesh_object: data}); + this.file = file; + this.mesh = this.load(mesh); + } + + load(vertices, indices) { + let geo = new THREE.BufferGeometry(); + if (indices) geo.setIndex(new THREE.BufferAttribute(indices, 1)); + geo.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); + geo.setAttribute('normal', undefined); + let meh = new THREE.Mesh(geo, mesh.material.solid); + geo.computeFaceNormals(); + geo.computeVertexNormals(); + meh.material.side = THREE.DoubleSide; + meh.receiveShadow = true; + meh.castShadow = true; + meh.renderOrder = 1; + return meh; } }; diff --git a/src/mesh/pool.js b/src/mesh/pool.js index 27935635..48953b30 100644 --- a/src/mesh/pool.js +++ b/src/mesh/pool.js @@ -6,7 +6,7 @@ gapp.finalize("mesh.pool", [ "moto.license", // dep: moto.license - "moto.worker" // dep: moto.worker + "moto.worker", // dep: moto.worker ]); })(); diff --git a/src/mesh/tool.js b/src/mesh/tool.js new file mode 100644 index 00000000..12930787 --- /dev/null +++ b/src/mesh/tool.js @@ -0,0 +1,386 @@ +/** Copyright Stewart Allen -- All Rights Reserved */ + +"use strict"; + +(function() { + +// dep: ext.earcut +gapp.register("mesh.tool"); + +let mesh = self.mesh = self.mesh || {}; +if (mesh.object) return; + +mesh.tool = class MeshTool { + constructor(params = {}) { + this.precision = Math.pow(10, params.precision || 6); + if (params.vertices) { + this.setData(params.vertices, params.faces); + } + } + + /** + * set vertices and faces of the mesh. if only vertices are provided + * then the data is assumed to be un-indexed and will be indexed into + * faces. if faces are provided, they are assumed to be index references + * into the vertex data. removes invalid faces. + * + * the end result is face data mapped to an indexed set of unique vertices. + * + * @param {number[]} vertices + * @param {number[]} faces + */ + setData(vertices, faces) { + if (!vertices) { + throw "missing vertices"; + } + if (vertices.length % 3 !== 0) { + throw "invalid vertices"; + } + // when face/index data is missing, vertices need to be normalized + if (!faces) { + if (TEST) console.time('generate faces'); + faces = []; + let fcac = {}; // seen face hash + let fnew = []; // accumulate vertex triplets + let nuvt = []; // new vertex list + let hash = {}; // find vertex matches + let prec = this.precision; + let dups = 0; + // let cull = 0; + for (let i=0, l=vertices.length; i l !== this && !l.used && l.faces.length === 1); + } + + adjacentUsed() { + return vmap[this.v1] + .concat(vmap[this.v2]) + .filter(l => l !== this && l.used && l.faces.length === 1); + } + + touches(line) { + return this.v1 === line.v1 || + this.v1 === line.v2 || + this.v2 === line.v1 || + this.v2 === line.v2; + } + + unitVector() { + if (this.unit !== undefined) { + return this.unit; + } + let v1 = this.v1; + let v2 = this.v2; + let d = [ + vertices[v1] - vertices[v2], + vertices[v1 + 1] - vertices[v2 + 1], + vertices[v1 + 2] - vertices[v2 + 2], + ]; + let max = Math.max(...d.map(v => Math.abs(v))); + // console.log({d, max}); + return this.unit = d.map(v => v / max); + } + } + + function diffUnitVector(uv1, uv2) { + let diff = 0; + for (let i=0; i<3; i++) { + diff += Math.abs(uv1[i] - uv2[i]); + } + return diff; + } + + function getLine(v1, v2) { + let s1 = vertices[v1] + vertices[v1+1]*1000 + vertices[v1+2]*100000; + let s2 = vertices[v2] + vertices[v2+1]*1000 + vertices[v2+2]*100000; + let key = s1 <= s2 ? [v1,v2].join('-') : [v2,v1].join('-'); + let line = hash[key]; + if (!line) { + // point order will be swapped for reconstructed faces + // to preserve chirality (assuming face normals are correct) + line = hash[key] = new Line(v1, v2); + line.key = key; + lines.push(line); + let lm1 = vmap[v1] = vmap[v1] || []; + let lm2 = vmap[v2] = vmap[v2] || []; + lm1.push(line); + lm2.push(line); + } + return line; + } + + function getXYZ(v) { + return {x: vertices[v], y: vertices[v+1], z: vertices[v+2]}; + } + + for (let i=0, l=this.faces.length; i l.faces.length === 1); + let loops = this.loops = []; + + // connect edge lines into closed loops + for (let i=0, l=edges.length; i 2) { + // console.log('cannot start on a split'); + continue; + } + // line.used = true; + let loop = [ ]; + // build line through adjacent lines + while (true) { + let adjacent = line.adjacentUnused(); + if (adjacent.length === 0) { + let first = loop[0]; + let last = loop.last(); + loop.open = loop.length < 3 + || (first.v1 !== last.v1 + && first.v2 !== last.v2 + && first.v1 !== last.v2 + && first.v2 !== last.v1); + // console.log({term: line, adj: line.adjacentUsed(), open, loop}); + break; + } else if (adjacent.length === 1) { + if (line.v2 !== adjacent[0].v1) { + let tmp = adjacent[0].v1; + adjacent[0].v1 = adjacent[0].v2; + adjacent[0].v2 = tmp; + if (TEST > 1) console.log('chirality mismatch fixed'); + } + line = adjacent[0]; + } else if (adjacent.length === 2) { + // follow edges according to chirality + line = adjacent[0].v1 === line.v2 ? + adjacent[0] : + adjacent[1] + } else { + if (TEST > 1) console.log('error adjacent', line, adjacent, loops.length, loop.length); + line.split = adjacent; + line = adjacent[0]; + } + loop.push(line); + line.used = true; + } + if (loop.length) { + loops.push(loop); + } + } + + // attempt to connect open loops + if (loops.length > 1) { + outer: for (let i=0; i l && l.length <= 2); + if (TEST) console.log({edges, loops, shorts: this.shorts}); + + // filter out null and short loops + this.loops = loops = loops.filter(l => l && l.length > 2); + if (TEST > 1) console.log({loops_filtered: loops}); + + // rotate/flatten to Z plane and use earcut to generate faces + // because earcut emits point indexes, no need to un-rotate + function emitLoop(loop) { + let lastPoint; + let pindex = []; + let points = []; + let dx = 0, dy = 0, dz = 0; + + // gather points, find axis with least deviaton to swap with Z + let loop1 = loop.slice(); + loop1.push(loop[0]); + for (let i=0; i= 0) { + dx = Math.max(dx, Math.abs(vertices[lastPoint] - vertices[nextPoint])); + dy = Math.max(dy, Math.abs(vertices[lastPoint+1] - vertices[nextPoint+1])); + dz = Math.max(dz, Math.abs(vertices[lastPoint+2] - vertices[nextPoint+2])); + } + lastPoint = nextPoint; + } + + // swap Z axis with least delta axis + let swap = 2; + if (dy < dx && dy < dz) { + // swap y,z + swap = 1; + } else if (dx < dy && dx < dz) { + // swap x,z + swap = 0; + } + if (swap < 2) { + // console.log({swap_axis: swap}); + for (let point of points) { + let tmpz = point[2]; + point[2] = point[swap]; + point[swap] = tmpz; + } + } + + let fpoints = points.flat(); + let ec = earcut(fpoints, undefined, 3); + if (TEST > 2) console.log({points, fpoints, ec, dx, dy, dz}); + + for (let point of ec) { + faces.push(pindex[point]); + } + } + + // emit loops + let faceCount = this.faces.length; + for (let loop of loops) { + emitLoop(loop); + } + + this.newFaces = this.faces.length - faceCount; + if (TEST) console.log({newFaces: this.newFaces}); + if (TEST) console.timeEnd('heal'); + return this; + } +}; + +})(); diff --git a/src/mesh/work.js b/src/mesh/work.js index 9d04b946..c630153d 100644 --- a/src/mesh/work.js +++ b/src/mesh/work.js @@ -11,7 +11,9 @@ moto.client.start(`/code/mesh_pool?${gapp.version}`, moto.client.max() * 0); // dep: ext.three-bgu gapp.finalize("mesh.work", [ "moto.license", // dep: moto.license - "moto.client" // dep: moto.client + "moto.client", // dep: moto.client + "moto.worker", // dep: moto.worker + "mesh.tool", // dep: mesh.tool ]); })();