Browse Source

Added skeleton support to AssetImporter.
Added setBones() function to Skeleton, which does not copy the bones.
AnimatedModel now shows skeleton in debug geometry.

Lasse Öörni 15 years ago
parent
commit
2d2d98fd5d

+ 7 - 0
Engine/Renderer/AnimatedModel.cpp

@@ -26,6 +26,7 @@
 #include "Animation.h"
 #include "AnimationState.h"
 #include "Camera.h"
+#include "DebugRenderer.h"
 #include "Geometry.h"
 #include "IndexBuffer.h"
 #include "Log.h"
@@ -587,6 +588,12 @@ bool AnimatedModel::getVertexShaderParameter(unsigned batchIndex, VSParameter pa
     return false;
 }
 
+void AnimatedModel::drawDebugGeometry(DebugRenderer* debug)
+{
+    debug->addBoundingBox(getWorldBoundingBox(), Color(0.0f, 1.0f, 0.0f), false);
+    debug->addSkeleton(mSkeleton, Color(0.75f, 0.75f, 0.75f), false);
+}
+
 bool AnimatedModel::setModel(Model* model)
 {
     if (model == mModel)

+ 2 - 0
Engine/Renderer/AnimatedModel.h

@@ -71,6 +71,8 @@ public:
     virtual void updateGeometry(const FrameInfo& frame, Renderer* renderer);
     //! Return vertex shader parameter
     virtual bool getVertexShaderParameter(unsigned batchIndex, VSParameter parameter, const float** data, unsigned* count);
+    //! Draw debug geometry
+    virtual void drawDebugGeometry(DebugRenderer* debug);
     
     //! Set model
     bool setModel(Model* model);

+ 6 - 0
Engine/Renderer/Skeleton.cpp

@@ -174,6 +174,12 @@ void Skeleton::define(const std::vector<SharedPtr<Bone > >& srcBones)
     }
 }
 
+void Skeleton::setBones(const std::vector<SharedPtr<Bone> >& bones, Bone* rootBone)
+{
+    mBones = bones;
+    mRootBone = rootBone;
+}
+
 void Skeleton::reset(bool force)
 {
     // Start with resetting the root bone so that node dirtying is done most efficiently

+ 2 - 0
Engine/Renderer/Skeleton.h

@@ -45,6 +45,8 @@ public:
     void save(Serializer& dest);
     //! Define from source bones
     void define(const std::vector<SharedPtr<Bone> >& srcBones);
+    //! Set bones without copying
+    void setBones(const std::vector<SharedPtr<Bone> >& bones, Bone* rootBone);
     //! Reset all animated bones, or all bones if forced
     void reset(bool force = false);
     

+ 298 - 13
Tools/AssetImporter/AssetImporter.cpp

@@ -48,7 +48,8 @@ struct ExportModel
 {
     ExportModel() :
         mTotalVertices(0),
-        mTotalIndices(0)
+        mTotalIndices(0),
+        mRootBone(0)
     {
     }
     
@@ -57,6 +58,10 @@ struct ExportModel
     aiNode* mRootNode;
     std::vector<aiMesh*> mMeshes;
     std::vector<aiNode*> mMeshNodes;
+    std::vector<aiNode*> mBones;
+    std::vector<float> mBoneRadii;
+    std::vector<BoundingBox> mBoneHitboxes;
+    aiNode* mRootBone;
     unsigned mTotalVertices;
     unsigned mTotalIndices;
 };
@@ -66,13 +71,20 @@ void run(const std::vector<std::string>& arguments);
 void dumpNodes(aiNode* rootNode, unsigned level);
 void exportModel(ExportModel& model);
 void collectMeshes(ExportModel& model, aiNode* node);
+void collectBones(ExportModel& model);
+void collectBonesFinal(std::vector<aiNode*>& dest, const std::set<aiNode*>& necessary, aiNode* node);
+void generateBoneCollisionInfo(ExportModel& model);
 void buildModel(ExportModel& model);
+unsigned getBoneIndex(ExportModel& model, const std::string& boneName);
+aiBone* getMeshBone(ExportModel& model, const std::string& boneName);
+void getBlendData(ExportModel& model, aiMesh* mesh, std::vector<unsigned>& boneMappings, std::vector<std::vector<unsigned char> >& blendIndices, std::vector<std::vector<float> >& blendWeights);
 void writeShortIndices(unsigned short*& dest, aiMesh* mesh, unsigned index, unsigned offset);
 void writeLargeIndices(unsigned*& dest, aiMesh* mesh, unsigned index, unsigned offset);
 void writeVertex(float*& dest, aiMesh* mesh, unsigned index, unsigned elementMask, BoundingBox& box,
-    const Matrix4x3& vertexTransform, const Matrix3& normalTransform);
+    const Matrix4x3& vertexTransform, const Matrix3& normalTransform, std::vector<std::vector<unsigned char> >& blendIndices,
+    std::vector<std::vector<float> >& blendWeights);
 unsigned getElementMask(aiMesh* mesh);
