Page 1 of 2

Support for Skeletal Blend Masks

Posted: Sun Mar 02, 2008 4:52 pm
by Injector
I had a go at adding support for blend masks for skeletal animations today and it was actually really to easy to do that. The idea is that instead of applying a single weight factor for a skeletal animation, weight factors can be assigned per bone, greatly simplifying building layered animations at run-time (e.g. a character drawing a sword while running).

But before submitting my changes as a patch, I would like to know whether what I did can be considered the "correct" way to do it from a design standpoint, or if there are some things I missed that have to changed/added before this can be accepted as a patch, so here goes:

Additional methods/data in OgreAnimationState.h:

Code: Select all

      /// create a new blend mask with the given number of entries
      void createBlendMask(ushort p_BlendMaskSize, bool p_Fill = true);
      /// destroy the currently set blend mask
      void destroyBlendMask();
      /// set the blend mask (allowing for per bone weights)
      void setBlendMask(const Real* p_BlendMask, ushort p_BlendMaskSize);
      /// get the current blend mask (const version, may be 0) 
      const Real* getBlendMask() const {return mBlendMask;}
      /// get the current blend mask (may be 0) 
      Real* getBlendMask() {return mBlendMask;}
      /// return whether there is currently a valid blend mask set
      bool hasBlendMask() const {return mBlendMask != 0;}
      /// set the weight for the bone identified by the given handle
      inline void setBoneWeight(ushort p_BoneHandle, Real p_Weight)
        assert(mBlendMaskSize > p_BoneHandle);
        mBlendMask[p_BoneHandle] = p_Weight;
      /// get the weight for the bone identified by the given handle
      inline Real getBoneWeight(ushort p_BoneHandle) const
        assert(mBlendMaskSize > p_BoneHandle);
        return mBlendMask[p_BoneHandle];
      /// the blend mask (containing per bone weights)
      Real* mBlendMask;
      /// the size of the blend mask (array size)
      ushort mBlendMaskSize;
One additional method in OgreAnimation.h:

Code: Select all

     void apply(Skeleton* skeleton, Real timePos, float weight,
       const float* blendMask, Real scale);

Code: Select all

    void Animation::apply(Skeleton* skel, Real timePos, float weight,
      const float* blendMask, Real scale)
      // Calculate time index for fast keyframe search
      TimeIndex timeIndex = _getTimeIndex(timePos);

      NodeTrackList::iterator i;
      unsigned short handle = 0;
      for (i = mNodeTrackList.begin(); i != mNodeTrackList.end(); ++i)
        // get bone to apply to 
        Bone* b = skel->getBone(i->first);
        i->second->applyToNode(b, timeIndex, blendMask[handle++] * weight, scale);
Finally, in OgreSkeleton.cpp, the calls to Animation::apply within the setAnimationState are adjusted like this:

Code: Select all

        // tolerate state entries for animations we're not aware of
        if (anim)
            anim->apply(this, animState->getTimePosition(), animState->getWeight() * weightFactor,
              animState->getBlendMask(), linked ? linked->scale : 1.0f);
            anim->apply(this, animState->getTimePosition(), 
              animState->getWeight() * weightFactor, linked ? linked->scale : 1.0f);
That's it already - basically. Note that the default behaviour doesn't change, the mask will be 0 by default. Also, the memory overhead will be minimal if this feature is not used. Minimizing the overhead is also the reason why I went for a plain float array rather than an stl container.

Please let me know what you think.

Posted: Sun Mar 02, 2008 7:50 pm
by sinbad
I think it's a great idea, thanks!

On the implementation, I have a few comments:

1. I'd go for 'float' rather than 'Real' on the masks, since that's consistent with the 'weight' type and we don't need double-precision on this.

2. I'm a little uncomfortable with the user having to keep a separate structure allocated outside the AnimationState - it feels like that might be dangerous. I realise why you did it, to avoid having an extra STL container internally, but how about having a pointer to a (appropriately typedeffed) std::vector<float> inside AnimationState which is simply created when createBlendMask() is called? It can be destroyed either manually or at destruction of the AnimationState but keeps things all in a neat package without really needing much extra memory (just the vector housekeeping), and it keeps the size aspects together.

