I have a Three.js project with an animation going on and I would like to find the skeleton bones position at different times.
If I go for example to: https://modelviewer.dev/examples/animation/index.html and find the Three.js scene:
const modelViewer = document.querySelector("model-viewer");
const scene = modelViewer[Object.getOwnPropertySymbols(modelViewer)[14]];
I can then access for example the position of the LowerArmR
which is animated in this case:
scene.children[0].children[0].children[0].children[0].children[1].children[0].skeleton.bones.find(b => b.name == "LowerArmR").position
If I do that, at different points of the animation, I always get the same position:
{x: 1.86264503820865e-10, y: 0.00575238140299916, z: -1.02445485428149e-9}
If I perform this process on a humanoid avatar animating, and then try to plot the positions of the bones, I just get the T-pose, but not the actual positions in every given time:
From here I can see that:
Skeletal Animations are handled on the GPU so this data is not exactly accessible in js code at runtime.
Perhaps someone knows some workaround, but as far as I am aware there is none.
And I am looking for that workaround or canonical way to do it.
Skeletal animation (or "skinning") is applied to the individual mesh vertices on the GPU1, because there are often many more vertices than there are bones, and updating them all on the CPU would be computationally expensive.
However, the transformations of the bones themselves are computed on the CPU in three.js. The distinctions that may not be obvious here are:
.position
property of the bone, inherited from THREE.Object3D's .position property, is a local position, relative to the position of its parent, and its parent, and so on.Putting all of this together, what you want to find is the world position rather than the local position of a particular bone. The Object3D parent class has a method to help with this, object.getWorldPosition:
const position = new THREE.Vector3();
bone.getWorldPosition( position );
Note that this method incurs a bit of a performance overhead, because it first updates the bone's world matrix — traversing its ancestors — before computing the current world position. If you need the positions of many bones, or other properties like world rotation and scale, it's better to ensure all global transforms are up to date once (scene.updateMatrixWorld()
, if needed) and then decompose the object's .matrixWorld
:
const position = new THREE.Vector3();
position.setFromMatrixPosition( bone.matrixWorld );
1 If you do happen to need to compute the position of a particular vertex on the CPU, the three.js SkinnedMesh class has a helper method for this: .boneTransform().