// NeL - MMORPG Framework // Copyright (C) 2015 Winch Gate Property Limited // Author: Jan Boon // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include #include "mesh_utils.h" #include #include #include #include "database_config.h" #include "scene_meta.h" #include #include #include CMeshUtilsSettings::CMeshUtilsSettings() { /*ShapeDirectory = "shape"; IGDirectory = "ig"; SkelDirectory = "skel";*/ } struct CNodeContext { CNodeContext() : AssimpNode(NULL), IsBone(false) { } const aiNode *AssimpNode; bool IsBone; }; typedef std::map TNodeContextMap; struct CMeshUtilsContext { CMeshUtilsContext(const CMeshUtilsSettings &settings) : Settings(settings), AssimpScene(NULL) { } const CMeshUtilsSettings &Settings; NLMISC::CToolLogger ToolLogger; const aiScene *AssimpScene; CSceneMeta SceneMeta; TNodeContextMap Nodes; // std::map MeshNames; // Maps meshes to a node name ********************* todo *************** }; void importNode(CMeshUtilsContext &context, const aiNode *node) { if (node->mNumMeshes) { // TODO } for (unsigned int i = 0; i < node->mNumChildren; ++i) importNode(context, node->mChildren[i]); } void validateAssimpNodeNames(CMeshUtilsContext &context, const aiNode *node) { if (!node->mParent || node == context.AssimpScene->mRootNode) { // do nothing } else if (node->mName.length == 0) { tlwarning(context.ToolLogger, context.Settings.SourceFilePath.c_str(), "Node has no name"); } else { CNodeContext &nodeContext = context.Nodes[node->mName.C_Str()]; if (nodeContext.AssimpNode && nodeContext.AssimpNode != node) { tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(), "Node name '%s' appears multiple times", node->mName.C_Str()); } else { nodeContext.AssimpNode = node; } } for (unsigned int i = 0; i < node->mNumChildren; ++i) validateAssimpNodeNames(context, node->mChildren[i]); } void flagAssimpBones(CMeshUtilsContext &context) { // Find out which nodes are bones by checking the mesh meta info const aiScene *scene = context.AssimpScene; for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { // nldebug("FOUND MESH '%s'\n", scene->mMeshes[i]->mName.C_Str()); const aiMesh *mesh = scene->mMeshes[i]; for (unsigned int j = 0; j < mesh->mNumBones; ++j) { CNodeContext &nodeContext = context.Nodes[mesh->mBones[j]->mName.C_Str()]; if (!nodeContext.AssimpNode) { tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(), "Bone '%s' has no associated node", mesh->mBones[j]->mName.C_Str()); } else { // Flag as bone nodeContext.IsBone = true; // Flag all parents as bones /*const aiNode *parent = nodeContext.AssimpNode; while (parent = parent->mParent) if (parent->mName.length) { context.Nodes[parent->mName.C_Str()].IsBone = true; }*/ } } } } void flagRecursiveBones(CMeshUtilsContext &context, CNodeContext &nodeContext) { nodeContext.IsBone = true; const aiNode *node = nodeContext.AssimpNode; nlassert(node); for (unsigned int i = 0; i < node->mNumChildren; ++i) flagRecursiveBones(context, context.Nodes[node->mName.C_Str()]); } void flagMetaBones(CMeshUtilsContext &context) { for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it) { CNodeContext &ctx = it->second; CNodeMeta &meta = context.SceneMeta.Nodes[it->first]; if (meta.ExportBone == TBoneForce) ctx.IsBone = true; else if (meta.ExportBone == TBoneRoot) flagRecursiveBones(context, ctx); } } void flagLocalParentBones(CMeshUtilsContext &context, CNodeContext &nodeContext) { const aiNode *node = nodeContext.AssimpNode; } void flagAllParentBones(CMeshUtilsContext &context, CNodeContext &nodeContext) { const aiNode *parent = nodeContext.AssimpNode; while (parent = parent->mParent) if (parent->mName.length) context.Nodes[parent->mName.C_Str()].IsBone = true; } void flagExpandedBones(CMeshUtilsContext &context) { switch (context.SceneMeta.SkeletonMode) { case TSkelLocal: for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it) { CNodeContext &nodeContext = it->second; if (nodeContext.IsBone) { } } break; case TSkelRoot: for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it) { CNodeContext &nodeContext = it->second; if (nodeContext.IsBone) { } } break; case TSkelFull: for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it) { CNodeContext &nodeContext = it->second; if (nodeContext.IsBone) { } } break; } } // TODO: Separate load scene and save scene functions int exportScene(const CMeshUtilsSettings &settings) { CMeshUtilsContext context(settings); NLMISC::CFile::createDirectoryTree(settings.DestinationDirectoryPath); if (!settings.ToolDependLog.empty()) context.ToolLogger.initDepend(settings.ToolDependLog); if (!settings.ToolErrorLog.empty()) context.ToolLogger.initError(settings.ToolErrorLog); context.ToolLogger.writeDepend(NLMISC::BUILD, "*", context.Settings.SourceFilePath.c_str()); // Base input file // Apply database configuration CDatabaseConfig::init(settings.SourceFilePath); CDatabaseConfig::initTextureSearchDirectories(); Assimp::Importer importer; const aiScene *scene = importer.ReadFile(settings.SourceFilePath, aiProcess_Triangulate | aiProcess_ValidateDataStructure); // aiProcess_SplitLargeMeshes | aiProcess_LimitBoneWeights if (!scene) { const char *errs = importer.GetErrorString(); if (errs) tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(), "Assimp failed to load the scene: '%s'", errs); else tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(), "Unable to load scene"); return EXIT_FAILURE; } // aiProcess_Triangulate // aiProcess_ValidateDataStructure: TODO: Catch Assimp error output stream // aiProcess_RemoveRedundantMaterials: Not used because we may override materials with NeL Material from meta // aiProcess_ImproveCacheLocality: TODO: Verify this does not modify vertex indices //scene->mRootNode->mMetaData context.AssimpScene = scene; if (context.SceneMeta.load(context.Settings.SourceFilePath)) context.ToolLogger.writeDepend(NLMISC::BUILD, "*", context.SceneMeta.metaFilePath().c_str()); // Meta input file validateAssimpNodeNames(context, context.AssimpScene->mRootNode); flagAssimpBones(context); flagMetaBones(context); flagExpandedBones(context); importNode(context, scene->mRootNode); return EXIT_SUCCESS; } /* end of file */