3. 'getBoneWeight' and 'setBoneWeight' feel wrong in terms of their name, since 'mask' is used elsewhere and 'weight' can be a little ambiguous with animation weight etc. I think we should use one term throughout - 'mask' is acceptable although its bitwise connotations make me wonder whether there's a better term, but I can't think of one right now that doesn't involve 'weight' which is potentially confusing, so I'm ok with mask for now.

I think this will be a very useful addition.

Posted: Sun Mar 02, 2008 9:14 pm
by xavier
I would also recommend contacting novaumas, as he is working on very similar runtime code right now -- I believe he wanted this exact functionality so shoot him a PM if you could.

Posted: Sun Mar 02, 2008 10:42 pm
by novaumas
I think it's a great and necessary addition to the current Ogre animation functionalities. Per bone animation weights are really useful.

I don't know if it's really a feature to let the user too much control over the real structure behind the implementation, even if it's something simple like a vector. Maybe the get and set blend mask functions could follow the _getBlendMask and _setBlendMask nomenclature to indicate that it may not be safe to call them if you don't know what you're doing? I'd also use the STL whenever possible :D

Other than that, great work. I hope it makes it into Ogre, I'm eager to use it in my little project, as I had some interesting ideas using this feature :D. Thanks!

Posted: Mon Mar 03, 2008 2:52 pm
by iloseall
In our project, we need our arts make skeleton animation for evry body with part weight(
juse like pre multiply "blend mask") now.
I shall like try to replace that with separate blend mask throught the patch.
Thanks your addition.

about the float* :
Can We make a BlendMaskManager for store the blend mask values to hide float pointer?
juse like :

Code: Select all

AnimationBlendMaskPtr mask=BlendMaskManager::getsingleton().getBlendMask(name);
just use name:

Posted: Mon Mar 03, 2008 7:39 pm
by Injector
Patch submitted.

Edit: Damn that refresh button... :oops:

As suggested by novaumas, I added the underscore to the _setBlendMask method. Also, the data is now held within an stl vector which is dynamically allocated when requested. Thirdly, setBoneWeight/getBoneWeight have been renamed to setBlendMaskEntry/getBlendMaskEntry to be more consistent with the names of the other methods.

Concerning the BlendMaskManager idea, I am not sure if a manager is really necessary for this.

Posted: Mon Mar 03, 2008 10:54 pm
by novaumas
I liked the setBoneWeight and getBoneWeight names better, as it made really clear what was going on. The current names are ok too, but I just feel they obscure it a little bit :?

I too don't see a real need for a BlendMaskManager... I don't see myself using it in a significant manner at least.

Thanks! Hope that there's a 1.4.7 release that includes this patch :wink:

Posted: Tue Mar 04, 2008 5:53 am
by Chaster
Hey, cool - I was just thinking that this functionality would be great if Ogre had it...


Posted: Tue Mar 04, 2008 7:48 am
by Injector
I liked the setBoneWeight and getBoneWeight names better.
I do not really have a preference concerning the method names - I think both are ok. But I am sure Sinbad will see to it (I will probably stick with _setBlendMask anyway) :wink: .

Posted: Sat Mar 15, 2008 9:02 am
by ShadeOgre
novaumas wrote:I liked the setBoneWeight and getBoneWeight names better, as it made really clear what was going on. The current names are ok too, but I just feel they obscure it a little bit :?

I too don't see a real need for a BlendMaskManager... I don't see myself using it in a significant manner at least.

Thanks! Hope that there's a 1.4.7 release that includes this patch :wink:
Sorry I'm a newbe: where can I get the patches? (especially, where can I get this patch or code)

Posted: Sat Mar 15, 2008 5:39 pm
by Injector

