Fix game:addSpawnShapesByZone
[ryzomcore.git] / nel / tools / 3d / mesh_utils / assimp_shape.cpp
blobe50313335e261f3b652fbc19467c5e44c8b92e06
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 "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"
41 using namespace std;
42 using namespace NLMISC;
43 using namespace NL3D;
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..
50 // TODO: pacs prim
52 // TODO: buildWaterShape specifics when node has water material
54 // TODO: CMeshMultiLod::CMeshMultiLodBuild multiLodBuild; export_mesh.cpp ln 228
55 // TODO: LOD MRM
57 // TODO: Skinned - reverse transform by skeleton root bone to align?
59 /*inline CMatrix convMatrix(const aiMatrix4x4 &tf)
61 CMatrix m;
62 for (int i = 0; i < 16; ++i)
63 m.set(&tf.a1);
64 return m;
65 }*/
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
92 // Load materials
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];
100 aiString amname;
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);
107 else
109 buildBaseMesh.Materials[mi] = *context.SceneMeta.Materials[amname.C_Str()];
113 // Positioning
114 const aiMatrix4x4 &root = context.InternalScene->mRootNode->mTransformation;
115 const aiMatrix4x4 &tf = nodeContext.InternalNode->mTransformation; // COORDINATE CONVERSION HERE INSTEAD OF PER VERTEX ??
116 aiVector3D scaling;
117 aiQuaternion rotation;
118 aiVector3D position;
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());
131 // Meta
132 // dst.CollisionMeshGeneration = src.CollisionMeshGeneration;
134 // TODO: Morph
137 bool assimpBuildMesh(CMesh::CMeshBuild &buildMesh, CMeshBase::CMeshBaseBuild &buildBaseMesh, CMeshUtilsContext &context, CNodeContext &nodeContext)
139 // TODO
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());
157 return false;
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());
163 return false;
165 if (!mesh->HasNormals())
167 tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
168 "(%s) !mesh->HasNormals()", node->mName.C_Str());
169 return false;
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);
187 numVertices = 0;
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;
204 ++numVertices;
206 else
208 vertexRemapping[mi][vi] = vecit->second;
212 buildMesh.Vertices.resize(numVertices);
214 // Process all faces
215 // WONT IMPLEMENT: Radial faces generation... is linked to smoothing group...
216 // leave radial normals generation to modeling tool for now...
217 sint32 numFaces = 0;
218 for (unsigned int mi = 0; mi < node->mNumMeshes; ++mi)
219 numFaces += context.InternalScene->mMeshes[node->mMeshes[mi]]->mNumFaces;
220 buildMesh.Faces.resize(numFaces);
221 numFaces = 0;
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
270 if (cleanupMesh)
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
287 // UV channels
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]]);
307 else
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]]);
319 else
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
326 ++numFaces;
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();
340 // TODO: Export VP
341 buildMesh.MeshVertexProgram = NULL;
343 return true;
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))
360 return false;
362 // Make a CMesh object
363 CMesh *mesh = new CMesh();
365 // Build the mesh with the build interface
366 mesh->build(buildBaseMesh, buildMesh);
368 // TODO
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;
380 return true;
383 /* end of file */