Three.js Collada - What's the proper way to dispose() and release memory (garbage collection)?
Asked Answered
G

4

10

I've successfully imported a .dae scene via ColladaLoader.

The problem is, I need to switch between several .dae files.

I can't seem to implement the dispose method properly.

        dae.traverse(function(obj) {

            console.log('unloading ' + obj.id);

            scene.remove(obj);

            if(obj.geometry)
                obj.geometry.dispose();
            if(obj.material)
                obj.material.dispose();
            if(obj.mesh)
                obj.mesh.dispose();
            if(obj.texture)
                obj.texture.dispose();

        });

        scene.remove(dae);

What could I be possibly doing wrong?

Thanks so much in advance!


EDIT:

Here's the entire code.

    var renderer = null;
    var scene = null;
    var camera = null;
    var controls = null;
    var dae = null;
    //var loader = null;

    function init() {


        renderer = new THREE.WebGLRenderer( { alpha: 1, antialias: true, clearColor: 0xffffff } );
        renderer.setSize( 800, 600 );

        var elem = $('.main3d')[0];
        elem.appendChild( renderer.domElement );

        scene = new THREE.Scene();

        camera = new THREE.PerspectiveCamera( 20, 800/600, 1, 1000 );
        camera.position.set( 0, -100, 50 );
        //camera.lookAt( scene.position );
        controls = new THREE.TrackballControls( camera, renderer.domElement );

        var light = new THREE.AmbientLight( 0xffffff ); // soft white light
        scene.add( light );

        threeAnimate();


    }

    function load(url) {
        loader = new THREE.ColladaLoader();

            loader.load(url, function (collada) {
                dae = collada.scene;
                scene.add(dae);

            });

    }

    function unload() {

        dae.traverse(function(obj) {

            console.log('unloading ' + obj.id);

            scene.remove(obj);

            if(obj.geometry)
                obj.geometry.dispose();
            if(obj.material)
                obj.material.dispose();
            if(obj.mesh)
                obj.mesh.dispose();
            if(obj.texture)
                obj.texture.dispose();

        });

        scene.remove(dae);

    }

    var animFrame = null;
    function animate()  {

        animFrame = requestAnimationFrame( threeAnimate );
        renderer.render( scene, camera );
        controls.update();

    }
Guardant answered 15/10, 2015 at 15:8 Comment(0)
G
32

This should do the job:

function disposeNode (node)
{
    if (node instanceof THREE.Mesh)
    {
        if (node.geometry)
        {
            node.geometry.dispose ();
        }

        if (node.material)
        {
            if (node.material instanceof THREE.MeshFaceMaterial)
            {
                $.each (node.material.materials, function (idx, mtrl)
                {
                    if (mtrl.map)               mtrl.map.dispose ();
                    if (mtrl.lightMap)          mtrl.lightMap.dispose ();
                    if (mtrl.bumpMap)           mtrl.bumpMap.dispose ();
                    if (mtrl.normalMap)         mtrl.normalMap.dispose ();
                    if (mtrl.specularMap)       mtrl.specularMap.dispose ();
                    if (mtrl.envMap)            mtrl.envMap.dispose ();
                    if (mtrl.alphaMap)          mtrl.alphaMap.dispose();
                    if (mtrl.aoMap)             mtrl.aoMap.dispose();
                    if (mtrl.displacementMap)   mtrl.displacementMap.dispose();
                    if (mtrl.emissiveMap)       mtrl.emissiveMap.dispose();
                    if (mtrl.gradientMap)       mtrl.gradientMap.dispose();
                    if (mtrl.metalnessMap)      mtrl.metalnessMap.dispose();
                    if (mtrl.roughnessMap)      mtrl.roughnessMap.dispose();

                    mtrl.dispose ();    // disposes any programs associated with the material
                });
            }
            else
            {
                if (node.material.map)              node.material.map.dispose ();
                if (node.material.lightMap)         node.material.lightMap.dispose ();
                if (node.material.bumpMap)          node.material.bumpMap.dispose ();
                if (node.material.normalMap)        node.material.normalMap.dispose ();
                if (node.material.specularMap)      node.material.specularMap.dispose ();
                if (node.material.envMap)           node.material.envMap.dispose ();
                if (node.material.alphaMap)         node.material.alphaMap.dispose();
                if (node.material.aoMap)            node.material.aoMap.dispose();
                if (node.material.displacementMap)  node.material.displacementMap.dispose();
                if (node.material.emissiveMap)      node.material.emissiveMap.dispose();
                if (node.material.gradientMap)      node.material.gradientMap.dispose();
                if (node.material.metalnessMap)     node.material.metalnessMap.dispose();
                if (node.material.roughnessMap)     node.material.roughnessMap.dispose();

                node.material.dispose ();   // disposes any programs associated with the material
            }
        }
    }
}   // disposeNode

function disposeHierarchy (node, callback)
{
    for (var i = node.children.length - 1; i >= 0; i--)
    {
        var child = node.children[i];
        disposeHierarchy (child, callback);
        callback (child);
    }
}

and you use it

