Ammo.js OOM error when importing a large GLB file

I am using playcanvas standalone engine to upload a GLB file. This is a large GLB file of size 230 MB
here are the stats of the GLB file

  • Stats:
    • 379 draw calls
    • 0 animations
    • 122 materials
    • 2359648 vertices
    • 3362654 triangles
  • Extensions:
    • KHR_materials_specular
    • KHR_materials_ior
    • KHR_materials_anisotropy
    • KHR_materials_transmission
    • KHR_texture_transform
    • KHR_materials_clearcoat

i am using the below code to import a GLB file and make its mesh colliders on the go.

assembleAndLoadModel4() {
        if (this.modelAssembled) {
            debugLog('Model already assembled. Skipping...');
            return;
        }
        this.modelAssembled = true;
        debugLog('Assembling model...');

        const assembledBuffer = new ArrayBuffer(this.chunks.reduce((total, chunk) => total + chunk.byteLength, 0));
        const assembledView = new Uint8Array(assembledBuffer);

        let offset = 0;
        this.chunks.forEach(chunk => {
            assembledView.set(new Uint8Array(chunk), offset);
            offset += chunk.byteLength;
        });

        this.chunks = [];

        const modelBlob = new Blob([assembledBuffer], { type: 'model/gltf-binary' });
        const modelUrl = URL.createObjectURL(modelBlob);

        const asset = new Asset('assembled-model', 'container', { url: modelUrl });
        asset.streaming = true;

        this.app.assets.add(asset);

        asset.on('load', () => {
            URL.revokeObjectURL(modelUrl);
            debugLog('Model fully loaded and assembled');

            this.modelContainer.children.slice().forEach(child => child.destroy());

            // Create the main entity
            const mainEntity = new Entity('MainModel');
            this.modelContainer.addChild(mainEntity);

            if (asset.resource && asset.resource.model) {
                mainEntity.addComponent('model', {
                    type: 'asset',
                    asset: asset.resource.model
                });
                
                debugLog('Added model component to main entity');

                this.addMeshCollidersToModel(mainEntity, asset.resource);
            } else {
                debugLog('Error: No model resource found in the loaded asset');
                return;
            }

            // Center the model in view
            if (mainEntity.model && mainEntity.model.model && mainEntity.model.model.meshInstances) {
                const aabb = new pc.BoundingBox();
                mainEntity.model.model.meshInstances.forEach(function (meshInstance) {
                    aabb.add(meshInstance.aabb);
                });
                const center = aabb.center;
                const min = aabb.getMin();
                this.app.fire('model:loaded', {
                    position: new Vec3(center.x, min.y + CAMERA_DEFAULT_HEIGHT, center.z),
                });
            }
            // this.logModelBoundingBox(mainEntity);
        });

        asset.on('error', (err) => {
            debugLog('Error loading assembled model:', err);
        });

        this.app.assets.load(asset);
    }

    addMeshCollidersToModel(mainEntity, model) {
        if (!model || !model.model.resource.meshInstances) {
            debugLog('Error: Invalid model structure');
            return;
        }

        const meshInstances = model.model.resource.meshInstances;
        debugLog(`Adding colliders to ${meshInstances.length} mesh instances`);

        for (let i = 0; i < meshInstances.length; i++) {
            const meshInstance = meshInstances[i];
            const meshEntity = new Entity(`MeshEntity_${i}`);
            mainEntity.addChild(meshEntity);
            meshEntity.setLocalPosition(meshInstance.node.getLocalPosition());
            meshEntity.setLocalRotation(meshInstance.node.getLocalRotation());
            meshEntity.setLocalScale(meshInstance.node.getLocalScale());
            meshEntity.addComponent('render', {
                meshInstances: [meshInstance]
            });
            this.addSimplifiedCollider(meshEntity, meshInstance, i);
            meshEntity.addComponent('rigidbody', {
                type: 'static',
                restitution: 0.5,
                friction: 0.3
            });

            // meshEntity.collision.on('collisionstart', (result) => {
            //     debugLog(`Collision started for MeshEntity_${i} with ${result.other.name}`);
            // });

            // meshEntity.collision.on('collisionend', (result) => {
            //     debugLog(`Collision ended for MeshEntity_${i} with ${result.other.name}`);
            // });

            debugLog(`Added collider to MeshEntity_${i}`);
        }
        debugLog(`Added colliders to ${meshInstances.length} mesh instances`);
    }

    addSimplifiedCollider(meshEntity, meshInstance, index) {
        try {

            meshEntity.addComponent('collision', {
                type: 'mesh',
                convex: true
            });

            var node = new GraphNode();
            var collisionModel = new Model();
            collisionModel.graph = node;
            collisionModel.meshInstances.push(meshInstance);

            meshEntity.collision.model = collisionModel;

            debugLog(`Added simplified mesh collider to MeshEntity_${index}`);
        } catch (error) {
            debugLog(`Failed to add simplified mesh collider to MeshEntity_${index}. Error: ${error.message}`);
            debugLog('Falling back to box collider');

            // Remove the failed collision component if it was added
            if (meshEntity.collision) {
                meshEntity.removeComponent('collision');
            }

            // Calculate bounding box for the mesh instance
            const aabb = new BoundingBox();
            aabb.copy(meshInstance.aabb);

            // Add a box collider based on the bounding box
            meshEntity.addComponent('collision', {
                type: 'box',
                halfExtents: aabb.halfExtents
            });

            var node = new GraphNode();
            var collisionModel = new Model();
            collisionModel.graph = node;
            collisionModel.meshInstances.push(meshInstance);

            meshEntity.collision.model = collisionModel;

            debugLog(`Added box collider to MeshEntity_${index} as fallback`);
        }
    }

I even added a catch statement with help of GPT to handle the error but after generating colliders for some mesh ammo goes oom

Below is the error statement

ammo.wasm.js:14 Uncaught RuntimeError: Aborted(OOM). Build with -sASSERTIONS for more info.
at oa (ammo.wasm.js:14:197)
at c (ammo.wasm.js:23:211)
at ammo.wasm.wasm:0xb8de
at ammo.wasm.wasm:0x1a2dd
at ammo.wasm.wasm:0x3dd2
at ammo.wasm.wasm:0x4018
at ammo.wasm.wasm:0x80f96
at ammo.wasm.wasm:0x2f7bd
at x.stepSimulation (ammo.wasm.js:578:204)
at RigidBodyComponentSystem.onUpdate (playcanvas.js?v=4e08f406:60799:24)

ammo.wasm.js:14 failed to initialize module=Ammo error=wasm module aborted.

Well, as you see, this is too large to fit into AMMO memory. You could compile a custom AMMO wasm which has larger memory size available (not too simple to do), or do not use your large GLB for collision - as the performance of collision of meshes with this many triangles would not be great. Either simplified meshes, or shapes (capsule, box …) instead of many of the meshes.