3

I can get the model to render, I can rotate, and zoom and everything is good UNTIL, I try to clear the scene. I want to clear the scene by clicking the button#clear.

I expect the scene to be traversed, and have its children added to an array. I then expect to iterate over the array and for each item in the array clear it out by calling dispose() on it if it's a geometry or a material and then remove the mesh from the scene.

<html>
  <head>
    <title>Three.js</title>
    <link rel="stylesheet" href="css/main.css" />
  </head>
  <body>
    <button id="clear">Clear Three Out</button>
    <script src="js/three.js"></script>
    <script src="js/OrbitControls.js"></script>
    <script src="js/ObjectLoader.js"></script>
    <script>
    <script>
      (function(){var script=document.createElement('script');script.onload=function(){var stats=new Stats();document.body.appendChild(stats.dom);requestAnimationFrame(function loop(){stats.update();requestAnimationFrame(loop)});};script.src='//rawgit.com/mrdoob/stats.js/master/build/stats.min.js';document.head.appendChild(script);})();
      var scene = new THREE.Scene();

      var width = window.innerWidth;
      var height = window.innerHeight;
      var camera = new THREE.OrthographicCamera(width / - 2, width / 2, height / 2, height / - 2, -1000, 1000); // new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

      var blackReflectiveMaterial = new THREE.MeshBasicMaterial();     
      var renderer = new THREE.WebGLRenderer();
      renderer.setSize( window.innerWidth, window.innerHeight );
      document.body.appendChild( renderer.domElement );

      // handle resize event
      window.addEventListener( 'resize', function() {
        var width = window.innerWidth;
        var height = window.innerHeight;

        renderer.setSize(width, height);
        camera.aspect = width / height;
        camera.updateProjectionMatrix();
      } );

      // controls
      controls = new THREE.OrbitControls( camera, renderer.domElement );
      camera.position.z = 3;

      // model loader
      var loader = new THREE.ObjectLoader();

      loader.load(
        'models/revol-uv.json', // model of a compound bow
        function( object ) {
          console.log('object: ', object);
          object.scale.x = object.scale.y = object.scale.z = 200;

          object.traverse( function(child) {
            child.material = blackReflectiveMaterial;
          });

          scene.add( object );
        }
      );

      scene.translateY(-345);

      // array to hold Scene's children
      var holder = [];

      // when click the #clear button, call this function
      document.getElementById('clear').onclick = function() {
        scene.traverse( function ( child ) {
          if (child.type === "Mesh") {
            holder.push(child);
          }

        } );
        console.log('holder before clean: ', holder);
        for (var i = 0; i < holder.length; i++) {
          if (holder[i].geometry) {
            holder[i].geometry.dispose();
          }

          console.log('holder[i].material: ', holder[i].material);
          if (holder[i].material) {
            if (holder[i].map) {
              holder[i].material.map.dispose();
            }
            holder[i].material.dispose();
          }

          console.log('holder[i]: ', holder[i]);
          scene.remove(holder[i]);
        }
        console.log('holder after clean: ', holder); // all the objects are still there
      }

      // logic
      var update = function() {

        // any code to rotate ... blah blah not important
      };

      // draw Scene
      var render = function() {
        renderer.render( scene, camera );
      };

      // run loop (update, render, repeat)
      var bowLoop = function() {
        requestAnimationFrame( bowLoop );

        update();
        render();
      };
      bowLoop();
    </script>
  </body>
</html>

Here's the console logs:

The children of the scene

