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 "assimp_shape.h"
25 #include <assimp/postprocess.h>
26 #include <assimp/scene.h>
27 #include <assimp/Importer.hpp>
29 #define NL_NODE_INTERNAL_TYPE aiNode
30 #define NL_SCENE_INTERNAL_TYPE aiScene
31 #include "scene_context.h"
33 #include <nel/misc/debug.h>
34 #include <nel/misc/path.h>
35 #include <nel/pipeline/tool_logger.h>
37 #include <nel/3d/mesh.h>
39 #include "assimp_material.h"
42 using namespace NLMISC
;
45 // TODO: buildParticleSystem ??
46 // TODO: buildWaveMakerShape ??
47 // TODO: buildRemanence ??
48 // TODO: buildFlare ??
49 // Probably specific settings we can only do in meta editor on a dummy node..
52 // TODO: buildWaterShape specifics when node has water material
54 // TODO: CMeshMultiLod::CMeshMultiLodBuild multiLodBuild; export_mesh.cpp ln 228
57 // TODO: Skinned - reverse transform by skeleton root bone to align?
59 /*inline CMatrix convMatrix(const aiMatrix4x4 &tf)
62 for (int i = 0; i < 16; ++i)
67 inline CVector
convVector(const aiVector3D
&av
)
69 return CVector(av
.x
, av
.y
, av
.z
); // COORDINATE CONVERSION
72 inline CRGBA
convColor(const aiColor4D
&ac
)
74 return CRGBA(ac
.r
* 255.99f
, ac
.g
* 255.99f
, ac
.b
* 255.99f
, ac
.a
* 255.99f
);
77 inline CUVW
convUvw(const aiVector3D
&av
)
79 return CUVW(av
.x
, -av
.y
, av
.z
); // UH OH COORDINATE CONVERSION ?! ONLY FOR TEXTURES !!
82 inline CQuat
convQuat(const aiQuaternion
&aq
)
84 return CQuat(aq
.x
, aq
.y
, aq
.z
, aq
.w
);
87 void assimpBuildBaseMesh(CMeshBase::CMeshBaseBuild
&buildBaseMesh
, CMeshUtilsContext
&context
, CNodeContext
&nodeContext
)
89 const aiNode
*node
= nodeContext
.InternalNode
;
90 // Reference CExportNel::buildBaseMeshInterface
93 buildBaseMesh
.Materials
.resize(node
->mNumMeshes
);
95 for (unsigned int mi
= 0; mi
< node
->mNumMeshes
; ++mi
)
97 const aiMesh
*mesh
= context
.InternalScene
->mMeshes
[node
->mMeshes
[mi
]];
98 const aiMaterial
*am
= context
.InternalScene
->mMaterials
[mesh
->mMaterialIndex
];
101 if (am
->Get(AI_MATKEY_NAME
, amname
) != aiReturn_SUCCESS
)
103 tlerror(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
104 "Material used by node '%s' has no name", node
->mName
.C_Str()); // TODO: Maybe autogen names by index in mesh or node if this is actually a thing
105 assimpMaterial(buildBaseMesh
.Materials
[mi
], context
, am
);
109 buildBaseMesh
.Materials
[mi
] = *context
.SceneMeta
.Materials
[amname
.C_Str()];
114 const aiMatrix4x4
&root
= context
.InternalScene
->mRootNode
->mTransformation
;
115 const aiMatrix4x4
&tf
= nodeContext
.InternalNode
->mTransformation
; // COORDINATE CONVERSION HERE INSTEAD OF PER VERTEX ??
117 aiQuaternion rotation
;
119 tf
.Decompose(scaling
, rotation
, position
);
120 buildBaseMesh
.DefaultScale
= convVector(scaling
);
121 buildBaseMesh
.DefaultRotQuat
= convQuat(rotation
);
122 buildBaseMesh
.DefaultRotEuler
= CVector(0, 0, 0);
123 buildBaseMesh
.DefaultPivot
= CVector(0, 0, 0);
124 buildBaseMesh
.DefaultPos
= convVector(position
);
125 if (buildBaseMesh
.DefaultScale
.x
!= 1.0f
|| buildBaseMesh
.DefaultScale
.y
!= 1.0f
|| buildBaseMesh
.DefaultScale
.z
!= 1.0f
)
127 tlmessage(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
128 "Node '%s' has a scaled transformation. This may be a mistake", node
->mName
.C_Str());
132 // dst.CollisionMeshGeneration = src.CollisionMeshGeneration;
137 bool assimpBuildMesh(CMesh::CMeshBuild
&buildMesh
, CMeshBase::CMeshBaseBuild
&buildBaseMesh
, CMeshUtilsContext
&context
, CNodeContext
&nodeContext
)
140 // *** If the mesh is skined, vertices will be exported in world space.
141 // *** If the mesh is not skined, vertices will be exported in offset space.
143 // TODO Support skinning
145 const aiNode
*node
= nodeContext
.InternalNode
;
146 nlassert(node
->mNumMeshes
);
148 // Basic validations before processing starts
149 for (unsigned int mi
= 0; mi
< node
->mNumMeshes
; ++mi
)
151 // TODO: Maybe needs to be the same count too for all meshes, so compare with mesh 0
152 const aiMesh
*mesh
= context
.InternalScene
->mMeshes
[node
->mMeshes
[mi
]];
153 if (mesh
->GetNumColorChannels() > 2)
155 tlerror(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
156 "(%s) mesh->GetNumColorChannels() > 2", node
->mName
.C_Str());
159 if (mesh
->GetNumUVChannels() > CVertexBuffer::MaxStage
)
161 tlerror(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
162 "(%s) mesh->GetNumUVChannels() > CVertexBuffer::MaxStage", node
->mName
.C_Str());
165 if (!mesh
->HasNormals())
167 tlerror(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
168 "(%s) !mesh->HasNormals()", node
->mName
.C_Str());
173 // Default vertex flags
174 buildMesh
.VertexFlags
= CVertexBuffer::PositionFlag
| CVertexBuffer::NormalFlag
;
176 // TODO: UV Channels routing to correct texture stage
177 for (uint i
= 0; i
< CVertexBuffer::MaxStage
; ++i
)
178 buildMesh
.UVRouting
[i
] = i
;
180 // Meshes in assimp are separated per material, so we need to re-merge them for the mesh build process
181 // This process also deduplicates vertices
182 bool cleanupMesh
= true;
183 sint32 numVertices
= 0;
184 for (unsigned int mi
= 0; mi
< node
->mNumMeshes
; ++mi
)
185 numVertices
+= context
.InternalScene
->mMeshes
[node
->mMeshes
[mi
]]->mNumVertices
;
186 buildMesh
.Vertices
.resize(numVertices
);
188 map
<CVector
, sint32
> vertexIdentifiers
;
189 vector
<vector
<sint32
> > vertexRemapping
;
190 vertexRemapping
.resize(node
->mNumMeshes
);
191 for (unsigned int mi
= 0; mi
< node
->mNumMeshes
; ++mi
)
193 const aiMesh
*mesh
= context
.InternalScene
->mMeshes
[node
->mMeshes
[mi
]];
194 vertexRemapping
[mi
].resize(mesh
->mNumVertices
);
195 for (unsigned int vi
= 0; vi
< mesh
->mNumVertices
; ++vi
)
197 CVector vec
= convVector(mesh
->mVertices
[vi
]);
198 map
<CVector
, sint32
>::iterator vecit
= vertexIdentifiers
.find(vec
);
199 if (vecit
== vertexIdentifiers
.end())
201 buildMesh
.Vertices
[numVertices
] = vec
;
202 if (cleanupMesh
) vertexIdentifiers
[vec
] = numVertices
; // Don't remap if we don't wan't to lose vertex indices
203 vertexRemapping
[mi
][vi
] = numVertices
;
208 vertexRemapping
[mi
][vi
] = vecit
->second
;
212 buildMesh
.Vertices
.resize(numVertices
);
215 // WONT IMPLEMENT: Radial faces generation... is linked to smoothing group...
216 // leave radial normals generation to modeling tool for now...
218 for (unsigned int mi
= 0; mi
< node
->mNumMeshes
; ++mi
)
219 numFaces
+= context
.InternalScene
->mMeshes
[node
->mMeshes
[mi
]]->mNumFaces
;
220 buildMesh
.Faces
.resize(numFaces
);
222 unsigned int refNumColorChannels
= context
.InternalScene
->mMeshes
[node
->mMeshes
[0]]->GetNumColorChannels();
223 unsigned int refNumUVChannels
= context
.InternalScene
->mMeshes
[node
->mMeshes
[0]]->GetNumUVChannels();
224 for (unsigned int mi
= 0; mi
< node
->mNumMeshes
; ++mi
)
226 const aiMesh
*mesh
= context
.InternalScene
->mMeshes
[node
->mMeshes
[mi
]];
228 // Get channel numbers
229 unsigned int numColorChannels
= mesh
->GetNumColorChannels();
230 if (numColorChannels
> 2)
232 tlerror(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
233 "Shape '%s' has too many color channels in mesh %i (%i channels found)", node
->mName
.C_Str(), mi
, numColorChannels
);
235 if (numColorChannels
> 0)
237 buildMesh
.VertexFlags
|= CVertexBuffer::PrimaryColorFlag
;
238 if (numColorChannels
> 1)
240 buildMesh
.VertexFlags
|= CVertexBuffer::SecondaryColorFlag
;
243 unsigned int numUVChannels
= mesh
->GetNumUVChannels();
244 if (numUVChannels
> CVertexBuffer::MaxStage
)
246 tlerror(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
247 "Shape '%s' has too many uv channels in mesh %i (%i channels found)", node
->mName
.C_Str(), mi
, numUVChannels
);
248 numUVChannels
= CVertexBuffer::MaxStage
;
250 for (unsigned int ui
= 0; ui
< numUVChannels
; ++ui
)
251 buildMesh
.VertexFlags
|= (CVertexBuffer::TexCoord0Flag
<< ui
); // TODO: Coord UV tex stage rerouting
253 // TODO: Channels do in fact differ between submeshes, so we need to correctly recount and reroute the materials properly
254 if (numColorChannels
!= refNumColorChannels
)
255 tlerror(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
256 "Shape '%s' mismatch of nb color channel in mesh '%i', please contact developer", node
->mName
.C_Str(), mi
);
257 if (numUVChannels
!= refNumUVChannels
)
258 tlerror(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
259 "Shape '%s' mismatch of nb uv channel in mesh '%i', please contact developer", node
->mName
.C_Str(), mi
);
261 for (unsigned int fi
= 0; fi
< mesh
->mNumFaces
; ++fi
)
263 const aiFace
&af
= mesh
->mFaces
[fi
];
264 if (af
.mNumIndices
!= 3)
266 tlerror(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
267 "(%s) Face %i on mesh %i has %i faces", node
->mName
.C_Str(), fi
, mi
, af
.mNumIndices
);
268 continue; // return false; Keep going, just drop the face for better user experience
272 if (vertexRemapping
[mi
][af
.mIndices
[0]] == vertexRemapping
[mi
][af
.mIndices
[1]]
273 || vertexRemapping
[mi
][af
.mIndices
[1]] == vertexRemapping
[mi
][af
.mIndices
[2]]
274 || vertexRemapping
[mi
][af
.mIndices
[2]] == vertexRemapping
[mi
][af
.mIndices
[0]])
275 continue; // Not a triangle
277 CMesh::CFace
&face
= buildMesh
.Faces
[numFaces
];
278 face
.MaterialId
= mi
;
279 face
.SmoothGroup
= 0; // No smoothing groups (bitfield)
280 face
.Corner
[0].Vertex
= vertexRemapping
[mi
][af
.mIndices
[0]];
281 face
.Corner
[1].Vertex
= vertexRemapping
[mi
][af
.mIndices
[1]];
282 face
.Corner
[2].Vertex
= vertexRemapping
[mi
][af
.mIndices
[2]];
283 face
.Corner
[0].Normal
= convVector(mesh
->mNormals
[af
.mIndices
[0]]);
284 face
.Corner
[1].Normal
= convVector(mesh
->mNormals
[af
.mIndices
[1]]);
285 face
.Corner
[2].Normal
= convVector(mesh
->mNormals
[af
.mIndices
[2]]);
286 // TODO: If we want normal maps, we need to add tangent vectors to CFace and build process
288 for (unsigned int ui
= 0; ui
< numUVChannels
; ++ui
) // TODO: UV Rerouting
290 face
.Corner
[0].Uvws
[ui
] = convUvw(mesh
->mTextureCoords
[ui
][af
.mIndices
[0]]);
291 face
.Corner
[1].Uvws
[ui
] = convUvw(mesh
->mTextureCoords
[ui
][af
.mIndices
[1]]);
292 face
.Corner
[2].Uvws
[ui
] = convUvw(mesh
->mTextureCoords
[ui
][af
.mIndices
[2]]);
294 for (unsigned int ui
= numUVChannels
; ui
< CVertexBuffer::MaxStage
; ++ui
)
296 face
.Corner
[0].Uvws
[ui
] = CUVW(0, 0, 0);
297 face
.Corner
[1].Uvws
[ui
] = CUVW(0, 0, 0);
298 face
.Corner
[2].Uvws
[ui
] = CUVW(0, 0, 0);
300 // Primary and secondary color channels
301 if (numColorChannels
> 0) // TODO: Verify
303 face
.Corner
[0].Color
= convColor(mesh
->mColors
[0][af
.mIndices
[0]]);
304 face
.Corner
[1].Color
= convColor(mesh
->mColors
[0][af
.mIndices
[1]]);
305 face
.Corner
[2].Color
= convColor(mesh
->mColors
[0][af
.mIndices
[2]]);
309 face
.Corner
[0].Color
= CRGBA(255, 255, 255, 255);
310 face
.Corner
[1].Color
= CRGBA(255, 255, 255, 255);
311 face
.Corner
[2].Color
= CRGBA(255, 255, 255, 255);
313 if (numColorChannels
> 1) // TODO: Verify
315 face
.Corner
[0].Specular
= convColor(mesh
->mColors
[1][af
.mIndices
[0]]);
316 face
.Corner
[1].Specular
= convColor(mesh
->mColors
[1][af
.mIndices
[1]]);
317 face
.Corner
[2].Specular
= convColor(mesh
->mColors
[1][af
.mIndices
[2]]);
321 face
.Corner
[0].Specular
= CRGBA(255, 255, 255, 255);
322 face
.Corner
[1].Specular
= CRGBA(255, 255, 255, 255);
323 face
.Corner
[2].Specular
= CRGBA(255, 255, 255, 255);
325 // TODO: Color modulate, alpha, use color alpha for vp tree, etc
329 if (numFaces
!= buildMesh
.Faces
.size())
331 tlmessage(context
.ToolLogger
, context
.Settings
.SourceFilePath
.c_str(),
332 "Removed %u degenerate faces in shape '%s'", (uint32
)(buildMesh
.Faces
.size() - numFaces
), node
->mName
.C_Str());
333 buildMesh
.Faces
.resize(numFaces
);
336 // clear for MRM info
337 buildMesh
.Interfaces
.clear();
338 buildMesh
.InterfaceLinks
.clear();
341 buildMesh
.MeshVertexProgram
= NULL
;
346 bool assimpShape(CMeshUtilsContext
&context
, CNodeContext
&nodeContext
)
348 // Reference: export_mesh.cpp, buildShape
349 nodeContext
.Shape
= NULL
;
351 const aiNode
*node
= nodeContext
.InternalNode
;
352 nlassert(node
->mNumMeshes
);
354 // Fill the build interface of CMesh
355 CMeshBase::CMeshBaseBuild buildBaseMesh
;
356 assimpBuildBaseMesh(buildBaseMesh
, context
, nodeContext
);
358 CMesh::CMeshBuild buildMesh
;
359 if (!assimpBuildMesh(buildMesh
, buildBaseMesh
, context
, nodeContext
))
362 // Make a CMesh object
363 CMesh
*mesh
= new CMesh();
365 // Build the mesh with the build interface
366 mesh
->build(buildBaseMesh
, buildMesh
);
369 // Reference: export_mesh.cpp, buildShape
370 // Must be done after the build to update vertex links
371 // Pass to buildMeshMorph if the original mesh is skinned or not
372 // buildMeshMorph(buildMesh, node, time, nodeMap != NULL);
373 // mesh->setBlendShapes(buildMesh.BlendShapes);
375 // optimize number of material
376 // mesh->optimizeMaterialUsage(materialRemap);
378 // Store mesh in context
379 nodeContext
.Shape
= mesh
;