1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2015 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2016 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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";
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())
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
)
82 else if (node
->mName
.length
== 0)
84 tlwarning(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
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());
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());
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
141 void flagRecursiveBones(CMeshUtilsContext
&context
, CNodeContext
&nodeContext
, bool autoStop
= false)
143 nodeContext
.IsBone
= true;
144 const aiNode
*node
= nodeContext
.InternalNode
;
146 for (unsigned int i
= 0; i
< node
->mNumChildren
; ++i
)
148 CNodeContext
&ctx
= context
.Nodes
[node
->mName
.C_Str()];
149 if (autoStop
&& ctx
.IsBone
)
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
)
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
)
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;
193 void flagExpandedBones(CMeshUtilsContext
&context
)
195 switch (context
.SceneMeta
.SkeletonMode
)
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);
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);
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);
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(), "*");
240 if (f
.open(shapePath
, false, false, true))
244 NL3D::CShapeStream
shapeStream(nodeContext
.Shape
);
245 shapeStream
.serial(f
);
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());
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
,
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
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");
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
);
333 flagAssimpBones(context
);
334 flagMetaBones(context
);
335 flagExpandedBones(context
);
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
345 // First import materials
346 assimpMaterials(context
);
349 importShapes(context
, context
.InternalScene
->mRootNode
);
352 exportShapes(context
);