Browse Source

Per-bone weight control of animation state tracks. Not serialized yet. Eliminated map lookups from applying animations.

Lasse Öörni 12 years ago
parent
commit
96e80a2366

+ 7 - 0
Docs/ScriptAPI.dox

@@ -2463,6 +2463,12 @@ Methods:<br>
 - void AddWeight(float)
 - void AddTime(float)
 - void Apply()
+- void SetBoneWeight(uint, float)
+- void SetBoneWeight(StringHash, float)
+- uint GetTrackIndex(const String&) const
+- uint GetTrackIndex(StringHash) const
+- float GetBoneWeight(uint) const
+- float GetBoneWeight(StringHash) const
 
 Properties:<br>
 - int refs (readonly)
@@ -2477,6 +2483,7 @@ Properties:<br>
 - Node@ node (readonly)
 - bool enabled (readonly)
 - float length (readonly)
+- float[] boneWeights
 
 
 AnimatedModel

+ 124 - 70
Engine/Graphics/AnimationState.cpp

@@ -34,6 +34,18 @@
 namespace Urho3D
 {
 
+AnimationStateTrack::AnimationStateTrack() :
+    track_(0),
+    bone_(0),
+    weight_(1.0f),
+    keyFrame_(0)
+{
+}
+
+AnimationStateTrack::~AnimationStateTrack()
+{
+}
+
 AnimationState::AnimationState(AnimatedModel* model, Animation* animation) :
     model_(model),
     animation_(animation),
@@ -43,16 +55,8 @@ AnimationState::AnimationState(AnimatedModel* model, Animation* animation) :
     time_(0.0f),
     layer_(0)
 {
+    // Set default start bone (use all tracks.)
     SetStartBone(0);
-    
-    if (animation_)
-    {
-        // Setup a cache for last keyframe of each track
-        lastKeyFrame_.Resize(animation_->GetNumTracks());
-    }
-    
-    for (unsigned i = 0; i < lastKeyFrame_.Size(); ++i)
-        lastKeyFrame_[i] = 0;
 }
 
 AnimationState::AnimationState(Node* node, Animation* animation) :
@@ -66,34 +70,34 @@ AnimationState::AnimationState(Node* node, Animation* animation) :
 {
     if (animation_)
     {
-        // Setup a cache for last keyframe of each track
-        lastKeyFrame_.Resize(animation_->GetNumTracks());
-        
         // Setup animation track to scene node mapping
         if (node_)
         {
             const Vector<AnimationTrack>& tracks = animation_->GetTracks();
+            stateTracks_.Clear();
             
             for (unsigned i = 0; i < tracks.Size(); ++i)
             {
                 const StringHash& nameHash = tracks[i].nameHash_;
-                
+                AnimationStateTrack stateTrack;
+                stateTrack.track_ = &tracks[i];
+
                 if (node_->GetNameHash() == nameHash || tracks.Size() == 1)
-                    trackToNodeMap_[i] = node_;
+                    stateTrack.node_ = node_;
                 else
                 {
                     Node* targetNode = node_->GetChild(nameHash, true);
                     if (targetNode)
-                        trackToNodeMap_[i] = targetNode;
+                        stateTrack.node_ = targetNode;
                     else
                         LOGWARNING("Node " + tracks[i].name_ + " not found for node animation " + animation_->GetName());
                 }
+
+                if (stateTrack.node_)
+                    stateTracks_.Push(stateTrack);
             }
         }
     }
-    
-    for (unsigned i = 0; i < lastKeyFrame_.Size(); ++i)
-        lastKeyFrame_[i] = 0;
 }
 
 
@@ -116,19 +120,22 @@ void AnimationState::SetStartBone(Bone* startBone)
     }
     
     // Do not reassign if the start bone did not actually change, and we already have valid bone nodes
-    if (startBone == startBone_ && !trackToBoneMap_.Empty())
+    if (startBone == startBone_ && !stateTracks_.Empty())
         return;
     
     startBone_ = startBone;
     
-    trackToBoneMap_.Clear();
+    const Vector<AnimationTrack>& tracks = animation_->GetTracks();
+    stateTracks_.Clear();
+    
     if (!startBone->node_)
         return;
     
