Bladeren bron

As a possible fix to #409, remove the optimization to not mark a node dirty again if it is already dirty. As a consequence the TransformChanged() script function will work more logically. This change would cause reduced performance with a large amount of animating models; to counteract, introduce a "silent update" mechanism for node transforms.

Lasse Öörni 11 jaren geleden
bovenliggende
commit
4a9ab417a8

+ 1 - 1
Docs/Reference.dox

@@ -432,7 +432,7 @@ The Start() and Stop() methods do not have direct counterparts in C++ components
 
 When a scene node hierarchy with script objects is instantiated (such as when loading a scene) any child nodes may not have been created yet when Start() is executed, and can thus not be relied upon for initialization. The DelayedStart() method can be used in this case instead: if defined, it is called immediately before any of the Update() calls.
 
-TransformChanged() is called whenever the scene node transform changes, similar to C++ components' OnMarkedDirty() function. Due to an optimization mechanism for repeated scene graph updates, you need to "undirty" the scene node by for example reading its world position to get further transform changed notifications.
+TransformChanged() is called whenever the scene node transform changes, similar to C++ components' OnMarkedDirty() function.
 
 Subscribing to \ref Events "events" in script behaves differently depending on whether \ref Object::SubscribeToEvent "SubscribeToEvent()" is called from a script object's method, or from a procedural script function. If called from an instantiated script object, the ScriptInstance becomes the event receiver on the C++ side, and calls the specified handler method when the event arrives. If called from a function, the ScriptFile will be the event receiver and the handler must be a free function in the same script file. The third case is if the event is subscribed to from a script object that does not belong to a ScriptInstance. In that case the ScriptFile will create a proxy C++ object on demand to be able to forward the event to the script object.
 

+ 4 - 1
Source/Engine/Graphics/AnimatedModel.cpp

@@ -1134,10 +1134,13 @@ void AnimatedModel::UpdateAnimation(const FrameInfo& frame)
     // (first AnimatedModel in a node)
     if (isMaster_)
     {
-        skeleton_.Reset();
+        skeleton_.ResetSilent();
         for (Vector<SharedPtr<AnimationState> >::Iterator i = animationStates_.Begin(); i != animationStates_.End(); ++i)
             (*i)->Apply();
         
+        // Skeleton reset and animations apply the node transforms "silently" to avoid repeated marking dirty. Mark dirty now
+        node_->MarkDirty();
+
         // Calculate new bone bounding box
         UpdateBoneBoundingBox();
     }

+ 65 - 9
Source/Engine/Graphics/AnimationState.cpp

@@ -401,9 +401,9 @@ void AnimationState::ApplyToModel()
             continue;
         
         if (Equals(finalWeight, 1.0f))
-            ApplyTrackFullWeight(stateTrack);
+            ApplyTrackFullWeightSilent(stateTrack);
         else
-            ApplyTrackBlended(stateTrack, finalWeight);
+            ApplyTrackBlendedSilent(stateTrack, finalWeight);
     }
 }
 
@@ -470,7 +470,63 @@ void AnimationState::ApplyTrackFullWeight(AnimationStateTrack& stateTrack)
     }
 }
 