-aiNode* findNode(const std::string& name, aiNode* rootNode);
+aiNode* findNode(const std::string& name, aiNode* rootNode, bool caseSensitive = true);
 std::string toStdString(const aiString& str);
 Vector3 toVector3(const aiVector3D& vec);
 Vector2 toVector2(const aiVector2D& vec);
@@ -132,7 +144,7 @@ void run(const std::vector<std::string>& arguments)
         model.mRootNode = scene->mRootNode;
     else
     {
-        model.mRootNode = findNode(arguments[2], scene->mRootNode);
+        model.mRootNode = findNode(arguments[2], scene->mRootNode, false);
         if (!model.mRootNode)
             errorExit("Could not find scene node " + arguments[2]);
     }
@@ -165,6 +177,8 @@ void dumpNodes(aiNode* rootNode, unsigned level)
 void exportModel(ExportModel& model)
 {
     collectMeshes(model, model.mRootNode);
+    collectBones(model);
+    generateBoneCollisionInfo(model);
     buildModel(model);
 }
 
@@ -192,17 +206,117 @@ void collectMeshes(ExportModel& model, aiNode* node)
         collectMeshes(model, node->mChildren[i]);
 }
 
+void collectBones(ExportModel& model)
+{
+    std::set<aiNode*> necessary;
+    std::set<aiNode*> rootNodes;
+    
+    for (unsigned i = 0; i < model.mMeshes.size(); ++i)
+    {
+        aiMesh* mesh = model.mMeshes[i];
+        aiNode* meshNode = model.mMeshNodes[i];
+        aiNode* meshParentNode = meshNode->mParent;
+        aiNode* rootNode = 0;
+        
+        for (unsigned j = 0; j < mesh->mNumBones; ++j)
+        {
+            aiBone* bone = mesh->mBones[j];
+            std::string boneName(toStdString(bone->mName));
+            aiNode* boneNode = findNode(boneName, model.mScene->mRootNode, true);
+            if (!boneNode)
+                errorExit("Could not find scene node for bone " + boneName);
+            necessary.insert(boneNode);
+            rootNode = boneNode;
+            
+            for (;;)
+            {
+                boneNode = boneNode->mParent;
+                if ((!boneNode) || (boneNode == meshNode) || (boneNode == meshParentNode))
+                    break;
+                rootNode = boneNode;
+                necessary.insert(boneNode);
+            }
+            
+            rootNodes.insert(rootNode);
+            if (rootNodes.size() > 1)
+                errorExit("Multiple skeleton root nodes found, not supported");
+        }
+    }
+    
+    model.mRootBone = *rootNodes.begin();
+    collectBonesFinal(model.mBones, necessary, model.mRootBone);
+    // Initialize the bone collision info
+    model.mBoneRadii.resize(model.mBones.size());
+    model.mBoneHitboxes.resize(model.mBones.size());
+    for (unsigned i = 0; i < model.mBones.size(); ++i)
+    {
+        model.mBoneRadii[i] = 0.0f;
+        model.mBoneHitboxes[i] = BoundingBox(0.0f, 0.0f);
+    }
+}
+
+void collectBonesFinal(std::vector<aiNode*>& dest, const std::set<aiNode*>& necessary, aiNode* node)
+{
+    if (necessary.find(node) != necessary.end())
+    {
+        dest.push_back(node);
+        for (unsigned i = 0; i < node->mNumChildren; ++i)
+            collectBonesFinal(dest, necessary, node->mChildren[i]);
+    }
+}
+
+void generateBoneCollisionInfo(ExportModel& model)
+{
+    for (unsigned i = 0; i < model.mMeshes.size(); ++i)
+    {
+        aiMesh* mesh = model.mMeshes[i];
+        aiMatrix4x4 meshWorldTransform = getWorldTransform(model.mMeshNodes[i], model.mRootNode);
+        for (unsigned j = 0; j < mesh->mNumBones; ++j)
+        {
+            aiBone* bone = mesh->mBones[j];
+            std::string boneName = toStdString(bone->mName);
+            unsigned boneIndex = getBoneIndex(model, boneName);
+            aiNode* boneNode = model.mBones[boneIndex];
+            aiMatrix4x4 boneWorldTransform = getWorldTransform(boneNode, model.mRootNode);
+            aiMatrix4x4 boneInverse = boneWorldTransform;
+            boneInverse.Inverse();
+            for (unsigned k = 0; k < bone->mNumWeights; ++k)
+            {
+                float weight = bone->mWeights[k].mWeight;
+                if (weight > 0.33f)
+                {
+                    aiVector3D vertexWorldSpace = meshWorldTransform * mesh->mVertices[bone->mWeights[k].mVertexId];
+                    aiVector3D vertexBoneSpace = boneInverse * vertexWorldSpace;
+                    Vector3 vertex = toVector3(vertexBoneSpace);
+                    float radius = vertex.getLength();
+                    if (radius > model.mBoneRadii[boneIndex])
+                        model.mBoneRadii[boneIndex] = radius;
+                    model.mBoneHitboxes[boneIndex].merge(vertex);
+                }
+            }
+        }
+    }
+}
+
 void buildModel(ExportModel& model)
 {
     if (!model.mRootNode)
         errorExit("Null root node for model");
+    std::string rootNodeName = toStdString(model.mRootNode->mName);
     if (!model.mMeshes.size())
-        errorExit("No meshes");
+        errorExit("No geometries found starting from node " + rootNodeName);
+    
+    std::cout << "Writing model from node " << rootNodeName << std::endl;
     
-    std::cout << "Writing model from node " << toStdString(model.mRootNode->mName) << std::endl;
+    if (model.mBones.size())
+    {
+        std::cout << "Model has skeleton with " << model.mBones.size() << " bones, root bone is " +
+            toStdString(model.mRootBone->mName) << std::endl;
+    }
     
     SharedPtr<Model> outModel(new Model(0));
     outModel->setNumGeometries(model.mMeshes.size());
+    std::vector<std::vector<unsigned> > allBoneMappings;
     BoundingBox box;
     
     bool combineBuffers = true;
@@ -271,10 +385,17 @@ void buildModel(ExportModel& model)
             }
             
             // Build the vertex data
+            // If there are bones, get blend data
+            std::vector<std::vector<unsigned char> > blendIndices;
+            std::vector<std::vector<float> > blendWeights;
+            std::vector<unsigned> boneMappings;
+            if (model.mBones.size())
+                getBlendData(model, mesh, boneMappings, blendIndices, blendWeights);
+            
             void* vertexData = vb->lock(0, vb->getVertexCount(), LOCK_NORMAL);
             float* dest = (float*)vertexData;
             for (unsigned j = 0; j < mesh->mNumVertices; ++j)
-                writeVertex(dest, mesh, j, elementMask, box, vertexTransform, normalTransform);
+                writeVertex(dest, mesh, j, elementMask, box, vertexTransform, normalTransform, blendIndices, blendWeights);
             
             ib->unlock();
             vb->unlock();
@@ -285,6 +406,8 @@ void buildModel(ExportModel& model)
             geom->setDrawRange(TRIANGLE_LIST, 0, mesh->mNumFaces * 3, true);
             outModel->setNumGeometryLodLevels(i, 1);
             outModel->setGeometry(i, 0, geom);
+            if (model.mBones.size() > MAX_SKIN_MATRICES)
+                allBoneMappings.push_back(boneMappings);
         }
     }
     else
@@ -336,9 +459,16 @@ void buildModel(ExportModel& model)
             }
             
             // Build the vertex data
+            // If there are bones, get blend data
+            std::vector<std::vector<unsigned char> > blendIndices;
+            std::vector<std::vector<float> > blendWeights;
+            std::vector<unsigned> boneMappings;
+            if (model.mBones.size())
+                getBlendData(model, mesh, boneMappings, blendIndices, blendWeights);
+            
             float* dest = (float*)((unsigned char*)vertexData + startVertexOffset * vb->getVertexSize());
             for (unsigned j = 0; j < mesh->mNumVertices; ++j)
-                writeVertex(dest, mesh, j, elementMask, box, vertexTransform, normalTransform);
+                writeVertex(dest, mesh, j, elementMask, box, vertexTransform, normalTransform, blendIndices, blendWeights);
             
             // Define the geometry
             geom->setIndexBuffer(ib);
