ソースを参照

Added initial scene save support to AssetImporter.

Lasse Öörni 15 年 前
コミット
cd9a426fc9

+ 12 - 3
Engine/Scene/Scene.cpp

@@ -53,9 +53,6 @@ Scene::Scene(ResourceCache* cache, const std::string& name) :
     mPlayback(false),
     mAsyncLoading(false)
 {
-    if (!mCache)
-        EXCEPTION("Null resource cache for Scene");
-    
     LOGINFO("Scene " + mName + " created");
 }
 
@@ -155,6 +152,9 @@ void Scene::load(Deserializer& source, bool throwOnError)
 {
     LOGINFO("Loading scene from " + source.getName());
     
+    if (!mCache)
+        EXCEPTION("Null resource cache");
+    
     stopAsyncLoading();
     removeAllEntities();
     
@@ -211,6 +211,9 @@ void Scene::loadXML(Deserializer& source, bool throwOnError)
 {
     LOGINFO("Loading XML scene from " + source.getName());
     
+    if (!mCache)
+        EXCEPTION("Null resource cache");
+    
     XMLFile xml;
     xml.load(source, mCache);
     XMLElement sceneElem = xml.getRootElement();
@@ -408,6 +411,9 @@ void Scene::loadAsync(File* file)
     if (!file)
         EXCEPTION("Null file for async scene loading");
     
+    if (!mCache)
+        EXCEPTION("Null resource cache");
+    
     stopAsyncLoading();
     removeAllEntities();
     
@@ -430,6 +436,9 @@ void Scene::loadAsyncXML(File* file)
     if (!file)
         EXCEPTION("Null file for async XML scene loading");
     
+    if (!mCache)
+        EXCEPTION("Null resource cache");
+    
     file->seek(0);
     SharedPtr<XMLFile> xml(new XMLFile());
     xml->load(*file, mCache);

+ 317 - 30
Tools/AssetImporter/AssetImporter.cpp

@@ -27,7 +27,11 @@
 #include "Geometry.h"
 #include "IndexBuffer.h"
 #include "Model.h"
+#include "Octree.h"
+#include "PhysicsWorld.h"
 #include "Quaternion.h"
+#include "Scene.h"
+#include "StaticModel.h"
 #include "StringUtils.h"
 #include "Vector3.h"
 #include "VertexBuffer.h"
@@ -57,6 +61,7 @@ struct ExportModel
     std::string mOutName;
     const aiScene* mScene;
     aiNode* mRootNode;
+    std::set<unsigned> mMeshIndices;
     std::vector<aiMesh*> mMeshes;
     std::vector<aiNode*> mMeshNodes;
     std::vector<aiNode*> mBones;
@@ -68,10 +73,22 @@ struct ExportModel
     unsigned mTotalIndices;
 };
 
+struct ExportScene
+{
+    std::string mOutName;
+    bool mLocalIDs;
+    bool mNoExtensions;
+    const aiScene* mScene;
+    aiNode* mRootNode;
+    std::vector<ExportModel> mModels;
+    std::vector<aiNode*> mNodes;
+    std::vector<unsigned> mNodeModelIndices;
+};
+
 int main(int argc, char** argv);
 void run(const std::vector<std::string>& arguments);
-void dumpNodes(aiNode* rootNode, unsigned level);
-void exportModel(ExportModel& model);
+void dumpNodes(const aiScene* scene, aiNode* rootNode, unsigned level);
+void exportModel(const aiScene* scene, aiNode* rootNode, const std::string& outName, bool noAnimations);
 void collectMeshes(ExportModel& model, aiNode* node);
 void collectBones(ExportModel& model);
 void collectBonesFinal(std::vector<aiNode*>& dest, const std::set<aiNode*>& necessary, aiNode* node);
@@ -79,6 +96,13 @@ void collectAnimations(ExportModel& model);
 void buildBoneCollisionInfo(ExportModel& model);
 void buildAndSaveModel(ExportModel& model);
 void buildAndSaveAnimations(ExportModel& model);
+void exportScene(const aiScene* scene, aiNode* rootNode, const std::string& outName, bool localIDs, bool noExtensions);
+void collectSceneModels(ExportScene& scene, aiNode* node);
+void collectSceneNodes(ExportScene& scene, aiNode* node);
+void buildAndSaveScene(ExportScene& scene);
+std::set<unsigned> getMeshesUnderNodeSet(aiNode* node);
+std::vector<std::pair<aiNode*, aiMesh*> > getMeshesUnderNode(const aiScene* scene, aiNode* node);
+unsigned getMeshIndex(const aiScene* scene, aiMesh* mesh);
 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);
