Three.js r89 memory leak with STL models - javascript

I'm having a bit of trouble with a page I'm building. I think it's fairly simple, but I'm quickly running into issues from what I think is a memory leak.
First off, I've spent the majority of the day searching for an answer, so if I've missed something obvious I'm sorry, I promise I tried. Everything I've found has pointed me to the method I'm currently using, I'm at a loss now.
I have 30 STL models, all 120kb or less that I swap between. Only 3 are on screen at a time and the viewer can swap them out to customize the complete model.
I currently change the colors of the models using:
var selectedObject = scene.getObjectByName(object);
newMaterial = '#'+matHex[newMaterial-1];
newMaterial = hexToRgb(newMaterial);
selectedObject.material.color = newMaterial;
That part works just fine and doesn't seem to slow anything down.
When it comes to replacing the model I use:
var mesh = scene.getObjectByName(object);
if (mesh instanceof THREE.Mesh)
{
scene.remove(mesh);
mesh.geometry.dispose();
mesh.geometry = null;
mesh.material.dispose();
mesh.material = null;
mesh = null;
}
After that I call a function that adds the model back into the scene:
function addHandle(){
loader.load( stlPath+'Handle'+handleID+'.stl', function ( geometry ) {
material = '0x'+matHex[handleMat-1]; //set color hex from array
var handleMaterial = new THREE.MeshPhongMaterial( { color: parseInt(material), specular: specular, shininess: shininess } );
var handleMesh = new THREE.Mesh( geometry, handleMaterial );
handleMesh.position.set( 0, 0, 0 );
handleMesh.rotation.set( Math.PI/2, - Math.PI/2, 0 );
handleMesh.scale.set( .008, .008, .008 );
handleMesh.name = "Handle";
handleMesh.id = handleID;
handleMesh.castShadow = true;
handleMesh.receiveShadow = true;
scene.add( handleMesh );
updateHandle(); //check if Handle needs to rotate
} );
}
From everything I have been able to find this is the proper method for disposing of meshes but after running through about a dozen of them the camera rotation starts to slow down, it takes slightly longer to load the next model, etc. It's especially noticeable on mobile devices.
Hopefully someone out there is able to notice something obvious that I'm missing, it would be hugely appreciated!

I think I solved the problem, and it was my fault like I suspected. I discovered that I was calling animate(); every time a model was replaced. After removing those it is running much much smoother. I'll report back if it ends up not being fixed!

Related

ID Rendering + Diffuse Rendering in THREE.js

