Problems extracting global vertex positions from loaded GLTF

I want to extract vertices and face indices from a gltf file so I can feed them to a physics engine as collision mesh. It works fine on most gltf files but with some, it doesn’t. For debugging I have reconstructed a new mesh so the problem becomes visible.

Here is the code to reproduce:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Debug Mesh Reconstruction</title>
    <script type="importmap">
{
  "imports": {
    "three": "https://cdn.jsdelivr.net/npm/three@v0.183.0/build/three.module.js",
    "three/addons/": "https://cdn.jsdelivr.net/npm/three@v0.183.0/examples/jsm/"
  }
}
</script>
</head>

<body style="margin:0; overflow:hidden;">
    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

        const scene = new THREE.Scene();

        const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);
        camera.position.set(2, 2, 2);

        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(innerWidth, innerHeight);
        document.body.appendChild(renderer.domElement);

        const controls = new OrbitControls(camera, renderer.domElement);

        const loader = new GLTFLoader();
        loader.load('Menger Sponge.glb', gltf => {
            const original = gltf.scene;
            scene.add(original);

            const originalData = [];

            original.updateWorldMatrix(true, true);
            original.traverse(child => {
                if (!child.isMesh) return;

                child.material = new THREE.MeshNormalMaterial()

                const geom = child.geometry;
                const posAttr = geom.attributes.position;
                const indexAttr = geom.index;

                const worldMatrix = child.matrixWorld;
                const v = new THREE.Vector3();

                const vertices = new Float32Array(posAttr.count * 3);
                for (let i = 0; i < posAttr.count; i++) {
                    v.fromBufferAttribute(posAttr, i).applyMatrix4(worldMatrix);
                    vertices[i * 3 + 0] = v.x;
                    vertices[i * 3 + 1] = v.y;
                    vertices[i * 3 + 2] = v.z;
                }
                const faces = new Uint16Array(indexAttr.array);

                originalData.push({ vertices, faces });
            });

            const reconstructed = new THREE.Group();
            for (const data of originalData) {
                const geometry = new THREE.BufferGeometry();
                geometry.setAttribute('position', new THREE.BufferAttribute(data.vertices, 3));
                geometry.setIndex(new THREE.BufferAttribute(data.faces, 1));
                geometry.computeVertexNormals();

                const mesh = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
                reconstructed.add(mesh);
            }
            scene.add(reconstructed);
            reconstructed.position.x = 3
        });

        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>

</html>

Here is the problematic model: Menger Sponge - Download Free 3D model by myboy2012 [40bce22] - Sketchfab

Here is what it looks like:


Original on the left, reconstruction on the right.

I can debug further on my own but maybe someone knows immediately what’s going on…

My guess is that your incoming indexes are not Uint16, nothing else really stand out. Why are you making that assumption? This mesh looks super heavy i doubt it can fit into 16 bits of indexes.

edit

Right, the mesh is 10 times larger than the 2^16 limit:
image
Just use Uint32 no need to be fancy.

3 Likes

:partying_face:
That was it, thanks! Would have never occurred to me. I checked the types on one of the loaded glb files and just used that.

1 Like

You could perhaps check for a limit, and then use 16 or 32, i guess the loader might be doing that if you saw some 16s in there. It will be smaller for sure.

Going to win against my OCD for once and just use 32 :stuck_out_tongue:

1 Like

Totally fair :slight_smile: