1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include "AssParser.h"
4
5 #include "3DModel.h"
6 #include "3DModelLog.h"
7 #include "S3OParser.h"
8 #include "AssIO.h"
9
10 #include "Lua/LuaParser.h"
11 #include "Sim/Misc/CollisionVolume.h"
12 #include "System/Util.h"
13 #include "System/Log/ILog.h"
14 #include "System/Platform/errorhandler.h"
15 #include "System/Exceptions.h"
16 #include "System/ScopedFPUSettings.h"
17 #include "System/FileSystem/FileHandler.h"
18 #include "System/FileSystem/FileSystem.h"
19
20 #include "lib/assimp/include/assimp/config.h"
21 #include "lib/assimp/include/assimp/defs.h"
22 #include "lib/assimp/include/assimp/types.h"
23 #include "lib/assimp/include/assimp/scene.h"
24 #include "lib/assimp/include/assimp/postprocess.h"
25 #include "lib/assimp/include/assimp/Importer.hpp"
26 #include "lib/assimp/include/assimp/DefaultLogger.hpp"
27 #include "Rendering/Textures/S3OTextureHandler.h"
28 #ifndef BITMAP_NO_OPENGL
29 #include "Rendering/GL/myGL.h"
30 #endif
31
32
33 #define IS_QNAN(f) (f != f)
34 static const float DEGTORAD = PI / 180.0f;
35 static const float RADTODEG = 180.0f / PI;
36
37 // triangulate guarantees the most complex mesh is a triangle
38 // sortbytype ensure only 1 type of primitive type per mesh is used
39 static const unsigned int ASS_POSTPROCESS_OPTIONS =
40 aiProcess_RemoveComponent
41 | aiProcess_FindInvalidData
42 | aiProcess_CalcTangentSpace
43 | aiProcess_GenSmoothNormals
44 | aiProcess_Triangulate
45 | aiProcess_GenUVCoords
46 | aiProcess_SortByPType
47 | aiProcess_JoinIdenticalVertices
48 //| aiProcess_ImproveCacheLocality // FIXME crashes in an assert in VertexTriangleAdjancency.h (date 04/2011)
49 | aiProcess_SplitLargeMeshes;
50
51 static const unsigned int ASS_IMPORTER_OPTIONS =
52 aiComponent_CAMERAS |
53 aiComponent_LIGHTS |
54 aiComponent_TEXTURES |
55 aiComponent_ANIMATIONS;
56 static const unsigned int ASS_LOGGING_OPTIONS =
57 Assimp::Logger::Debugging |
58 Assimp::Logger::Info |
59 Assimp::Logger::Err |
60 Assimp::Logger::Warn;
61
62
63
aiVectorToFloat3(const aiVector3D v)64 static inline float3 aiVectorToFloat3(const aiVector3D v)
65 {
66 // default (AssImp's internal coordinate-system matches Spring's!)
67 return float3(v.x, v.y, v.z);
68
69 // Blender --> Spring
70 // return float3(v.x, v.z, -v.y);
71 }
72
aiMatrixToMatrix(const aiMatrix4x4t<float> & m)73 static inline CMatrix44f aiMatrixToMatrix(const aiMatrix4x4t<float>& m)
74 {
75 CMatrix44f n;
76
77 // a{1, ..., 4} represent the first column of an AssImp matrix
78 // b{1, ..., 4} represent the second column of an AssImp matrix
79 // AssImp matrix data (colums) is transposed wrt. Spring's data
80 n[ 0] = m.a1; n[ 1] = m.a2; n[ 2] = m.a3; n[ 3] = m.a4;
81 n[ 4] = m.b1; n[ 5] = m.b2; n[ 6] = m.b3; n[ 7] = m.b4;
82 n[ 8] = m.c1; n[ 9] = m.c2; n[10] = m.c3; n[11] = m.c4;
83 n[12] = m.d1; n[13] = m.d2; n[14] = m.d3; n[15] = m.d4;
84
85 // AssImp (row-major) --> Spring (column-major)
86 return (n.Transpose());
87
88 // default
89 // return (CMatrix44f(n.GetPos(), n.GetX(), n.GetY(), n.GetZ()));
90
91 // Blender --> Spring
92 // return (CMatrix44f(n.GetPos(), n.GetX(), n.GetZ(), -n.GetY()));
93 }
94
95 /*
96 static float3 aiQuaternionToRadianAngles(const aiQuaternion q1)
97 {
98 const float sqw = q1.w * q1.w;
99 const float sqx = q1.x * q1.x;
100 const float sqy = q1.y * q1.y;
101 const float sqz = q1.z * q1.z;
102 // <unit> is 1 if normalised, otherwise correction factor
103 const float unit = sqx + sqy + sqz + sqw;
104 const float test = q1.x * q1.y + q1.z * q1.w;
105
106 aiVector3D angles;
107
108 if (test > (0.499f * unit)) {
109 // singularity at north pole
110 angles.x = 2.0f * math::atan2(q1.x, q1.w);
111 angles.y = PI * 0.5f;
112 } else if (test < (-0.499f * unit)) {
113 // singularity at south pole
114 angles.x = -2.0f * math::atan2(q1.x, q1.w);
115 angles.y = -PI * 0.5f;
116 } else {
117 angles.x = math::atan2(2.0f * q1.y * q1.w - 2.0f * q1.x * q1.z, sqx - sqy - sqz + sqw);
118 angles.y = math::asin((2.0f * test) / unit);
119 angles.z = math::atan2(2.0f * q1.x * q1.w - 2.0f * q1.y * q1.z, -sqx + sqy - sqz + sqw);
120 }
121
122 return (aiVectorToFloat3(angles));
123 }
124 */
125
126
127
128
129 class AssLogStream : public Assimp::LogStream
130 {
131 public:
write(const char * message)132 void write(const char* message) {
133 LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "Assimp: %s", message);
134 }
135 };
136
137
138
Load(const std::string & modelFilePath)139 S3DModel* CAssParser::Load(const std::string& modelFilePath)
140 {
141 LOG_SL(LOG_SECTION_MODEL, L_INFO, "Loading model: %s", modelFilePath.c_str());
142
143 const std::string& modelPath = FileSystem::GetDirectory(modelFilePath);
144 const std::string& modelName = FileSystem::GetBasename(modelFilePath);
145
146 // Load the lua metafile. This contains properties unique to Spring models and must return a table
147 std::string metaFileName = modelFilePath + ".lua";
148
149 if (!CFileHandler::FileExists(metaFileName, SPRING_VFS_ZIP)) {
150 // Try again without the model file extension
151 metaFileName = modelPath + '/' + modelName + ".lua";
152 }
153 if (!CFileHandler::FileExists(metaFileName, SPRING_VFS_ZIP)) {
154 LOG_SL(LOG_SECTION_MODEL, L_INFO, "No meta-file '%s'. Using defaults.", metaFileName.c_str());
155 }
156
157 LuaParser metaFileParser(metaFileName, SPRING_VFS_MOD_BASE, SPRING_VFS_ZIP);
158
159 if (!metaFileParser.Execute()) {
160 LOG_SL(LOG_SECTION_MODEL, L_ERROR, "'%s': %s. Using defaults.", metaFileName.c_str(), metaFileParser.GetErrorLog().c_str());
161 }
162
163 // Get the (root-level) model table
164 const LuaTable& modelTable = metaFileParser.GetRoot();
165
166 if (!modelTable.IsValid()) {
167 LOG_SL(LOG_SECTION_MODEL, L_INFO, "No valid model metadata in '%s' or no meta-file", metaFileName.c_str());
168 }
169
170
171 // Create a model importer instance
172 Assimp::Importer importer;
173
174 // Create a logger for debugging model loading issues
175 Assimp::DefaultLogger::create("", Assimp::Logger::VERBOSE);
176 Assimp::DefaultLogger::get()->attachStream(new AssLogStream(), ASS_LOGGING_OPTIONS);
177
178 // Give the importer an IO class that handles Spring's VFS
179 importer.SetIOHandler(new AssVFSSystem());
180 // Speed-up processing by skipping things we don't need
181 importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, ASS_IMPORTER_OPTIONS);
182
183 #ifndef BITMAP_NO_OPENGL
184 {
185 // Optimize VBO-Mesh sizes/ranges
186 GLint maxIndices = 1024;
187 GLint maxVertices = 1024;
188 // FIXME returns non-optimal data, at best compute it ourselves (pre-TL cache size!)
189 glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &maxIndices);
190 glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &maxVertices);
191 importer.SetPropertyInteger(AI_CONFIG_PP_SLM_VERTEX_LIMIT, maxVertices);
192 importer.SetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT, maxIndices / 3);
193 }
194 #endif
195
196 // Read the model file to build a scene object
197 LOG_SL(LOG_SECTION_MODEL, L_INFO, "Importing model file: %s", modelFilePath.c_str());
198
199 const aiScene* scene;
200 {
201 // ASSIMP spams many SIGFPEs atm in normal & tangent generation
202 ScopedDisableFpuExceptions fe;
203 scene = importer.ReadFile(modelFilePath, ASS_POSTPROCESS_OPTIONS);
204 }
205
206 if (scene != NULL) {
207 LOG_SL(LOG_SECTION_MODEL, L_INFO,
208 "Processing scene for model: %s (%d meshes / %d materials / %d textures)",
209 modelFilePath.c_str(), scene->mNumMeshes, scene->mNumMaterials,
210 scene->mNumTextures);
211 } else {
212 throw content_error("[AssimpParser] Model Import: " + std::string(importer.GetErrorString()));
213 }
214
215 S3DModel* model = new S3DModel();
216 model->name = modelFilePath;
217 model->type = MODELTYPE_ASS;
218
219 // Load textures
220 FindTextures(model, scene, modelTable, modelPath, modelName);
221 LOG_SL(LOG_SECTION_MODEL, L_INFO, "Loading textures. Tex1: '%s' Tex2: '%s'", model->tex1.c_str(), model->tex2.c_str());
222 texturehandlerS3O->LoadS3OTexture(model);
223
224 // Load all pieces in the model
225 LOG_SL(LOG_SECTION_MODEL, L_INFO, "Loading pieces from root node '%s'", scene->mRootNode->mName.data);
226 LoadPiece(model, scene->mRootNode, scene, modelTable);
227
228 // Update piece hierarchy based on metadata
229 BuildPieceHierarchy(model);
230 CalculateModelProperties(model, modelTable);
231
232 // Verbose logging of model properties
233 LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->name: %s", model->name.c_str());
234 LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->numobjects: %d", model->numPieces);
235 LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->radius: %f", model->radius);
236 LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->height: %f", model->height);
237 LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->drawRadius: %f", model->drawRadius);
238 LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->mins: (%f,%f,%f)", model->mins[0], model->mins[1], model->mins[2]);
239 LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->maxs: (%f,%f,%f)", model->maxs[0], model->maxs[1], model->maxs[2]);
240 LOG_SL(LOG_SECTION_MODEL, L_INFO, "Model %s Imported.", model->name.c_str());
241 return model;
242 }
243
244
245 /*
246 void CAssParser::CalculateModelMeshBounds(S3DModel* model, const aiScene* scene)
247 {
248 model->meshBounds.resize(scene->mNumMeshes * 2);
249
250 // calculate bounds for each individual mesh of
251 // the model; currently we have no use for this
252 // and S3DModel has only one pair of bounds
253 //
254 for (size_t i = 0; i < scene->mNumMeshes; i++) {
255 const aiMesh* mesh = scene->mMeshes[i];
256
257 float3& mins = model->meshBounds[i*2 + 0];
258 float3& maxs = model->meshBounds[i*2 + 1];
259
260 mins = DEF_MIN_SIZE;
261 maxs = DEF_MAX_SIZE;
262
263 for (size_t vertexIndex= 0; vertexIndex < mesh->mNumVertices; vertexIndex++) {
264 const aiVector3D& aiVertex = mesh->mVertices[vertexIndex];
265 mins = std::min(mins, aiVectorToFloat3(aiVertex));
266 maxs = std::max(maxs, aiVectorToFloat3(aiVertex));
267 }
268
269 if (mins == DEF_MIN_SIZE) { mins = ZeroVector; }
270 if (maxs == DEF_MAX_SIZE) { maxs = ZeroVector; }
271 }
272 }
273 */
274
275
LoadPieceTransformations(SAssPiece * piece,const S3DModel * model,const aiNode * pieceNode,const LuaTable & pieceTable)276 void CAssParser::LoadPieceTransformations(
277 SAssPiece* piece,
278 const S3DModel* model,
279 const aiNode* pieceNode,
280 const LuaTable& pieceTable
281 ) {
282 aiVector3D aiScaleVec, aiTransVec;
283 aiQuaternion aiRotateQuat;
284
285 // process transforms
286 pieceNode->mTransformation.Decompose(aiScaleVec, aiRotateQuat, aiTransVec);
287
288 // metadata-scaling
289 piece->scales = pieceTable.GetFloat3("scale", aiVectorToFloat3(aiScaleVec));
290 piece->scales.x = pieceTable.GetFloat("scalex", piece->scales.x);
291 piece->scales.y = pieceTable.GetFloat("scaley", piece->scales.y);
292 piece->scales.z = pieceTable.GetFloat("scalez", piece->scales.z);
293
294 if (piece->scales.x != piece->scales.y || piece->scales.y != piece->scales.z) {
295 // LOG_SL(LOG_SECTION_MODEL, L_WARNING, "Spring doesn't support non-uniform scaling");
296 piece->scales.y = piece->scales.x;
297 piece->scales.z = piece->scales.x;
298 }
299
300 // metadata-translation
301 piece->offset = pieceTable.GetFloat3("offset", aiVectorToFloat3(aiTransVec));
302 piece->offset.x = pieceTable.GetFloat("offsetx", piece->offset.x);
303 piece->offset.y = pieceTable.GetFloat("offsety", piece->offset.y);
304 piece->offset.z = pieceTable.GetFloat("offsetz", piece->offset.z);
305
306 // metadata-rotation
307 // NOTE:
308 // these rotations are "pre-scripting" but "post-modelling"
309 // together with the (baked) aiRotateQuad they determine the
310 // model's pose *before* any animations execute
311 //
312 // float3 rotAngles = pieceTable.GetFloat3("rotate", aiQuaternionToRadianAngles(aiRotateQuat) * RADTODEG);
313 float3 pieceRotAngles = pieceTable.GetFloat3("rotate", ZeroVector);
314
315 pieceRotAngles.x = pieceTable.GetFloat("rotatex", pieceRotAngles.x);
316 pieceRotAngles.y = pieceTable.GetFloat("rotatey", pieceRotAngles.y);
317 pieceRotAngles.z = pieceTable.GetFloat("rotatez", pieceRotAngles.z);
318 pieceRotAngles *= DEGTORAD;
319
320 LOG_SL(LOG_SECTION_PIECE, L_INFO,
321 "(%d:%s) Assimp offset (%f,%f,%f), rotate (%f,%f,%f,%f), scale (%f,%f,%f)",
322 model->numPieces, piece->name.c_str(),
323 aiTransVec.x, aiTransVec.y, aiTransVec.z,
324 aiRotateQuat.w, aiRotateQuat.x, aiRotateQuat.y, aiRotateQuat.z,
325 aiScaleVec.x, aiScaleVec.y, aiScaleVec.z
326 );
327 LOG_SL(LOG_SECTION_PIECE, L_INFO,
328 "(%d:%s) Relative offset (%f,%f,%f), rotate (%f,%f,%f), scale (%f,%f,%f)",
329 model->numPieces, piece->name.c_str(),
330 piece->offset.x, piece->offset.y, piece->offset.z,
331 pieceRotAngles.x, pieceRotAngles.y, pieceRotAngles.z,
332 piece->scales.x, piece->scales.y, piece->scales.z
333 );
334
335 // NOTE:
336 // at least collada (.dae) files generated by Blender represent
337 // a coordinate-system that differs from the "standard" formats
338 // (3DO, S3O, ...) for which existing tools at least have prior
339 // knowledge of Spring's expectations --> let the user override
340 // the ROOT rotational transform and the rotation-axis mapping
341 // used by animation scripts (but re-modelling/re-exporting is
342 // always preferred!) even though AssImp should convert models
343 // to its own system which matches that of Spring
344 //
345 // .dae : x=Rgt, y=-Fwd, z= Up, as=(-1, -1, 1), am=AXIS_XZY (if Z_UP)
346 // .dae : x=Rgt, y=-Fwd, z= Up, as=(-1, -1, 1), am=AXIS_XZY (if Y_UP) [!?]
347 // .blend: ????
348 piece->bakedRotMatrix = aiMatrixToMatrix(aiMatrix4x4t<float>(aiRotateQuat.GetMatrix()));
349
350 if (piece == model->GetRootPiece()) {
351 const float3 xaxis = pieceTable.GetFloat3("xaxis", piece->bakedRotMatrix.GetX());
352 const float3 yaxis = pieceTable.GetFloat3("yaxis", piece->bakedRotMatrix.GetY());
353 const float3 zaxis = pieceTable.GetFloat3("zaxis", piece->bakedRotMatrix.GetZ());
354
355 if (math::fabs(xaxis.SqLength() - yaxis.SqLength()) < 0.01f && math::fabs(yaxis.SqLength() - zaxis.SqLength()) < 0.01f) {
356 piece->bakedRotMatrix = CMatrix44f(ZeroVector, xaxis, yaxis, zaxis);
357 }
358 }
359
360 piece->rotAxisSigns = pieceTable.GetFloat3("rotAxisSigns", float3(-OnesVector));
361 piece->axisMapType = AxisMappingType(pieceTable.GetInt("rotAxisMap", AXIS_MAPPING_XYZ));
362
363 // construct 'baked' part of the piece-space matrix
364 // AssImp order is translate * rotate * scale * v;
365 // we leave the translation and scale parts out and
366 // put those in <offset> and <scales> --> transform
367 // is just R instead of T * R * S
368 //
369 // note: for all non-AssImp models this is identity!
370 //
371 piece->ComposeRotation(piece->bakedRotMatrix, pieceRotAngles);
372 piece->SetHasIdentityRotation(piece->bakedRotMatrix.IsIdentity() == 0);
373
374 assert(piece->bakedRotMatrix.IsOrthoNormal() == 0);
375 }
376
SetModelRadiusAndHeight(S3DModel * model,const SAssPiece * piece,const aiNode * pieceNode,const LuaTable & pieceTable)377 bool CAssParser::SetModelRadiusAndHeight(
378 S3DModel* model,
379 const SAssPiece* piece,
380 const aiNode* pieceNode,
381 const LuaTable& pieceTable
382 ) {
383 // check if this piece is "special" (ie, used to set Spring model properties)
384 // if so, extract them and then remove the piece from the hierarchy entirely
385 //
386 if (piece->name == "SpringHeight") {
387 // set the model height to this node's Y-value (FIXME: 'y' is Assimp/Blender-specific)
388 if (!pieceTable.KeyExists("height")) {
389 model->height = piece->offset.y;
390
391 LOG_SL(LOG_SECTION_MODEL, L_INFO, "Model height of %f set by special node 'SpringHeight'", model->height);
392 }
393
394 --model->numPieces;
395 delete piece;
396 return true;
397 }
398
399 if (piece->name == "SpringRadius") {
400 if (!pieceTable.KeyExists("midpos")) {
401 CMatrix44f scaleRotMat;
402 piece->ComposeTransform(scaleRotMat, ZeroVector, ZeroVector, piece->scales);
403
404 // NOTE:
405 // this makes little sense because the "SpringRadius"
406 // piece can be placed anywhere within the hierarchy
407 model->relMidPos = scaleRotMat.Mul(piece->offset);
408
409 LOG_SL(LOG_SECTION_MODEL, L_INFO,
410 "Model midpos of (%f,%f,%f) set by special node 'SpringRadius'",
411 model->relMidPos.x, model->relMidPos.y, model->relMidPos.z);
412 }
413 if (!pieceTable.KeyExists("radius")) {
414 if (true || piece->maxs.x <= 0.00001f) {
415 // scales have been set at this point
416 // the Blender import script only sets the scale property [?]
417 //
418 // model->radius = piece->scales.Length();
419 model->radius = piece->scales.x;
420 } else {
421 // FIXME:
422 // geometry bounds are calculated by LoadPieceGeometry
423 // which is called after SetModelRadiusAndHeight -> can
424 // not take this branch yet
425 // use the transformed mesh extents (FIXME: the bounds are NOT
426 // actually transformed but derived from raw vertex positions!)
427 //
428 // model->radius = ((piece->maxs - piece->mins) * 0.5f).Length();
429 model->radius = piece->maxs.x;
430 }
431
432 LOG_SL(LOG_SECTION_MODEL, L_INFO, "Model radius of %f set by special node 'SpringRadius'", model->radius);
433 }
434
435 --model->numPieces;
436 delete piece;
437 return true;
438 }
439
440 return false;
441 }
442
SetPieceName(SAssPiece * piece,const S3DModel * model,const aiNode * pieceNode)443 void CAssParser::SetPieceName(SAssPiece* piece, const S3DModel* model, const aiNode* pieceNode)
444 {
445 assert(piece->name.empty());
446 piece->name = std::string(pieceNode->mName.data);
447
448 if (piece->name.empty()) {
449 if (piece == model->GetRootPiece()) {
450 // root is always the first piece created, so safe to assign this
451 piece->name = "$$root$$";
452 return;
453 } else {
454 piece->name = "$$piece$$";
455 }
456 }
457
458 // find a new name if none given or if a piece with the same name already exists
459 ModelPieceMap::const_iterator it = model->pieceMap.find(piece->name);
460
461 for (unsigned int i = 0; it != model->pieceMap.end(); i++) {
462 const std::string newPieceName = piece->name + IntToString(i, "%02i");
463
464 if ((it = model->pieceMap.find(newPieceName)) == model->pieceMap.end()) {
465 piece->name = newPieceName; break;
466 }
467 }
468 }
469
SetPieceParentName(SAssPiece * piece,const S3DModel * model,const aiNode * pieceNode,const LuaTable & pieceTable)470 void CAssParser::SetPieceParentName(
471 SAssPiece* piece,
472 const S3DModel* model,
473 const aiNode* pieceNode,
474 const LuaTable& pieceTable
475 ) {
476 // Get parent name from metadata or model
477 if (pieceTable.KeyExists("parent")) {
478 piece->parentName = pieceTable.GetString("parent", "");
479 } else if (pieceNode->mParent != NULL) {
480 if (pieceNode->mParent->mParent != NULL) {
481 piece->parentName = std::string(pieceNode->mParent->mName.data);
482 } else {
483 // my parent is the root (which must already exist)
484 assert(model->GetRootPiece() != NULL);
485 piece->parentName = model->GetRootPiece()->name;
486 }
487 }
488 }
489
LoadPieceGeometry(SAssPiece * piece,const aiNode * pieceNode,const aiScene * scene)490 void CAssParser::LoadPieceGeometry(SAssPiece* piece, const aiNode* pieceNode, const aiScene* scene)
491 {
492 // Get vertex data from node meshes
493 for (unsigned meshListIndex = 0; meshListIndex < pieceNode->mNumMeshes; ++meshListIndex) {
494 const unsigned int meshIndex = pieceNode->mMeshes[meshListIndex];
495 const aiMesh* mesh = scene->mMeshes[meshIndex];
496
497 LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching mesh %d from scene", meshIndex);
498 LOG_SL(LOG_SECTION_PIECE, L_DEBUG,
499 "Processing vertices for mesh %d (%d vertices)",
500 meshIndex, mesh->mNumVertices);
501 LOG_SL(LOG_SECTION_PIECE, L_DEBUG,
502 "Normals: %s Tangents/Bitangents: %s TexCoords: %s",
503 (mesh->HasNormals() ? "Y" : "N"),
504 (mesh->HasTangentsAndBitangents() ? "Y" : "N"),
505 (mesh->HasTextureCoords(0) ? "Y" : "N"));
506
507 piece->vertices.reserve(piece->vertices.size() + mesh->mNumVertices);
508 piece->vertexDrawIndices.reserve(piece->vertexDrawIndices.size() + mesh->mNumFaces * 3);
509
510 std::vector<unsigned> mesh_vertex_mapping;
511
512 // extract vertex data per mesh
513 for (unsigned vertexIndex = 0; vertexIndex < mesh->mNumVertices; ++vertexIndex) {
514 const aiVector3D& aiVertex = mesh->mVertices[vertexIndex];
515
516 SAssVertex vertex;
517
518 // vertex coordinates
519 vertex.pos = aiVectorToFloat3(aiVertex);
520
521 // update piece min/max extents
522 piece->mins = float3::min(piece->mins, vertex.pos);
523 piece->maxs = float3::max(piece->maxs, vertex.pos);
524
525 // vertex normal
526 LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching normal for vertex %d", vertexIndex);
527
528 const aiVector3D& aiNormal = mesh->mNormals[vertexIndex];
529
530 if (!IS_QNAN(aiNormal)) {
531 vertex.normal = aiVectorToFloat3(aiNormal);
532 }
533
534 // vertex tangent, x is positive in texture axis
535 if (mesh->HasTangentsAndBitangents()) {
536 LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching tangent for vertex %d", vertexIndex);
537
538 const aiVector3D& aiTangent = mesh->mTangents[vertexIndex];
539 const aiVector3D& aiBitangent = mesh->mBitangents[vertexIndex];
540
541 vertex.sTangent = aiVectorToFloat3(aiTangent);
542 vertex.tTangent = aiVectorToFloat3(aiBitangent);
543 }
544
545 // vertex tex-coords per channel
546 for (unsigned int uvChanIndex = 0; uvChanIndex < NUM_MODEL_UVCHANNS; uvChanIndex++) {
547 if (!mesh->HasTextureCoords(uvChanIndex))
548 break;
549
550 piece->SetNumTexCoorChannels(uvChanIndex + 1);
551
552 vertex.texCoords[uvChanIndex].x = mesh->mTextureCoords[uvChanIndex][vertexIndex].x;
553 vertex.texCoords[uvChanIndex].y = mesh->mTextureCoords[uvChanIndex][vertexIndex].y;
554 }
555
556 mesh_vertex_mapping.push_back(piece->vertices.size());
557 piece->vertices.push_back(vertex);
558 }
559
560 // extract face data
561 LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Processing faces for mesh %d (%d faces)", meshIndex, mesh->mNumFaces);
562
563 /*
564 * since aiProcess_SortByPType is being used,
565 * we're sure we'll get only 1 type here,
566 * so combination check isn't needed, also
567 * anything more complex than triangles is
568 * being split thanks to aiProcess_Triangulate
569 */
570 for (unsigned faceIndex = 0; faceIndex < mesh->mNumFaces; ++faceIndex) {
571 const aiFace& face = mesh->mFaces[faceIndex];
572
573 // some models contain lines (mNumIndices == 2) which
574 // we cannot render and they would need a 2nd drawcall)
575 if (face.mNumIndices != 3)
576 continue;
577
578 for (unsigned vertexListID = 0; vertexListID < face.mNumIndices; ++vertexListID) {
579 const unsigned int vertexFaceIdx = face.mIndices[vertexListID];
580 const unsigned int vertexDrawIdx = mesh_vertex_mapping[vertexFaceIdx];
581 piece->vertexDrawIndices.push_back(vertexDrawIdx);
582 }
583 }
584 }
585
586 piece->SetHasGeometryData(!piece->vertices.empty());
587 }
588
LoadPiece(S3DModel * model,const aiNode * pieceNode,const aiScene * scene,const LuaTable & modelTable)589 SAssPiece* CAssParser::LoadPiece(
590 S3DModel* model,
591 const aiNode* pieceNode,
592 const aiScene* scene,
593 const LuaTable& modelTable
594 ) {
595 ++model->numPieces;
596
597 SAssPiece* piece = new SAssPiece();
598
599 if (pieceNode->mParent == NULL) {
600 // set the model's root piece ASAP, needed later
601 assert(pieceNode == scene->mRootNode);
602 assert(model->GetRootPiece() == NULL);
603 model->SetRootPiece(piece);
604 }
605
606 SetPieceName(piece, model, pieceNode);
607
608 LOG_SL(LOG_SECTION_PIECE, L_INFO, "Converting node '%s' to piece '%s' (%d meshes).", pieceNode->mName.data, piece->name.c_str(), pieceNode->mNumMeshes);
609
610 // Load additional piece properties from metadata
611 const LuaTable& pieceTable = modelTable.SubTable("pieces").SubTable(piece->name);
612
613 if (pieceTable.IsValid()) {
614 LOG_SL(LOG_SECTION_PIECE, L_INFO, "Found metadata for piece '%s'", piece->name.c_str());
615 }
616
617 // Load transforms
618 LoadPieceTransformations(piece, model, pieceNode, pieceTable);
619
620 if (SetModelRadiusAndHeight(model, piece, pieceNode, pieceTable))
621 return NULL;
622
623 LoadPieceGeometry(piece, pieceNode, scene);
624 SetPieceParentName(piece, model, pieceNode, pieceTable);
625
626 // Verbose logging of piece properties
627 LOG_SL(LOG_SECTION_PIECE, L_INFO, "Loaded model piece: %s with %d meshes", piece->name.c_str(), pieceNode->mNumMeshes);
628 LOG_SL(LOG_SECTION_PIECE, L_INFO, "piece->name: %s", piece->name.c_str());
629 LOG_SL(LOG_SECTION_PIECE, L_INFO, "piece->parent: %s", piece->parentName.c_str());
630
631 // Recursively process all child pieces
632 for (unsigned int i = 0; i < pieceNode->mNumChildren; ++i) {
633 LoadPiece(model, pieceNode->mChildren[i], scene, modelTable);
634 }
635
636 model->pieceMap[piece->name] = piece;
637 return piece;
638 }
639
640
641 // Because of metadata overrides we don't know the true hierarchy until all pieces have been loaded
BuildPieceHierarchy(S3DModel * model)642 void CAssParser::BuildPieceHierarchy(S3DModel* model)
643 {
644 // Loop through all pieces and create missing hierarchy info
645 for (ModelPieceMap::const_iterator it = model->pieceMap.begin(); it != model->pieceMap.end(); ++it) {
646 SAssPiece* piece = static_cast<SAssPiece*>(it->second);
647
648 if (piece == model->GetRootPiece()) {
649 assert(piece->parent == NULL);
650 assert(model->GetRootPiece() == piece);
651 continue;
652 }
653
654 if (!piece->parentName.empty()) {
655 if ((piece->parent = model->FindPiece(piece->parentName)) == NULL) {
656 LOG_SL(LOG_SECTION_PIECE, L_ERROR,
657 "Missing piece '%s' declared as parent of '%s'.",
658 piece->parentName.c_str(), piece->name.c_str());
659 } else {
660 piece->parent->children.push_back(piece);
661 }
662
663 continue;
664 }
665
666 // a piece with no named parent that isn't the root (orphan)
667 // link these to the root piece if it exists (which it should)
668 if ((piece->parent = model->GetRootPiece()) == NULL) {
669 LOG_SL(LOG_SECTION_PIECE, L_ERROR, "Missing root piece");
670 } else {
671 piece->parent->children.push_back(piece);
672 }
673 }
674 }
675
676
677 // Iterate over the model and calculate its overall dimensions
CalculateModelDimensions(S3DModel * model,S3DModelPiece * piece)678 void CAssParser::CalculateModelDimensions(S3DModel* model, S3DModelPiece* piece)
679 {
680 CMatrix44f scaleRotMat;
681 piece->ComposeTransform(scaleRotMat, ZeroVector, ZeroVector, piece->scales);
682
683 // cannot set this until parent relations are known, so either here or in BuildPieceHierarchy()
684 piece->goffset = scaleRotMat.Mul(piece->offset) + ((piece->parent != NULL)? piece->parent->goffset: ZeroVector);
685
686 // update model min/max extents
687 model->mins = float3::min(piece->goffset + piece->mins, model->mins);
688 model->maxs = float3::max(piece->goffset + piece->maxs, model->maxs);
689
690 piece->SetCollisionVolume(new CollisionVolume("box", piece->maxs - piece->mins, (piece->maxs + piece->mins) * 0.5f));
691
692 // Repeat with children
693 for (unsigned int i = 0; i < piece->children.size(); i++) {
694 CalculateModelDimensions(model, piece->children[i]);
695 }
696 }
697
698 // Calculate model radius from the min/max extents
CalculateModelProperties(S3DModel * model,const LuaTable & modelTable)699 void CAssParser::CalculateModelProperties(S3DModel* model, const LuaTable& modelTable)
700 {
701 CalculateModelDimensions(model, model->rootPiece);
702
703 // note: overrides default midpos of the SpringRadius piece
704 model->relMidPos.y = (model->maxs.y + model->mins.y) * 0.5f;
705
706 // Simplified dimensions used for rough calculations
707 model->radius = modelTable.GetFloat("radius", float3::max(float3::fabs(model->maxs), float3::fabs(model->mins)).Length());
708 model->height = modelTable.GetFloat("height", model->maxs.y);
709 model->relMidPos = modelTable.GetFloat3("midpos", model->relMidPos);
710 model->mins = modelTable.GetFloat3("mins", model->mins);
711 model->maxs = modelTable.GetFloat3("maxs", model->maxs);
712
713 model->drawRadius = model->radius;
714 }
715
716
FindTexture(std::string testTextureFile,const std::string & modelPath,const std::string & fallback)717 static std::string FindTexture(std::string testTextureFile, const std::string& modelPath, const std::string& fallback)
718 {
719 if (testTextureFile.empty())
720 return fallback;
721
722 // blender denotes relative paths with "//..", remove it
723 if (testTextureFile.find("//..") == 0)
724 testTextureFile = testTextureFile.substr(4);
725
726 if (CFileHandler::FileExists(testTextureFile, SPRING_VFS_ZIP_FIRST))
727 return testTextureFile;
728
729 if (CFileHandler::FileExists("unittextures/" + testTextureFile, SPRING_VFS_ZIP_FIRST))
730 return "unittextures/" + testTextureFile;
731
732 if (CFileHandler::FileExists(modelPath + testTextureFile, SPRING_VFS_ZIP_FIRST))
733 return modelPath + testTextureFile;
734
735 return fallback;
736 }
737
738
FindTextureByRegex(const std::string & regex_path,const std::string & regex)739 static std::string FindTextureByRegex(const std::string& regex_path, const std::string& regex)
740 {
741 //FIXME instead of ".*" only check imagetypes!
742 const std::vector<std::string>& files = CFileHandler::FindFiles(regex_path, regex + ".*");
743
744 if (!files.empty()) {
745 return FindTexture(FileSystem::GetFilename(files[0]), "", "");
746 }
747 return "";
748 }
749
750
FindTextures(S3DModel * model,const aiScene * scene,const LuaTable & modelTable,const std::string & modelPath,const std::string & modelName)751 void CAssParser::FindTextures(
752 S3DModel* model,
753 const aiScene* scene,
754 const LuaTable& modelTable,
755 const std::string& modelPath,
756 const std::string& modelName
757 ) {
758 // Assign textures
759 // The S3O texture handler uses two textures.
760 // The first contains diffuse color (RGB) and teamcolor (A)
761 // The second contains glow (R), reflectivity (G) and 1-bit Alpha (A).
762
763
764 // 1. try to find by name (lowest prioriy)
765 if (model->tex1.empty()) model->tex1 = FindTextureByRegex("unittextures/", modelName); // high priority
766 if (model->tex1.empty()) model->tex1 = FindTextureByRegex("unittextures/", modelName + "1");
767 if (model->tex2.empty()) model->tex2 = FindTextureByRegex("unittextures/", modelName + "2");
768 if (model->tex1.empty()) model->tex1 = FindTextureByRegex(modelPath, "tex1");
769 if (model->tex2.empty()) model->tex2 = FindTextureByRegex(modelPath, "tex2");
770 if (model->tex1.empty()) model->tex1 = FindTextureByRegex(modelPath, "diffuse");
771 if (model->tex2.empty()) model->tex2 = FindTextureByRegex(modelPath, "glow"); // low priority
772
773
774 // 2. gather model-defined textures of first material (medium priority)
775 if (scene->mNumMaterials > 0) {
776 const unsigned int texTypes[] = {
777 aiTextureType_SPECULAR,
778 aiTextureType_UNKNOWN,
779 aiTextureType_DIFFUSE,
780 /*
781 // TODO: support these too (we need to allow constructing tex1 & tex2 from several sources)
782 aiTextureType_EMISSIVE,
783 aiTextureType_HEIGHT,
784 aiTextureType_NORMALS,
785 aiTextureType_SHININESS,
786 aiTextureType_OPACITY,
787 */
788 };
789 for (unsigned int n = 0; n < (sizeof(texTypes) / sizeof(texTypes[0])); n++) {
790 aiString textureFile;
791
792 if (scene->mMaterials[0]->Get(AI_MATKEY_TEXTURE(texTypes[n], 0), textureFile) != aiReturn_SUCCESS)
793 continue;
794
795 assert(textureFile.length > 0);
796 model->tex1 = FindTexture(textureFile.data, modelPath, model->tex1);
797 }
798 }
799
800
801 // 3. try to load from metafile (highest priority)
802 model->tex1 = FindTexture(modelTable.GetString("tex1", ""), modelPath, model->tex1);
803 model->tex2 = FindTexture(modelTable.GetString("tex2", ""), modelPath, model->tex2);
804
805 model->invertTexYAxis = modelTable.GetBool("fliptextures", true); // Flip texture upside down
806 model->invertTexAlpha = modelTable.GetBool("invertteamcolor", true); // Reverse teamcolor levels
807 }
808
809
UploadGeometryVBOs()810 void SAssPiece::UploadGeometryVBOs()
811 {
812 if (!hasGeometryData)
813 return;
814
815 //FIXME share 1 VBO for ALL models
816 vboAttributes.Bind(GL_ARRAY_BUFFER);
817 vboAttributes.New(vertices.size() * sizeof(SAssVertex), GL_STATIC_DRAW, &vertices[0]);
818 vboAttributes.Unbind();
819
820 vboIndices.Bind(GL_ELEMENT_ARRAY_BUFFER);
821 vboIndices.New(vertexDrawIndices.size() * sizeof(unsigned int), GL_STATIC_DRAW, &vertexDrawIndices[0]);
822 vboIndices.Unbind();
823
824 // NOTE: wasteful to keep these around, but still needed (eg. for Shatter())
825 // vertices.clear();
826 // vertexDrawIndices.clear();
827 }
828
829
DrawForList() const830 void SAssPiece::DrawForList() const
831 {
832 if (!hasGeometryData)
833 return;
834
835 vboAttributes.Bind(GL_ARRAY_BUFFER);
836 glEnableClientState(GL_VERTEX_ARRAY);
837 glVertexPointer(3, GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, pos)));
838
839 glEnableClientState(GL_NORMAL_ARRAY);
840 glNormalPointer(GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, normal)));
841
842 // primary and secondary texture use first UV channel
843 for (unsigned int n = 0; n < NUM_MODEL_TEXTURES; n++) {
844 glClientActiveTexture(GL_TEXTURE0 + n);
845 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
846 glTexCoordPointer(2, GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, texCoords) + (0 * sizeof(float2))));
847 }
848
849 // extra UV channels (currently at most one)
850 for (unsigned int n = 1; n < GetNumTexCoorChannels(); n++) {
851 glClientActiveTexture(GL_TEXTURE0 + NUM_MODEL_TEXTURES + (n - 1));
852 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
853
854 glTexCoordPointer(2, GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, texCoords) + (n * sizeof(float2))));
855 }
856
857 glClientActiveTexture(GL_TEXTURE5);
858 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
859 glTexCoordPointer(3, GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, sTangent)));
860
861 glClientActiveTexture(GL_TEXTURE6);
862 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
863 glTexCoordPointer(3, GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, tTangent)));
864 vboAttributes.Unbind();
865
866 vboIndices.Bind(GL_ELEMENT_ARRAY_BUFFER);
867 /*
868 * since aiProcess_SortByPType is being used,
869 * we're sure we'll get only 1 type here,
870 * so combination check isn't needed, also
871 * anything more complex than triangles is
872 * being split thanks to aiProcess_Triangulate
873 */
874 glDrawRangeElements(GL_TRIANGLES, 0, vertices.size() - 1, vertexDrawIndices.size(), GL_UNSIGNED_INT, vboIndices.GetPtr());
875 vboIndices.Unbind();
876
877 glClientActiveTexture(GL_TEXTURE6);
878 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
879
880 glClientActiveTexture(GL_TEXTURE5);
881 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
882
883 glClientActiveTexture(GL_TEXTURE2);
884 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
885
886 glClientActiveTexture(GL_TEXTURE1);
887 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
888
889 glClientActiveTexture(GL_TEXTURE0);
890 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
891
892 glDisableClientState(GL_VERTEX_ARRAY);
893 glDisableClientState(GL_NORMAL_ARRAY);
894 }
895
896