I'm trying to use onBeforeRender to add an id material override function in my THREEjs app. This is a pretty common function for rendering applications, and I'd like to be able to use a scene.overrideMaterial to do it, but from my research it seems like I'm going to roll my own.
My approach is as follows.
When I instantiate objects:
...
m.onBeforeRender = function (){
if(Renderer._renderID){
this.material = new THREE.MeshBasicMaterial(this.userData.idColor.getHex());
this.material.needsUpdate = true;
// Attempt at using a shader material
//this.material = Renderer._idMat;
//this.material.uniforms.idColor.value = this.userData.idColor;
//this.material.uniforms.needsUpdate = true;
}
}
m.onAfterRender = function (){
if(Renderer._renderID){
this.material = this.userData.material;
}
}
...
In my render loop:
...
var idTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight);
Renderer._renderID = true;
Renderer._renderer.render(Renderer._scene, Renderer._camera, idTarget);
Renderer._renderID = false;
...
Right now the id buffer and diffuse buffer are both rendering with the diffuse material.
Any help would be much appreciated. Please let me know if additional information is needed, I will update this post.
Three.js has an official gpu picking example which can be seen here.
They seem to be rendering things in two different scenes and with vertex colors. That's not a bad approach, and i disagree with the comment and your commented out code. Changing the uniforms is not the only way to achieve this, the official example uses vertex colors and merges all the geometries.
There are many ways to skin this cat.
You should by all means avoid creating new materials in tight loops.
This is suuuuper bad:
m.onBeforeRender = function (){
if(Renderer._renderID){
this.material = new THREE.MeshBasicMaterial(this.userData.idColor.getHex()); //just a big NO
this.material.needsUpdate = true; //no it doesnt! it's a new material it has this true by default
To achieve something like this, or your own custom override:
const myMesh = new Mesh()
myMesh.myMaterials = [
someRenderMaterial,
someIDMaterial,
]
scene.traverse(o=>{if(o.myMaterials) o.material = o.myMaterials[1]})
renderer.render(scene,camera)
scene.traverse(o=>{if(o.myMaterials) o.material = o.myMaterials[0]})
renderer.render(scene,camera)
But again, there's many many ways to do this. You could just make another scene and render vertex colors. You could make an override material and render vertex colors. Many parameters, many permutations.

Three.js: flip image loaded with loadTextureCube

I have a legacy code snippet that has long since been part of a web application. The goal is to render a cube from 6 image urls via three.js. The problem is, that the bottom image is flipped around on its y axis. All other sides look fine.
There is no way to flip the original image, since they are provided by an external service. Also when I open the bottom image directly, it is displayed correctly, only in the three.js cube its flipped.
Since I have no prior experience with three.js, I first looked at the documentation for this functionality and realized that the code used in our script is deprecated. unfortunately there is no detailed documentation of the old function (THREE.ImageUtils.loadTextureCube) - or I was simply unable to find it (if someone knows where it is, a link would be great!).
I know that for the long term, a reimplementation to replace all deprecated functions would in order, but since this would take a lot more time (for someone unfamiliar with three.js), I first wanted to check if the problem can be fixed using the existing code base.
I read in various other posts/issues that this is a known issue because three.js for some internal reason defaults to flipY=true on textures. The problem I have is that I don't know (due to lack of documentation) how to change this parameter on one side of the cube only. I expect a function can be applied to the loader that could update this parameter for one of the images or a transformation could be applied to the loaded image afterwards.
I really hope somebody knows (remembers) how this can be achieved with the old loader function.
The following partial code is from my script:
// passed list of urls (images.bottom is flipped around)
var urls = [ images.left, images.right, images.top, images.bottom, images.back, images.front ];
// SCENE
scene = new THREE.Scene();
// TEXTURES
textureCube = THREE.ImageUtils.loadTextureCube( urls );
textureCube.format = THREE.RGBFormat;
textureCube.mapping = THREE.CubeReflectionMapping;
// MATERIALS
var cubeShader = THREE.ShaderLib[ "cube" ];
var cubeMaterial = new THREE.ShaderMaterial( {
fragmentShader: cubeShader.fragmentShader,
vertexShader: cubeShader.vertexShader,
uniforms: cubeShader.uniforms,
depthWrite: false,
side: THREE.BackSide
} );
cubeMaterial.uniforms[ "tCube" ].value = textureCube;
var cubeGeometry = new THREE.BoxGeometry(50,50,50);
cube = new THREE.Mesh( cubeGeometry, cubeMaterial );
scene.add( cube );
// RENDER
renderer = new THREE.WebGLRenderer();
renderer.autoClear = false;
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( containerWidth, containerHeight );
renderer.setFaceCulling( THREE.CullFaceNone );
$container.append( renderer.domElement );
...

Three.js r74 JSONLoader binds duplicate of all geometry to first bone

I'm rewriting this question since I understand more about the bug now. It looks like when using the JSONLoader in r74, the first named bone in an exported Maya scene gets a duplicate of all the geometry.
EDIT: Here's a JSFiddle
In this example I have 2 boxes. Each box is bound to a single bone, and each of those bones has keyframes that animate the position and rotation. There is another bone that has no geometry bound to it, and has keyframes that make no change to its position or rotation.
The stationary bone is called "joint1" in Maya. The bones that actually have geometry bound to them are called "joint2" and "joint3". If I were to rename the stationary bone "joint4" the result would be a duplicate of both boxes attached to the currently animating "joint2".
My guess is that either this is a bug, or I'm doing something wrong when loading the animations. Any tips would be appreciated. The only workaround I can figure out right now is to separate each animated object into a separate file, and that's really not feasible. Plus, that wouldn't solve the issue when I have a multi-bone skeleton. This example is just single bone rigs with no actual deformation.
Here's my current loader code.
//Load Scene, Materials, and Animation
var mixer, mesh;
var actions = {};
var sceneLoader = new THREE.JSONLoader();
sceneLoader.load( sceneFile, function( geometry,materials ) {
materials.forEach( function( material ){
material.skinning = true;
});
mesh = new THREE.SkinnedMesh( geometry, new THREE.MeshFaceMaterial( materials ) );
mixer = new THREE.AnimationMixer( mesh );
actions.main = mixer.clipAction( geometry.animations[ 0 ]);
actions.main.setEffectiveWeight( 1 );
actions.main.play();
scene.add( mesh );
});
//Render
var render = function () {
requestAnimationFrame( render );
controls.update;
var delta = clock.getDelta();
var theta = clock.getElapsedTime();
if ( mixer ) { mixer.update( delta ); }
renderer.render(scene, camera)
}
render();
This is still an issue, but I've found an ok workaround.
Since the duplicated geometry always gets assigned the default lambert shader that is always included when using the Maya Exporter. As long as all the objects that are intended to be kept have a material other than the default in Maya, you can insert
mesh.material.materials[0].visible = false;
into the loader code which will make any material with the default lambert invisible.
Here's a fiddle

How to remove and dispose of all child geometry and meshes from an Object3D?

In this project I am working on I have several Collada models being displayed and then removed when not needed anymore. There seems to be a memory leak somewhere in the project and I am looking for ways to get it to run as smooth as possible as time is not on my sideā€¦
I feel like I don't remove the meshes the right way and that this might cause some of the memory leakage I have.
I load the objects with LoadObject(level_1_character, "Assets/Level_1_Character.dae"); for example, where level_1_character is a Object3D. This calls the following function:
function LoadObject(name, url) {
var mesh, geometry, material, animation;
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load(url, function(col) {
mesh = col;
geometry = col.scene;
name.name = url.toString();
name.add(geometry);
});
}
I add the objects to the scene depending on the level through scene.add(level_1_character); and then remove it by doing the following:
level_1_character.traverse(function(child){
if (child instanceof THREE.Mesh) {
child.material.dispose();
child.geometry.dispose();
}
});
I am not sure if this actually fully removes the object though. It seems as though the objects still are present in memory. Any idea what I'm doing wrong here?

Three js memory management

I have a large scene with a lot of Mesh and MorphAnimMesh. I want to free memory when the meshes are removed. If i know right this is the best way to do:
for ( var i = scene.children.length - 1; i >= 0 ; i -- ) {
var obj = scene.children[i];
scene.remove(obj);
obj.deallocate();
obj.geometry.deallocate();
obj.material.deallocate();
obj.material.map.deallocate();
}
if i check the memory usage at task manager after this, nothing changes. ( tried to wait a few min for GC but nothing. ) Google Chrome memory snapshot shows the objects still there. morphTargets in THREE.Geometry #1862203 etc.
Tried to set the obj to null, but still no memory decrease.
Any idea what am i doing wrong?
Its a game with levels and the player can change from one to another. After a few change memory usage increases to really high. Thats why i want to remove all object from memory before the level change.
Most likely, you need to add some, or all, of the following:
geometry.dispose();
material.dispose();
texture.dispose();
Check out these examples:
http://mrdoob.github.com/three.js/examples/webgl_test_memory.html
http://mrdoob.github.com/three.js/examples/webgl_test_memory2.html
three.js r.60
I did try all the dispose and deallocate methods but nothing worked.
Then I did the following for my ionic application which is using webgl renderer to render a 360 image.
this.renderer = new THREE.WebGLRenderer({ antialias: true });
RicohView.prototype.stopRendering = function () {
this.canRender = false;
this.renderer.forceContextLoss();
this.renderer.dispose();
console.log('renderer disposed');
cancelAnimationFrame(this.requestId);
}
requestId is something which can be captured from
this.requestId = requestAnimationFrame(render);

Categories