Browse Source

Update AnimatedModel's bounding box with bone accuracy only when the animation is updated. Otherwise transform the previous last calculated bone bounding box. This results in a significant performance boost when a large amount of AnimatedModels are both moved, and animated.

Lasse Öörni 12 years ago
parent
commit
54a0c90d7a

+ 31 - 24
Source/Engine/Graphics/AnimatedModel.cpp

@@ -331,6 +331,8 @@ void AnimatedModel::SetModel(Model* model, bool createBones)
 
 
         // Copy bounding box & skeleton
         // Copy bounding box & skeleton
         SetBoundingBox(model->GetBoundingBox());
         SetBoundingBox(model->GetBoundingBox());
+        // Initial bone bounding box is just the one stored in the model
+        boneBoundingBox_ = boundingBox_;
         SetSkeleton(model->GetSkeleton(), createBones);
         SetSkeleton(model->GetSkeleton(), createBones);
         ResetLodLevels();
         ResetLodLevels();
 
 
@@ -871,27 +873,7 @@ void AnimatedModel::OnMarkedDirty(Node* node)
 
 
 void AnimatedModel::OnWorldBoundingBoxUpdate()
 void AnimatedModel::OnWorldBoundingBoxUpdate()
 {
 {
-    if (!skeleton_.GetNumBones())
-        worldBoundingBox_ = boundingBox_.Transformed(node_->GetWorldTransform());
-    else
-    {
-        // If has bones, update world bounding box based on them
-        worldBoundingBox_.defined_ = false;
-
-        const Vector<Bone>& bones = skeleton_.GetBones();
-        for (Vector<Bone>::ConstIterator i = bones.Begin(); i != bones.End(); ++i)
-        {
-            Node* boneNode = i->node_;
-            if (!boneNode)
-                continue;
-
-            // Use hitbox if available. If not, use only half of the sphere radius
-            if (i->collisionMask_ & BONECOLLISION_BOX)
-                worldBoundingBox_.Merge(i->boundingBox_.Transformed(boneNode->GetWorldTransform()));
-            else if (i->collisionMask_ & BONECOLLISION_SPHERE)
-                worldBoundingBox_.Merge(Sphere(boneNode->GetWorldPosition(), i->radius_ * 0.5f));
-        }
-    }
+    worldBoundingBox_ = boneBoundingBox_.Transformed(node_->GetWorldTransform());
 }
 }
 
 
 void AnimatedModel::AssignBoneNodes()
 void AnimatedModel::AssignBoneNodes()
@@ -1138,13 +1120,38 @@ void AnimatedModel::UpdateAnimation(const FrameInfo& frame)
     for (Vector<SharedPtr<AnimationState> >::Iterator i = animationStates_.Begin(); i != animationStates_.End(); ++i)
     for (Vector<SharedPtr<AnimationState> >::Iterator i = animationStates_.Begin(); i != animationStates_.End(); ++i)
         (*i)->Apply();
         (*i)->Apply();
 
 
-    // Animation has changed the bounding box: mark node for octree reinsertion
+    // Calculate new local bounding box from the bone positions, then mark for octree reinsertion
+    UpdateBoneBoundingBox();
     Drawable::OnMarkedDirty(node_);
     Drawable::OnMarkedDirty(node_);
-    // For optimization, recalculate world bounding box already here (during the threaded update)
-    GetWorldBoundingBox();
+    
     animationDirty_ = false;
     animationDirty_ = false;
 }
 }
 
 