-    const Vector<AnimationTrack>& tracks = animation_->GetTracks();
-    
     for (unsigned i = 0; i < tracks.Size(); ++i)
     {
+        AnimationStateTrack stateTrack;
+        stateTrack.track_ = &tracks[i];
+        
         // Include those tracks that are either the start bone itself, or its children
         Bone* trackBone = 0;
         const StringHash& nameHash = tracks[i].nameHash_;
@@ -142,8 +149,12 @@ void AnimationState::SetStartBone(Bone* startBone)
                 trackBone = skeleton.GetBone(nameHash);
         }
         
-        if (trackBone)
-            trackToBoneMap_[i] = trackBone;
+        if (trackBone && trackBone->node_)
+        {
+            stateTrack.bone_ = trackBone;
+            stateTrack.node_ = trackBone->node_;
+            stateTracks_.Push(stateTrack);
+        }
     }
     
     model_->MarkAnimationDirty();
@@ -182,6 +193,24 @@ void AnimationState::SetTime(float time)
     }
 }
 
+void AnimationState::SetBoneWeight(unsigned index, float weight)
+{
+    if (index >= stateTracks_.Size())
+        return;
+    
+    stateTracks_[index].weight_ = Clamp(weight, 0.0f, 1.0f);
+}
+
+void AnimationState::SetBoneWeight(const String& name, float weight)
+{
+    SetBoneWeight(GetTrackIndex(name), weight);
+}
+
+void AnimationState::SetBoneWeight(StringHash nameHash, float weight)
+{
+    SetBoneWeight(GetTrackIndex(nameHash), weight);
+}
+
 void AnimationState::AddWeight(float delta)
 {
     if (delta == 0.0f)
@@ -272,6 +301,45 @@ Bone* AnimationState::GetStartBone() const
     return model_ ? startBone_ : 0;
 }
 
+float AnimationState::GetBoneWeight(unsigned index) const
+{
+    return index < stateTracks_.Size() ? stateTracks_[index].weight_ : 0.0f;
+}
+
+float AnimationState::GetBoneWeight(const String& name) const
+{
+    return GetBoneWeight(GetTrackIndex(name));
+}
+
+float AnimationState::GetBoneWeight(StringHash nameHash) const
+{
+    return GetBoneWeight(GetTrackIndex(nameHash));
+}
+
+unsigned AnimationState::GetTrackIndex(const String& name) const
+{
+    for (unsigned i = 0; i < stateTracks_.Size(); ++i)
+    {
+        Node* node = stateTracks_[i].node_;
+        if (node && node->GetName() == name)
+            return i;
+    }
+    
+    return M_MAX_UNSIGNED;
+}
+
+unsigned AnimationState::GetTrackIndex(StringHash nameHash) const
+{
+    for (unsigned i = 0; i < stateTracks_.Size(); ++i)
+    {
+        Node* node = stateTracks_[i].node_;
+        if (node && node->GetNameHash() == nameHash)
+            return i;
+    }
+
+    return M_MAX_UNSIGNED;
+}
+
 float AnimationState::GetLength() const
 {
     return animation_ ? animation_->GetLength() : 0.0f;
@@ -290,53 +358,38 @@ void AnimationState::Apply()
 
 void AnimationState::ApplyToModel()
 {
-    // Check first if full weight or blending
-    if (Equals(weight_, 1.0f))
-    {
-        for (HashMap<unsigned, Bone*>::ConstIterator i = trackToBoneMap_.Begin(); i != trackToBoneMap_.End(); ++i)
-        {
-            Bone* bone = i->second_;
-            Node* boneNode = bone->node_;
-            if (!boneNode || !bone->animated_)
-                continue;
-            
-            ApplyTrackToNodeFullWeight(i->first_, boneNode);
-        }
-    }
-    else
+    for (Vector<AnimationStateTrack>::Iterator i = stateTracks_.Begin(); i != stateTracks_.End(); ++i)
     {
-        for (HashMap<unsigned, Bone*>::ConstIterator i = trackToBoneMap_.Begin(); i != trackToBoneMap_.End(); ++i)
-        {
-            Bone* bone = i->second_;
-            Node* boneNode = bone->node_;
-            if (!boneNode || !bone->animated_)
-                continue;
-            
-            ApplyTrackToNodeBlended(i->first_, boneNode);
-        }
+        AnimationStateTrack& stateTrack = *i;
+        float finalWeight = weight_ * stateTrack.weight_;
+        
+        // Do not apply if zero effective weight or the bone has animation disabled
+        if (Equals(finalWeight, 0.0f) || !stateTrack.bone_->animated_)
+            continue;
+        
+        if (Equals(finalWeight, 1.0f))
+            ApplyTrackFullWeight(stateTrack);
+        else
+            ApplyTrackBlended(stateTrack, finalWeight);
     }
 }
 
 void AnimationState::ApplyToNodes()
 {
     // When applying to a node hierarchy, can only use full weight (nothing to blend to)
-    for (HashMap<unsigned, WeakPtr<Node> >::ConstIterator i = trackToNodeMap_.Begin(); i != trackToNodeMap_.End(); ++i)
-    {
-        Node* node = i->second_;
-        if (!node)
-            continue;
-        
-        ApplyTrackToNodeFullWeight(i->first_, node);
-    }
+    for (Vector<AnimationStateTrack>::Iterator i = stateTracks_.Begin(); i != stateTracks_.End(); ++i)
+        ApplyTrackFullWeight(*i);
 }
 
-void AnimationState::ApplyTrackToNodeFullWeight(unsigned index, Node* node)
+void AnimationState::ApplyTrackFullWeight(AnimationStateTrack& stateTrack)
 {
-    const AnimationTrack* track = animation_->GetTrack(index);
-    if (track->keyFrames_.Empty())
+    const AnimationTrack* track = stateTrack.track_;
+    Node* node = stateTrack.node_;
+    
+    if (track->keyFrames_.Empty() || !node)
         return;
     
-    unsigned& frame = lastKeyFrame_[index];
+    unsigned& frame = stateTrack.keyFrame_;
     track->GetKeyFrameIndex(time_, frame);
     
     // Check if next frame to interpolate to is valid, or if wrapping is needed (looping animation only)
@@ -384,13 +437,15 @@ void AnimationState::ApplyTrackToNodeFullWeight(unsigned index, Node* node)
     }
 }
 
