In this case, I would like for the joint to play the most recently played animation. For example, I have a rootMask that will often be playing the “walk” animation, and while walking, the upperBodyMask can play the “swing” animation, but currently I don’t think it works unless I stop the “walk” animation on the entire rootMask first since the rootMask is also affecting the joints in the upperBodyMask.
Hmm i must have been unaware of this. Can you point me to the relevent code or example? Or are you referring to the PhysicsLinks and BoneLink? In which case I’m not sure if I’m using them to their fullest potential. I just get the PhysicsLink or BoneLink associated with the armature mask immediately prior to setting dynamic mode.
Essentially, I am trying to organize my project in a way that I can do something like this within my class that manages the current animation state, to easily toggle a set of bones between running animations and operating in ragdoll/dynamic mode.
AnimationMask upperBodyMask;
AnimationMask legsMask;
AnimationMask rootMask;
public void upperBoyFlinch(){
upperBodyAnimTimer = 1.0f; //set timer so upper body won't run normal animation cycle while flinch is happening
bonesList = getListOfBonesForMask(upperBodyMask);
for(Joing joint : bonesList){
selectedDynamicAnimControl.findBoneLink(joint.getName()).setDynamic(flinchVec);
}
}
public void update(){
if( upperBodyUniqueAnimTimer < 0){
//play default walk animation if its not already playing
animComposer.setCurrentAction(defaultWalkAnim, "upperBody");
} else{
upperBodyUniqueAnimTimer -= tpf;
}
}
I think it should work now if I use the code you suggested to get the list of joints in a getListOfBonesForMask() method. But this is the way my anim state manager is set up from using the old animation system with animChannels, where I had a unique timer for each channel to allow that channel to do non-default animation cycle things temporarily, and then when the timer is up the update loop takes care of resuming the default animation cycle on that layer
Personally, I’ve never loved the current ArmatureMask class. Unfortunately this is a problem that we have inherited from previous engine versions.
Method signature is not homogeneous.
There are methods that return an ‘ArmatureMask’ and others void.
There is a method called addBones, when the whole API refers to Joint.
You have to pass the Armature as a parameter every time, which seems repetitive to me.
The only good thing is that the new animation system refers to the AnimationMask interface. So everyone is free to write their own implementation. IMO it would be better to deprecate the current one in favor of a better one in the next releases. If it can be useful as a starting point for an interesting discussion, here is my version with the addition of the ‘getAffectedJoints’ method suggested by @Ali_RS and @sgold :
package com.capdevon.anim;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.anim.AnimationMask;
import com.jme3.anim.Armature;
import com.jme3.anim.Joint;
/**
* An AnimationMask to select joints from a single Armature.
* @author capdevon
*/
public class AnimMaskBuilder implements AnimationMask {
private static final Logger logger = Logger.getLogger(AnimMaskBuilder.class.getName());
private final BitSet affectedJoints;
private final Armature armature;
/**
* Instantiate a mask that affects no joints.
*
* @param armature
*/
public AnimMaskBuilder(Armature armature) {
this.armature = armature;
this.affectedJoints = new BitSet(armature.getJointCount());
logger.log(Level.INFO, "Joint count: {0}", armature.getJointCount());
}
/**
* Add all the bones of the model's armature to be influenced by this
* animation mask.
*
* @return AnimMaskBuilder
*/
public AnimMaskBuilder addAllJoints() {
int numJoints = armature.getJointCount();
affectedJoints.set(0, numJoints);
return this;
}
/**
* Add joints to be influenced by this animation mask.
*
* @param jointNames
* @return AnimMaskBuilder
*/
public AnimMaskBuilder addJoints(String...jointNames) {
for (String jointName: jointNames) {
Joint joint = findJoint(jointName);
affectedJoints.set(joint.getId());
}
return this;
}
private Joint findJoint(String jointName) {
Joint joint = armature.getJoint(jointName);
if (joint == null) {
throw new IllegalArgumentException("Cannot find joint " + jointName);
}
return joint;
}
/**
* Add a joint and all its sub armature joints to be influenced by this
* animation mask.
*
* @param jointName the starting point (may be null, unaffected)
* @return AnimMaskBuilder
*/
public AnimMaskBuilder addFromJoint(String jointName) {
Joint joint = findJoint(jointName);
addFromJoint(joint);
return this;
}
private void addFromJoint(Joint joint) {
affectedJoints.set(joint.getId());
for (Joint j: joint.getChildren()) {
addFromJoint(j);
}
}
/**
* Remove a joint and all its sub armature joints to be influenced by this
* animation mask.
*
* @param jointName the starting point (may be null, unaffected)
* @return AnimMaskBuilder
*/
public AnimMaskBuilder removeFromJoint(String jointName) {
Joint joint = findJoint(jointName);
removeFromJoint(joint);
return this;
}
private void removeFromJoint(Joint joint) {
affectedJoints.clear(joint.getId());
for (Joint j: joint.getChildren()) {
removeFromJoint(j);
}
}
/**
* Add the specified Joint and all its ancestors.
*
* @param jointName the starting point (may be null, unaffected)
* @return AnimMaskBuilder
*/
public AnimMaskBuilder addAncestors(String jointName) {
Joint joint = findJoint(jointName);
addAncestors(joint);
return this;
}
private void addAncestors(Joint start) {
for (Joint joint = start; joint != null; joint = joint.getParent()) {
affectedJoints.set(joint.getId());
}
}
/**
* Remove the specified Joint and all its ancestors.
*
* @param jointName the starting point (may be null, unaffected)
* @return AnimMaskBuilder
*/
public AnimMaskBuilder removeAncestors(String jointName) {
Joint joint = findJoint(jointName);
removeAncestors(joint);
return this;
}
private void removeAncestors(Joint start) {
for (Joint joint = start; joint != null; joint = joint.getParent()) {
affectedJoints.clear(joint.getId());
}
}
/**
* Remove the named joints.
*
* @param jointNames the names of the joints to be removed
* @return AnimMaskBuilder
*/
public AnimMaskBuilder removeJoints(String...jointNames) {
for (String jointName: jointNames) {
Joint joint = findJoint(jointName);
affectedJoints.clear(joint.getId());
}
return this;
}
/**
* Get the list of joints affected by this animation mask.
*
* @return
*/
public List<Joint> getAffectedJoints() {
List<Joint> lst = new ArrayList<>();
for (Joint joint : armature.getJointList()) {
if (contains(joint)) {
lst.add(joint);
}
}
return lst;
}
@Override
public boolean contains(Object target) {
Joint joint = (Joint) target;
return affectedJoints.get(joint.getId());
}
}
I like AnimMaskBuilder a lot, but not enough to add it to jme3-core myself. If someone else wants to add it to jme3-core, I won’t stop them.
The discussion about ArmatureMask seems off-topic to me. DynamicAnimControl interposes itself between the AnimComposer and the SkinningControl. It has full control over all joint transforms the SkinningControl receives. It can pass the ones generated by AnimComposer through unchanged (kinematic mode) or replace them with something totally different (dynamic mode).
For the use case of a brief upper-body flinch in a humanoid character, it’s not necessary to do anything to the AnimComposer. What’s needed are mechanisms to specify the affected bone links in the DynamicAnimControl. Assuming those links form a complete subtree of the ragdoll hierarchy, we already have:
public void setDynamicSubtree(PhysicsLink rootLink,
Vector3f uniformAcceleration, boolean lockAll);
and
public void animateSubtree(PhysicsLink rootLink, float blendInterval);
The thing that especially bugged me about this part is that if you pass a totally foreign armature to these methods then it will return nonsense results.
On the other hand, I understand the desire not to have a generally redundant reference inside of ArmatureMask… especially since there really are cases where the same AnimationMask instance can be used for many different Armature instances. (ie: shared masks across multiple instances of the same model).
Thank you all for the help, I believe I have all the information I need to get things working how I’d like now.
Since an ArmatureMask isn’t made to work with DAC, i will keep on my current path of using my custom BoneChannel class to store the DAC’s BoneLinks that best correspond to the ArmatureMask for that channel. So I will also be mindful when using the DACWizard to setup a model and its bone links so that they correspond cleanly to the respective ArmatureMask’s joints.
I also notice no other jme users have seemed to show any interest in my idea of making a class that works like AnimChannel from the old system, I’m guessing not many others are in a similar position where they need to upgrade from the old anim system to the new, so the BoneChannel class I made likely looks like more clutter than it does help. But if anyone by chance is lurking and wants a complete version of the class feel free to leave a message and I’ll post it once its cleaned up and done, it saved me from refactoring at least a days worth of code when upgrading from the old system, and more importantly allows my anim state manager class to remain small since I can put much of the ArmatureMask related code for keeping track of the time/speed/loopmode/blending into the BoneChannel class instead.
Another thing to consider when messing around with abstractions and designs is the difference between “things I will use to wire this all together” and “things the engine will use to animate my spatial as quickly as possible”.
For example, a joint list is nice to have at wiring time and can be used as the source for making armature masks, DACs, whatever… while at runtime, the ArmatureMask has a lot going for it.
I wonder if in the original case it would have been enough to hold onto your joint lists and create everything you need (armature masks, whatever) from that… rather than trying to reverse engineer armature masks.
I don’t know if the importers are already muddying this issue or not.
Coming back to this now that I’ve made more progress:
So I worded it wrong since technically a bone can be in more than one mask, but I should have instead said that the result is buggy if you do put a bone in two masks.
For example, if there is a root Mask (containing every joint) and a Head mask (containing just head joints), and the root mask is playing walk, If I then play a talk animation on the head, it will just keep playing the walk animation and occasionally twitch (as if it is trying to play the talk animation for 1 frame everytime the walk animation ends and is about to loop again).
So is this intended use case? If so then I think I was originally correct to say that I will need to keep a list of sub-masks to put masks within masks so that a bone can operate correctly while located in more than 1 mask like I mentioned in my original post.
Because it appears the only way to play “talk” on the head while the “root” is walking is to not use a root mask, and to instead split into “head” and “non-head” mask. And then make the root mask actually a dumby class that stores a reference to the “head” and “non-head” mask so I can make the whole body walk still.
I should also note the old animation system handled this without any problems when using AnimChannels. If the channel I’m trying to play an animation on contained a bone that is already playing an animation on another channel, it would simply listen to the most recently played channel. But the new system appears to try to play both animations from 2 different masks on the same bone at the same time.
The person who designed the new animation system is no longer active. He integrated his code while it was unfinished and inadequately documented. Since that day, we’ve been struggling to recover: to figure out how it works, what’s broken, and what’s missing.
I won’t speculate about his design intent for the situation where multiple layers try to animate the same joint.
Most of the time the second one wins actually, but still is buggy and won’t blend to the next animation correctly since it still thinks the first animation needs played again as soon as the recently played one is finished.
The only time the first animation appears to be playing over the more recently played animation one is rare cases where my code is calling setTime() for the first animation while its still playing on the root mask and that seems to make that animation take over on the head as well since the head joint is in both masks.
I guess it can be okay to have the functionality work this way, but then it is just important that users know that if a joint is in two masks, they need to stop the animation from playing on that mask prior to running an animation on another mask that contains that joint (i.e achieving a non-loop mode and easy way to cancel anims). Or make sure that each joint is only located in a single mask at a time. I think I will likely do both to make sure things work as clean as possible.
My understanding is that the first one still applies its transforms to the joint, and then right after that the next one comes and operates based on those modified values.
I guess I would need to understand what setup was trying to animate head turning on top of walk but only halfway. And either way, some layer is going to reset that transform every frame or there is already problems without layers or masks. (Edit: so it wouldn’t jump around, it would be consistently mixed.)
Someone needs to setup a full simple test case so we can see if the test case is crazy or the code is buggy.