tldr: I found a solution for a problem I didn’t see any information on on the internet: when using glb/blender and running two animations at the same time for different bones, you need to uncheck “animation → optimize animation → force keeping channels for bones” in the export setting for glb in blender.
Full description:
So, I have a player model in blender with animations and I’m exporting the file in glb format and loading it into threejs (copying the code from three.js examples )
I wanted to have this character both run and shoot with different animations. For example, in my game, my character could run without shooting, shoot without running, or do both, three different states. I want these three different states to be possible while only creating two animations in blender. This makes sense since shooting only involves the upper body (arms) and running only involves the lower body (legs). So, I went ahead and created these two animations and exported it, but when I mixed the animations in threejs (just calling the play function on both actions) the run animation was also controlling the upper arms (and vice-versa for the shoot animation). This looked weird (the upper arms were between the T-pose and shooting and the legs were similar for running, looking more like a walk). I couldn’t find help on the internet and suspected it was a problem with the export settings in blender since it seemed to be adding keyframes to the animation. I eventually narrowed it down to the setting “animation → optimize animation → force keeping channels for bones (unchecked)” unchecking this box made the animations work as expected without interacting with each other. These settings are on the right side of the export window in blender in drop down menus. This might change between blender versions, I’m using Blender version 4.0.2
1 Like
Three.animationAction has ._iterpolants. controlling the animation in bones is based on it.
//internal use to partial animatil weight bones
async _setPartialAnimation(model, boneNames, newPose, lerpTime) {
return new Promise(resolve => {
const mixer = model.mixamo.mixer;
const action = model.mixamo.action[newPose];
action.play(); // Garante que a ação está ativa e pronta para ser reproduzida
action.setEffectiveWeight(0); // Começa com peso zero para interpolar gradualmente
// Armazena os bindings, interpolants e pesos originais se ainda não foram armazenados
if (!action._originalState) {
action._originalState = {
bindings: action._propertyBindings.slice(),
interpolants: action._interpolants.slice(),
weights: action._propertyBindings.map(binding => binding.weight)
};
}
const filteredBindings = [];
const filteredInterpolants = [];
const bindings = action._propertyBindings || [];
const interpolants = action._interpolants || [];
bindings.forEach((propertyMixer, index) => {
const { binding } = propertyMixer;
if (binding && binding.targetObject && boneNames.includes(binding.targetObject.name)) {
filteredBindings.push(propertyMixer);
filteredInterpolants.push(interpolants[index]);
}
});
action._propertyBindings = filteredBindings;
action._interpolants = filteredInterpolants;
// Define o peso dos bindings filtrados para 1
filteredBindings.forEach(binding => binding.weight = 0);
// Função para interpolar o peso dos bindings
const startTime = Date.now();
function animateWeights() {
const now = Date.now();
const elapsed = now - startTime;
const t = Math.min(1, elapsed / (lerpTime * 1000));
// Interpolação dos pesos da nova animação
action.setEffectiveWeight(t);
// Interpolação dos pesos dos bindings
filteredBindings.forEach(binding => binding.weight = t);
if (t < 1) {
requestAnimationFrame(animateWeights);
// Garante que outras animações não afetem a parte superior do corpo
mixer._actions.forEach(otherAction => {
if (otherAction !== action) {
if (!otherAction._originalState) {
otherAction._originalState = {
bindings: otherAction._propertyBindings.slice(),
interpolants: otherAction._interpolants.slice(),
weights: otherAction._propertyBindings.map(binding => binding.weight)
};
}
const otherBindings = otherAction._propertyBindings || [];
const otherInterpolants = otherAction._interpolants || [];
const newOtherBindings = [];
const newOtherInterpolants = [];
otherBindings.forEach((propertyMixer, index) => {
const { binding } = propertyMixer;
if (binding && binding.targetObject && !boneNames.includes(binding.targetObject.name)) {
newOtherBindings.push(propertyMixer);
newOtherInterpolants.push(otherInterpolants[index]);
}
});
otherAction._propertyBindings = newOtherBindings;
otherAction._interpolants = newOtherInterpolants;
// Define o peso dos bindings restantes para 1
newOtherBindings.forEach(binding => binding.weight = 1);
}
});
} else {
resolve(action);
}
}
animateWeights();
});
}
this funcion is used here:
https://didisoftwares.ddns.net/6/index.html
in console use:
MM.setAnimationLowerBody(model,'walk01',1);
wow i had to create an account just to thank you for how much time you have saved me, really thanks, i had no idea that it was blender’s fault. <3