-void AnimationState::ApplyTrackToNodeBlended(unsigned index, Node* node)
+void AnimationState::ApplyTrackBlended(AnimationStateTrack& stateTrack, float weight)
 {
-    const AnimationTrack* track = animation_->GetTrack(index);
-    if (track->keyFrames_.Empty())
+    const AnimationTrack* track = stateTrack.track_;
+    Node* node = stateTrack.node_;
+    
+    if (track->keyFrames_.Empty() || !node)
         return;
     
-    unsigned& frame = lastKeyFrame_[index];
+    unsigned& frame = stateTrack.keyFrame_;
     track->GetKeyFrameIndex(time_, frame);
     
     // Check if next frame to interpolate to is valid, or if wrapping is needed (looping animation only)
@@ -414,11 +469,11 @@ void AnimationState::ApplyTrackToNodeBlended(unsigned index, Node* node)
     {
         // No interpolation, blend between old transform & animation
         if (channelMask & CHANNEL_POSITION)
-            node->SetPosition(node->GetPosition().Lerp(keyFrame->position_, weight_));
+            node->SetPosition(node->GetPosition().Lerp(keyFrame->position_, weight));
         if (channelMask & CHANNEL_ROTATION)
-            node->SetRotation(node->GetRotation().Slerp(keyFrame->rotation_, weight_));
+            node->SetRotation(node->GetRotation().Slerp(keyFrame->rotation_, weight));
         if (channelMask & CHANNEL_SCALE)
-            node->SetScale(node->GetScale().Lerp(keyFrame->scale_, weight_));
+            node->SetScale(node->GetScale().Lerp(keyFrame->scale_, weight));
     }
     else
     {
@@ -432,20 +487,19 @@ void AnimationState::ApplyTrackToNodeBlended(unsigned index, Node* node)
         if (channelMask & CHANNEL_POSITION)
         {
             node->SetPosition(node->GetPosition().Lerp(
-                keyFrame->position_.Lerp(nextKeyFrame->position_, t), weight_));
+                keyFrame->position_.Lerp(nextKeyFrame->position_, t), weight));
         }
         if (channelMask & CHANNEL_ROTATION)
         {
             node->SetRotation(node->GetRotation().Slerp(
-                keyFrame->rotation_.Slerp(nextKeyFrame->rotation_, t), weight_));
+                keyFrame->rotation_.Slerp(nextKeyFrame->rotation_, t), weight));
         }
         if (channelMask & CHANNEL_SCALE)
         {
             node->SetScale(node->GetScale().Lerp(
-                keyFrame->scale_.Lerp(nextKeyFrame->scale_, t), weight_));
+                keyFrame->scale_.Lerp(nextKeyFrame->scale_, t), weight));
         }
     }
 }
 
