1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3 
4 #include "Loader.h"
5 #include "BinaryConverter.h"
6 #include "CollisionGeometry.h"
7 #include "FileSystem.h"
8 #include "LOD.h"
9 #include "Parser.h"
10 #include "SceneGraph.h"
11 #include "StringF.h"
12 #include "graphics/RenderState.h"
13 #include "graphics/Renderer.h"
14 #include "graphics/TextureBuilder.h"
15 #include "scenegraph/Animation.h"
16 #include "utils.h"
17 #include <assimp/material.h>
18 #include <assimp/postprocess.h>
19 #include <assimp/scene.h>
20 #include <assimp/IOStream.hpp>
21 #include <assimp/IOSystem.hpp>
22 #include <assimp/Importer.hpp>
23 
24 namespace {
25 	class AssimpFileReadStream : public Assimp::IOStream {
26 	public:
AssimpFileReadStream(const RefCountedPtr<FileSystem::FileData> & data)27 		explicit AssimpFileReadStream(const RefCountedPtr<FileSystem::FileData> &data) :
28 			m_data(data)
29 		{
30 			m_cursor = m_data->GetData();
31 		}
32 
~AssimpFileReadStream()33 		virtual ~AssimpFileReadStream() {}
34 
FileSize() const35 		virtual size_t FileSize() const { return m_data->GetSize(); }
36 
Read(void * buf,size_t size,size_t count)37 		virtual size_t Read(void *buf, size_t size, size_t count)
38 		{
39 			const char *const data_end = m_data->GetData() + m_data->GetSize();
40 			const size_t remaining = (data_end - m_cursor);
41 			const size_t requested = size * count;
42 			const size_t len = std::min(remaining, requested);
43 			memcpy(static_cast<char *>(buf), m_cursor, len);
44 			m_cursor += len;
45 			return len;
46 		}
47 
Seek(size_t offset,aiOrigin origin)48 		virtual aiReturn Seek(size_t offset, aiOrigin origin)
49 		{
50 			switch (origin) {
51 			case aiOrigin_SET: break;
52 			case aiOrigin_CUR: offset += Tell(); break;
53 			case aiOrigin_END: offset += m_data->GetSize(); break;
54 			default: assert(0); break;
55 			}
56 			if (offset > m_data->GetSize())
57 				return aiReturn_FAILURE;
58 			m_cursor = m_data->GetData() + offset;
59 			return aiReturn_SUCCESS;
60 		}
61 
Tell() const62 		virtual size_t Tell() const
63 		{
64 			return size_t(m_cursor - m_data->GetData());
65 		}
66 
Write(const void * buf,size_t size,size_t count)67 		virtual size_t Write(const void *buf, size_t size, size_t count) __attribute((noreturn))
68 		{
69 			assert(0);
70 			abort();
71 			RETURN_ZERO_NONGNU_ONLY;
72 		}
73 
Flush()74 		virtual void Flush()
75 		{
76 			assert(0);
77 			abort();
78 		}
79 
80 	private:
81 		RefCountedPtr<FileSystem::FileData> m_data;
82 		const char *m_cursor;
83 	};
84 
85 	class AssimpFileSystem : public Assimp::IOSystem {
86 	public:
AssimpFileSystem(FileSystem::FileSource & fs)87 		AssimpFileSystem(FileSystem::FileSource &fs) :
88 			m_fs(fs) {}
~AssimpFileSystem()89 		virtual ~AssimpFileSystem() {}
90 
Exists(const char * path) const91 		virtual bool Exists(const char *path) const
92 		{
93 			const FileSystem::FileInfo info = m_fs.Lookup(path);
94 			return info.Exists();
95 		}
96 
getOsSeparator() const97 		virtual char getOsSeparator() const { return '/'; }
98 
Open(const char * path,const char * mode)99 		virtual Assimp::IOStream *Open(const char *path, const char *mode)
100 		{
101 			assert(mode[0] == 'r');
102 			assert(!strchr(mode, '+'));
103 			RefCountedPtr<FileSystem::FileData> data = m_fs.ReadFile(path);
104 			return (data ? new AssimpFileReadStream(data) : 0);
105 		}
106 
Close(Assimp::IOStream * file)107 		virtual void Close(Assimp::IOStream *file)
108 		{
109 			delete file;
110 		}
111 
112 	private:
113 		FileSystem::FileSource &m_fs;
114 	};
115 } // anonymous namespace
116 
117 namespace SceneGraph {
Loader(Graphics::Renderer * r,bool logWarnings,bool loadSGMfiles)118 	Loader::Loader(Graphics::Renderer *r, bool logWarnings, bool loadSGMfiles) :
119 		BaseLoader(r),
120 		m_doLog(logWarnings),
121 		m_loadSGMs(loadSGMfiles),
122 		m_mostDetailedLod(false)
123 	{
124 	}
125 
LoadModel(const std::string & filename)126 	Model *Loader::LoadModel(const std::string &filename)
127 	{
128 		return LoadModel(filename, "models");
129 	}
130 
LoadModel(const std::string & shortname,const std::string & basepath)131 	Model *Loader::LoadModel(const std::string &shortname, const std::string &basepath)
132 	{
133 		PROFILE_SCOPED()
134 		m_logMessages.clear();
135 
136 		std::vector<std::string> list_model;
137 		std::vector<std::string> list_sgm;
138 		FileSystem::FileSource &fileSource = FileSystem::gameDataFiles;
139 		for (FileSystem::FileEnumerator files(fileSource, basepath, FileSystem::FileEnumerator::Recurse); !files.Finished(); files.Next()) {
140 			const FileSystem::FileInfo &info = files.Current();
141 			const std::string &fpath = info.GetPath();
142 
143 			//check it's the expected type
144 			if (info.IsFile()) {
145 				if (ends_with_ci(fpath, ".model")) { // store the path for ".model" files
146 					list_model.push_back(fpath);
147 				} else if (m_loadSGMs & ends_with_ci(fpath, ".sgm")) { // store only the shortname for ".sgm" files.
148 					list_sgm.push_back(info.GetName().substr(0, info.GetName().size() - 4));
149 				}
150 			}
151 		}
152 
153 		if (m_loadSGMs) {
154 			for (auto &sgmname : list_sgm) {
155 				if (sgmname == shortname) {
156 					//binary loader expects extension-less name. Might want to change this.
157 					SceneGraph::BinaryConverter bc(m_renderer);
158 					m_model = bc.Load(shortname);
159 					if (m_model)
160 						return m_model;
161 					else
162 						break; // we'll have to load the non-sgm file
163 				}
164 			}
165 		}
166 
167 		for (auto &fpath : list_model) {
168 			RefCountedPtr<FileSystem::FileData> filedata = FileSystem::gameDataFiles.ReadFile(fpath);
169 			if (!filedata) {
170 				Output("LoadModel: %s: could not read file\n", fpath.c_str());
171 				return nullptr;
172 			}
173 
174 			//check it's the wanted name & load it
175 			const FileSystem::FileInfo &info = filedata->GetInfo();
176 			const std::string name = info.GetName();
177 			if (name.substr(0, name.length() - 6) == shortname) {
178 				ModelDefinition modelDefinition;
179 				try {
180 					//curPath is used to find textures, patterns,
181 					//possibly other data files for this model.
182 					//Strip trailing slash
183 					m_curPath = info.GetDir();
184 					assert(!m_curPath.empty());
185 					if (m_curPath[m_curPath.length() - 1] == '/')
186 						m_curPath = m_curPath.substr(0, m_curPath.length() - 1);
187 
188 					Parser p(fileSource, fpath, m_curPath);
189 					p.Parse(&modelDefinition);
190 				} catch (ParseError &err) {
191 					Output("%s\n", err.what());
192 					throw LoadingError(err.what());
193 				}
194 				modelDefinition.name = shortname;
195 				return CreateModel(modelDefinition);
196 			}
197 		}
198 		throw(LoadingError("File not found"));
199 	}
200 
CreateModel(ModelDefinition & def)201 	Model *Loader::CreateModel(ModelDefinition &def)
202 	{
203 		PROFILE_SCOPED()
204 		using Graphics::Material;
205 		if (def.matDefs.empty()) return 0;
206 		if (def.lodDefs.empty()) return 0;
207 
208 		Model *model = new Model(m_renderer, def.name);
209 		m_model = model;
210 		bool patternsUsed = false;
211 
212 		m_thrustersRoot.Reset(new Group(m_renderer));
213 		m_billboardsRoot.Reset(new Group(m_renderer));
214 
215 		//create materials from definitions
216 		for (std::vector<MaterialDefinition>::const_iterator it = def.matDefs.begin();
217 			 it != def.matDefs.end(); ++it) {
218 			if (it->use_pattern) patternsUsed = true;
219 			ConvertMaterialDefinition(*it);
220 		}
221 		//Output("Loaded %d materials\n", int(model->m_materials.size()));
222 
223 		//load meshes
224 		//"mesh" here refers to a "mesh xxx.yyy"
225 		//defined in the .model
226 		std::map<std::string, RefCountedPtr<Node>> meshCache;
227 		LOD *lodNode = 0;
228 		if (def.lodDefs.size() > 1) { //don't bother with a lod node if only one level
229 			lodNode = new LOD(m_renderer);
230 			model->GetRoot()->AddChild(lodNode);
231 		}
232 		for (std::vector<LodDefinition>::const_iterator lod = def.lodDefs.begin();
233 			 lod != def.lodDefs.end(); ++lod) {
234 			m_mostDetailedLod = (lod == def.lodDefs.end() - 1);
235 
236 			//does a detail level have multiple meshes? If so, we need a Group.
237 			Group *group = 0;
238 			if (lodNode && (*lod).meshNames.size() > 1) {
239 				group = new Group(m_renderer);
240 				lodNode->AddLevel((*lod).pixelSize, group);
241 			}
242 			for (std::vector<std::string>::const_iterator it = (*lod).meshNames.begin();
243 				 it != (*lod).meshNames.end(); ++it) {
244 				try {
245 					//multiple lods might use the same mesh
246 					RefCountedPtr<Node> mesh;
247 					std::map<std::string, RefCountedPtr<Node>>::iterator cacheIt = meshCache.find((*it));
248 					if (cacheIt != meshCache.end())
249 						mesh = (*cacheIt).second;
250 					else {
251 						try {
252 							mesh = LoadMesh(*it, def.animDefs);
253 						} catch (LoadingError &err) {
254 							//append filename - easiest to do here
255 							throw(LoadingError(stringf("%0:\n%1", *it, err.what())));
256 						}
257 						meshCache[*(it)] = mesh;
258 					}
259 					assert(mesh.Valid());
260 
261 					if (group)
262 						group->AddChild(mesh.Get());
263 					else if (lodNode) {
264 						lodNode->AddLevel((*lod).pixelSize, mesh.Get());
265 					} else
266 						model->GetRoot()->AddChild(mesh.Get());
267 				} catch (LoadingError &err) {
268 					delete model;
269 					Output("%s\n", err.what());
270 					throw;
271 				}
272 			}
273 		}
274 
275 		if (m_thrustersRoot->GetNumChildren() > 0) {
276 			m_thrustersRoot->SetName("thrusters");
277 			m_thrustersRoot->SetNodeMask(NODE_TRANSPARENT);
278 			model->GetRoot()->AddChild(m_thrustersRoot.Get());
279 		}
280 
281 		if (m_billboardsRoot->GetNumChildren() > 0) {
282 			m_billboardsRoot->SetName("navlights");
283 			m_billboardsRoot->SetNodeMask(NODE_TRANSPARENT);
284 			model->GetRoot()->AddChild(m_billboardsRoot.Get());
285 		}
286 
287 		// Load collision meshes
288 		// They are added at the top level of the model root as CollisionGeometry nodes
289 		for (std::vector<std::string>::const_iterator it = def.collisionDefs.begin();
290 			 it != def.collisionDefs.end(); ++it) {
291 			try {
292 				LoadCollision(*it);
293 			} catch (LoadingError &err) {
294 				throw(LoadingError(stringf("%0:\n%1", *it, err.what())));
295 			}
296 		}
297 
298 		// Run CollisionVisitor to create the initial CM and its GeomTree.
299 		// If no collision mesh is defined, a simple bounding box will be generated
300 		Output("CreateCollisionMesh for : (%s)\n", m_model->m_name.c_str());
301 		m_model->CreateCollisionMesh();
302 
303 		// Do an initial animation update to get all the animation transforms correct
304 		m_model->InitAnimations();
305 
306 		//find usable pattern textures from the model directory
307 		if (patternsUsed)
308 			SetUpPatterns();
309 
310 		return model;
311 	}
312 
LoadMesh(const std::string & filename,const AnimList & animDefs)313 	RefCountedPtr<Node> Loader::LoadMesh(const std::string &filename, const AnimList &animDefs)
314 	{
315 		PROFILE_SCOPED()
316 		//remove path from filename for nicer logging
317 		size_t slashpos = filename.rfind("/");
318 		m_curMeshDef = filename.substr(slashpos + 1, filename.length() - slashpos);
319 
320 		Assimp::Importer importer;
321 		importer.SetIOHandler(new AssimpFileSystem(FileSystem::gameDataFiles));
322 
323 		//Removing components is suggested to optimize loading. We do not care about vtx colors now.
324 		importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, aiComponent_COLORS);
325 		importer.SetPropertyInteger(AI_CONFIG_PP_SLM_VERTEX_LIMIT, AI_SLM_DEFAULT_MAX_VERTICES);
326 
327 		//There are several optimizations assimp can do, intentionally skipping them now
328 		const aiScene *scene = importer.ReadFile(
329 			filename,
330 			aiProcess_RemoveComponent |
331 				aiProcess_Triangulate |
332 				aiProcess_SortByPType | //ignore point, line primitive types (collada dummy nodes seem to be fine)
333 				aiProcess_GenUVCoords |
334 				aiProcess_FlipUVs |
335 				aiProcess_CalcTangentSpace |
336 				aiProcess_JoinIdenticalVertices |
337 				aiProcess_GenSmoothNormals | //only if normals not specified
338 				aiProcess_ImproveCacheLocality |
339 				aiProcess_LimitBoneWeights |
340 				aiProcess_FindDegenerates |
341 				aiProcess_FindInvalidData);
342 
343 		if (!scene)
344 			throw LoadingError("Couldn't load file");
345 
346 		if (scene->mNumMeshes == 0)
347 			throw LoadingError("No geometry found");
348 
349 		//turn all scene aiMeshes into Surfaces
350 		//Index matches assimp index.
351 		std::vector<RefCountedPtr<StaticGeometry>> geoms;
352 		ConvertAiMeshes(geoms, scene);
353 
354 		// Recursive structure conversion. Matrix needs to be accumulated for
355 		// special features that are absolute-positioned (thrusters)
356 		RefCountedPtr<Node> meshRoot(new Group(m_renderer));
357 
358 		ConvertNodes(scene->mRootNode, static_cast<Group *>(meshRoot.Get()), geoms, matrix4x4f::Identity());
359 		ConvertAnimations(scene, animDefs, static_cast<Group *>(meshRoot.Get()));
360 
361 		return meshRoot;
362 	}
363 
in_range(double keytime,double start,double end)364 	static bool in_range(double keytime, double start, double end)
365 	{
366 		return (keytime >= start - 0.001 && keytime - 0.001 <= end);
367 	}
368 
369 	// check animation channel has a key within time range
CheckKeysInRange(const aiNodeAnim * chan,double start,double end)370 	bool Loader::CheckKeysInRange(const aiNodeAnim *chan, double start, double end)
371 	{
372 		int posKeysInRange = 0;
373 		int rotKeysInRange = 0;
374 		int sclKeysInRange = 0;
375 
376 		for (unsigned int k = 0; k < chan->mNumPositionKeys; k++) {
377 			const aiVectorKey &aikey = chan->mPositionKeys[k];
378 			if (in_range(aikey.mTime, start, end)) posKeysInRange++;
379 		}
380 
381 		for (unsigned int k = 0; k < chan->mNumRotationKeys; k++) {
382 			const aiQuatKey &aikey = chan->mRotationKeys[k];
383 			if (in_range(aikey.mTime, start, end)) rotKeysInRange++;
384 		}
385 
386 		for (unsigned int k = 0; k < chan->mNumScalingKeys; k++) {
387 			const aiVectorKey &aikey = chan->mScalingKeys[k];
388 			if (in_range(aikey.mTime, start, end)) sclKeysInRange++;
389 		}
390 
391 		return (posKeysInRange > 0 || rotKeysInRange > 0 || sclKeysInRange > 0);
392 	}
393 
AddLog(const std::string & msg)394 	void Loader::AddLog(const std::string &msg)
395 	{
396 		if (m_doLog) m_logMessages.push_back(msg);
397 	}
398 
CheckAnimationConflicts(const Animation * anim,const std::vector<Animation * > & otherAnims)399 	void Loader::CheckAnimationConflicts(const Animation *anim, const std::vector<Animation *> &otherAnims)
400 	{
401 		typedef std::vector<AnimationChannel>::const_iterator ChannelIterator;
402 		typedef std::vector<Animation *>::const_iterator AnimIterator;
403 
404 		if (anim->m_channels.empty() || otherAnims.empty()) return;
405 
406 		//check all other animations that they don't control the same nodes as this animation, since
407 		//that is not supported at this point
408 		for (ChannelIterator chan = anim->m_channels.begin(); chan != anim->m_channels.end(); ++chan) {
409 			for (AnimIterator other = otherAnims.begin(); other != otherAnims.end(); ++other) {
410 				const Animation *otherAnim = (*other);
411 				if (otherAnim == anim)
412 					continue;
413 				for (ChannelIterator otherChan = otherAnim->m_channels.begin(); otherChan != otherAnim->m_channels.end(); ++otherChan) {
414 					//warnings as errors mentality - this is not really fatal
415 					if (chan->node == otherChan->node)
416 						throw LoadingError(stringf("Animations %0 and %1 both control node: %2", anim->GetName(), otherAnim->GetName(), chan->node->GetName()));
417 				}
418 			}
419 		}
420 	}
421 
422 #pragma pack(push, 4)
423 	struct ModelVtx {
424 		vector3f pos;
425 		vector3f nrm;
426 		vector2f uv0;
427 	};
428 
429 	struct ModelTangentVtx {
430 		vector3f pos;
431 		vector3f nrm;
432 		vector2f uv0;
433 		vector3f tangent;
434 	};
435 #pragma pack(pop)
436 
ConvertAiMeshes(std::vector<RefCountedPtr<StaticGeometry>> & geoms,const aiScene * scene)437 	void Loader::ConvertAiMeshes(std::vector<RefCountedPtr<StaticGeometry>> &geoms, const aiScene *scene)
438 	{
439 		PROFILE_SCOPED()
440 		//XXX sigh, workaround for obj loader
441 		int matIdxOffs = 0;
442 		if (scene->mNumMaterials > scene->mNumMeshes)
443 			matIdxOffs = 1;
444 
445 		//turn meshes into static geometry nodes
446 		for (unsigned int i = 0; i < scene->mNumMeshes; i++) {
447 			const aiMesh *mesh = scene->mMeshes[i];
448 			assert(mesh->HasNormals());
449 
450 			RefCountedPtr<StaticGeometry> geom(new StaticGeometry(m_renderer));
451 			geom->SetName(stringf("sgMesh%0{u}", i));
452 
453 			const bool hasUVs = mesh->HasTextureCoords(0);
454 			const bool hasTangents = mesh->HasTangentsAndBitangents();
455 			if (!hasUVs)
456 				AddLog(stringf("%0: missing UV coordinates", m_curMeshDef));
457 			if (!hasTangents)
458 				AddLog(stringf("%0: missing Tangents and Bitangents coordinates", m_curMeshDef));
459 			//sadly, aimesh name is usually empty so no help for logging
460 
461 			//Material names are not consistent throughout formats.
462 			//try matching name first, if that fails use index
463 			RefCountedPtr<Graphics::Material> mat;
464 			const aiMaterial *amat = scene->mMaterials[mesh->mMaterialIndex];
465 			aiString aiMatName;
466 			if (AI_SUCCESS == amat->Get(AI_MATKEY_NAME, aiMatName))
467 				mat = m_model->GetMaterialByName(std::string(aiMatName.C_Str()));
468 
469 			if (!mat.Valid()) {
470 				const unsigned int matIdx = mesh->mMaterialIndex - matIdxOffs;
471 				AddLog(stringf("%0: no material %1, using material %2{u} instead", m_curMeshDef, aiMatName.C_Str(), matIdx + 1));
472 				mat = m_model->GetMaterialByIndex(matIdx);
473 			}
474 			assert(mat.Valid());
475 
476 			Graphics::RenderStateDesc rsd;
477 			//turn on alpha blending and mark entire node as transparent
478 			//(all importers split by material so far)
479 			if (mat->diffuse.a < 255) {
480 				geom->SetNodeMask(NODE_TRANSPARENT);
481 				geom->m_blendMode = Graphics::BLEND_ALPHA;
482 				rsd.blendMode = Graphics::BLEND_ALPHA;
483 				rsd.depthWrite = false;
484 			}
485 
486 			geom->SetRenderState(m_renderer->CreateRenderState(rsd));
487 
488 			Graphics::VertexBufferDesc vbd;
489 			vbd.attrib[0].semantic = Graphics::ATTRIB_POSITION;
490 			vbd.attrib[0].format = Graphics::ATTRIB_FORMAT_FLOAT3;
491 			vbd.attrib[0].offset = hasTangents ? offsetof(ModelTangentVtx, pos) : offsetof(ModelVtx, pos);
492 			vbd.attrib[1].semantic = Graphics::ATTRIB_NORMAL;
493 			vbd.attrib[1].format = Graphics::ATTRIB_FORMAT_FLOAT3;
494 			vbd.attrib[1].offset = hasTangents ? offsetof(ModelTangentVtx, nrm) : offsetof(ModelVtx, nrm);
495 			vbd.attrib[2].semantic = Graphics::ATTRIB_UV0;
496 			vbd.attrib[2].format = Graphics::ATTRIB_FORMAT_FLOAT2;
497 			vbd.attrib[2].offset = hasTangents ? offsetof(ModelTangentVtx, uv0) : offsetof(ModelVtx, uv0);
498 			if (hasTangents) {
499 				vbd.attrib[3].semantic = Graphics::ATTRIB_TANGENT;
500 				vbd.attrib[3].format = Graphics::ATTRIB_FORMAT_FLOAT3;
501 				vbd.attrib[3].offset = offsetof(ModelTangentVtx, tangent);
502 			}
503 			vbd.stride = hasTangents ? sizeof(ModelTangentVtx) : sizeof(ModelVtx);
504 			vbd.numVertices = mesh->mNumVertices;
505 			vbd.usage = Graphics::BUFFER_USAGE_STATIC;
506 
507 			RefCountedPtr<Graphics::VertexBuffer> vb(m_renderer->CreateVertexBuffer(vbd));
508 
509 			// huge meshes are split by the importer so this should not exceed 65K indices
510 			std::vector<Uint32> indices;
511 			if (mesh->mNumFaces > 0) {
512 				indices.reserve(mesh->mNumFaces * 3);
513 				for (unsigned int f = 0; f < mesh->mNumFaces; f++) {
514 					const aiFace *face = &mesh->mFaces[f];
515 					for (unsigned int j = 0; j < face->mNumIndices; j++) {
516 						indices.push_back(face->mIndices[j]);
517 					}
518 				}
519 			} else {
520 				//generate dummy indices
521 				AddLog(stringf("Missing indices in mesh %0{u}", i));
522 				indices.reserve(mesh->mNumVertices);
523 				for (unsigned int v = 0; v < mesh->mNumVertices; v++)
524 					indices.push_back(v);
525 			}
526 
527 			assert(indices.size() > 0);
528 
529 			//create buffer & copy
530 			RefCountedPtr<Graphics::IndexBuffer> ib(m_renderer->CreateIndexBuffer(indices.size(), Graphics::BUFFER_USAGE_STATIC));
531 			Uint32 *idxPtr = ib->Map(Graphics::BUFFER_MAP_WRITE);
532 			for (Uint32 j = 0; j < indices.size(); j++)
533 				idxPtr[j] = indices[j];
534 			ib->Unmap();
535 
536 			//copy vertices, always assume normals
537 			//replace nonexistent UVs with zeros
538 			if (!hasTangents) {
539 				ModelVtx *vtxPtr = vb->Map<ModelVtx>(Graphics::BUFFER_MAP_WRITE);
540 				for (unsigned int v = 0; v < mesh->mNumVertices; v++) {
541 					const aiVector3D &vtx = mesh->mVertices[v];
542 					const aiVector3D &norm = mesh->mNormals[v];
543 					const aiVector3D &uv0 = hasUVs ? mesh->mTextureCoords[0][v] : aiVector3D(0.f);
544 					vtxPtr[v].pos = vector3f(vtx.x, vtx.y, vtx.z);
545 					vtxPtr[v].nrm = vector3f(norm.x, norm.y, norm.z);
546 					vtxPtr[v].uv0 = vector2f(uv0.x, uv0.y);
547 
548 					//update bounding box
549 					//untransformed points, collision visitor will transform
550 					geom->m_boundingBox.Update(vtx.x, vtx.y, vtx.z);
551 				}
552 				vb->Unmap();
553 			} else {
554 				ModelTangentVtx *vtxPtr = vb->Map<ModelTangentVtx>(Graphics::BUFFER_MAP_WRITE);
555 				for (unsigned int v = 0; v < mesh->mNumVertices; v++) {
556 					const aiVector3D &vtx = mesh->mVertices[v];
557 					const aiVector3D &norm = mesh->mNormals[v];
558 					const aiVector3D &uv0 = hasUVs ? mesh->mTextureCoords[0][v] : aiVector3D(0.f);
559 					const aiVector3D &tangents = mesh->mTangents[v];
560 					vtxPtr[v].pos = vector3f(vtx.x, vtx.y, vtx.z);
561 					vtxPtr[v].nrm = vector3f(norm.x, norm.y, norm.z);
562 					vtxPtr[v].uv0 = vector2f(uv0.x, uv0.y);
563 					vtxPtr[v].tangent = vector3f(tangents.x, tangents.y, tangents.z);
564 
565 					//update bounding box
566 					//untransformed points, collision visitor will transform
567 					geom->m_boundingBox.Update(vtx.x, vtx.y, vtx.z);
568 				}
569 				vb->Unmap();
570 			}
571 
572 			geom->AddMesh(vb, ib, mat);
573 
574 			geoms.push_back(geom);
575 		}
576 	}
577 
ConvertAnimations(const aiScene * scene,const AnimList & animDefs,Node * meshRoot)578 	void Loader::ConvertAnimations(const aiScene *scene, const AnimList &animDefs, Node *meshRoot)
579 	{
580 		PROFILE_SCOPED()
581 		//Split convert assimp animations according to anim defs
582 		//This is very limited, and all animdefs are processed for all
583 		//meshes, potentially leading to duplicate and wrongly split animations
584 		if (animDefs.empty() || scene->mNumAnimations == 0) return;
585 		if (scene->mNumAnimations > 1) Output("File has %d animations, treating as one animation\n", scene->mNumAnimations);
586 
587 		std::vector<Animation *> &animations = m_model->m_animations;
588 
589 		for (AnimList::const_iterator def = animDefs.begin();
590 			 def != animDefs.end();
591 			 ++def) {
592 			//XXX format differences: for a 40-frame animation exported from Blender,
593 			//.X results in duration 39 and Collada in Duration 1.25.
594 			//duration is calculated after adding all keys
595 			//take TPS from the first animation
596 			const aiAnimation *firstAnim = scene->mAnimations[0];
597 			const double ticksPerSecond = firstAnim->mTicksPerSecond > 0.0 ? firstAnim->mTicksPerSecond : 24.0;
598 			const double secondsPerTick = 1.0 / ticksPerSecond;
599 
600 			double start = DBL_MAX;
601 			double end = -DBL_MAX;
602 
603 			//Ranges are specified in frames (since that's nice) but Collada
604 			//uses seconds. This is easiest to detect from ticksPerSecond,
605 			//but assuming 24 FPS here
606 			//Could make FPS an additional define or always require 24
607 			double defStart = def->start;
608 			double defEnd = def->end;
609 			if (is_equal_exact(ticksPerSecond, 1.0)) {
610 				defStart /= 24.0;
611 				defEnd /= 24.0;
612 			}
613 
614 			// Add channels to current animation if it's already present
615 			// Necessary to make animations work in multiple LODs
616 			Animation *animation = m_model->FindAnimation(def->name);
617 			const bool newAnim = !animation;
618 			if (newAnim) animation = new Animation(def->name, 0.0);
619 
620 			const size_t first_new_channel = animation->m_channels.size();
621 
622 			for (unsigned int i = 0; i < scene->mNumAnimations; i++) {
623 				const aiAnimation *aianim = scene->mAnimations[i];
624 				for (unsigned int j = 0; j < aianim->mNumChannels; j++) {
625 					const aiNodeAnim *aichan = aianim->mChannels[j];
626 					//do a preliminary check that at least two keys in one channel are within range
627 					if (!CheckKeysInRange(aichan, defStart, defEnd))
628 						continue;
629 
630 					const std::string channame(aichan->mNodeName.C_Str());
631 					MatrixTransform *trans = dynamic_cast<MatrixTransform *>(meshRoot->FindNode(channame));
632 					assert(trans);
633 					animation->m_channels.push_back(AnimationChannel(trans));
634 					AnimationChannel &chan = animation->m_channels.back();
635 
636 					for (unsigned int k = 0; k < aichan->mNumPositionKeys; k++) {
637 						const aiVectorKey &aikey = aichan->mPositionKeys[k];
638 						const aiVector3D &aipos = aikey.mValue;
639 						if (in_range(aikey.mTime, defStart, defEnd)) {
640 							const double t = aikey.mTime * secondsPerTick;
641 							chan.positionKeys.push_back(PositionKey(t, vector3f(aipos.x, aipos.y, aipos.z)));
642 							start = std::min(start, t);
643 							end = std::max(end, t);
644 						}
645 					}
646 
647 					//scale interpolation will blow up without rotation keys,
648 					//so skipping them when rotkeys < 2 is correct
649 					if (aichan->mNumRotationKeys < 2) continue;
650 
651 					for (unsigned int k = 0; k < aichan->mNumRotationKeys; k++) {
652 						const aiQuatKey &aikey = aichan->mRotationKeys[k];
653 						const aiQuaternion &airot = aikey.mValue;
654 						if (in_range(aikey.mTime, defStart, defEnd)) {
655 							const double t = aikey.mTime * secondsPerTick;
656 							chan.rotationKeys.push_back(RotationKey(t, Quaternionf(airot.w, airot.x, airot.y, airot.z)));
657 							start = std::min(start, t);
658 							end = std::max(end, t);
659 						}
660 					}
661 
662 					for (unsigned int k = 0; k < aichan->mNumScalingKeys; k++) {
663 						const aiVectorKey &aikey = aichan->mScalingKeys[k];
664 						const aiVector3D &aipos = aikey.mValue;
665 						if (in_range(aikey.mTime, defStart, defEnd)) {
666 							const double t = aikey.mTime * secondsPerTick;
667 							chan.scaleKeys.push_back(ScaleKey(t, vector3f(aipos.x, aipos.y, aipos.z)));
668 							start = std::min(start, t);
669 							end = std::max(end, t);
670 						}
671 					}
672 				}
673 			}
674 
675 			// convert remove initial offset (so the first keyframe is at exactly t=0)
676 			for (std::vector<AnimationChannel>::iterator chan = animation->m_channels.begin() + first_new_channel;
677 				 chan != animation->m_channels.end(); ++chan) {
678 				for (unsigned int k = 0; k < chan->positionKeys.size(); ++k) {
679 					chan->positionKeys[k].time -= start;
680 					assert(chan->positionKeys[k].time >= 0.0);
681 				}
682 				for (unsigned int k = 0; k < chan->rotationKeys.size(); ++k) {
683 					chan->rotationKeys[k].time -= start;
684 					assert(chan->rotationKeys[k].time >= 0.0);
685 				}
686 				for (unsigned int k = 0; k < chan->scaleKeys.size(); ++k) {
687 					chan->scaleKeys[k].time -= start;
688 					assert(chan->scaleKeys[k].time >= 0.0);
689 				}
690 			}
691 
692 			// set actual duration
693 			const double dur = end - start;
694 			animation->m_duration = newAnim ? dur : std::max(animation->m_duration, dur);
695 
696 			//do final sanity checking before adding
697 			try {
698 				CheckAnimationConflicts(animation, animations);
699 			} catch (LoadingError &) {
700 				if (newAnim) delete animation;
701 				throw;
702 			}
703 
704 			if (newAnim) {
705 				if (animation->m_channels.empty())
706 					delete animation;
707 				else
708 					animations.push_back(animation);
709 			}
710 		}
711 	}
712 
ConvertMatrix(const aiMatrix4x4 & trans) const713 	matrix4x4f Loader::ConvertMatrix(const aiMatrix4x4 &trans) const
714 	{
715 		matrix4x4f m;
716 		m[0] = trans.a1;
717 		m[1] = trans.b1;
718 		m[2] = trans.c1;
719 		m[3] = trans.d1;
720 
721 		m[4] = trans.a2;
722 		m[5] = trans.b2;
723 		m[6] = trans.c2;
724 		m[7] = trans.d2;
725 
726 		m[8] = trans.a3;
727 		m[9] = trans.b3;
728 		m[10] = trans.c3;
729 		m[11] = trans.d3;
730 
731 		m[12] = trans.a4;
732 		m[13] = trans.b4;
733 		m[14] = trans.c4;
734 		m[15] = trans.d4;
735 		return m;
736 	}
737 
CreateLabel(Group * parent,const matrix4x4f & m)738 	void Loader::CreateLabel(Group *parent, const matrix4x4f &m)
739 	{
740 		PROFILE_SCOPED()
741 		MatrixTransform *trans = new MatrixTransform(m_renderer, m);
742 		Label3D *label = new Label3D(m_renderer, m_labelFont);
743 		label->SetText("Bananas");
744 		trans->AddChild(label);
745 		parent->AddChild(trans);
746 	}
747 
CreateThruster(const std::string & name,const matrix4x4f & m)748 	void Loader::CreateThruster(const std::string &name, const matrix4x4f &m)
749 	{
750 		PROFILE_SCOPED()
751 		if (!m_mostDetailedLod) return AddLog("Thruster outside highest LOD, ignored");
752 
753 		const bool linear = starts_with(name, "thruster_linear");
754 
755 		matrix4x4f transform = m;
756 
757 		MatrixTransform *trans = new MatrixTransform(m_renderer, transform);
758 
759 		const vector3f pos = transform.GetTranslate();
760 		transform.ClearToRotOnly();
761 
762 		const vector3f direction = transform * vector3f(0.f, 0.f, 1.f);
763 
764 		Thruster *thruster = new Thruster(m_renderer, linear,
765 			pos, direction.Normalized());
766 
767 		thruster->SetName(name);
768 		trans->AddChild(thruster);
769 
770 		m_thrustersRoot->AddChild(trans);
771 	}
772 
CreateNavlight(const std::string & name,const matrix4x4f & m)773 	void Loader::CreateNavlight(const std::string &name, const matrix4x4f &m)
774 	{
775 		PROFILE_SCOPED()
776 		if (!m_mostDetailedLod) return AddLog("Navlight outside highest LOD, ignored");
777 
778 		//Create a MT, lights are attached by client
779 		//we only really need the final position, so this is
780 		//a waste of transform
781 		const matrix4x4f lightPos = matrix4x4f::Translation(m.GetTranslate());
782 		MatrixTransform *lightPoint = new MatrixTransform(m_renderer, lightPos);
783 		lightPoint->SetNodeMask(0x0); //don't render
784 		lightPoint->SetName(name);
785 
786 		m_billboardsRoot->AddChild(lightPoint);
787 	}
788 
CreateCollisionGeometry(RefCountedPtr<StaticGeometry> geom,unsigned int collFlag)789 	RefCountedPtr<CollisionGeometry> Loader::CreateCollisionGeometry(RefCountedPtr<StaticGeometry> geom, unsigned int collFlag)
790 	{
791 		PROFILE_SCOPED()
792 		//Convert StaticMesh points & indices into cgeom
793 		//note: it's not slow, but the amount of data being copied is just stupid:
794 		//assimp to vtxbuffer, vtxbuffer to vector, vector to cgeom, cgeom to geomtree...
795 		assert(geom->GetNumMeshes() == 1);
796 		StaticGeometry::Mesh mesh = geom->GetMeshAt(0);
797 
798 		const Uint32 posOffs = mesh.vertexBuffer->GetDesc().GetOffset(Graphics::ATTRIB_POSITION);
799 		const Uint32 stride = mesh.vertexBuffer->GetDesc().stride;
800 		const Uint32 numVtx = mesh.vertexBuffer->GetDesc().numVertices;
801 		const Uint32 numIdx = mesh.indexBuffer->GetSize();
802 
803 		//copy vertex positions from buffer
804 		std::vector<vector3f> pos;
805 		pos.reserve(numVtx);
806 
807 		Uint8 *vtxPtr = mesh.vertexBuffer->Map<Uint8>(Graphics::BUFFER_MAP_READ);
808 		for (Uint32 i = 0; i < numVtx; i++)
809 			pos.push_back(*reinterpret_cast<vector3f *>(vtxPtr + (i * stride) + posOffs));
810 		mesh.vertexBuffer->Unmap();
811 
812 		//copy indices from buffer
813 		std::vector<Uint32> idx;
814 		idx.reserve(numIdx);
815 
816 		Uint32 *idxPtr = mesh.indexBuffer->Map(Graphics::BUFFER_MAP_READ);
817 		for (Uint32 i = 0; i < numIdx; i++)
818 			idx.push_back(idxPtr[i]);
819 		mesh.indexBuffer->Unmap();
820 		RefCountedPtr<CollisionGeometry> cgeom(new CollisionGeometry(m_renderer, pos, idx, collFlag));
821 		return cgeom;
822 	}
823 
ConvertNodes(aiNode * node,Group * _parent,std::vector<RefCountedPtr<StaticGeometry>> & geoms,const matrix4x4f & accum)824 	void Loader::ConvertNodes(aiNode *node, Group *_parent, std::vector<RefCountedPtr<StaticGeometry>> &geoms, const matrix4x4f &accum)
825 	{
826 		PROFILE_SCOPED()
827 		Group *parent = _parent;
828 		const std::string nodename(node->mName.C_Str());
829 		const aiMatrix4x4 &trans = node->mTransformation;
830 		matrix4x4f m = ConvertMatrix(trans);
831 
832 		//lights, and possibly other special nodes should be leaf nodes (without meshes)
833 		if (node->mNumChildren == 0 && node->mNumMeshes == 0) {
834 			if (starts_with(nodename, "navlight_")) {
835 				CreateNavlight(nodename, accum * m);
836 			} else if (starts_with(nodename, "thruster_")) {
837 				CreateThruster(nodename, accum * m);
838 			} else if (starts_with(nodename, "label_")) {
839 				CreateLabel(parent, m);
840 			} else if (starts_with(nodename, "tag_")) {
841 				m_model->AddTag(nodename, new MatrixTransform(m_renderer, accum * m));
842 			} else if (starts_with(nodename, "entrance_")) {
843 				m_model->AddTag(nodename, new MatrixTransform(m_renderer, m));
844 			} else if (starts_with(nodename, "loc_")) {
845 				m_model->AddTag(nodename, new MatrixTransform(m_renderer, m));
846 			} else if (starts_with(nodename, "exit_")) {
847 				m_model->AddTag(nodename, new MatrixTransform(m_renderer, m));
848 			}
849 			return;
850 		}
851 
852 		//if the transform is identity and the node is not animated,
853 		//could just add a group
854 		parent = new MatrixTransform(m_renderer, m);
855 		_parent->AddChild(parent);
856 		parent->SetName(nodename);
857 
858 		//nodes named collision_* are not added as renderable geometry
859 		if (node->mNumMeshes == 1 && starts_with(nodename, "collision_")) {
860 			const unsigned int collflag = GetGeomFlagForNodeName(nodename);
861 			RefCountedPtr<CollisionGeometry> cgeom = CreateCollisionGeometry(geoms.at(node->mMeshes[0]), collflag);
862 			cgeom->SetName(nodename + "_cgeom");
863 			cgeom->SetDynamic(starts_with(nodename, "collision_d"));
864 			parent->AddChild(cgeom.Get());
865 			return;
866 		}
867 
868 		//nodes with visible geometry (StaticGeometry and decals)
869 		if (node->mNumMeshes > 0) {
870 			//expecting decal_0X
871 			unsigned int numDecal = 0;
872 			if (starts_with(nodename, "decal_")) {
873 				numDecal = atoi(nodename.substr(7, 1).c_str());
874 				if (numDecal > 4)
875 					throw LoadingError("More than 4 different decals");
876 			}
877 
878 			for (unsigned int i = 0; i < node->mNumMeshes; i++) {
879 				RefCountedPtr<StaticGeometry> geom = geoms.at(node->mMeshes[i]);
880 
881 				//handle special decal material
882 				//set special material for decals
883 				if (numDecal > 0) {
884 					geom->SetNodeMask(NODE_TRANSPARENT);
885 					geom->m_blendMode = Graphics::BLEND_ALPHA;
886 					geom->GetMeshAt(0).material = GetDecalMaterial(numDecal);
887 					geom->SetNodeFlags(geom->GetNodeFlags() | NODE_DECAL);
888 					Graphics::RenderStateDesc rsd;
889 					rsd.blendMode = Graphics::BLEND_ALPHA;
890 					rsd.depthWrite = false;
891 					//XXX add polygon offset to decal state
892 					geom->SetRenderState(m_renderer->CreateRenderState(rsd));
893 				}
894 
895 				parent->AddChild(geom.Get());
896 			}
897 		}
898 
899 		for (unsigned int i = 0; i < node->mNumChildren; i++) {
900 			aiNode *child = node->mChildren[i];
901 			ConvertNodes(child, parent, geoms, accum * m);
902 		}
903 	}
904 
LoadCollision(const std::string & filename)905 	void Loader::LoadCollision(const std::string &filename)
906 	{
907 		PROFILE_SCOPED()
908 		//Convert all found aiMeshes into a geomtree. Materials,
909 		//Animations and node structure can be ignored
910 		assert(m_model);
911 
912 		Assimp::Importer importer;
913 		importer.SetIOHandler(new AssimpFileSystem(FileSystem::gameDataFiles));
914 
915 		//discard extra data
916 		importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS,
917 			aiComponent_COLORS |
918 				aiComponent_TEXCOORDS |
919 				aiComponent_NORMALS |
920 				aiComponent_MATERIALS);
921 		const aiScene *scene = importer.ReadFile(
922 			filename,
923 			aiProcess_RemoveComponent |
924 				aiProcess_Triangulate |
925 				aiProcess_PreTransformVertices //"bake" transformations so we can disregard the structure
926 		);
927 
928 		if (!scene)
929 			throw LoadingError("Could not load file");
930 
931 		if (scene->mNumMeshes == 0)
932 			throw LoadingError("No geometry found");
933 
934 		std::vector<Uint32> indices;
935 		std::vector<vector3f> vertices;
936 		Uint32 indexOffset = 0;
937 
938 		for (Uint32 i = 0; i < scene->mNumMeshes; i++) {
939 			aiMesh *mesh = scene->mMeshes[i];
940 
941 			//copy indices
942 			//we assume aiProcess_Triangulate does its job
943 			assert(mesh->mNumFaces > 0);
944 			for (unsigned int f = 0; f < mesh->mNumFaces; f++) {
945 				const aiFace *face = &mesh->mFaces[f];
946 				for (unsigned int j = 0; j < face->mNumIndices; j++) {
947 					indices.push_back(indexOffset + face->mIndices[j]);
948 				}
949 			}
950 			indexOffset += mesh->mNumFaces * 3;
951 
952 			//vertices
953 			for (unsigned int v = 0; v < mesh->mNumVertices; v++) {
954 				const aiVector3D &vtx = mesh->mVertices[v];
955 				vertices.push_back(vector3f(vtx.x, vtx.y, vtx.z));
956 			}
957 		}
958 
959 		assert(!vertices.empty() && !indices.empty());
960 
961 		//add pre-transformed geometry at the top level
962 		m_model->GetRoot()->AddChild(new CollisionGeometry(m_renderer, vertices, indices, 0));
963 	}
964 
GetGeomFlagForNodeName(const std::string & nodename)965 	unsigned int Loader::GetGeomFlagForNodeName(const std::string &nodename)
966 	{
967 		PROFILE_SCOPED()
968 		//special names after collision_
969 		if (nodename.length() > 10) {
970 			//landing pads
971 			if (nodename.length() >= 14 && nodename.substr(10, 3) == "pad") {
972 				const std::string pad = nodename.substr(13);
973 				const int padID = atoi(pad.c_str()) - 1;
974 				if (padID < 240) {
975 					return 0x10 + padID;
976 				}
977 			}
978 		}
979 		//anything else is static collision
980 		return 0x0;
981 	}
982 
983 } // namespace SceneGraph
984