Support for Skeletal Blend Masks

Discussion area about developing or extending OGRE, adding plugins for it or building applications on it. No newbie questions please, use the Help forum for that.
User avatar
Injector
Gremlin
Posts: 174
Joined: Wed Jan 21, 2004 2:42 pm
Location: Frankfurt, Germany

Support for Skeletal Blend Masks

Post 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];
      }
    protected:
      /// 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)
        {
          if(animState->hasBlendMask())
          {
            anim->apply(this, animState->getTimePosition(), animState->getWeight() * weightFactor,
              animState->getBlendMask(), linked ? linked->scale : 1.0f);
          }
          else
          {
            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.
User avatar
sinbad
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 19269
Joined: Sun Oct 06, 2002 11:19 pm
Location: Guernsey, Channel Islands
x 66
Contact:

Post 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.
User avatar
xavier
OGRE Retired Moderator
OGRE Retired Moderator
Posts: 9481
Joined: Fri Feb 18, 2005 2:03 am
Location: Dublin, CA, US
x 22

Post 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.
Do you need help? What have you tried?

Image

Angels can fly because they take themselves lightly.
User avatar
novaumas
Greenskin
Posts: 107
Joined: Mon Jan 21, 2008 9:44 am
Location: Barcelona
x 4
Contact:

Post 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!
User avatar
iloseall
Gremlin
Posts: 156
Joined: Sun Sep 14, 2003 3:54 am
Location: Beijing China
Contact:

Post 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);
animation->setBlendMask(mask);
or 
just use name:
animation->setBlendMask(name);
User avatar
Injector
Gremlin
Posts: 174
Joined: Wed Jan 21, 2004 2:42 pm
Location: Frankfurt, Germany

Post 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.
User avatar
novaumas
Greenskin
Posts: 107
Joined: Mon Jan 21, 2008 9:44 am
Location: Barcelona
x 4
Contact:

Post 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:
Chaster
OGRE Expert User
OGRE Expert User
Posts: 557
Joined: Wed May 05, 2004 3:19 pm
Location: Portland, OR, USA
Contact:

Post by Chaster »

Hey, cool - I was just thinking that this functionality would be great if Ogre had it...

Chaster
User avatar
Injector
Gremlin
Posts: 174
Joined: Wed Jan 21, 2004 2:42 pm
Location: Frankfurt, Germany

Post 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: .
ShadeOgre
Gremlin
Posts: 158
Joined: Mon Mar 10, 2008 10:55 pm
Location: Budapest, Hungary
x 1

Post 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)
User avatar
Injector
Gremlin
Posts: 174
Joined: Wed Jan 21, 2004 2:42 pm
Location: Frankfurt, Germany

Post by Injector »

ShadeOgre
Gremlin
Posts: 158
Joined: Mon Mar 10, 2008 10:55 pm
Location: Budapest, Hungary
x 1

Post 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

g_AnimationBlender->getSource()->createBlendMask(g_AvatarEntity->getSkeleton()->getNumBones());

		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() );
		}
		g_AnimationBlender->getSource()->getParent()->_notifyDirty();
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)
	{
		handles.push_back(in_bone->getHandle());

		Ogre::Node::ChildNodeIterator iter = in_bone->getChildIterator();
		while( iter.hasMoreElements() )
		{
			getInfectedBoneHandles( dynamic_cast<Ogre::Bone*>(iter.getNext()), handles );			
		}
	}
User avatar
Injector
Gremlin
Posts: 174
Joined: Wed Jan 21, 2004 2:42 pm
Location: Frankfurt, Germany

Post 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);
by

Code: Select all

        i->second->applyToNode(b, timeIndex, (*blendMask)[b->getHandle()] * weight, scale);
ShadeOgre
Gremlin
Posts: 158
Joined: Mon Mar 10, 2008 10:55 pm
Location: Budapest, Hungary
x 1

Post 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...
lordofcoder
Gnoblar
Posts: 8
Joined: Sat Feb 23, 2008 12:18 pm
Location: beijing,china

Post 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?
thanks
User avatar
Praetor
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 3335
Joined: Tue Jun 21, 2005 8:26 pm
Location: Rochester, New York, US
x 3
Contact:

Post 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.
User avatar
Injector
Gremlin
Posts: 174
Joined: Wed Jan 21, 2004 2:42 pm
Location: Frankfurt, Germany

Post 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.
ShadeOgre
Gremlin
Posts: 158
Joined: Mon Mar 10, 2008 10:55 pm
Location: Budapest, Hungary
x 1

Post 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 (http://www.gamasutra.com/features/20030 ... 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:
Image

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:
Image

So the question is, how can I get what I want? Is it impossible to completely disable some bones in an animation?
User avatar
Injector
Gremlin
Posts: 174
Joined: Wed Jan 21, 2004 2:42 pm
Location: Frankfurt, Germany

Post 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

 skeleton->setBlendMode(ANIMBLEND_CUMULATIVE);
ShadeOgre
Gremlin
Posts: 158
Joined: Mon Mar 10, 2008 10:55 pm
Location: Budapest, Hungary
x 1

Post by ShadeOgre »

Thanks, you helped me again ;)
ShadeOgre
Gremlin
Posts: 158
Joined: Mon Mar 10, 2008 10:55 pm
Location: Budapest, Hungary
x 1

Post 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)
User avatar
Injector
Gremlin
Posts: 174
Joined: Wed Jan 21, 2004 2:42 pm
Location: Frankfurt, Germany

Post 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 @@
       _setBlendMaskData(&(*blendMask)[0]);
     }
 	//---------------------------------------------------------------------
-    void AnimationState::createBlendMask(size_t blendMaskSizeHint, bool fill)
+    void AnimationState::createBlendMask(size_t blendMaskSizeHint, float initialWeight)
     {
       if(!mBlendMask)
       {
-        if(fill)
+        if(initialWeight >= 0)
         {
-          mBlendMask = new BoneBlendMask(blendMaskSizeHint, 1.f);
+          mBlendMask = new BoneBlendMask(blendMaskSizeHint, initialWeight);
         }
         else
         {
Regarding the second one: You can already do this, because _setBlendMask will create the blend mask if it is non-existent.
MattStevens
Goblin
Posts: 239
Joined: Mon Apr 07, 2008 10:27 pm
x 4

Post 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
User avatar
Praetor
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 3335
Joined: Tue Jun 21, 2005 8:26 pm
Location: Rochester, New York, US
x 3
Contact:

Post 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.
User avatar
Praetor
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 3335
Joined: Tue Jun 21, 2005 8:26 pm
Location: Rochester, New York, US
x 3
Contact:

Post 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.
Post Reply