@@ -121,10 +145,38 @@ int main(int argc, char** argv)
 void run(const std::vector<std::string>& arguments)
 {
     if (arguments.size() < 2)
-        errorExit("Usage: AssetImporter <inputfile> <outputfile> [rootnode]\n");
+    {
+        errorExit(
+            "Usage: AssetImporter <command> <input file> <output file> [options]\n"
+            "See http://assimp.sourceforge.net/main_features_formats.html for input formats\n\n"
+            "Commands:\n"
+            "model     Export a model and animations. Output file is the model name\n"
+            "scene     Export a scene. Output file is the scene XML file name\n"
+            "dumpnodes Dump scene node structure. No output file is generated\n"
+            "\n"
+            "Options:\n"
+            "-l        Use local ID's for scene entities\n"
+            "-na       Do not export animations\n"
+            "-ne       Do not create Octree & PhysicsWorld extensions to the scene\n"
+            "-nm       Do not export materials\n"
+            "-rX       Use scene node X as root node\n"
+            "-t        Generate tangents to model(s)\n"
+        );
+    }
     
-    Assimp::Importer importer;
-    const aiScene* scene = importer.ReadFile(arguments[0].c_str(),
+    std::string command = toLower(arguments[0]);
+    std::string inFile = arguments[1];
+    std::string outFile;
+    std::string rootNodeName;
+    if (arguments.size() > 2)
+        outFile = arguments[2];
+    
+    bool noMaterials = false;
+    bool noAnimations = false;
+    bool noExtensions = false;
+    bool localIDs = false;
+    
+    unsigned flags = 
         aiProcess_ConvertToLeftHanded |
         aiProcess_JoinIdenticalVertices |
         aiProcess_Triangulate |
@@ -134,28 +186,74 @@ void run(const std::vector<std::string>& arguments)
         aiProcess_FixInfacingNormals |
         aiProcess_FindInvalidData |
         aiProcess_FindInstances |
-        aiProcess_OptimizeMeshes);
+        aiProcess_OptimizeMeshes;
     
-    if (!scene)
-        errorExit("Could not open input file " + arguments[0]);
+    for (unsigned i = 3; i < arguments.size(); ++i)
+    {
+        if ((arguments[i].length() >= 2) && (arguments[i][0] == '-'))
+        {
+            std::string parameter;
+            if (arguments[i].length() >= 3)
+                parameter = arguments[i].substr(2);
+            
+            switch (tolower(arguments[i][1]))
+            {
+            case 't':
+                flags |= aiProcess_CalcTangentSpace;
+                break;
+            case 'l':
+                localIDs = true;
+                break;
+            case 'r':
+                rootNodeName = parameter;
+                break;
+            case 'n':
+                if (!parameter.empty())
+                {
+                    switch (tolower(parameter[0]))
+                    {
+                    case 'a':
+                        noAnimations = true;
+                        break;
+                    case 'e':
+                        noExtensions = true;
+                        break;
+                    case 'm':
+                        noMaterials = true;
+                        break;
+                    }
+                }
+                break;
+            }
+        }
+    }
     
-    ExportModel model;
-    model.mOutName = arguments[1];
-    model.mScene = scene;
+    if ((command != "model") && (command != "scene") && (command != "dumpnodes"))
+        errorExit("Unrecognized command " + command);
     
-    if (arguments.size() < 3)
-        model.mRootNode = scene->mRootNode;
-    else
+    Assimp::Importer importer;
+    std::cout << "Reading file " << inFile << std::endl;
+    const aiScene* scene = importer.ReadFile(inFile.c_str(), flags);
+    if (!scene)
+        errorExit("Could not open or parse input file " + inFile);
+    
+    aiNode* rootNode = scene->mRootNode;
+    if (!rootNodeName.empty())
     {
-        model.mRootNode = findNode(arguments[2], scene->mRootNode, false);
-        if (!model.mRootNode)
-            errorExit("Could not find scene node " + arguments[2]);
+        rootNode = findNode(rootNodeName, scene->mRootNode, false);
+        if (!rootNode)
+            errorExit("Could not find scene node " + rootNodeName);
     }
     
-    exportModel(model);
+    if (command == "model")
+        exportModel(scene, rootNode, outFile, noAnimations);
+    if (command == "scene")
+        exportScene(scene, rootNode, outFile, localIDs, noExtensions);
+    if (command == "dumpnodes")
+        dumpNodes(scene, rootNode, 0);
 }
 
