'Three.js Collada - What's the proper way to dispose() and release memory (garbage collection)?
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();
}
Solution 1:[1]
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);
Solution 2:[2]
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
}
}
}
});
}
Solution 3:[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();
}
}
}
});
}
Solution 4:[4]
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();
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Gaurav Kale |
Solution 2 | deejbee |
Solution 3 | Paul E |
Solution 4 |