Posted: Mon Mar 17, 2008 7:20 am
by ShadeOgre
I tried your patch (I'm kind of newbe in Ogre but I was wandering, how this thing wasn't in the engine until you made that, It's basic, I think). I applied it to 1.4.7's source code. It works, if I change nothing (if I don't call createBlendMask) and still it makes what it supposed to do if I call createBlendMask but let the mask as 1.0f s or simply modify all the mask elements with the same weight, but If I try to modify only a part of it, it fails (it modifies but not that part...).

So the code works until I try to modify only a subtree in the BlendMask. If I do this, it makes wired things: I try to disable or slow down the left arm (begin with shoulder.l) and the part of the foot (!) is affected. Here's my code:
In createScene():

Code: Select all


		vector<unsigned short> v;
		vector<String> vs;
		this->getInfectedBoneHandles(g_AvatarEntity->getSkeleton()->getBone("shoulder.l"), v);
		Ogre::AnimationState::BoneBlendMask* bm = 
			const_cast<Ogre::AnimationState::BoneBlendMask*>( g_AnimationBlender->getSource()->getBlendMask() );
		for( int i = 0; i < v.size(); i++ )
			//g_AnimationBlender->getSource()->setBlendMaskEntry(v[i], 0.0f);
			(*bm)[v[i]] = 0.0f;
			vs.push_back( g_AvatarEntity->getSkeleton()->getBone(v[i])->getName() );
Again: if I set all the mask entries to 1.0 it works correctly (even if I do that in this way -> if I get the master bone with getInfectedBoneHandles (means: all the bones) and set it to whatever, it makes what I expect).

getInfectedBoneHandles is my function to collect all the bones under the specified bone in the hiearchy. It collects them to v. vs is an arbitary variable to test whether I pick the right bones or not (I tested, it recieved the right hiearchy, so v containts the good bone handles). Is there anything I missed?

By the way, heres getInfectedBoneHandle's source (but I tested and it return with the right handle-list, as I sad):

Code: Select all

void getInfectedBoneHandles(Bone* in_bone, vector<unsigned short> &handles)

		Ogre::Node::ChildNodeIterator iter = in_bone->getChildIterator();
		while( iter.hasMoreElements() )
			getInfectedBoneHandles( dynamic_cast<Ogre::Bone*>(iter.getNext()), handles );			

Posted: Mon Mar 17, 2008 8:40 am
by Injector
Thanks for the info, I'll check. Probable reason is that the node track list does not necessarily store the tracks in ascending bone id order (or some tracks may have been optimised out).

Edit: I can't test it right now, but I think I found the problem. In OgreAnimation.cpp (apply() method, 344ff), try replacing

Code: Select all

        i->second->applyToNode(b, timeIndex, (*blendMask)[handle++] * weight, scale);

Code: Select all

        i->second->applyToNode(b, timeIndex, (*blendMask)[b->getHandle()] * weight, scale);

Posted: Mon Mar 17, 2008 7:35 pm
by ShadeOgre
Thank U, it seems perfect ;) I hope it'll be the part of the code as fast as possible. I can't believe that nobody didn't missed this feature until now...

Posted: Tue Mar 18, 2008 8:21 am
by lordofcoder
How about its performance?
In small-scale application,it looks good,but i warry about that it will serious reduce skeleton system performance
Is there some performance evaluation?

Posted: Tue Mar 18, 2008 11:02 pm
by Praetor
I just applied this patch to HEAD. I did a few small-scale tests with Ogre's skeletal animation demo and everything behaved properly. I included the bone->getHandle() fix that you posted above, Injector. I did not do any sort of stress or performance testing. With it part of HEAD hopefully it will get some wider use. I welcome any more in-depth testing or any other suggestions to add to this. As it is now, I think this a very useful and robust enhancement.

Posted: Wed Mar 19, 2008 10:23 am
by Injector
I just applied this patch to HEAD.
Great, thanks. :D
How about its performance?
While I did not profile the code, given that the only computational overhead is indexing into the weight array once per bone (per enabled animation state), I really don't think there will be any performance problems.

Posted: Sat Apr 12, 2008 12:35 pm
by ShadeOgre
Hy, I'm in trouble again. The blend-mask system works well, I just can't get what I want.

I try to make an animation system based on animation-blending ( ... l_01.shtml). My problem is, that if I want to make a mix from 2 animations (standing + walking) with partial blending (with the blend-mask system) I get incorrect result.

I tried a lot and I think the problem is with the basic animation system in Ogre. If you put 0.0 weight to a bone, then it won't be transformed, but if you blend it with another animation with 1.0 weight in that bone the result will be an interpolation between the basic mesh-skeleton-bone and the 1.0 weighted transformed animation-bone.

To make it clear I made some pics (sorry for the big differences, but the development must go on... :wink: ):
1.) Standing animation with blend-mask -> everything got 0.0 bone-blend-weight except the arms (which have 1.0), so only the arms have to animate during the animation: Image