-void dumpNodes(aiNode* rootNode, unsigned level)
+void dumpNodes(const aiScene* scene, aiNode* rootNode, unsigned level)
 {
     if (!rootNode)
         return;
@@ -165,26 +263,40 @@ void dumpNodes(aiNode* rootNode, unsigned level)
     for (unsigned i = 0; i < level * 2; ++i)
         indent[i] = ' ';
     
-    if (!rootNode->mNumMeshes)
-        std::cout << indent << "Node " << toStdString(rootNode->mName) << std::endl;
-    else
-    {
-        std::cout << indent << "Node " << toStdString(rootNode->mName) << " - " << rootNode->mNumMeshes << " geometries"
-            << std::endl;
-    }
+    Vector3 pos, scale;
+    Quaternion rot;
+    getPosRotScale(rootNode->mTransformation, pos, rot, scale);
+    
+    std::cout << indent << "Node " << toStdString(rootNode->mName) << " pos " << toString(pos) << std::endl;
+  
+    if (rootNode->mNumMeshes == 1)
+        std::cout << indent << "  " << rootNode->mNumMeshes << " geometry" << std::endl;
+    if (rootNode->mNumMeshes > 1)
+        std::cout << indent << "  " << rootNode->mNumMeshes << " geometries" << std::endl;
     
     for (unsigned i = 0; i < rootNode->mNumChildren; ++i)
-        dumpNodes(rootNode->mChildren[i], level + 1);
+        dumpNodes(scene, rootNode->mChildren[i], level + 1);
 }
 
-void exportModel(ExportModel& model)
+void exportModel(const aiScene* scene, aiNode* rootNode, const std::string& outName, bool noAnimations)
 {
+    if (outName.empty())
+        errorExit("No output file defined");
+    
+    ExportModel model;
+    model.mScene = scene;
+    model.mRootNode = rootNode;
+    model.mOutName = outName;
+    
     collectMeshes(model, model.mRootNode);
     collectBones(model);
-    collectAnimations(model);
     buildBoneCollisionInfo(model);
     buildAndSaveModel(model);
-    buildAndSaveAnimations(model);
+    if (!noAnimations)
+    {
+        collectAnimations(model);
+        buildAndSaveAnimations(model);
+    }
 }
 
 void collectMeshes(ExportModel& model, aiNode* node)
@@ -201,6 +313,7 @@ void collectMeshes(ExportModel& model, aiNode* node)
             }
         }
         
+        model.mMeshIndices.insert(node->mMeshes[i]);
         model.mMeshes.push_back(mesh);
         model.mMeshNodes.push_back(node);
         model.mTotalVertices += mesh->mNumVertices;
@@ -709,6 +822,180 @@ void buildAndSaveAnimations(ExportModel& model)
     }
 }
 