Array(10)
0:THREE.Mesh {uuid: "5574129D-7093-4567-8927-4F07BB7A3928", name: "cable-arm", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
1:THREE.Mesh {uuid: "ED867F5C-F6C4-45BC-BBF7-9DD5061A0CF6", name: "cams", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
2:THREE.Mesh {uuid: "D69C58BB-1E03-4D00-8050-6F2F4739DFE9", name: "handles", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
3:THREE.Mesh {uuid: "50A98720-4E8F-4873-B987-0C993D05FE65", name: "hardware", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
4:THREE.Mesh {uuid: "6CE44421-0614-40E9-8013-FE939CFA0191", name: "limb-pockets", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
5:THREE.Mesh {uuid: "60FF4521-B19B-477D-8223-8B4BFA2A0F5A", name: "limbs", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
6:THREE.Mesh {uuid: "D2980C91-0B19-4757-A831-A3B46546E579", name: "limbsavers", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
7:THREE.Mesh {uuid: "D9858B1C-E4F7-40C4-98DD-163230F9A917", name: "riser", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
8:THREE.Mesh {uuid: "BC7C199B-2867-4446-86FE-D244D7A32E07", name: "string-supressor", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
9:THREE.Mesh {uuid: "02A038E9-FB0B-43FF-B3F2-BCCAFD7E10AC", name: "strings", type: "Mesh", parent: T…E.Scene, children: Array(0), …}

The holder array before the clean:

Array(10)
0:THREE.Mesh {uuid: "5574129D-7093-4567-8927-4F07BB7A3928", name: "cable-arm", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
1:THREE.Mesh {uuid: "ED867F5C-F6C4-45BC-BBF7-9DD5061A0CF6", name: "cams", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
2:THREE.Mesh {uuid: "D69C58BB-1E03-4D00-8050-6F2F4739DFE9", name: "handles", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
3:THREE.Mesh {uuid: "50A98720-4E8F-4873-B987-0C993D05FE65", name: "hardware", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
4:THREE.Mesh {uuid: "6CE44421-0614-40E9-8013-FE939CFA0191", name: "limb-pockets", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
5:THREE.Mesh {uuid: "60FF4521-B19B-477D-8223-8B4BFA2A0F5A", name: "limbs", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
6:THREE.Mesh {uuid: "D2980C91-0B19-4757-A831-A3B46546E579", name: "limbsavers", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
7:THREE.Mesh {uuid: "D9858B1C-E4F7-40C4-98DD-163230F9A917", name: "riser", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
8:THREE.Mesh {uuid: "BC7C199B-2867-4446-86FE-D244D7A32E07", name: "string-supressor", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
9:THREE.Mesh {uuid: "02A038E9-FB0B-43FF-B3F2-BCCAFD7E10AC", name: "strings", type: "Mesh", parent: T…E.Scene, children: Array(0), …}

The holder array after the clean:

Array(10)
0:THREE.Mesh {uuid: "5574129D-7093-4567-8927-4F07BB7A3928", name: "cable-arm", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
1:THREE.Mesh {uuid: "ED867F5C-F6C4-45BC-BBF7-9DD5061A0CF6", name: "cams", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
2:THREE.Mesh {uuid: "D69C58BB-1E03-4D00-8050-6F2F4739DFE9", name: "handles", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
3:THREE.Mesh {uuid: "50A98720-4E8F-4873-B987-0C993D05FE65", name: "hardware", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
4:THREE.Mesh {uuid: "6CE44421-0614-40E9-8013-FE939CFA0191", name: "limb-pockets", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
5:THREE.Mesh {uuid: "60FF4521-B19B-477D-8223-8B4BFA2A0F5A", name: "limbs", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
6:THREE.Mesh {uuid: "D2980C91-0B19-4757-A831-A3B46546E579", name: "limbsavers", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
7:THREE.Mesh {uuid: "D9858B1C-E4F7-40C4-98DD-163230F9A917", name: "riser", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
8:THREE.Mesh {uuid: "BC7C199B-2867-4446-86FE-D244D7A32E07", name: "string-supressor", type: "Mesh", parent: T…E.Scene, children: Array(0), …}
9:THREE.Mesh {uuid: "02A038E9-FB0B-43FF-B3F2-BCCAFD7E10AC", name: "strings", type: "Mesh", parent: T…E.Scene, children: Array(0), …}

No change in memory ... objects are still in heap.

Tom O.
  • 5,730
  • 2
  • 21
  • 35
Joe C
  • 1,685
  • 2
  • 16
  • 26

3 Answers3

1

Two things come to mind:

  • it looks like holder is still holding references to all objects after the clear-function was called (h/t @pailhead). You should add something like holder = []; at the end of the function to get rid of them as well. Also make sure that there are no other accessible variables holding references to any of the objects.

  • be aware that the objects will not automatically disappear from memory but only after the garbage-collector did its work. Depending on how much is going on otherwise that might take a moment.

Martin Schuhfuß
  • 6,814
  • 1
  • 36
  • 44
0

@gaitat - thank you for supplying a tremendous bulk of the solution.

I needed to just modify some lines to meet my needs:

  function disposeNode(node) {
    if (node instanceof THREE.Mesh) {
      if (node.geometry) {
        node.geometry.dispose();
        node.geometry = undefined; // fixed problem
      }

      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();
            mrtl = undefined; // fixed problem
          } );
        }
        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();
          node.material = undefined; // fixed problem
        }
      }
    }
    console.log('node before removal: ', node);
    scene.remove( node );
    renderer.dispose(); // ***EDIT*** improved even memory more original scene heap is 12.4 MB; add objects increases to 116 MB or 250 MB (different models), clearing always brings down to 13.3 MB ... there still might be some artifacts.  
    node = undefined; // unnecessary
  }

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

      disposeHierchy(child, callback);
      callback(child);
    }
  }

Call it:

document.getElementById('clear').onclick = function() {
    disposeHierchy(scene, disposeNode);
    console.log('renderer.info.memory after: ', renderer.info);
  }
Joe C
  • 1,685
  • 2
  • 16
  • 26
  • As is I don't think that this Q/A are very helpful to others. It's a lot of code through comb to, perhaps you should outline - put in a separate block, or comment, the lines that did the fix. `node = undefined` actually doesn't seem like it should do anything. If you are holding a reference to that thing somewhere else it's still going to be there. – pailhead Sep 14 '17 at 20:27
  • @pailhead. Done. – Joe C Sep 14 '17 at 20:37
  • I think you still may run into the same issue with this. All it takes is to have `var foo = someNode.geometry` and that object will stay. The way you traverse seems to make sense, but i'm not sure what kind of advice can be given or what would be the best practice to make sure that you don't leave a reference dangling somewhere where you don't want it to. – pailhead Sep 14 '17 at 21:01
  • what do you propose? When the object is loaded, the heap is 134MB, when I clear it, it drops down to 27.9MB. The results are consistent. – Joe C Sep 14 '17 at 21:11
  • Lets see what is the main responsibility of `foo.dispose()` in three.js... Because textures and geometries need to live on the GPU in order to be used, they also have to be removed from the GPU via an actual webgl call, such as `gl.deleteBuffer( buffer )`. You can release all references to a geometry, but it's content will stay on the gpu otherwise. Back in JS land though, there isn't really a `delete myGeometryInstance`, rather, you have to manage this yourself and understand how garbage collection works. – pailhead Sep 14 '17 at 21:26
  • I just did some digging, [check this out](https://github.com/mrdoob/three.js/blob/dev/src/renderers/webgl/WebGLGeometries.js#L14), i think the only management that happens is making sure the deleteBuffer is issued to the GPU, and that the internal geometry cache is cleared of the attribute. At a first glance, it looks like the actual geometry class instance remains intact. IE. if it has an attribute, the attribute stays there. TL:DR; 1. make sure you call `.dispose()` no matter what, then nuke the object anyway you see fit. – pailhead Sep 14 '17 at 21:31
  • I thought dispose() preps the gpu to dereference the geometry or material. Setting it to undefined after dispose,() would call the garbage collector to clean it up because it's no longer referenced. scene.remove() removes mesh from scene. What specifically would need to be done to be more thorough? – Joe C Sep 14 '17 at 21:36
  • I'm not sure lol, I'm hoping someone can confirm that this is what the problem is. I guess be aware that `scene.remove()` may not be enough – pailhead Sep 14 '17 at 21:40
  • Regardless, thanks for donating your time. I appreciate it. – Joe C Sep 14 '17 at 21:41
  • this should be recursive though as meshes can have meshes within – yeahdixon Apr 29 '19 at 19:29
0

Please check the below code for loading the GLTF file and release memory...

let ZomModel = undefined;
function loadZoom() {
    const gltfLoader = new GLTFLoader();
    gltfLoader.load(modelName, (gltf) => {
        const root = gltf.scene;
        setmaterial(root);
        ZomModel = root;
        root.position.set(0, -3, 0);
        root.scale.set(5, 5, 5);
        scene.add(root);
        console.log(root, 'root')
    });
}
function loadNew(isLoad) {
    console.log("reload " + isLoad);
    // return;
    if (isLoad) {
        if (ZomModel == undefined)
            loadZoom();
    } else {
        // defultmaterial(ZomModel);
        if (ZomModel == undefined) return;
        ZomModel.traverse(object => {
            if (!object.isMesh) return
            scene.remove(object);
            console.log('dispose geometry!')
            object.geometry.dispose()
            if (object.material.isMaterial) {
                cleanMaterial(object.material)
            } else {
                // an array of materials
                for (const material of object.material) cleanMaterial(material)
            }
            object.geometry = undefined;
            object = undefined;
        });
        scene.remove(ZomModel);
        ZomModel = undefined;
    }
}

const cleanMaterial = material => {
    console.log('dispose material!')
    // dispose textures
    for (const key of Object.keys(material)) {
        const value = material[key]
        if (value && typeof value === 'object' && 'minFilter' in value) {
            console.log('dispose texture! ' + key)
            value.dispose();
        }
    }
    material.dispose();
    material = undefined;

}
Yogesh Bangar
  • 480
  • 4
  • 12