disposeHierarchy (YOUR_OBJECT3D, disposeNode);
Gamopetalous answered 18/10, 2015 at 15:12 Comment(10)
Wow! That's pretty comprehensive! I'll check it out in a bit. Thank you for taking the time to answer!Narton
I also want to know if the answer is based on your own research or if you have any sources. Thanks again!Narton
Awesome answer! I was able to release 700MB+ using the disposeHierarchy() function! Just awesome man. Thanks a lot. I hope this answer helps others too.Narton
I posted another question (somewhat in relation to this one). It would be awesome if you could share your knowledge on the topic! Thanks in advance!Narton
thanks for your comments. my answer is based on my own research and development.Gamopetalous
@Gamopetalous there is no dispose method on light classBrowder
@Gamopetalous we are not passing the main node to disposeNode i.e parent is it ok?Browder
All the lines of code with node = undefined; are useless. All it's doing is setting the local reference to undefined which has no impact on anything in this example.Islamize
THANK YOU from 2021... this is insane it isn't documented in ThreeJS. I have been working on an MMO for years and years, my client FPS has always sucked. I started to figure this out recently that things weren't adding up, after 8 days of research this function has restored my game to a playable state! I get ThreeJS's stance on material loading and stuff but damn, common, there needs to be some OPTIONS for this kind of stuff. Thank you for writing this.Admirable
@andy glad it helped. dont forget to award points for helpful answers.Gamopetalous
C
6

I tweaked gaitat's already awesome answer to just use the now built in scene traverse function, to remove $ and also handle MultiMaterial. Why, oh why is there not a built in memory cleanup in THREE!!? Surely it should do it when you do scene.dispose(). I'm still trying to track down a couple more textures I'm using but don't seem to get dispose()ed according to renderer.info.memory.textures

this.disposeNode = function (parentObject) {

    parentObject.traverse(function (node) {
        if (node instanceof THREE.Mesh) {
            if (node.geometry) {
                node.geometry.dispose();
            }

            if (node.material) {

                if (node.material instanceof THREE.MeshFaceMaterial || node.material instanceof THREE.MultiMaterial) {
                    node.material.materials.forEach(function (mtrl, idx) {
                        if (mtrl.map) mtrl.map.dispose();
                        if (mtrl.lightMap) mtrl.lightMap.dispose();
                        if (mtrl.bumpMap) mtrl.bumpMap.dispose();
                        if (mtrl.normalMap) mtrl.normalMap.dispose();
                        if (mtrl.specularMap) mtrl.specularMap.dispose();
                        if (mtrl.envMap) mtrl.envMap.dispose();

                        mtrl.dispose();    // disposes any programs associated with the material
                    });
                }
                else {
                    if (node.material.map) node.material.map.dispose();
                    if (node.material.lightMap) node.material.lightMap.dispose();
                    if (node.material.bumpMap) node.material.bumpMap.dispose();
                    if (node.material.normalMap) node.material.normalMap.dispose();
                    if (node.material.specularMap) node.material.specularMap.dispose();
                    if (node.material.envMap) node.material.envMap.dispose();

                    node.material.dispose();   // disposes any programs associated with the material
                }
            }
        }
    });
}
Commendatory answered 21/10, 2016 at 14:4 Comment(2)
This is why it is not automagically removed. Delocating heap objects.... The programer has to be involved, because it is not as straightforward as you think it is. In large enough projects, things get complicated and you end up reusing materials, geometries and even meshes between scenes (not kidding, you might end up doing all of the above). So deleting stuff so happily is only going to bring pain and sorrow. Having a learning curve and having to be involved as a programer keeps you from doing silly mistakes.Duckpin
There is a pretty nice article now in the official docs about disposing things: threejs.org/docs/index.html?q=dispose#manual/en/introduction/…Mustee
D
3

Building off the answers here, this code handles arrays of materials.

function disposeNode(parentObject) {
    parentObject.traverse(function (node) {
        if (node instanceof THREE.Mesh) {
            if (node.geometry) {
                node.geometry.dispose();
            }
            if (node.material) {
                var materialArray;
                if (node.material instanceof THREE.MeshFaceMaterial || node.material instanceof THREE.MultiMaterial) {
                    materialArray = node.material.materials;
                }
                else if(node.material instanceof Array) {
                    materialArray = node.material;
                }
                if(materialArray) {
                    materialArray.forEach(function (mtrl, idx) {
                        if (mtrl.map) mtrl.map.dispose();
                        if (mtrl.lightMap) mtrl.lightMap.dispose();
                        if (mtrl.bumpMap) mtrl.bumpMap.dispose();
                        if (mtrl.normalMap) mtrl.normalMap.dispose();
                        if (mtrl.specularMap) mtrl.specularMap.dispose();
                        if (mtrl.envMap) mtrl.envMap.dispose();
                        mtrl.dispose();
                    });
                }
                else {
                    if (node.material.map) node.material.map.dispose();
                    if (node.material.lightMap) node.material.lightMap.dispose();
                    if (node.material.bumpMap) node.material.bumpMap.dispose();
                    if (node.material.normalMap) node.material.normalMap.dispose();
                    if (node.material.specularMap) node.material.specularMap.dispose();
                    if (node.material.envMap) node.material.envMap.dispose();
                    node.material.dispose();
                }
            }
        }
    });
}
Dionisio answered 28/7, 2017 at 22:45 Comment(0)
A
1

After research on the web and couple of refactoring, here is what I came up with:

  disposeNode = ( node, recursive = false ) => {

    if ( !node ) return;

    if ( recursive && node.children)
      for ( const child of node.children )
        disposeNode( child , recursive );

    node.geometry && node.geometry.dispose();
    
    if ( !node.material ) return;

    const materials = node.material.length === undefined ? [ node.material ] : node.material

    for ( const material of materials ) {

        for ( const key in material ) {

          const value = material[key];

          if ( value && typeof value === 'object' && 'minFilter' in value )
            value.dispose();

        }

        material && material.dispose();    
        
    }

  }
Andersen answered 5/4, 2022 at 16:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.