How to clone a Skinned Mesh? - animation

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);
(...)

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

Define prototype.updatePosition so that when an Object is clicked it animates/rotates/scales to certain value

Having problem understanding the class system in Three.js
I have a code in player.js :
function Player() {
var mesh = new THREE.Object3D();
this.player = null;
this.loader = new THREE.JSONLoader();
this.name = 'player';
this.loader.load(
'obj/models/minecraft_sole.json',
function ( geometry, materials ) {
var material = new THREE.MultiMaterial( materials );
this.player = new THREE.Mesh( geometry, material );
this.player.position.set(0, 0, 0);
this.player.scale.set(.5,.5,.5);
this.player.castShadow = true;
this.player.receiveShadow = false;
mesh.add( this.player );
}
);
Player.prototype.constructor = Player;
Player.prototype = Object.create(THREE.Object3D.prototype);
}
Player.prototype.updatePosition = function(){
this.mesh.position.y += 0.05;
}
And basically what I'm trying to achieve:
In main.js after all standard setup for init()/render()/animate() and all...
I create variable called johny:
var johny = new Player();
Now everything loads great and all, but in player.js i want to be able to define some prototype ? method, and in that method I want to listen for a click event. After that event is called I want my player mesh to animate to certain position or start rotating/scaling.
My pseudo code for better understanding is:
var Player = function(){
// define mesh and all
}
player.add.eventListener( 'click' ){
//code to animate player
}
Remember that all this; I want to be a part of player.js so that after calling:
var johny = new Player();
I don't have to add event listening functions to the main.js and all that.
And second of all I want my code to be modular, as You may already noticed :)
So I did managed to understand it.

Object color apply does not work?

I have loaded object in three js. When i apply the color it does not work.
I have below code used i got error in,
child.material.color is undefined
I have used below code
var geometry = new THREE.PlaneGeometry( 0.8, 1 );
var loader = new THREE.OBJLoader( manager );
loader.load( file, function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material.ambient.setHex(0xFF0000);
child.material.color.setHex(0x00FF00);
}
} );
I think child.material.color.setHex() is enough to apply/set the color of an object.
So try to remove the following line from your code.
child.material.ambient.setHex(0xFF0000);
Here is the working fiddle sample: http://jsfiddle.net/ev3tuLuc/109/

WebGLRenderingContext ERROR loading texture maps

First of all, thank you for this wonderfull work, i'm having a lot of fun working with three.js.
I tried to find answer about a recurent issue, .WebGLRenderingContext: GL ERROR :GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 2
I'm making a website in webgl, i spend few week understanding all about three.js but i can't fix this issue.
I get this message on Chrome and firefox (latest) each time i try to load a canvas into a map, bumpmap and specmap.
All my mesh are loaded from obj files, by the way i rewrote OBJMTLLoader.js to be able to load more parameters from obj files and more.
here the code used to load image.
THREE.MTLLoader.loadTexture = function ( url, mapping, onLoad, onError ) {
var isCompressed = url.toLowerCase().endsWith( ".dds" );
var texture = null;
if ( isCompressed ) {
texture = THREE.ImageUtils.loadCompressedTexture( url, mapping, onLoad, onError );
} else {
var image = new Image();
texture = new THREE.Texture( image, mapping );
var loader = new THREE.ImageLoader();
loader.addEventListener( 'load', function ( event ) {
texture.image = THREE.MTLLoader.ensurePowerOfTwo_( event.content );
texture.needsUpdate = true;
if ( onLoad )
onLoad( texture );
} );
loader.addEventListener( 'error', function ( event ) {
if ( onError ) onError( event.message );
} );
loader.crossOrigin = this.crossOrigin;
loader.load( url, image );
}
return texture;
};
I'm pretty sure it is from this, because when i disable this function, no more warning.
Is it because the mesh has a texture with an empty image while loading datas ?
Is there any restriction on the dimensions of image ?
For now everything works fines, but i feel strange having those message in console.
Thanks
This error become because the Three.js buffers are outdated. When your add some textures (map,bumpMap ...) to a Mesh, you must recompose the buffers like this :
ob is THREE.Mesh, mt is a Material, tex is a texture.
tex.needsUpdate = true;
mt.map = tex;
ob.material = mt;
ob.geometry.buffersNeedUpdate = true;
ob.geometry.uvsNeedUpdate = true;
mt.needsUpdate = true;
That's all folks !
Hope it's help.
Regards.
Sayris

Resources