visibility of LOD DAE object in Three JS - three.js

i'm trying to use the THREE.LOD object in my ThreeJS scene. i've been inspired by http://threejs.org/examples/webgl_lod.html
But I wanted to push the idea further and use DAE model (using this loader : http://threejs.org/examples/webgl_loader_collada.html)
Problem is i can't switch the visibility of the lod level. First, i tried an automated one in my Render function (based on distance to camera, found in the example):
this.m_Scene.traverse( function ( object ) {
if ( object instanceof THREE.LOD ) {
object.update( that.m_Camera );
}
} );
As it wasn't working (All my lod were displayed at the same time). I try something more manual. and it appears the Object3D.visibility attribute isn't really used by the renderer, or not inherited by children.
As far as I understand, this attribute is for meshes. But i'm not sure it's checked at render time.
So this doesn't work as expected:
var LodTemporaryObject = new THREE.LOD();
function LoadLod1()
{
//TEST COLLADA LOADER
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load(Lod2Path, function ( collada ) {
dae = collada.scene;
dae.scale.x = dae.scale.y = dae.scale.z = 0.1;
dae.updateMatrix();
dae.visible = false; //THIS HAS NO EFFECT
LodTemporaryObject.addLevel(dae,100);
AddLodToScene(LodTemporaryObject ); //where the lod is added to the threeJS scene object
} );
}
so question : how do I actually set (in)visible any Object3D or subScene ?

EDIT: The answer below is outdated. Visibility is now inherited. See, for example, Show children of invisible parents.
three.js r.71
Visibility is not inherited by children with WebGLRenderer.
The work-around is to use a pattern like so:
object.traverse( function( child ) {
if ( child instanceof THREE.Mesh ) {
child.visible = false;
}
}
three.js r.64

Thx to WestLangley answer above, i came up with an recursive solution to my problem:
first, a recursive function to update visibility of children to match the parent's:
function SetChildrenVisible(parent)
{
if(parent instanceof THREE.Object3D)
{
for(var i = 0; i< parent.children.length; i ++)
{
parent.children[i].visible = parent.visible;
SetChildrenVisible(parent.children[i]);
}
}
}
then in my render loop:
this.m_Scene.traverse( function ( object ) {
if ( object instanceof THREE.LOD ) {
//save all lodLevel state before updating
var oldVisible =[]; object.visible;
for(var i = 0; i< object.children.length; i++)
{
oldVisible.push(object.children[i].visible)
}
//Update Lod
object.update( that.m_Camera );
//Check for changes and update accordingly
for(var i = 0; i< object.children.length; i++)
{
if(oldVisible[i] != object.children[i].visible )
{
SetChildrenVisible(object.children[i]);
}
}
}
} );
Goal is to only update object whose attribute changed.

Related

THREE.js - Merging / Instancing Multiples of The Same GLTF Model, Without Shader Material..?

I am using the code below to load and clone a GLTF model. In practice this works, however it is way too resource heavy and the model is around a 2mb. It has no textures and the materials are a single MeshPhongMaterial.
I understand two ways to optimise this are to merge them into one mesh instead of cloning, however following numerous attempts (such as iterating within the load function with a for loop), I haven't been able to successfully do this. The second being instancing, however from a number of examples I understand this requires a shader material to handle to the attributes.
I'm a little out of my depth here and would massively appreciate any assistance.
Many thanks!
loader.load('obj/floor/floor-base-details-base.gltf', (gltf) => {
floorBaseModel = gltf.scene;
floorBaseModel.traverse( function ( node ) {
if ( node.isMesh ) {
node.castShadow = true;
node.receiveShadow = true;
if ( node instanceof THREE.Mesh ) {
node.material = base_material;
}
}
});
for(var i = 0; i < 15; i++){
var floorBaseClone = floorBaseModel.clone();
offsetPos = (i+1)* -595;
floorBaseClone.position.set(0,0,offsetPos);
floorBaseGroup.add(floorBaseClone);
}
});
floorBaseGroup.scale.set(1,1,1);
floorBaseGroup.position.set(0,-15,425);
scene.add(floorBaseGroup);

Three.js clone FBX with animation

I can’t seem to be able to clone an FBX model (FBX downloaded from Mixamo) while retaining animation keyframes.
Have attempted a number of approaches including using the cloneFbx gist (included in the example below); all to no avail. Even placing the entire FBXLoader() function inside a loop does not work as expected since only one of the models will animate at a time.
This issue has been partially addressed here, but I cannot seem to ‘copy’ the animation sequence as answer suggests.
Can anyone point out where I’m going wrong?
Here's a rough example of one of my tests:
Load fbx model and store animation:
var loader = new THREE.FBXLoader();
loader.load( 'models/Walking.fbx', function ( fbx ) {
clip = fbx.animations[ 0 ];
// createVehicle(fbx); // Works! Creates one animated model via FBX
// cloneFbx via: https://gist.github.com/kevincharm/bf12a2c673b43a3988f0f171a05794c1
for (var i = 0; i < 2; i++) {
const model = cloneFbx(fbx);
createVehicle(model);
}
});
Add mixers and actions based on stored clip, add model to scene:
function createVehicle(model){
model.mixer = new THREE.AnimationMixer( model );
mixers.push( model.mixer );
var action = model.mixer.clipAction( clip );
action.play();
model.traverse( function ( child ) {
if ( child.isMesh ) {
child.castShadow = true;
child.receiveShadow = true;
}
});
const x = Math.random() * groundSize - groundSize/2;
const z = Math.random() * groundSize - groundSize/2;
model.position.set(x, 0, z);
const vehicle = new Vehicle(model, x, z);
vehicles.push(vehicle);
scene.add( model );
}
Animation cycle:
if ( mixers.length > 0 ) {
for ( var i = 0; i < mixers.length; i ++ ) {
mixers[ 0 ].update( clock.getDelta() );
}
}
Couldn’t figure out an elegant solution to this. Best I could come up with is creating a loop with the loading sequence inside of it; this is very slow (since the FBX has to be parsed each time).
The key here was having an animation mixer controlling the animated objects as a group as opposed to creating a mixer per animated object.
If anyone can figure out a better solution, I would be super keen to hear it (perhaps using the cloneFbx script properly).
Create mixer, load FBX:
// Create mixer to run animations
mixer = new THREE.AnimationMixer( scene );
// Load fbx
var loader = new THREE.FBXLoader();
for (var i = 0; i < 5; i++) {
loader.load( 'models/Walking.fbx', function ( fbx ) {
mixer.clipAction( fbx.animations[ 0 ], fbx )
.startAt( - Math.random() )
.play();
createVehicle(fbx);
});
}
Create class instances, add to scene:
function createVehicle(model){
const x = Math.random() * groundSize - groundSize/2;
const z = Math.random() * groundSize - groundSize/2;
model.position.set(x, 0, z);
const vehicle = new Vehicle(model, x, z);
vehicles.push(vehicle);
scene.add( model );
}
Draw cycle:
mixer.update( clock.getDelta() );
I found out that SkeletonUtils.clone() works good for me.
https://threejs.org/docs/index.html#examples/en/utils/SkeletonUtils.clone

How to clone a Skinned Mesh?

I need to have multiple identical, animated models on a scene. If possible, I would like them to have a shared geometry and material, but if it is impossible, having them instanced per model will suffice too.
Unfortunately, the only way to achieve this result I found is to go through JSONLoader for every model instance.
SkinnedMesh does have a clone() method, but it seems not to be fully implemented yet. If used and both original and cloned mesh are present on the scene, only one will appear, and cloned one will be without animation.
I have attempted to use this example with shared skeletons:
https://github.com/mrdoob/three.js/pull/11666
...and indeed it works, but I need to be able to play different animations for every model instance, having them all play the same one is not sufficient, sadly. I hoped I could do similar hax and insert my own skeleton (made out of bones from the JSON file), but it behaves very much like if I just used clone() from SkinnedMesh.
I am using this code:
https://github.com/arturitu/threejs-animation-workflow/blob/master/js/main.js
Basically what I'd like to achieve is
var onLoad = function (geometry, materials) {
window.geometry = geometry;
character = new THREE.SkinnedMesh(
geometry,
new THREE.MeshFaceMaterial(materials)
);
character2 = character.someMagicalClone();
scene.add(character);
scene.add(character2);
(...)
I need any clue... and while I wait for help, I am busily deconstructing constructor for SkinnedMesh and JSONLoader for clues ;)
Thanks in advance!
I found a solution in this pull request:
https://github.com/mrdoob/three.js/pull/14494
in short, there are two functions added:
function cloneAnimated( source ) {
var cloneLookup = new Map();
var clone = source.clone();
parallelTraverse( source, clone, function ( sourceNode, clonedNode ) {
cloneLookup.set( sourceNode, clonedNode );
} );
source.traverse( function ( sourceMesh ) {
if ( ! sourceMesh.isSkinnedMesh ) return;
var sourceBones = sourceMesh.skeleton.bones;
var clonedMesh = cloneLookup.get( sourceMesh );
clonedMesh.skeleton = sourceMesh.skeleton.clone();
clonedMesh.skeleton.bones = sourceBones.map( function ( sourceBone ) {
if ( ! cloneLookup.has( sourceBone ) ) {
throw new Error( 'THREE.AnimationUtils: Required bones are not descendants of the given object.' );
}
return cloneLookup.get( sourceBone );
} );
clonedMesh.bind( clonedMesh.skeleton, sourceMesh.bindMatrix );
} );
return clone;
}
function parallelTraverse( a, b, callback ) {
callback( a, b );
for ( var i = 0; i < a.children.length; i ++ ) {
parallelTraverse( a.children[ i ], b.children[ i ], callback );
}
}
As I understand it rebinds cloned skeleton to the cloned mesh.
so topic example could look like:
var onLoad = function (geometry, materials) {
window.geometry = geometry;
character = new THREE.SkinnedMesh(
geometry,
new THREE.MeshFaceMaterial(materials)
);
character2 = cloneAnimated(character); // <-- used that new function
scene.add(character);
scene.add(character2);
(...)

Texture Dispose Not Working in Three JS release 73

texture.dispose() doesn't seem to work in r73 but works fine in r75. What should I do to get it up and running on r73.
It doesn't either work in r77.
When using texture.dispose() the dispose normally should call onTextureDispose :
function onTextureDispose( event ) {
var texture = event.target;
texture.removeEventListener( 'dispose', onTextureDispose );
deallocateTexture( texture );
_infoMemory.textures --;
}
but it does not. Dont know if its a bug or something but in order to deallocate the texture I had to do a material.map.dispose() while I was clearing the scene (so to prepare for other texture to be inserted in the scene).
For example:
var clearScene = function(){
stopAnimate();
if(scene && scene.children.length>0){
for( var i = scene.children.length - 1; i >= 0; i-- ) {
if( scene.children[i] instanceof THREE.Sprite ){
if( scene.children[i].material.map ) scene.children[i].material.map.dispose();
}
if( scene.children[i] instanceof THREE.Mesh ){
if( scene.children[i].material.map ) scene.children[i].material.map.dispose();
}
scene.children[i].material.dispose();
scene.remove(scene.children[i]);
scene.children.splice(i,1);
}
}
};
Note: by using just:
scene.children[i].material.dispose();
the Textures of a material don't get disposed according to the documentation. http://threejs.org/docs/#Reference/Materials/Material
Hope that helps you.

get back to normal after the traverse in threejs

in threejs am working around with the traverse method to apply the WireframeHelper for the models that is loaded using OBJMTLLoder, for this kind of models we must use traverse to apply the Wireframe for child of the Object, so here i can apply the wireframes and so on using traverse but after the traverse i can't get back to my normal object meaning that i can't remove the wireframes with the traversed mesh, the mesh is added with the scene with scene.add( wfh ); where wfh is WireframeHelper , but if i use scene.remove( wfh ); to remove the meshed WireframeHelper it doesn't work
i need to know that after the traverse we can get back to normal ?? in most cases am using traverse to make changes on my model:
Here is the code:
scene.traverse ( function (child)
{
if (child instanceof THREE.Mesh)
{
wfh = new THREE.WireframeHelper( child, 0xffffff );
scene.add( wfh );
}
});
updated code:
globalObject.traverse ( function (child) {
if (child instanceof THREE.Mesh)
{
wfh = new THREE.WireframeHelper( child,whfcolor );
wfh.name = "wireframe_helper";
wfh.material.opacity = 0.2;
wfh.material.transparent = true;
globalObject.add( wfh );
}
});
here globalObject is global variable assigned to Object now i can see the wireframe_helper on the child of the Object and can remove the wireframe by following code
globalObject.traverse( function ( child ) {
if (child instanceof THREE.Object3D)
{
//alert(child.name);
if ( child.name && child.name === "wireframe_helper" && child.material ) {
//alert('hi');male-02-1noCullingID_male-02-1noCulling.JP
globalObject.remove( child );
//child.material.wireframe = true;
}
}
});
after removed the wireframe still wireframe is remains some part of the Object any clue on this ?? and am getting
TypeError: this.children[i] is undefined
this.children[ i ].traverse( callback );
on line three.js 7885
WireframeHelper() creates an object that is added to the scene-graph. When you are using the helper inside a traverse() operation you are adding many objects to the scene. So if you want to remove them, you have to save them off to a variable (array in this case since you have many of them). So something like this should work:
First name the helper inside the first traverse():
wfh = new ...
wfh.name = "wireframe_helper";
scene.add( wfh );
then you should be able to do:
scene.traverse ( function (child)
{
if (child.name === "wireframe_helper")
{
scene.remove( child );
}
}
The code above would probably crash when trying to traverse a child that has been removed.
Here is updated code:
to_remove = [];
scene.traverse ( function (child)
{
if (child.name === "wireframe_helper")
to_remove.push( child );
}
for (var i = 0; i < to_remove.length(); i++)
scene.remove( to_remove[i] );

Resources