+void AnimatedModel::UpdateBoneBoundingBox()
+{
+    if (skeleton_.GetNumBones())
+    {
+        // The bone bounding box is in local space, so need the node's inverse transform
+        boneBoundingBox_.defined_ = false;
+        Matrix3x4 inverseNodeTransform = node_->GetWorldTransform().Inverse();
+        
+        const Vector<Bone>& bones = skeleton_.GetBones();
+        for (Vector<Bone>::ConstIterator i = bones.Begin(); i != bones.End(); ++i)
+        {
+            Node* boneNode = i->node_;
+            if (!boneNode)
+                continue;
+
+            // Use hitbox if available. If not, use only half of the sphere radius
+            /// \todo The sphere radius should be multiplied with bone scale
+            if (i->collisionMask_ & BONECOLLISION_BOX)
+                boneBoundingBox_.Merge(i->boundingBox_.Transformed(inverseNodeTransform * boneNode->GetWorldTransform()));
+            else if (i->collisionMask_ & BONECOLLISION_SPHERE)
+                boneBoundingBox_.Merge(Sphere(inverseNodeTransform * boneNode->GetWorldPosition(), i->radius_ * 0.5f));
+        }
+    }
+}
+
 void AnimatedModel::UpdateSkinning()
 void AnimatedModel::UpdateSkinning()
 {
 {
     // Note: the model's world transform will be baked in the skin matrices
     // Note: the model's world transform will be baked in the skin matrices

+ 5 - 1
Source/Engine/Graphics/AnimatedModel.h

@@ -178,6 +178,8 @@ private:
     void CopyMorphVertices(void* dest, void* src, unsigned vertexCount, VertexBuffer* clone, VertexBuffer* original);
     void CopyMorphVertices(void* dest, void* src, unsigned vertexCount, VertexBuffer* clone, VertexBuffer* original);
     /// Recalculate animations. Called from Update().
     /// Recalculate animations. Called from Update().
     void UpdateAnimation(const FrameInfo& frame);
     void UpdateAnimation(const FrameInfo& frame);
+    /// Recalculate the bone bounding box.
+    void UpdateBoneBoundingBox();
     /// Recalculate skinning.
     /// Recalculate skinning.
     void UpdateSkinning();
     void UpdateSkinning();
     /// Reapply all vertex morphs.
     /// Reapply all vertex morphs.
@@ -203,7 +205,9 @@ private:
     Vector<PODVector<Matrix3x4> > geometrySkinMatrices_;
     Vector<PODVector<Matrix3x4> > geometrySkinMatrices_;
     /// Subgeometry skinning matrix pointers, if more bones than skinning shader can manage.
     /// Subgeometry skinning matrix pointers, if more bones than skinning shader can manage.
     Vector<PODVector<Matrix3x4*> > geometrySkinMatrixPtrs_;
     Vector<PODVector<Matrix3x4*> > geometrySkinMatrixPtrs_;
-   /// Attribute buffer.
+    /// Bounding box calculated from bones.
+    BoundingBox boneBoundingBox_;
+    /// Attribute buffer.
     mutable VectorBuffer attrBuffer_;
     mutable VectorBuffer attrBuffer_;
     /// The frame number animation LOD distance was last calculated on.
     /// The frame number animation LOD distance was last calculated on.
     unsigned animationLodFrameNumber_;
     unsigned animationLodFrameNumber_;

+ 1 - 0
Source/Engine/Graphics/DecalSet.cpp

@@ -667,6 +667,7 @@ void DecalSet::OnWorldBoundingBoxUpdate()
                 continue;
                 continue;
             
             
             // Use hitbox if available. If not, use only half of the sphere radius
             // Use hitbox if available. If not, use only half of the sphere radius
+            /// \todo The sphere radius should be multiplied with bone scale
             if (i->collisionMask_ & BONECOLLISION_BOX)
             if (i->collisionMask_ & BONECOLLISION_BOX)
                 worldBoundingBox_.Merge(i->boundingBox_.Transformed(boneNode->GetWorldTransform()));
                 worldBoundingBox_.Merge(i->boundingBox_.Transformed(boneNode->GetWorldTransform()));
             else if (i->collisionMask_ & BONECOLLISION_SPHERE)
             else if (i->collisionMask_ & BONECOLLISION_SPHERE)

+ 2 - 2
Source/Samples/06_SkeletalAnimation/SkeletalAnimation.cpp

@@ -115,12 +115,12 @@ void SkeletalAnimation::CreateScene()
     light->SetShadowCascade(CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f));
     light->SetShadowCascade(CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f));
     
     
     // Create animated models
     // Create animated models
-    const unsigned NUM_OBJECTS = 100;
+    const unsigned NUM_MODELS = 100;
     const float MODEL_MOVE_SPEED = 2.0f;
     const float MODEL_MOVE_SPEED = 2.0f;
     const float MODEL_ROTATE_SPEED = 100.0f;
     const float MODEL_ROTATE_SPEED = 100.0f;
     const BoundingBox bounds(Vector3(-47.0f, 0.0f, -47.0f), Vector3(47.0f, 0.0f, 47.0f));
     const BoundingBox bounds(Vector3(-47.0f, 0.0f, -47.0f), Vector3(47.0f, 0.0f, 47.0f));
     
     
-    for (unsigned i = 0; i < NUM_OBJECTS; ++i)
+    for (unsigned i = 0; i < NUM_MODELS; ++i)
     {
     {
         Node* modelNode = scene_->CreateChild("Jack");
         Node* modelNode = scene_->CreateChild("Jack");
         modelNode->SetPosition(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
         modelNode->SetPosition(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));