Any exported scene with Morph Target animations not targeting the root node will fail. In my case it was an animation with multiple meshes each with Morph Targets and each having tracks for each of their Morph Target influences.
See Lines (1593-1597):
if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) {
outputItemSize /= trackNode.morphTargetInfluences.length;
}
You end up getting an error "cannot get length of undefined" from the middle line (1595). Because the trackNode var will always point to the root node of your export rather than the node actually being targeted by that animation track.
This is because the function GLTFExporter.Utils.mergeMorphTargetTracks() will always set a merged morphTargetTrack name to ".morphTargetInfluences".
In turn, when this track is processed, the trackBinding var gets malformed where all properties are undefined except for the propertyName which is equal to "morphTargetInfluences". Then the trackNode var tries to find the node by checking the trackBinding.nodeName which is undefined, causing the trackNode to always default to the root node of the export.
var trackNode = THREE.PropertyBinding.findNode( root, trackBinding.nodeName );
Any exported scene with Morph Target animations not targeting the root node will fail. This is why.
I figured out a two line fix for this. But my understanding of merged morphtarget tracks is slim.
On line 2256 (currently blank). Add:
mergedTrack["targetCount"]=targetCount;
And then change line 1595 to:
outputItemSize /= track.targetCount;
Basically all this fix does is add the MorphTargetInfluences length (previously calculated for the targetCount var) to the merged MorphTarget track. Then we are able to reference the appropriate morphTargetInfluence length when the merged track is being processed, even though we don't have a reference to the correct node.
If anyone has any insight for a better fix. Please let me know you're up for taking a stab at it. Otherwise I'll submit this fix as a PR.
I think ideally I'd like an export option for not merging morphtarget tracks, but that will be it's own Feature Request post.
Would you be able to share an example file that reproduces the issue? You can drag .ZIP files into GitHub comments.
I think ideally I'd like an export option for not merging morphtarget tracks...
Unfortunately that isn't currently supported by the glTF format itself. If you split the morph target tracks into different clips before exporting, the exporter will have to write 0s for all unused morph targets in each animation, so that all morph targets have some data.
@donmccurdy
This one should do nicely. Free to download, and it has multiple meshes with animation tracks for morph targets. Try importing and exporting with the animation and it will trigger the error.
https://sketchfab.com/3d-models/3-seconds-of-vacations-a5b47f042b854aa887523e49af8ee8d8
No files I can share because the bug doesn't seem to be triggered by morphTarget tracks that are already merged. And you can't save non-merged morphTarget tracks as we discussed. I'll see if I can set up a boilerplate which creates a morphTarget animation with separate tracks and triggers the error.
Unfortunately that isn't currently supported by the glTF format itself. If you split the morph target tracks into different clips before exporting, the exporter will have to write 0s for all unused morph targets in each animation, so that all morph targets have some data.
Anywhere I can go to suggest this feature for GLTF 3.0?
Tracing this bug for you in the code:
This is the line where it hard codes ".morphTargetInfluences" as the name for any merged MorphTarget track.
mergedTrack.name = '.morphTargetInfluences';
This is the line where the trackBinding is malformed with all properties being undefined except for propertyName.
var trackBinding = THREE.PropertyBinding.parseTrackName( track.name );
This is the line where the trackNode does a look up on the trackBindings undefined nodeName causing it to always grab the root node.
var trackNode = THREE.PropertyBinding.findNode( root, trackBinding.nodeName );
This is the line where you get the cannot get length of undefined error because it's trying to get .morphTargetInfluences.length from the root node instead of the target node for the merged morph target track.
outputItemSize /= trackNode.morphTargetInfluences.length;
Anywhere I can go to suggest this feature for GLTF 3.0?
Filed to https://github.com/KhronosGroup/glTF/issues/1675 – feel free to comment there if my summary missed anything!
Anywhere I can go to suggest this feature for GLTF 3.0?
Filed to KhronosGroup/glTF#1675 – feel free to comment there if my summary missed anything!
Thanks! I threw in my 2 cents. Definitely seems like a worthwhile addition and I don't see any reason it couldn't be finessed with such a flexible JSON based format.
Hopefully will have time to put together a sample project which triggers the bug soon.
Massively simplified an example to demonstrate morphTarget animations breaking GLTFExport. Getting error sooner than before, still seems to be related to improper node lookups.
All the relevant code is in bug.html. You can see errors in the console after clicking the export button. Live demo.
gltfExortBug.zip
See next comment
@donmccurdy and anyone else following along.
Here's a page that demonstrates a "bad export" where the mesh that's being targeted in a morphtarget animation is a child of the exported Group object. This export fails on this line of the GLTFExporter:
outputItemSize /= trackNode.morphTargetInfluences.length;
because the trackNode is pointing at the parent group rather than the child mesh with morph targets.
You can also see a "good export" where we export the same object but directly export the mesh with morph targets rather than the parent group object. This succeeds because the node being exported and the node with a morphTarget animation are coincidentally the same.
When morph target animations are being merged, the mergedTrack.name
is incorrectly set to ".morphTargetInfluences"
and the important data about the actual target node for that animation track is subsequently lost.
Changing this line:
mergedTrack.name = '.morphTargetInfluences';
To:
mergedTrack.name = sourceTrack.name
Fixes this bug. This makes sense because any animation track's name is supposed to also contain target node information. In the previous configuration, that was never possible.
Hi @Bug-Reaper, any word on whether your fix will be implemented? I'm facing this issue when I transfer shape keys from one mesh to another.
Hey @abelnation I'm going to make a PR for this tonight. I'm not sure it will solve your issue though as this change is specific to the GLTFExporter lib.
If you want to screenshot the console errors you get, I can confirm and also possibly address related problems in other parts of the codebase. Great to see other people doing more advanced morph target programming in threejs.
Hey sorry! I'm a bad person for not commiting this fix sooner. Going to check it's still present in the latest version and submit today. @aboutmedicine do you remember which functions you were using to transfer shape keys from one mesh to another? I'd be happy to investigate similar bugs but this change is specific to the gltfExporter helper lib.
Alright, I tested it out and the bug is very much still present. In case anyone wants a refresher on what is being fixed or wants to see an example which triggers the bug, you can check out this repo I made.
https://github.com/Bug-Reaper/Fucking-Morph-Target-Animations
It includes a technical explanation for the bug, why it occurs as well as code for re-creating the bug and a live demo.
Most helpful comment
Filed to https://github.com/KhronosGroup/glTF/issues/1675 – feel free to comment there if my summary missed anything!