-void AnimationState::ApplyTrackBlended(AnimationStateTrack& stateTrack, float weight)
+void AnimationState::ApplyTrackFullWeightSilent(AnimationStateTrack& stateTrack)
+{
+    const AnimationTrack* track = stateTrack.track_;
+    Node* node = stateTrack.node_;
+
+    if (track->keyFrames_.Empty() || !node)
+        return;
+
+    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)
+    unsigned nextFrame = frame + 1;
+    bool interpolate = true;
+    if (nextFrame >= track->keyFrames_.Size())
+    {
+        if (!looped_)
+        {
+            nextFrame = frame;
+            interpolate = false;
+        }
+        else
+            nextFrame = 0;
+    }
+
+    const AnimationKeyFrame* keyFrame = &track->keyFrames_[frame];
+    unsigned char channelMask = track->channelMask_;
+
+    if (!interpolate)
+    {
+        // No interpolation, full weight
+        if (channelMask & CHANNEL_POSITION)
+            node->SetPositionSilent(keyFrame->position_);
+        if (channelMask & CHANNEL_ROTATION)
+            node->SetRotationSilent(keyFrame->rotation_);
+        if (channelMask & CHANNEL_SCALE)
+            node->SetScaleSilent(keyFrame->scale_);
+    }
+    else
+    {
+        const AnimationKeyFrame* nextKeyFrame = &track->keyFrames_[nextFrame];
+        float timeInterval = nextKeyFrame->time_ - keyFrame->time_;
+        if (timeInterval < 0.0f)
+            timeInterval += animation_->GetLength();
+        float t = timeInterval > 0.0f ? (time_ - keyFrame->time_) / timeInterval : 1.0f;
+
+        // Interpolation, full weight
+        if (channelMask & CHANNEL_POSITION)
+            node->SetPositionSilent(keyFrame->position_.Lerp(nextKeyFrame->position_, t));
+        if (channelMask & CHANNEL_ROTATION)
+            node->SetRotationSilent(keyFrame->rotation_.Slerp(nextKeyFrame->rotation_, t));
+        if (channelMask & CHANNEL_SCALE)
+            node->SetScaleSilent(keyFrame->scale_.Lerp(nextKeyFrame->scale_, t));
+    }
+}
+
+void AnimationState::ApplyTrackBlendedSilent(AnimationStateTrack& stateTrack, float weight)
 {
     const AnimationTrack* track = stateTrack.track_;
     Node* node = stateTrack.node_;
@@ -502,11 +558,11 @@ void AnimationState::ApplyTrackBlended(AnimationStateTrack& stateTrack, float we
     {
         // No interpolation, blend between old transform & animation
         if (channelMask & CHANNEL_POSITION)
-            node->SetPosition(node->GetPosition().Lerp(keyFrame->position_, weight));
+            node->SetPositionSilent(node->GetPosition().Lerp(keyFrame->position_, weight));
         if (channelMask & CHANNEL_ROTATION)
-            node->SetRotation(node->GetRotation().Slerp(keyFrame->rotation_, weight));
+            node->SetRotationSilent(node->GetRotation().Slerp(keyFrame->rotation_, weight));
         if (channelMask & CHANNEL_SCALE)
-            node->SetScale(node->GetScale().Lerp(keyFrame->scale_, weight));
+            node->SetScaleSilent(node->GetScale().Lerp(keyFrame->scale_, weight));
     }
     else
     {
@@ -519,17 +575,17 @@ void AnimationState::ApplyTrackBlended(AnimationStateTrack& stateTrack, float we
         // Interpolation, blend between old transform & animation
         if (channelMask & CHANNEL_POSITION)
         {
-            node->SetPosition(node->GetPosition().Lerp(
+            node->SetPositionSilent(node->GetPosition().Lerp(
                 keyFrame->position_.Lerp(nextKeyFrame->position_, t), weight));
         }
         if (channelMask & CHANNEL_ROTATION)
         {
-            node->SetRotation(node->GetRotation().Slerp(
+            node->SetRotationSilent(node->GetRotation().Slerp(
                 keyFrame->rotation_.Slerp(nextKeyFrame->rotation_, t), weight));
         }
         if (channelMask & CHANNEL_SCALE)
         {
-            node->SetScale(node->GetScale().Lerp(
+            node->SetScaleSilent(node->GetScale().Lerp(
                 keyFrame->scale_.Lerp(nextKeyFrame->scale_, t), weight));
         }
     }

+ 6 - 4
Source/Engine/Graphics/AnimationState.h

@@ -125,15 +125,17 @@ public:
     void Apply();
     
 private:
-    /// Apply animation to a skeleton.
+    /// Apply animation to a skeleton. Transform changes are applied silently, so the model needs to dirty its root model afterward.
     void ApplyToModel();
     /// Apply animation to a scene node hierarchy.
     void ApplyToNodes();
     /// Apply animation track to a scene node, full weight.
     void ApplyTrackFullWeight(AnimationStateTrack& stateTrack);
-    /// Apply animation track to a scene node, blended with current node transform.
-    void ApplyTrackBlended(AnimationStateTrack& stateTrack, float weight);
-    
+    /// Apply animation track to a scene node, full weight. Apply transform changes silently without marking the node dirty.
+    void ApplyTrackFullWeightSilent(AnimationStateTrack& stateTrack);
+    /// Apply animation track to a scene node, blended with current node transform. Apply transform changes silently without marking the node dirty.
+    void ApplyTrackBlendedSilent(AnimationStateTrack& stateTrack, float weight);
+
     /// Animated model (model mode.)
     WeakPtr<AnimatedModel> model_;
     /// Root scene node (node hierarchy mode.)

+ 10 - 0
Source/Engine/Graphics/Skeleton.cpp

@@ -129,6 +129,16 @@ void Skeleton::Reset()
     }
 }
 
+void Skeleton::ResetSilent()
+{
+    for (Vector<Bone>::Iterator i = bones_.Begin(); i != bones_.End(); ++i)
+    {
+        if (i->animated_ && i->node_)
+            i->node_->SetTransformSilent(i->initialPosition_, i->initialRotation_, i->initialScale_);
+    }
+}
+
+
 Bone* Skeleton::GetRootBone()
 {
     return GetBone(rootBoneIndex_);

+ 3 - 0
Source/Engine/Graphics/Skeleton.h

@@ -114,6 +114,9 @@ public:
     /// Return bone by name hash.
     Bone* GetBone(StringHash boneNameHash);
     
+    /// Reset all animating bones to initial positions without marking the nodes dirty. Requires the node dirtying to be performed later.
+    void ResetSilent();
+
 private:
     /// Bones.
     Vector<Bone> bones_;

+ 7 - 3
Source/Engine/Scene/Node.cpp

@@ -589,9 +589,6 @@ void Node::SetOwner(Connection* owner)
 
 void Node::MarkDirty()
 {
-    if (dirty_)
-        return;
-
     dirty_ = true;
 
     // Notify listener components first, then mark child nodes
@@ -1503,6 +1500,13 @@ unsigned Node::GetNumPersistentComponents() const
     return ret;
 }
 
+void Node::SetTransformSilent(const Vector3& position, const Quaternion& rotation, const Vector3& scale)
+{
+    position_ = position;
+    rotation_ = rotation;
+    scale_ = scale;
+}
+
 void Node::OnAttributeAnimationAdded()
 {
     if (attributeAnimationInfos_.Size() == 1)

+ 8 - 0
Source/Engine/Scene/Node.h

@@ -449,6 +449,14 @@ public:
     unsigned GetNumPersistentChildren() const;
     /// Calculate number of non-temporary components.
     unsigned GetNumPersistentComponents() const;
+    /// Set position in parent space silently without marking the node & child nodes dirty. Used by animation code.
+    void SetPositionSilent(const Vector3& position) { position_ = position; }
+    /// Set position in parent space silently without marking the node & child nodes dirty. Used by animation code.
+    void SetRotationSilent(const Quaternion& rotation) { rotation_ = rotation; }
+    /// Set scale in parent space silently without marking the node & child nodes dirty. Used by animation code.
+    void SetScaleSilent(const Vector3& scale) { scale_ = scale; }
+    /// Set local transform silently without marking the node & child nodes dirty. Used by animation code.
+    void SetTransformSilent(const Vector3& position, const Quaternion& rotation, const Vector3& scale);
 
 protected:
     /// Handle attribute animation added.