2.) Walking animation with blend-mask -> I put 0.0 weights to the arms -> it doesn't transform the arms, works correctly:

3.) The problem is that if I blend this 2 animation together I don't get what I want/expect (walk with no arm animation): it's like if the 2 FULL animation would be blended together:

So the question is, how can I get what I want? Is it impossible to completely disable some bones in an animation?

Posted: Sat Apr 12, 2008 4:35 pm
by Injector
Which blend mode are you using on your skeleton? If you haven't already, try setting it to ANIMBLEND_CUMULATIVE:

Code: Select all


Posted: Sat Apr 12, 2008 5:23 pm
by ShadeOgre
Thanks, you helped me again ;)

Posted: Mon Apr 14, 2008 6:58 pm
by ShadeOgre
Just a remark: it would be great if I could set the initial weight; there's a 2nd parameter of createBlendMask (fill = true) -> I think it would be better if instead of this true/false we could set the initial weight (I mean -> float base_weight = 0.0f or 1.0f) and/or an already initialized blend mask (vector<float>* blend_mask)

Posted: Sun Apr 20, 2008 12:42 pm
by Injector
Regarding the first suggestion, here's the patch:

Code: Select all

Index: OgreMain/include/OgreAnimationState.h
RCS file: /cvsroot/ogre/ogrenew/OgreMain/include/OgreAnimationState.h,v
retrieving revision 1.30
diff -u -r1.30 OgreAnimationState.h
--- OgreMain/include/OgreAnimationState.h	18 Mar 2008 21:56:57 -0000	1.30
+++ OgreMain/include/OgreAnimationState.h	20 Apr 2008 11:35:10 -0000
@@ -116,10 +116,10 @@
        * @param blendMaskSizeHint 
        *   The number of bones of the skeleton owning this AnimationState.
-       * @param fill
-       *   If true, the all blend mask entries will be set to 1.0.
+       * @param initialWeight
+       *   The value all the blend mask entries will be initialised with (negative to skip initialisation)
-      void createBlendMask(size_t blendMaskSizeHint, bool fill = true);
+      void createBlendMask(size_t blendMaskSizeHint, float initialWeight = 1.0f);
       /// destroy the currently set blend mask
       void destroyBlendMask();
       /** @brief set the blend mask data (might be dangerous)
Index: OgreMain/src/OgreAnimationState.cpp
RCS file: /cvsroot/ogre/ogrenew/OgreMain/src/OgreAnimationState.cpp,v
retrieving revision 1.35
diff -u -r1.35 OgreAnimationState.cpp
--- OgreMain/src/OgreAnimationState.cpp	18 Mar 2008 21:56:57 -0000	1.35
+++ OgreMain/src/OgreAnimationState.cpp	20 Apr 2008 11:35:11 -0000
@@ -213,13 +213,13 @@
-    void AnimationState::createBlendMask(size_t blendMaskSizeHint, bool fill)
+    void AnimationState::createBlendMask(size_t blendMaskSizeHint, float initialWeight)
-        if(fill)
+        if(initialWeight >= 0)
-          mBlendMask = new BoneBlendMask(blendMaskSizeHint, 1.f);
+          mBlendMask = new BoneBlendMask(blendMaskSizeHint, initialWeight);
Regarding the second one: You can already do this, because _setBlendMask will create the blend mask if it is non-existent.

Posted: Thu Apr 24, 2008 5:03 pm
by MattStevens
I used your patch but I had a bug where the mask wasn't applied to the correct bones. The same bug as ShadeOgre. The fix you proposed fixed the problem, but it is still not in the official patch I downloaded. I think it should, because it can easily confuse people (and, after all, it's a bug)

Thanks for the skeleton blend mask, it's very useful.

- Matt Stevens

Posted: Thu Apr 24, 2008 7:50 pm
by Praetor
The "official" patch reflects the previous patch submission and is available in HEAD. This latest patch has not been applied to HEAD yet, but it will be.

Posted: Sat Apr 26, 2008 2:40 am
by Praetor
Injector, could you please collect the latest changes into a patch and put that up at sourceforge for me? I've been too busy lately to give this the attention it deserves.