+void exportScene(const aiScene* scene, aiNode* rootNode, const std::string& outName, bool localIDs, bool noExtensions)
+{
+    if (outName.empty())
+        errorExit("No output file defined");
+    
+    ExportScene outScene;
+    outScene.mOutName = outName;
+    outScene.mLocalIDs = localIDs;
+    outScene.mNoExtensions = noExtensions;
+    outScene.mScene = scene;
+    outScene.mRootNode = rootNode;
+    
+    collectSceneModels(outScene, rootNode);
+    collectSceneNodes(outScene, rootNode);
+    
+    // Save models
+    try
+    {
+        for (unsigned i = 0; i < outScene.mModels.size(); ++i)
+            buildAndSaveModel(outScene.mModels[i]);
+    }
+    catch (...)
+    {
+    }
+    
+    // Save scene
+    buildAndSaveScene(outScene);
+}
+
+void collectSceneModels(ExportScene& scene, aiNode* node)
+{
+    std::vector<std::pair<aiNode*, aiMesh*> > meshes = getMeshesUnderNode(scene.mScene, node);
+    // If meshes encountered, do not recurse further, but build a model for export
+    if (meshes.size())
+    {
+        ExportModel model;
+        model.mScene = scene.mScene;
+        model.mRootNode = node;
+        model.mOutName = toStdString(node->mName) + ".mdl";
+        std::cout << "Found model " << model.mOutName << std::endl;
+        for (unsigned i = 0; i < meshes.size(); ++i)
+        {
+            aiMesh* mesh = meshes[i].second;
+            unsigned meshIndex = getMeshIndex(scene.mScene, mesh);
+            model.mMeshIndices.insert(meshIndex);
+            model.mMeshes.push_back(mesh);
+            model.mMeshNodes.push_back(meshes[i].first);
+            model.mTotalVertices += mesh->mNumVertices;
+            model.mTotalIndices += mesh->mNumFaces * 3;
+        }
+        
+        // Check if a model with identical mesh indices already exists. If yes, do not export twice
+        bool unique = true;
+        for (unsigned i = 0; i < scene.mModels.size(); ++i)
+        {
+            if (scene.mModels[i].mMeshIndices == model.mMeshIndices)
+            {
+                unique = false;
+                break;
+            }
+        }
+        if (unique)
+        {
+            collectBones(model);
+            buildBoneCollisionInfo(model);
+            scene.mModels.push_back(model);
+        }
+        return;
+    }
+    // If no meshes found, recurse to child nodes
+    for (unsigned i = 0; i < node->mNumChildren; ++i)
+        collectSceneModels(scene, node->mChildren[i]);
+}
+
+void collectSceneNodes(ExportScene& scene, aiNode* node)
+{
+    std::set<unsigned> meshIndices = getMeshesUnderNodeSet(node);;
+    if (meshIndices.size())
+    {
+        // Check if a matching set of mesh indices is found from the models we are going to write (should be)
+        for (unsigned i = 0; i < scene.mModels.size(); ++i)
+        {
+            if (scene.mModels[i].mMeshIndices == meshIndices)
+            {
+                std::cout << "Found node " << toStdString(node->mName) << std::endl;
+                scene.mNodes.push_back(node);
+                scene.mNodeModelIndices.push_back(i);
+                break;
+            }
+        }
+        return;
+    }
+    // If no meshes found, recurse to child nodes
+    for (unsigned i = 0; i < node->mNumChildren; ++i)
+        collectSceneNodes(scene, node->mChildren[i]);
+}
+
+void buildAndSaveScene(ExportScene& scene)
+{
+    std::cout << "Writing scene" << std::endl;
+    
+    SharedPtr<Scene> outScene(new Scene(0, getFileName(scene.mOutName)));
+    if (!scene.mNoExtensions)
+    {
+        //! \todo Make the physics properties configurable
+        PhysicsWorld* physicsWorld = new PhysicsWorld(outScene);
+        physicsWorld->setGravity(Vector3(0.0f, -9.81f, 0.0f));
+        outScene->addExtension(physicsWorld);
+        
+        //! \todo Make the octree properties configurable, or detect from the scene contents
+        Octree* octree = new Octree(BoundingBox(-1000.0f, 1000.0f), 8, true);
+        outScene->addExtension(octree);
+    }
+    
+    for (unsigned i = 0; i < scene.mNodes.size(); ++i)
+    {
+        // Create a simple entity and static model component for each node
+        Entity* entity = outScene->createEntity(toStdString(scene.mNodes[i]->mName), scene.mLocalIDs);
+        StaticModel* staticModel = new StaticModel();
+        entity->addComponent(staticModel);
+        // Create a dummy model so that the reference can be stored
+        SharedPtr<Model> dummyModel(new Model(0, scene.mModels[scene.mNodeModelIndices[i]].mOutName));
+        staticModel->setModel(dummyModel);
+        // Set a flattened transform
+        Vector3 pos, scale;
+        Quaternion rot;
+        getPosRotScale(getWorldTransform(scene.mNodes[i], 0), pos, rot, scale);
+        staticModel->setTransform(pos, rot, scale);
+    }
+    
+    File file(scene.mOutName, FILE_WRITE);
+    outScene->saveXML(file);
+}
+
+std::set<unsigned> getMeshesUnderNodeSet(aiNode* node)
+{
+    std::set<unsigned> ret;
+    
+    // Do not check this model directly, but rather check if there are meshes in the immediate children
+    for (unsigned i = 0; i < node->mNumChildren; ++i)
+    {
+        aiNode* childNode = node->mChildren[i];
+        for (unsigned j = 0; j < childNode->mNumMeshes; ++j)
+            ret.insert(childNode->mMeshes[j]);
+    }
+    
+    return ret;
+}
+
+std::vector<std::pair<aiNode*, aiMesh*> > getMeshesUnderNode(const aiScene* scene, aiNode* node)
+{
+    std::vector<std::pair<aiNode*, aiMesh*> > ret;
+    
+    // Do not check this model directly, but rather check if there are meshes in the immediate children
+    for (unsigned i = 0; i < node->mNumChildren; ++i)
+    {
+        aiNode* childNode = node->mChildren[i];
+        for (unsigned j = 0; j < childNode->mNumMeshes; ++j)
+            ret.push_back(std::make_pair(childNode, scene->mMeshes[childNode->mMeshes[j]]));
+    }
+    
+    return ret;
+}
+
+unsigned getMeshIndex(const aiScene* scene, aiMesh* mesh)
+{
+    for (unsigned i = 0; i < scene->mNumMeshes; ++i)
+    {
+        if (scene->mMeshes[i] == mesh)
+            return i;
+    }
+    return M_MAX_UNSIGNED;
+}
+
 unsigned getBoneIndex(ExportModel& model, const std::string& boneName)
 {
     for (unsigned i = 0; i < model.mBones.size(); ++i)

+ 3 - 3
Tools/AssetImporter/CMakeLists.txt

@@ -8,12 +8,12 @@ set (SOURCE_FILES ${CPP_FILES} ${H_FILES})
 
 # Include directories
 include_directories (
-    ../../Engine/Common ../../Engine/Math ../../Engine/Renderer ../../Engine/Resource ../../Engine/Scene
-    ../../ThirdParty/Assimp/include
+    ../../Engine/Common ../../Engine/Event ../../Engine/Math ../../Engine/Physics ../../Engine/Renderer 
+    ../../Engine/Resource ../../Engine/Scene ../../ThirdParty/Assimp/include
 )
 
 # Define target & libraries to link
 add_executable (${TARGET_NAME} ${SOURCE_FILES})
-target_link_libraries (${TARGET_NAME} Assimp Common Math Renderer Scene Resource)
+target_link_libraries (${TARGET_NAME} Assimp Common Event Math Physics Renderer Scene Resource)
 finalize_exe ()