@@ -346,6 +476,8 @@ void buildModel(ExportModel& model)
             geom->setDrawRange(TRIANGLE_LIST, startIndexOffset, mesh->mNumFaces * 3, true);
             outModel->setNumGeometryLodLevels(i, 1);
             outModel->setGeometry(i, 0, geom);
+            if (model.mBones.size() > MAX_SKIN_MATRICES)
+                allBoneMappings.push_back(boneMappings);
             
             startVertexOffset += mesh->mNumVertices;
             startIndexOffset += mesh->mNumFaces * 3;
@@ -354,10 +486,130 @@ void buildModel(ExportModel& model)
     
     outModel->setBoundingBox(box);
     
+    // Build skeleton if necessary
+    if (model.mBones.size())
+    {
+        Skeleton skeleton;
+        std::vector<SharedPtr<Bone> > srcBones;
+        
+        for (unsigned i = 0; i < model.mBones.size(); ++i)
+        {
+            aiNode* boneNode = model.mBones[i];
+            std::string boneName(toStdString(boneNode->mName));
+            
+            srcBones.push_back(SharedPtr<Bone>(new Bone(0, boneName)));
+            srcBones[i]->setRootBone(srcBones[0]);
+            
+            aiMatrix4x4 transform;
+            Vector3 pos, scale;
+            Quaternion rot;
+            // Make the root bone transform relative to the model's root node, if it is not already
+            if (boneNode == model.mRootBone)
+                transform = getWorldTransform(boneNode, model.mRootNode);
+            else
+                transform = boneNode->mTransformation;
+            getPosRotScale(transform, pos, rot, scale);
+            
+            srcBones[i]->setBindTransform(pos, rot, scale);
+            srcBones[i]->setInitialTransform(pos, rot, scale);
+            srcBones[i]->setRadius(model.mBoneRadii[i]);
+            srcBones[i]->setBoundingBox(model.mBoneHitboxes[i]);
+        }
+        // Set the bone hierarchy
+        for (unsigned i = 1; i < model.mBones.size(); ++i)
+        {
+            std::string parentName = toStdString(model.mBones[i]->mParent->mName);
+            for (unsigned j = 0; j < srcBones.size(); ++j)
+            {
+                if ((srcBones[j]->getName() == parentName) && (i != j))
+                {
+                    srcBones[j]->addChild(srcBones[i]);
+                    break;
+                }
+            }
+        }
+        
+        skeleton.setBones(srcBones, srcBones[0]);
+        outModel->setSkeleton(skeleton);
+        if (model.mBones.size() > MAX_SKIN_MATRICES)
+            outModel->setGeometryBoneMappings(allBoneMappings);
+    }
+    
     File outFile(model.mOutName, FILE_WRITE);
     outModel->save(outFile);
 }
 
