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