-
 }

+ 41 - 9
Engine/Graphics/AnimationState.h

@@ -36,6 +36,26 @@ class Skeleton;
 struct AnimationTrack;
 struct Bone;
 
+/// %Animation instance per-track data.
+struct AnimationStateTrack
+{
+    /// Construct with defaults.
+    AnimationStateTrack();
+    /// Destruct
+    ~AnimationStateTrack();
+    
+    /// Animation track.
+    const AnimationTrack* track_;
+    /// Bone pointer.
+    Bone* bone_;
+    /// Scene node pointer.
+    WeakPtr<Node> node_;
+    /// Blending weight.
+    float weight_;
+    /// Last key frame.
+    unsigned keyFrame_;
+};
+
 /// %Animation instance.
 class AnimationState : public RefCounted
 {
@@ -47,7 +67,7 @@ public:
     /// Destruct.
     ~AnimationState();
     
-    /// Set start bone. Not supported in node animation mode.
+    /// Set start bone. Not supported in node animation mode. Resets any assigned per-bone weights.
     void SetStartBone(Bone* bone);
     /// Set looping enabled/disabled.
     void SetLooped(bool looped);
@@ -55,6 +75,12 @@ public:
     void SetWeight(float weight);
     /// Set time position. Does not fire animation triggers.
     void SetTime(float time);
+    /// Set per-bone blending weight by track index. Default is 1.0 (full), is multiplied  with the state's blending weight when applying the animation.
+    void SetBoneWeight(unsigned index, float weight);
+    /// Set per-bone blending weight by name.
+    void SetBoneWeight(const String& name, float weight);
+    /// Set per-bone blending weight by name hash.
+    void SetBoneWeight(StringHash nameHash, float weight);
     /// Modify blending weight.
     void AddWeight(float delta);
     /// Modify time position. %Animation triggers will be fired.
@@ -70,6 +96,16 @@ public:
     Node* GetNode() const;
     /// Return start bone.
     Bone* GetStartBone() const;
+    /// Return per-bone blending weight by track index.
+    float GetBoneWeight(unsigned index) const;
+    /// Return per-bone blending weight by name.
+    float GetBoneWeight(const String& name) const;
+    /// Return per-bone blending weight by name.
+    float GetBoneWeight(StringHash nameHash) const;
+    /// Return track index by bone name, or M_MAX_UNSIGNED if not found.
+    unsigned GetTrackIndex(const String& name) const;
+    /// Return track index by bone name hash, or M_MAX_UNSIGNED if not found.
+    unsigned GetTrackIndex(StringHash nameHash) const;
     /// Return whether weight is nonzero.
     bool IsEnabled() const { return weight_ > 0.0f; }
     /// Return whether looped.
@@ -92,9 +128,9 @@ private:
     /// Apply animation to a scene node hierarchy.
     void ApplyToNodes();
     /// Apply animation track to a scene node, full weight.
-    void ApplyTrackToNodeFullWeight(unsigned index, Node* node);
+    void ApplyTrackFullWeight(AnimationStateTrack& stateTrack);
     /// Apply animation track to a scene node, blended with current node transform.
-    void ApplyTrackToNodeBlended(unsigned index, Node* node);
+    void ApplyTrackBlended(AnimationStateTrack& stateTrack, float weight);
     
     /// Animated model (model mode.)
     WeakPtr<AnimatedModel> model_;
@@ -104,12 +140,8 @@ private:
     SharedPtr<Animation> animation_;
     /// Start bone.
     Bone* startBone_;
-    /// Mapping of animation track indices to bones.
-    HashMap<unsigned, Bone*> trackToBoneMap_;
-    /// Mapping of animation track indices to scene nodes.
-    HashMap<unsigned, WeakPtr<Node> > trackToNodeMap_;
-    /// Last keyframe on each animation track for optimized keyframe search.
-    PODVector<unsigned> lastKeyFrame_;
+    /// Per-track data.
+    Vector<AnimationStateTrack> stateTracks_;
     /// Looped flag.
     bool looped_;
     /// Blending weight.

+ 8 - 1
Engine/Script/GraphicsAPI.cpp

@@ -849,6 +849,12 @@ static void RegisterAnimatedModel(asIScriptEngine* engine)
     engine->RegisterObjectMethod("AnimationState", "void AddWeight(float)", asMETHOD(AnimationState, AddWeight), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimationState", "void AddTime(float)", asMETHOD(AnimationState, AddTime), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimationState", "void Apply()", asMETHOD(AnimationState, Apply), asCALL_THISCALL);
+    engine->RegisterObjectMethod("AnimationState", "void SetBoneWeight(uint, float)", asMETHODPR(AnimationState, SetBoneWeight, (unsigned, float), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("AnimationState", "void SetBoneWeight(StringHash, float)", asMETHODPR(AnimationState, SetBoneWeight, (StringHash, float), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("AnimationState", "float GetBoneWeight(uint) const", asMETHODPR(AnimationState, GetBoneWeight, (unsigned) const, float), asCALL_THISCALL);
+    engine->RegisterObjectMethod("AnimationState", "float GetBoneWeight(StringHash) const", asMETHODPR(AnimationState, GetBoneWeight, (StringHash) const, float), asCALL_THISCALL);
+    engine->RegisterObjectMethod("AnimationState", "uint GetTrackIndex(const String&in) const", asMETHODPR(AnimationState, GetTrackIndex, (const String&) const, unsigned), asCALL_THISCALL);
+    engine->RegisterObjectMethod("AnimationState", "uint GetTrackIndex(StringHash) const", asMETHODPR(AnimationState, GetTrackIndex, (StringHash) const, unsigned), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimationState", "void set_startBone(Bone@+)", asMETHOD(AnimationState, SetStartBone), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimationState", "Bone@+ get_startBone() const", asMETHOD(AnimationState, GetStartBone), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimationState", "void set_looped(bool)", asMETHOD(AnimationState, SetLooped), asCALL_THISCALL);
@@ -864,7 +870,8 @@ static void RegisterAnimatedModel(asIScriptEngine* engine)
     engine->RegisterObjectMethod("AnimationState", "Node@+ get_node() const", asMETHOD(AnimationState, GetNode), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimationState", "bool get_enabled() const", asMETHOD(AnimationState, IsEnabled), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimationState", "float get_length() const", asMETHOD(AnimationState, GetLength), asCALL_THISCALL);
-    
+    engine->RegisterObjectMethod("AnimationState", "void set_boneWeights(const String&in, float)", asMETHODPR(AnimationState, SetBoneWeight, (const String&, float), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("AnimationState", "float get_boneWeights(const String&in)", asMETHODPR(AnimationState, GetBoneWeight, (const String&) const, float), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimatedModel", "AnimationState@+ AddAnimationState(Animation@+)", asMETHOD(AnimatedModel, AddAnimationState), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimatedModel", "void RemoveAnimationState(Animation@+)", asMETHODPR(AnimatedModel, RemoveAnimationState, (Animation*), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimatedModel", "void RemoveAnimationState(const String&in)", asMETHODPR(AnimatedModel, RemoveAnimationState, (const String&), void), asCALL_THISCALL);

+ 11 - 3
Extras/LuaScript/pkgs/Graphics/AnimationState.pkg

@@ -5,24 +5,32 @@ class AnimationState
     AnimationState(AnimatedModel* model, Animation* animation);
     AnimationState(Node* node, Animation* animation);
     ~AnimationState();
-    
+
     void SetStartBone(Bone* bone);
     void SetLooped(bool looped);
     void SetWeight(float weight);
     void SetTime(float time);
+    void SetBoneWeight(unsigned index, float weight);
+    void SetBoneWeight(const String& name, float weight);
+    void SetBoneWeight(StringHash nameHash, float weight);
     void AddWeight(float delta);
     void AddTime(float delta);
     void SetLayer(unsigned char layer);
-    
+
     Animation* GetAnimation() const;
     Bone* GetStartBone() const;
+    float GetBoneWeight(unsigned index) const;
+    float GetBoneWeight(const String& name) const;
+    float GetBoneWeight(StringHash nameHash) const;
+    unsigned GetTrackIndex(const String& name) const;
+    unsigned GetTrackIndex(StringHash nameHash) const;
     bool IsEnabled() const;
     bool IsLooped() const;
     float GetWeight() const;
     float GetTime() const;
     float GetLength() const;
     unsigned char GetLayer() const;
-    
+
     tolua_readonly tolua_property__get_set Animation* animation;
     tolua_property__get_set Bone* startBone;
     tolua_readonly tolua_property__is_set bool enabled;