+unsigned getBoneIndex(ExportModel& model, const std::string& boneName)
+{
+    for (unsigned i = 0; i < model.mBones.size(); ++i)
+    {
+        if (toStdString(model.mBones[i]->mName) == boneName)
+            return i;
+    }
+    errorExit("Bone " + boneName + " not found");
+}
+
+aiBone* getMeshBone(ExportModel& model, const std::string& boneName)
+{
+    for (unsigned i = 0; i < model.mMeshes.size(); ++i)
+    {
+        aiMesh* mesh = model.mMeshes[i];
+        for (unsigned j = 0; j < mesh->mNumBones; ++j)
+        {
+            aiBone* bone = mesh->mBones[j];
+            if (toStdString(bone->mName) == boneName)
+                return bone;
+        }
+    }
+    return 0;
+}
+
+void getBlendData(ExportModel& model, aiMesh* mesh, std::vector<unsigned>& boneMappings, std::vector<std::vector<unsigned char> >&
+    blendIndices, std::vector<std::vector<float> >& blendWeights)
+{
+    blendIndices.resize(mesh->mNumVertices);
+    blendWeights.resize(mesh->mNumVertices);
+    boneMappings.clear();
+    
+    // If model has more bones than can fit vertex shader parameters, write the per-geometry mappings
+    if (model.mBones.size() > MAX_SKIN_MATRICES)
+    {
+        if (mesh->mNumBones > MAX_SKIN_MATRICES)
+            errorExit("Geometry has too many bone influences");
+        boneMappings.resize(mesh->mNumBones);
+        for (unsigned i = 0; i < mesh->mNumBones; ++i)
+        {
+            aiBone* bone = mesh->mBones[i];
+            unsigned globalIndex = getBoneIndex(model, toStdString(bone->mName));
+            boneMappings[i] = globalIndex;
+            for (unsigned j = 0; j < bone->mNumWeights; ++j)
+            {
+                unsigned vertex = bone->mWeights[j].mVertexId;
+                blendIndices[vertex].push_back(i);
+                blendWeights[vertex].push_back(bone->mWeights[j].mWeight);
+                if (blendWeights[vertex].size() > 4)
+                    errorExit("More than 4 bone influences on vertex");
+            }
+        }
+    }
+    else
+    {
+        for (unsigned i = 0; i < mesh->mNumBones; ++i)
+        {
+            aiBone* bone = mesh->mBones[i];
+            unsigned globalIndex = getBoneIndex(model, toStdString(bone->mName));
+            for (unsigned j = 0; j < bone->mNumWeights; ++j)
+            {
+                unsigned vertex = bone->mWeights[j].mVertexId;
+                blendIndices[vertex].push_back(globalIndex);
+                blendWeights[vertex].push_back(bone->mWeights[j].mWeight);
+                if (blendWeights[vertex].size() > 4)
+                    errorExit("More than 4 bone influences on vertex");
+            }
+        }
+    }
+}
+
 void writeShortIndices(unsigned short*& dest, aiMesh* mesh, unsigned index, unsigned offset)
 {
     *dest++ = mesh->mFaces[index].mIndices[0] + offset;
@@ -373,7 +625,8 @@ void writeLargeIndices(unsigned*& dest, aiMesh* mesh, unsigned index, unsigned o
 }
 
 void writeVertex(float*& dest, aiMesh* mesh, unsigned index, unsigned elementMask, BoundingBox& box,
-    const Matrix4x3& vertexTransform, const Matrix3& normalTransform)
+    const Matrix4x3& vertexTransform, const Matrix3& normalTransform, std::vector<std::vector<unsigned char> >& blendIndices,
+    std::vector<std::vector<float> >& blendWeights)
 {
     Vector3 vertex = vertexTransform * toVector3(mesh->mVertices[index]);
     box.merge(vertex);
@@ -420,6 +673,28 @@ void writeVertex(float*& dest, aiMesh* mesh, unsigned index, unsigned elementMas
         *dest++ = tangent.mZ;
         *dest++ = w;
     }
+    if (elementMask & MASK_BLENDWEIGHTS)
+    {
+        for (unsigned i = 0; i < 4; ++i)
+        {
+            if (i < blendWeights[index].size())
+                *dest++ = blendWeights[index][i];
+            else
+                *dest++ = 0.0f;
+        }
+    }
+    if (elementMask & MASK_BLENDINDICES)
+    {
+        unsigned char* destBytes = (unsigned char*)dest;
+        ++dest;
+        for (unsigned i = 0; i < 4; ++i)
+        {
+            if (i < blendIndices[index].size())
+                *destBytes++ = blendIndices[index][i];
+            else
+                *destBytes++ = 0;
+        }
+    }
 }
 
 unsigned getElementMask(aiMesh* mesh)
@@ -435,18 +710,28 @@ unsigned getElementMask(aiMesh* mesh)
         elementMask |= MASK_TEXCOORD1;
     if (mesh->GetNumUVChannels() > 1)
         elementMask |= MASK_TEXCOORD2;
+    if (mesh->HasBones())
+        elementMask |= (MASK_BLENDWEIGHTS | MASK_BLENDINDICES);
     return elementMask;
 }
 
-aiNode* findNode(const std::string& name, aiNode* rootNode)
+aiNode* findNode(const std::string& name, aiNode* rootNode, bool caseSensitive)
 {
     if (!rootNode)
         return 0;
-    if (toLower(toStdString(rootNode->mName)) == toLower(name))
-        return rootNode;
+    if (!caseSensitive)
+    {
+        if (toLower(toStdString(rootNode->mName)) == toLower(name))
+            return rootNode;
+    }
+    else
+    {
+        if (toStdString(rootNode->mName) == name)
+            return rootNode;
+    }
     for (unsigned i = 0; i < rootNode->mNumChildren; ++i)
     {
-        aiNode* found = findNode(name, rootNode->mChildren[i]);
+        aiNode* found = findNode(name, rootNode->mChildren[i], caseSensitive);
         if (found)
             return found;
     }