Fix game:addSpawnShapesByZone
[ryzomcore.git] / nel / tools / 3d / mesh_utils / mesh_utils.cpp
blob5d00965edd96bc38b0e499639773fa99d9d6e25d
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2015 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2016 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 // Author: Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
22 #include <nel/misc/types_nl.h>
23 #include "mesh_utils.h"
25 #include <nel/misc/debug.h>
26 #include <nel/pipeline/tool_logger.h>
27 #include <nel/pipeline/project_config.h>
28 #include <nel/misc/sstring.h>
29 #include <nel/misc/file.h>
30 #include <nel/misc/path.h>
32 #include <nel/3d/shape.h>
33 #include <nel/3d/mesh.h>
34 #include <nel/3d/texture_file.h>
36 #include "scene_meta.h"
38 #include <assimp/postprocess.h>
39 #include <assimp/scene.h>
40 #include <assimp/Importer.hpp>
42 #define NL_NODE_INTERNAL_TYPE aiNode
43 #define NL_SCENE_INTERNAL_TYPE aiScene
44 #include "scene_context.h"
46 #include "assimp_material.h"
47 #include "assimp_shape.h"
49 CMeshUtilsSettings::CMeshUtilsSettings()
51 /*ShapeDirectory = "shape";
52 IGDirectory = "ig";
53 SkelDirectory = "skel";*/
56 void importShapes(CMeshUtilsContext &context, const aiNode *node)
58 if (node != context.InternalScene->mRootNode)
60 CNodeContext &nodeContext = context.Nodes[node->mName.C_Str()];
61 CNodeMeta &nodeMeta = context.SceneMeta.Nodes[node->mName.C_Str()];
62 if (nodeMeta.ExportMesh == TMeshShape && nodeMeta.InstanceName.empty())
64 if (node->mNumMeshes)
66 nldebug("Shape '%s' found containing '%u' meshes", node->mName.C_Str(), node->mNumMeshes);
67 assimpShape(context, nodeContext);
72 for (unsigned int i = 0; i < node->mNumChildren; ++i)
73 importShapes(context, node->mChildren[i]);
76 void validateInternalNodeNames(CMeshUtilsContext &context, const aiNode *node)
78 if (!node->mParent || node == context.InternalScene->mRootNode)
80 // do nothing
82 else if (node->mName.length == 0)
84 tlwarning(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
85 "Node has no name");
87 else
89 CNodeContext &nodeContext = context.Nodes[node->mName.C_Str()];
91 if (nodeContext.InternalNode && nodeContext.InternalNode != node)
93 tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
94 "Node name '%s' appears multiple times", node->mName.C_Str());
96 else
98 nodeContext.InternalNode = node;
102 for (unsigned int i = 0; i < node->mNumChildren; ++i)
103 validateInternalNodeNames(context, node->mChildren[i]);
106 void flagAssimpBones(CMeshUtilsContext &context)
108 // Find out which nodes are bones by checking the mesh meta info
109 const aiScene *scene = context.InternalScene;
110 for (unsigned int i = 0; i < scene->mNumMeshes; ++i)
112 // nldebug("FOUND MESH '%s'\n", scene->mMeshes[i]->mName.C_Str());
113 const aiMesh *mesh = scene->mMeshes[i];
114 for (unsigned int j = 0; j < mesh->mNumBones; ++j)
116 CNodeContext &nodeContext = context.Nodes[mesh->mBones[j]->mName.C_Str()];
117 if (!nodeContext.InternalNode)
119 tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
120 "Bone '%s' has no associated node", mesh->mBones[j]->mName.C_Str());
122 else
124 // Flag as bone
125 nodeContext.IsBone = true;
127 // Flag all parents as bones
128 /*const aiNode *parent = nodeContext.InternalNode;
129 while (parent = parent->mParent) if (parent->mName.length)
131 context.Nodes[parent->mName.C_Str()].IsBone = true;
137 // Find out which nodes are bones by checking the animation info
138 // TODO
141 void flagRecursiveBones(CMeshUtilsContext &context, CNodeContext &nodeContext, bool autoStop = false)
143 nodeContext.IsBone = true;
144 const aiNode *node = nodeContext.InternalNode;
145 nlassert(node);
146 for (unsigned int i = 0; i < node->mNumChildren; ++i)
148 CNodeContext &ctx = context.Nodes[node->mName.C_Str()];
149 if (autoStop && ctx.IsBone)
150 continue;
151 flagRecursiveBones(context, ctx);
155 void flagMetaBones(CMeshUtilsContext &context)
157 for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
159 CNodeContext &ctx = it->second;
160 CNodeMeta &meta = context.SceneMeta.Nodes[it->first];
161 if (meta.ExportBone == TBoneForce)
162 ctx.IsBone = true;
163 else if (meta.ExportBone == TBoneRoot)
164 flagRecursiveBones(context, ctx);
168 void flagLocalParentBones(CMeshUtilsContext &context, CNodeContext &nodeContext)
170 const aiNode *node = nodeContext.InternalNode;
173 void flagAllParentBones(CMeshUtilsContext &context, CNodeContext &nodeContext, bool autoStop = false)
175 const aiNode *parent = nodeContext.InternalNode;
176 while (parent = parent->mParent) if (parent->mName.length && parent != context.InternalScene->mRootNode)
178 CNodeContext &ctx = context.Nodes[parent->mName.C_Str()];
179 if (autoStop && ctx.IsBone)
180 break;
181 ctx.IsBone = true;
185 bool hasIndirectParentBone(CMeshUtilsContext &context, CNodeContext &nodeContext)
187 const aiNode *parent = nodeContext.InternalNode;
188 while (parent = parent->mParent) if (parent->mName.length && parent != context.InternalScene->mRootNode)
189 if (context.Nodes[parent->mName.C_Str()].IsBone) return true;
190 return false;
193 void flagExpandedBones(CMeshUtilsContext &context)
195 switch (context.SceneMeta.SkeletonMode)
197 case TSkelLocal:
198 for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
200 CNodeContext &nodeContext = it->second;
201 if (nodeContext.IsBone && hasIndirectParentBone(context, nodeContext))
202 flagAllParentBones(context, nodeContext, true);
204 break;
205 case TSkelRoot:
206 for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
208 CNodeContext &nodeContext = it->second;
209 if (nodeContext.IsBone)
210 flagAllParentBones(context, nodeContext, true);
212 break;
213 case TSkelFull:
214 for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
216 CNodeContext &nodeContext = it->second;
217 if (nodeContext.IsBone)
218 flagAllParentBones(context, nodeContext, true);
220 for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
222 CNodeContext &nodeContext = it->second;
223 if (nodeContext.IsBone)
224 flagRecursiveBones(context, nodeContext, true);
226 break;
230 void exportShapes(CMeshUtilsContext &context)
232 for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
234 CNodeContext &nodeContext = it->second;
235 if (nodeContext.Shape)
237 std::string shapePath = NLMISC::CPath::standardizePath(context.Settings.DestinationDirectoryPath, true) + it->first + ".shape";
238 context.ToolLogger.writeDepend(NLPIPELINE::BUILD, shapePath.c_str(), "*");
239 NLMISC::COFile f;
240 if (f.open(shapePath, false, false, true))
244 NL3D::CShapeStream shapeStream(nodeContext.Shape);
245 shapeStream.serial(f);
246 f.close();
248 catch (...)
250 tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
251 "Shape '%s' serialization failed!", it->first.c_str());
254 if (NL3D::CMeshBase *mesh = dynamic_cast<NL3D::CMeshBase *>(nodeContext.Shape.getPtr()))
256 for (uint mi = 0; mi < mesh->getNbMaterial(); ++mi)
258 NL3D::CMaterial &mat = mesh->getMaterial(mi);
259 for (uint ti = 0; ti < NL3D::IDRV_MAT_MAXTEXTURES; ++ti)
261 if (NL3D::ITexture *itex = mat.getTexture(ti))
263 if (NL3D::CTextureFile *tex = dynamic_cast<NL3D::CTextureFile *>(itex))
265 std::string fileName = tex->getFileName();
266 std::string knownPath = NLMISC::CPath::lookup(fileName, false, false, false);
267 if (!knownPath.empty())
269 context.ToolLogger.writeDepend(NLPIPELINE::RUNTIME, shapePath.c_str(), knownPath.c_str());
271 else
273 // TODO: Move this warning into nelmeta serialization so it's shown before export
274 tlwarning(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
275 "Texture '%s' referenced in material but not found in the database search paths", fileName.c_str());
286 // TODO: Separate load scene and save scene functions
287 int exportScene(const CMeshUtilsSettings &settings)
289 CMeshUtilsContext context(settings);
290 NLMISC::CFile::createDirectoryTree(settings.DestinationDirectoryPath);
292 if (!settings.ToolDependLog.empty())
293 context.ToolLogger.initDepend(settings.ToolDependLog);
294 if (!settings.ToolErrorLog.empty())
295 context.ToolLogger.initError(settings.ToolErrorLog);
296 context.ToolLogger.writeDepend(NLPIPELINE::BUILD, "*", NLMISC::CPath::standardizePath(context.Settings.SourceFilePath, false).c_str()); // Base input file
298 // Apply database configuration
299 if (!NLPIPELINE::CProjectConfig::init(settings.SourceFilePath,
300 NLPIPELINE::CProjectConfig::DatabaseTextureSearchPaths,
301 true))
303 tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(), "Unable to find database.cfg in input path or any of its parents.");
304 // return EXIT_FAILURE; We can continue but the output will not be guaranteed...
307 Assimp::Importer importer;
308 const aiScene *scene = importer.ReadFile(settings.SourceFilePath, 0
309 | aiProcess_Triangulate
310 | aiProcess_ValidateDataStructure
311 | aiProcess_GenNormals // Or GenSmoothNormals? TODO: Validate smoothness between material boundaries!
312 ); // aiProcess_SplitLargeMeshes | aiProcess_LimitBoneWeights
313 if (!scene)
315 const char *errs = importer.GetErrorString();
316 if (errs) tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(), "Assimp failed to load the scene: '%s'", errs);
317 else tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(), "Unable to load scene");
318 return EXIT_FAILURE;
320 // aiProcess_Triangulate
321 // aiProcess_ValidateDataStructure: TODO: Catch Assimp error output stream
322 // aiProcess_RemoveRedundantMaterials: Not used because we may override materials with NeL Material from meta
323 // aiProcess_ImproveCacheLocality: TODO: Verify this does not modify vertex indices
324 //scene->mRootNode->mMetaData
326 context.InternalScene = scene;
327 if (context.SceneMeta.load(context.Settings.SourceFilePath))
328 context.ToolLogger.writeDepend(NLPIPELINE::BUILD, "*", context.SceneMeta.metaFilePath().c_str()); // Meta input file
330 validateInternalNodeNames(context, context.InternalScene->mRootNode);
332 // -- SKEL FLAG --
333 flagAssimpBones(context);
334 flagMetaBones(context);
335 flagExpandedBones(context);
336 // TODO
337 // [
338 // Only necessary in TSkelLocal
339 // For each shape test if all the bones have the same root bones for their skeleton
340 // 1) Iterate each until a different is found
341 // 2) When a different root is found, connect the two to the nearest common bone
342 // ]
343 // -- SKEL FLAG --
345 // First import materials
346 assimpMaterials(context);
348 // Import shapes
349 importShapes(context, context.InternalScene->mRootNode);
351 // Export shapes
352 exportShapes(context);
354 return EXIT_SUCCESS;
357 /* end of file */