-
-
Notifications
You must be signed in to change notification settings - Fork 36.1k
SkeletonUtils: Add clone() helper for skinned meshes. #14494
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
I think it's good to have such a function as a work around until we solve the real problem and have a consistent approach for skeleton animation in |
|
I haven't followed up the discussion yet, but why is cloning method for |
src/animation/AnimationUtils.js
Outdated
|
|
||
| source.traverse( function ( node ) { | ||
|
|
||
| sourceNodes[ node.name ] = node; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you assume node has an unique name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unique within the subtree being cloned, yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO it may be expecting too much... As you know, name is optional in Three.js and two or more nodes have the same name. In many cases, most of non-bone nodes have an empty strings name.
If I were you, I'd write like the following by making the use of that source and clone have the same tree structure.
var clone = source.clone();
var sourceToClone = new Map();
function makeMapping( object1, object2 ) {
sourceToClone( object1, object2 );
for ( var i = 0, il = object1.chilldren.length; i < il; i ++ ) {
traverse( object1.children[ i ], object2.children[ i ] );
}
}
makeMapping( source, clone );
source.traverse( function ( node ) {
if ( ! node.isSkinnedMesh ) return;
var sourceMesh = node;
var clonedMesh = sourceToClone.get( node );
var sourceBones = sourceMesh.skeleton.bones;
clonedMesh.skeleton = sourceMesh.skeleton.clone();
clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) {
return sourceToClone.get( bone );
} );
clonedMesh.bind( clonedMesh.skeleton, sourceMesh.bindMatrix );
} );
return clone;
EDIT: Updated the code.
I'd rather it just work via
There's no guarantee the new bones will exist at the time the skinned mesh is recursively cloned. It seems like we need somewhere else to put that logic, and so a Utils class seems as good as any. This solution is another, but relies on cloning happening at the Scene level, and not all loaders return Scene objects. |
|
Sorry I wrote poorly. I think it's good to have a workaround in Utils for now. My question is why is it in |
src/animation/AnimationUtils.js
Outdated
|
|
||
| clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) { | ||
|
|
||
| return clonedNodes[ bone.name ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly do you assume bones are in a subtree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes the bones are within the subtree of the object being cloned, but not necessarily descendants of the SkinnedMesh they're associated with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to check if bones are in a subtree and halt with warn/error log if they aren't. Because it isn't guaranteed, for example users might call Utils.clone( object2 ) for the following graph. Sometimes even bones wouldn't be in the scene graph.
- object1
- bone1
- bone2
- bone1
- object2
- skinned mesh
Because I can't think of a better place to put it, and skinning is at least somewhat related to animation. Suggestions welcome. 😉 EDIT: Maybe |
|
Hm, |
examples/js/loaders/GLTFLoader.js
Outdated
|
|
||
| } else { | ||
|
|
||
| node.name = 'gltfNode' + ( nextNodeIndex++ ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this for naming an unique name as much as possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. 👍
src/animation/AnimationUtils.js
Outdated
| var sourceBones = sourceMesh.skeleton.bones; | ||
|
|
||
| clonedMesh.skeleton = sourceMesh.skeleton.clone(); | ||
| clonedMesh.bindMatrix = sourceMesh.bindMatrix; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should copy the matrix, rather than we share?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done ✅
|
Updated to ensure cloning does not depend on names. I removed the part adding unique names to glTF nodes for now, but will probably try another PR for that in the future since animation tracks still require it. |
src/animation/AnimationUtils.js
Outdated
|
|
||
| } ); | ||
|
|
||
| clonedMesh.bind( clonedMesh.skeleton, clonedMesh.bindMatrix ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copying matrix to .bindMatrix is done in SkinnedMesh.bind().
https://github.com/mrdoob/three.js/blob/dev/src/objects/SkinnedMesh.js#L112
So I think you can replace this line with
clonedMesh.bind( clonedMesh.skeleton, sourceMesh.bindMatrix );
and remove the line above clonedMesh.bindMatrix.copy( sourceMesh.bindMatrix );.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done ✅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to pass "source"Mesh.bindMatrix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed ✅
3a0e2b8 to
a9cde37
Compare
src/animation/AnimationUtils.js
Outdated
| } ); | ||
|
|
||
| return clone; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we remove sourceLookup?
source.traverse( function ( node ) {
if ( ! node.isSkinnedMesh ) return;
var sourceMesh = node;
var sourceBones = sourceMesh.skeleton.bones;
var clonedMesh = cloneLookup.get( node );
clonedMesh.skeleton = sourceMesh.skeleton.clone();
clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) {
return cloneLookup.get( bone );
} );
clonedMesh.bind( clonedMesh.skeleton, sourceMesh.bindMatrix );
} );
return clone;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed ✅
|
Thanks for the update. So, you still want this method in |
src/animation/AnimationUtils.js
Outdated
|
|
||
| clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) { | ||
|
|
||
| return cloneLookup.get( bone ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you wanna check the existence of bones in the subtree as I mentioned?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done ✅
|
I'm OK with whatever namespace for this, but I don't think I have a better idea than the current name... maybe |
src/animation/AnimationUtils.js
Outdated
|
|
||
| throw new Error( 'THREE.AnimationUtils: Required bones are not descendants of the given object.' ); | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Using .has() may look more natural?
if ( ! cloneLookup.has( sourceBone ) ) {
throw new Error( 'THREE.AnimationUtils: Required bones are not descendants of the given object.' );
}
var clonedBone = cloneLookup.get( sourceBone );
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done ✅
|
Voting for |
|
If #14574 goes in, SkeletonUtils seems like a good place to put this. 🤔 |
|
Agreed. |
Merged. |
|
@takahirox @mrdoob Does |
|
I prefer the explicit one. |
|
I have lately used this code in a project and it works great. Would love to see it in the code base 😊 |
|
Me too. I've also tested it and it works fine for me. Just one question: the use of Map() is "es2015" (ES6), is that ok? |
|
I think using @donmccurdy Any chances to update the PR? 😇 |
AnimationUtils: Clean up lint errors. AnimationUtils: Ensure .clone() does not depend on node names. GLTFLoader: Revert automatic node naming.
6d20dc2 to
427863f
Compare
|
Thanks! I've moved the |
|
@mrdoob could this be included with r103? |
|
This just solved a huge issue I was having. Thank you. |
https://discourse.threejs.org/t/solved-issue-with-gltfloader-and-reusing-geometry/6697 |
Done! 😇 |
|
Thanks! |
Is there an option to clone the materials as well? In my case, I'd like some of my skinnedmesh objects to be transparent (but not all), even though they've been cloned from the same original. |
|
|
|
Three.is is frequently used in the world of character animations. I can’t find no functions, no working examples related to the replace of parts of characters. Almost all characters are composed by skinnedMesh children and a lot of time is necessary change a part of the character like clothes, hair, face within characters that have same skeleton without broke animation (ex. Readyplayes characters). Clone and SkeletonUtils.Clone doesn’t solve this problem. Exist one more specific function? |
Fixes #5878, fixes #11574.
Adds a helper method,
SkeletonUtils.clone( object )that preserves SkinnedMesh/Bone relationships. Ideally this would "just work" with the usualobject.clone(), but I can't see any clean way of doing that — bones are not necessarily children of the SkinnedMesh(es?) they're associated with, and trying to connect them requires logic that would need to live somewhere outside the recursive callback series. This helper method provides an easy way to work around the problem.Based on https://gist.github.com/zellski/be4e9207ab8e70c4e89062d48ce345ba#file-gltf_utils-js-L19 by @zellski and https://gist.github.com/cdata/f2d7a6ccdec071839bc1954c32595e87 by @cdata.