From 4e9636c74f83fe8a766774a57440ad6aeabc2faa Mon Sep 17 00:00:00 2001 From: Emmett Lalish Date: Mon, 15 May 2023 07:50:28 -0700 Subject: [PATCH] make-manifold (#428) * make-manifold working * prettier * fixed stack exceeded * download only manifolds * keep any meshes that are manifold * fixed extension accessor writer * fix read-write roundtrip --- bindings/wasm/CMakeLists.txt | 2 +- .../built/manifold-encapsulated-types.d.ts | 1 + bindings/wasm/examples/gltf-io.js | 127 ++++++++---- bindings/wasm/examples/make-manifold.html | 193 ++++++++++++++++++ bindings/wasm/examples/manifold-gltf.js | 22 +- .../wasm/manifold-encapsulated-types.d.ts | 1 + 6 files changed, 294 insertions(+), 52 deletions(-) create mode 100644 bindings/wasm/examples/make-manifold.html diff --git a/bindings/wasm/CMakeLists.txt b/bindings/wasm/CMakeLists.txt index 8ad7581db..14cf9c702 100644 --- a/bindings/wasm/CMakeLists.txt +++ b/bindings/wasm/CMakeLists.txt @@ -20,7 +20,7 @@ add_executable(manifoldjs bindings.cpp) set_source_files_properties(bindings.cpp OBJECT_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js) target_link_libraries(manifoldjs manifold sdf cross_section polygon) -target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS} -fexceptions) +target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS}) target_link_options(manifoldjs PUBLIC --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js --bind -sALLOW_TABLE_GROWTH=1 -sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1 -sEXPORT_ES6=1) diff --git a/bindings/wasm/examples/built/manifold-encapsulated-types.d.ts b/bindings/wasm/examples/built/manifold-encapsulated-types.d.ts index 351c20f27..b4afd92b8 100644 --- a/bindings/wasm/examples/built/manifold-encapsulated-types.d.ts +++ b/bindings/wasm/examples/built/manifold-encapsulated-types.d.ts @@ -435,6 +435,7 @@ export class Mesh { get numTri(): number; get numVert(): number; get numRun(): number; + merge(): boolean; verts(tri: number): SealedUint32Array<3>; position(vert: number): SealedFloat32Array<3>; extras(vert: number): Float32Array; diff --git a/bindings/wasm/examples/gltf-io.js b/bindings/wasm/examples/gltf-io.js index faed21c33..449a5835d 100644 --- a/bindings/wasm/examples/gltf-io.js +++ b/bindings/wasm/examples/gltf-io.js @@ -32,23 +32,17 @@ export function setupIO(io) { return io.registerExtensions([EXTManifold]); } -export function readMesh(mesh, attributes, materials) { - if (attributes.length < 1 || attributes[0] !== 'POSITION') - throw new Error('First attribute must be "POSITION".'); - - const primitives = mesh.listPrimitives(); - if (primitives.length === 0) { - return {}; - } - - const numProp = attributes.map((def) => attributeDefs[def].components) - .reduce((a, b) => a + b); - const position = primitives[0].getAttribute('POSITION'); - const numVert = position.getCount() - const vertProperties = new Float32Array(numProp * numVert); +function readPrimitive(primitive, numProp, attributes) { + const position = primitive.getAttribute('POSITION'); + const numVert = position.getCount(); + const vertProperties = []; let offset = 0; for (const attribute of attributes) { - const accessor = primitives[0].getAttribute(attribute); + if (attribute === 'SKIP') { + offset += attributeDefs[attribute].components; + continue; + } + const accessor = primitive.getAttribute(attribute); const array = accessor.getArray(); const size = accessor.getElementSize(); for (let i = 0; i < numVert; ++i) { @@ -58,36 +52,80 @@ export function readMesh(mesh, attributes, materials) { } offset += size; } + return vertProperties; +} + +export function readMesh(mesh, attributes, materials) { + const primitives = mesh.listPrimitives(); + if (primitives.length === 0) { + return {}; + } - const triVertArray = []; - const runIndexArray = [0]; - for (const primitive of primitives) { - if (primitive.getAttribute('POSITION') === position) { - triVertArray.push(...primitive.getIndices().getArray()); - runIndexArray.push(triVertArray.length); - materials.push(primitive.getMaterial()); - } else { - console.log('primitives do not share accessors!'); + if (attributes.length === 0) { + const attributeSet = new Set(); + for (const primitive of primitives) { + const semantics = primitive.listSemantics(); + for (const semantic of semantics) { + attributeSet.add(semantic); + } + } + for (const semantic in attributeDefs) { + if (attributeSet.has(semantic)) { + attributes.push(semantic); + attributeSet.delete(semantic); + } + } + for (const semantic of attributeSet.keys()) { + attributes.push(semantic); } } - const triVerts = new Uint32Array(triVertArray); - const runIndex = new Uint32Array(runIndexArray); + + if (attributes.length < 1 || attributes[0] !== 'POSITION') + throw new Error('First attribute must be "POSITION".'); + + const numProp = attributes.map((def) => attributeDefs[def].components) + .reduce((a, b) => a + b); const manifoldPrimitive = mesh.getExtension('EXT_manifold'); - const mergeTriVert = - manifoldPrimitive ? manifoldPrimitive.getMergeIndices().getArray() : []; - const mergeTo = - manifoldPrimitive ? manifoldPrimitive.getMergeValues().getArray() : []; - const vert2merge = new Map(); - for (const [i, idx] of mergeTriVert.entries()) { - vert2merge.set(triVerts[idx], mergeTo[i]); - } + + let vertPropArray = []; + let triVertArray = []; + const runIndexArray = [0]; const mergeFromVert = []; const mergeToVert = []; - for (const [from, to] of vert2merge.entries()) { - mergeFromVert.push(from); - mergeToVert.push(to); + if (manifoldPrimitive != null) { + vertPropArray = readPrimitive(primitives[0], numProp, attributes); + for (const primitive of primitives) { + triVertArray = [...triVertArray, ...primitive.getIndices().getArray()]; + runIndexArray.push(triVertArray.length); + materials.push(primitive.getMaterial()); + } + const mergeTriVert = manifoldPrimitive.getMergeIndices()?.getArray() ?? []; + const mergeTo = manifoldPrimitive.getMergeValues()?.getArray() ?? []; + const vert2merge = new Map(); + for (const [i, idx] of mergeTriVert.entries()) { + vert2merge.set(triVertArray[idx], mergeTo[i]); + } + for (const [from, to] of vert2merge.entries()) { + mergeFromVert.push(from); + mergeToVert.push(to); + } + } else { + for (const primitive of primitives) { + const numVert = vertPropArray.length / numProp; + vertPropArray = + [...vertPropArray, ...readPrimitive(primitive, numProp, attributes)]; + triVertArray = [ + ...triVertArray, + ...primitive.getIndices().getArray().map((i) => i + numVert) + ]; + runIndexArray.push(triVertArray.length); + materials.push(primitive.getMaterial()); + } } + const vertProperties = new Float32Array(vertPropArray); + const triVerts = new Uint32Array(triVertArray); + const runIndex = new Uint32Array(runIndexArray); return { numProp, @@ -106,14 +144,13 @@ export function writeMesh(doc, manifoldMesh, attributes, materials) { const buffer = doc.getRoot().listBuffers()[0]; const manifoldExtension = doc.createExtension(EXTManifold); - const indices = doc.createAccessor('indices') - .setBuffer(buffer) - .setType(Accessor.Type.SCALAR) - .setArray(manifoldMesh.triVerts); - const mesh = doc.createMesh(); const numPrimitive = manifoldMesh.runIndex.length - 1; for (let run = 0; run < numPrimitive; ++run) { + const indices = doc.createAccessor('indices') + .setBuffer(buffer) + .setType(Accessor.Type.SCALAR) + .setArray(new Uint32Array()); const primitive = doc.createPrimitive().setIndices(indices); const material = materials[run]; if (material) { @@ -159,6 +196,12 @@ export function writeMesh(doc, manifoldMesh, attributes, materials) { const manifoldPrimitive = manifoldExtension.createManifoldPrimitive(); mesh.setExtension('EXT_manifold', manifoldPrimitive); + + const indices = doc.createAccessor('indices') + .setBuffer(buffer) + .setType(Accessor.Type.SCALAR) + .setArray(manifoldMesh.triVerts); + manifoldPrimitive.setIndices(indices); manifoldPrimitive.setRunIndex(manifoldMesh.runIndex); const vert2merge = [...Array(manifoldMesh.numVert).keys()]; diff --git a/bindings/wasm/examples/make-manifold.html b/bindings/wasm/examples/make-manifold.html new file mode 100644 index 000000000..978b24c55 --- /dev/null +++ b/bindings/wasm/examples/make-manifold.html @@ -0,0 +1,193 @@ + + + + + Make manifold glTF + + + + + + + +

Load a glTF/GLB model and our Manifold library will attempt to + merge it into a set of manifold objects. If the model is water-tight, you can download the new GLB which will have + our EXT_manifold extension, thus preserving the + manifold data without losing any mesh properties.

+

If the View Manifold GLB checkbox is enabled, then some meshes in the model have open edges and don't represent a + solid object. Check this box to see only the manifold parts, which will also enable them to be downloaded. If the + download button is still disabled, it means there were no manifold meshes found.

+ + + + + + Drop a GLB here + + + + + + + \ No newline at end of file diff --git a/bindings/wasm/examples/manifold-gltf.js b/bindings/wasm/examples/manifold-gltf.js index 0e6b9a7bc..6b42e0428 100644 --- a/bindings/wasm/examples/manifold-gltf.js +++ b/bindings/wasm/examples/manifold-gltf.js @@ -53,6 +53,14 @@ export class ManifoldPrimitive extends ExtensionProperty { setRunIndex(runIndex) { return this.set('runIndex', runIndex); } + + setIndices(indices) { + return this.setRef('indices', indices); + } + + getIndices() { + return this.getRef('indices'); + } } export class EXTManifold extends Extension { @@ -124,7 +132,7 @@ export class EXTManifold extends Extension { const existingPrimitive = meshDef.primitives[0]; const primitive = { - indices: existingPrimitive.indices, + indices: context.accessorIndexMap.get(manifoldPrimitive.getIndices()), mode: existingPrimitive.mode, attributes: {'POSITION': existingPrimitive.attributes['POSITION']} }; @@ -150,14 +158,10 @@ export class EXTManifold extends Extension { } for (let i = 0; i < numPrimitive; ++i) { - meshDef.primitives[i].indices = json.accessors.length; - json.accessors.push({ - type: 'SCALAR', - componentType: indices.componentType, - count: runIndex[i + 1] - runIndex[i], - bufferView: indices.bufferView, - byteOffset: 4 * runIndex[i] - }); + const accessor = json.accessors[meshDef.primitives[i].indices]; + accessor.bufferView = indices.bufferView; + accessor.byteOffset = indices.byteOffset + 4 * runIndex[i]; + accessor.count = runIndex[i + 1] - runIndex[i]; } meshDef.extensions = meshDef.extensions || {}; diff --git a/bindings/wasm/manifold-encapsulated-types.d.ts b/bindings/wasm/manifold-encapsulated-types.d.ts index 351c20f27..b4afd92b8 100644 --- a/bindings/wasm/manifold-encapsulated-types.d.ts +++ b/bindings/wasm/manifold-encapsulated-types.d.ts @@ -435,6 +435,7 @@ export class Mesh { get numTri(): number; get numVert(): number; get numRun(): number; + merge(): boolean; verts(tri: number): SealedUint32Array<3>; position(vert: number): SealedFloat32Array<3>; extras(vert: number): Float32Array;