1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2021, assimp team
7 
8 All rights reserved.
9 
10 Redistribution and use of this software in source and binary forms,
11 with or without modification, are permitted provided that the following
12 conditions are met:
13 
14 * Redistributions of source code must retain the above
15   copyright notice, this list of conditions and the
16   following disclaimer.
17 
18 * Redistributions in binary form must reproduce the above
19   copyright notice, this list of conditions and the
20   following disclaimer in the documentation and/or other
21   materials provided with the distribution.
22 
23 * Neither the name of the assimp team, nor the names of its
24   contributors may be used to endorse or promote products
25   derived from this software without specific prior
26   written permission of the assimp team.
27 
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 ---------------------------------------------------------------------------
40 */
41 
42 /** @file  IRRLoader.cpp
43  *  @brief Implementation of the Irr importer class
44  */
45 
46 #ifndef ASSIMP_BUILD_NO_IRR_IMPORTER
47 
48 #include "AssetLib/Irr/IRRLoader.h"
49 #include "Common/Importer.h"
50 
51 #include <assimp/GenericProperty.h>
52 #include <assimp/MathFunctions.h>
53 #include <assimp/ParsingUtils.h>
54 #include <assimp/SceneCombiner.h>
55 #include <assimp/StandardShapes.h>
56 #include <assimp/fast_atof.h>
57 #include <assimp/importerdesc.h>
58 #include <assimp/material.h>
59 #include <assimp/mesh.h>
60 #include <assimp/postprocess.h>
61 #include <assimp/scene.h>
62 #include <assimp/DefaultLogger.hpp>
63 #include <assimp/IOSystem.hpp>
64 
65 #include <memory>
66 
67 using namespace Assimp;
68 
69 static const aiImporterDesc desc = {
70 	"Irrlicht Scene Reader",
71 	"",
72 	"",
73 	"http://irrlicht.sourceforge.net/",
74 	aiImporterFlags_SupportTextFlavour,
75 	0,
76 	0,
77 	0,
78 	0,
79 	"irr xml"
80 };
81 
82 // ------------------------------------------------------------------------------------------------
83 // Constructor to be privately used by Importer
IRRImporter()84 IRRImporter::IRRImporter() :
85 		fps(), configSpeedFlag() {
86 	// empty
87 }
88 
89 // ------------------------------------------------------------------------------------------------
90 // Destructor, private as well
~IRRImporter()91 IRRImporter::~IRRImporter() {
92 	// empty
93 }
94 
95 // ------------------------------------------------------------------------------------------------
96 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem * pIOHandler,bool checkSig) const97 bool IRRImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
98 	const std::string extension = GetExtension(pFile);
99 	if (extension == "irr") {
100 		return true;
101 	} else if (extension == "xml" || checkSig) {
102 		/*  If CanRead() is called in order to check whether we
103          *  support a specific file extension in general pIOHandler
104          *  might be nullptr and it's our duty to return true here.
105          */
106 		if (nullptr == pIOHandler) {
107 			return true;
108 		}
109 		static const char * const tokens[] = { "irr_scene" };
110 		return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
111 	}
112 
113 	return false;
114 }
115 
116 // ------------------------------------------------------------------------------------------------
GetInfo() const117 const aiImporterDesc *IRRImporter::GetInfo() const {
118 	return &desc;
119 }
120 
121 // ------------------------------------------------------------------------------------------------
SetupProperties(const Importer * pImp)122 void IRRImporter::SetupProperties(const Importer *pImp) {
123 	// read the output frame rate of all node animation channels
124 	fps = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IRR_ANIM_FPS, 100);
125 	if (fps < 10.) {
126 		ASSIMP_LOG_ERROR("IRR: Invalid FPS configuration");
127 		fps = 100;
128 	}
129 
130 	// AI_CONFIG_FAVOUR_SPEED
131 	configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED, 0));
132 }
133 
134 // ------------------------------------------------------------------------------------------------
135 // Build a mesh that consists of a single squad (a side of a skybox)
BuildSingleQuadMesh(const SkyboxVertex & v1,const SkyboxVertex & v2,const SkyboxVertex & v3,const SkyboxVertex & v4)136 aiMesh *IRRImporter::BuildSingleQuadMesh(const SkyboxVertex &v1,
137 		const SkyboxVertex &v2,
138 		const SkyboxVertex &v3,
139 		const SkyboxVertex &v4) {
140 	// allocate and prepare the mesh
141 	aiMesh *out = new aiMesh();
142 
143 	out->mPrimitiveTypes = aiPrimitiveType_POLYGON;
144 	out->mNumFaces = 1;
145 
146 	// build the face
147 	out->mFaces = new aiFace[1];
148 	aiFace &face = out->mFaces[0];
149 
150 	face.mNumIndices = 4;
151 	face.mIndices = new unsigned int[4];
152 	for (unsigned int i = 0; i < 4; ++i)
153 		face.mIndices[i] = i;
154 
155 	out->mNumVertices = 4;
156 
157 	// copy vertex positions
158 	aiVector3D *vec = out->mVertices = new aiVector3D[4];
159 	*vec++ = v1.position;
160 	*vec++ = v2.position;
161 	*vec++ = v3.position;
162 	*vec = v4.position;
163 
164 	// copy vertex normals
165 	vec = out->mNormals = new aiVector3D[4];
166 	*vec++ = v1.normal;
167 	*vec++ = v2.normal;
168 	*vec++ = v3.normal;
169 	*vec = v4.normal;
170 
171 	// copy texture coordinates
172 	vec = out->mTextureCoords[0] = new aiVector3D[4];
173 	*vec++ = v1.uv;
174 	*vec++ = v2.uv;
175 	*vec++ = v3.uv;
176 	*vec = v4.uv;
177 	return out;
178 }
179 
180 // ------------------------------------------------------------------------------------------------
BuildSkybox(std::vector<aiMesh * > & meshes,std::vector<aiMaterial * > materials)181 void IRRImporter::BuildSkybox(std::vector<aiMesh *> &meshes, std::vector<aiMaterial *> materials) {
182 	// Update the material of the skybox - replace the name and disable shading for skyboxes.
183 	for (unsigned int i = 0; i < 6; ++i) {
184 		aiMaterial *out = (aiMaterial *)(*(materials.end() - (6 - i)));
185 
186 		aiString s;
187 		s.length = ::ai_snprintf(s.data, MAXLEN, "SkyboxSide_%u", i);
188 		out->AddProperty(&s, AI_MATKEY_NAME);
189 
190 		int shading = aiShadingMode_NoShading;
191 		out->AddProperty(&shading, 1, AI_MATKEY_SHADING_MODEL);
192 	}
193 
194 	// Skyboxes are much more difficult. They are represented
195 	// by six single planes with different textures, so we'll
196 	// need to build six meshes.
197 
198 	const ai_real l = 10.0; // the size used by Irrlicht
199 
200 	// FRONT SIDE
201 	meshes.push_back(BuildSingleQuadMesh(
202 			SkyboxVertex(-l, -l, -l, 0, 0, 1, 1.0, 1.0),
203 			SkyboxVertex(l, -l, -l, 0, 0, 1, 0.0, 1.0),
204 			SkyboxVertex(l, l, -l, 0, 0, 1, 0.0, 0.0),
205 			SkyboxVertex(-l, l, -l, 0, 0, 1, 1.0, 0.0)));
206 	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 6u);
207 
208 	// LEFT SIDE
209 	meshes.push_back(BuildSingleQuadMesh(
210 			SkyboxVertex(l, -l, -l, -1, 0, 0, 1.0, 1.0),
211 			SkyboxVertex(l, -l, l, -1, 0, 0, 0.0, 1.0),
212 			SkyboxVertex(l, l, l, -1, 0, 0, 0.0, 0.0),
213 			SkyboxVertex(l, l, -l, -1, 0, 0, 1.0, 0.0)));
214 	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 5u);
215 
216 	// BACK SIDE
217 	meshes.push_back(BuildSingleQuadMesh(
218 			SkyboxVertex(l, -l, l, 0, 0, -1, 1.0, 1.0),
219 			SkyboxVertex(-l, -l, l, 0, 0, -1, 0.0, 1.0),
220 			SkyboxVertex(-l, l, l, 0, 0, -1, 0.0, 0.0),
221 			SkyboxVertex(l, l, l, 0, 0, -1, 1.0, 0.0)));
222 	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 4u);
223 
224 	// RIGHT SIDE
225 	meshes.push_back(BuildSingleQuadMesh(
226 			SkyboxVertex(-l, -l, l, 1, 0, 0, 1.0, 1.0),
227 			SkyboxVertex(-l, -l, -l, 1, 0, 0, 0.0, 1.0),
228 			SkyboxVertex(-l, l, -l, 1, 0, 0, 0.0, 0.0),
229 			SkyboxVertex(-l, l, l, 1, 0, 0, 1.0, 0.0)));
230 	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 3u);
231 
232 	// TOP SIDE
233 	meshes.push_back(BuildSingleQuadMesh(
234 			SkyboxVertex(l, l, -l, 0, -1, 0, 1.0, 1.0),
235 			SkyboxVertex(l, l, l, 0, -1, 0, 0.0, 1.0),
236 			SkyboxVertex(-l, l, l, 0, -1, 0, 0.0, 0.0),
237 			SkyboxVertex(-l, l, -l, 0, -1, 0, 1.0, 0.0)));
238 	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 2u);
239 
240 	// BOTTOM SIDE
241 	meshes.push_back(BuildSingleQuadMesh(
242 			SkyboxVertex(l, -l, l, 0, 1, 0, 0.0, 0.0),
243 			SkyboxVertex(l, -l, -l, 0, 1, 0, 1.0, 0.0),
244 			SkyboxVertex(-l, -l, -l, 0, 1, 0, 1.0, 1.0),
245 			SkyboxVertex(-l, -l, l, 0, 1, 0, 0.0, 1.0)));
246 	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 1u);
247 }
248 
249 // ------------------------------------------------------------------------------------------------
CopyMaterial(std::vector<aiMaterial * > & materials,std::vector<std::pair<aiMaterial *,unsigned int>> & inmaterials,unsigned int & defMatIdx,aiMesh * mesh)250 void IRRImporter::CopyMaterial(std::vector<aiMaterial *> &materials,
251 		std::vector<std::pair<aiMaterial *, unsigned int>> &inmaterials,
252 		unsigned int &defMatIdx,
253 		aiMesh *mesh) {
254 	if (inmaterials.empty()) {
255 		// Do we have a default material? If not we need to create one
256 		if (UINT_MAX == defMatIdx) {
257 			defMatIdx = (unsigned int)materials.size();
258 			//TODO: add this materials to someone?
259 			/*aiMaterial* mat = new aiMaterial();
260 
261             aiString s;
262             s.Set(AI_DEFAULT_MATERIAL_NAME);
263             mat->AddProperty(&s,AI_MATKEY_NAME);
264 
265             aiColor3D c(0.6f,0.6f,0.6f);
266             mat->AddProperty(&c,1,AI_MATKEY_COLOR_DIFFUSE);*/
267 		}
268 		mesh->mMaterialIndex = defMatIdx;
269 		return;
270 	} else if (inmaterials.size() > 1) {
271 		ASSIMP_LOG_INFO("IRR: Skipping additional materials");
272 	}
273 
274 	mesh->mMaterialIndex = (unsigned int)materials.size();
275 	materials.push_back(inmaterials[0].first);
276 }
277 
278 // ------------------------------------------------------------------------------------------------
ClampSpline(int idx,int size)279 inline int ClampSpline(int idx, int size) {
280 	return (idx < 0 ? size + idx : (idx >= size ? idx - size : idx));
281 }
282 
283 // ------------------------------------------------------------------------------------------------
FindSuitableMultiple(int & angle)284 inline void FindSuitableMultiple(int &angle) {
285 	if (angle < 3)
286 		angle = 3;
287 	else if (angle < 10)
288 		angle = 10;
289 	else if (angle < 20)
290 		angle = 20;
291 	else if (angle < 30)
292 		angle = 30;
293 }
294 
295 // ------------------------------------------------------------------------------------------------
ComputeAnimations(Node * root,aiNode * real,std::vector<aiNodeAnim * > & anims)296 void IRRImporter::ComputeAnimations(Node *root, aiNode *real, std::vector<aiNodeAnim *> &anims) {
297 	ai_assert(nullptr != root && nullptr != real);
298 
299 	// XXX totally WIP - doesn't produce proper results, need to evaluate
300 	// whether there's any use for Irrlicht's proprietary scene format
301 	// outside Irrlicht ...
302 	// This also applies to the above function of FindSuitableMultiple and ClampSpline which are
303 	// solely used in this function
304 
305 	if (root->animators.empty()) {
306 		return;
307 	}
308 	unsigned int total(0);
309 	for (std::list<Animator>::iterator it = root->animators.begin(); it != root->animators.end(); ++it) {
310 		if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) {
311 			ASSIMP_LOG_WARN("IRR: Skipping unknown or unsupported animator");
312 			continue;
313 		}
314 		++total;
315 	}
316 	if (!total) {
317 		return;
318 	} else if (1 == total) {
319 		ASSIMP_LOG_WARN("IRR: Adding dummy nodes to simulate multiple animators");
320 	}
321 
322 	// NOTE: 1 tick == i millisecond
323 
324 	unsigned int cur = 0;
325 	for (std::list<Animator>::iterator it = root->animators.begin();
326 			it != root->animators.end(); ++it) {
327 		if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) continue;
328 
329 		Animator &in = *it;
330 		aiNodeAnim *anim = new aiNodeAnim();
331 
332 		if (cur != total - 1) {
333 			// Build a new name - a prefix instead of a suffix because it is
334 			// easier to check against
335 			anim->mNodeName.length = ::ai_snprintf(anim->mNodeName.data, MAXLEN,
336 					"$INST_DUMMY_%i_%s", total - 1,
337 					(root->name.length() ? root->name.c_str() : ""));
338 
339 			// we'll also need to insert a dummy in the node hierarchy.
340 			aiNode *dummy = new aiNode();
341 
342 			for (unsigned int i = 0; i < real->mParent->mNumChildren; ++i)
343 				if (real->mParent->mChildren[i] == real)
344 					real->mParent->mChildren[i] = dummy;
345 
346 			dummy->mParent = real->mParent;
347 			dummy->mName = anim->mNodeName;
348 
349 			dummy->mNumChildren = 1;
350 			dummy->mChildren = new aiNode *[dummy->mNumChildren];
351 			dummy->mChildren[0] = real;
352 
353 			// the transformation matrix of the dummy node is the identity
354 
355 			real->mParent = dummy;
356 		} else
357 			anim->mNodeName.Set(root->name);
358 		++cur;
359 
360 		switch (in.type) {
361 			case Animator::ROTATION: {
362 				// -----------------------------------------------------
363 				// find out how long a full rotation will take
364 				// This is the least common multiple of 360.f and all
365 				// three euler angles. Although we'll surely find a
366 				// possible multiple (haha) it could be somewhat large
367 				// for our purposes. So we need to modify the angles
368 				// here in order to get good results.
369 				// -----------------------------------------------------
370 				int angles[3];
371 				angles[0] = (int)(in.direction.x * 100);
372 				angles[1] = (int)(in.direction.y * 100);
373 				angles[2] = (int)(in.direction.z * 100);
374 
375 				angles[0] %= 360;
376 				angles[1] %= 360;
377 				angles[2] %= 360;
378 
379 				if ((angles[0] * angles[1]) != 0 && (angles[1] * angles[2]) != 0) {
380 					FindSuitableMultiple(angles[0]);
381 					FindSuitableMultiple(angles[1]);
382 					FindSuitableMultiple(angles[2]);
383 				}
384 
385 				int lcm = 360;
386 
387 				if (angles[0])
388 					lcm = Math::lcm(lcm, angles[0]);
389 
390 				if (angles[1])
391 					lcm = Math::lcm(lcm, angles[1]);
392 
393 				if (angles[2])
394 					lcm = Math::lcm(lcm, angles[2]);
395 
396 				if (360 == lcm)
397 					break;
398 
399 
400 				// find out how many time units we'll need for the finest
401 				// track (in seconds) - this defines the number of output
402 				// keys (fps * seconds)
403 				float max = 0.f;
404 				if (angles[0])
405 					max = (float)lcm / angles[0];
406 				if (angles[1])
407 					max = std::max(max, (float)lcm / angles[1]);
408 				if (angles[2])
409 					max = std::max(max, (float)lcm / angles[2]);
410 
411 				anim->mNumRotationKeys = (unsigned int)(max * fps);
412 				anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys];
413 
414 				// begin with a zero angle
415 				aiVector3D angle;
416 				for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) {
417 					// build the quaternion for the given euler angles
418 					aiQuatKey &q = anim->mRotationKeys[i];
419 
420 					q.mValue = aiQuaternion(angle.x, angle.y, angle.z);
421 					q.mTime = (double)i;
422 
423 					// increase the angle
424 					angle += in.direction;
425 				}
426 
427 				// This animation is repeated and repeated ...
428 				anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT;
429 			} break;
430 
431 			case Animator::FLY_CIRCLE: {
432 				// -----------------------------------------------------
433 				// Find out how much time we'll need to perform a
434 				// full circle.
435 				// -----------------------------------------------------
436 				const double seconds = (1. / in.speed) / 1000.;
437 				const double tdelta = 1000. / fps;
438 
439 				anim->mNumPositionKeys = (unsigned int)(fps * seconds);
440 				anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
441 
442 				// from Irrlicht, what else should we do than copying it?
443 				aiVector3D vecU, vecV;
444 				if (in.direction.y) {
445 					vecV = aiVector3D(50, 0, 0) ^ in.direction;
446 				} else
447 					vecV = aiVector3D(0, 50, 00) ^ in.direction;
448 				vecV.Normalize();
449 				vecU = (vecV ^ in.direction).Normalize();
450 
451 				// build the output keys
452 				for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) {
453 					aiVectorKey &key = anim->mPositionKeys[i];
454 					key.mTime = i * tdelta;
455 
456 					const ai_real t = (ai_real)(in.speed * key.mTime);
457 					key.mValue = in.circleCenter + in.circleRadius * ((vecU * std::cos(t)) + (vecV * std::sin(t)));
458 				}
459 
460 				// This animation is repeated and repeated ...
461 				anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT;
462 			} break;
463 
464 			case Animator::FLY_STRAIGHT: {
465 				anim->mPostState = anim->mPreState = (in.loop ? aiAnimBehaviour_REPEAT : aiAnimBehaviour_CONSTANT);
466 				const double seconds = in.timeForWay / 1000.;
467 				const double tdelta = 1000. / fps;
468 
469 				anim->mNumPositionKeys = (unsigned int)(fps * seconds);
470 				anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
471 
472 				aiVector3D diff = in.direction - in.circleCenter;
473 				const ai_real lengthOfWay = diff.Length();
474 				diff.Normalize();
475 
476 				const double timeFactor = lengthOfWay / in.timeForWay;
477 
478 				// build the output keys
479 				for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) {
480 					aiVectorKey &key = anim->mPositionKeys[i];
481 					key.mTime = i * tdelta;
482 					key.mValue = in.circleCenter + diff * ai_real(timeFactor * key.mTime);
483 				}
484 			} break;
485 
486 			case Animator::FOLLOW_SPLINE: {
487 				// repeat outside the defined time range
488 				anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT;
489 				const int size = (int)in.splineKeys.size();
490 				if (!size) {
491 					// We have no point in the spline. That's bad. Really bad.
492 					ASSIMP_LOG_WARN("IRR: Spline animators with no points defined");
493 
494 					delete anim;
495 					anim = nullptr;
496 					break;
497 				} else if (size == 1) {
498 					// We have just one point in the spline so we don't need the full calculation
499 					anim->mNumPositionKeys = 1;
500 					anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
501 
502 					anim->mPositionKeys[0].mValue = in.splineKeys[0].mValue;
503 					anim->mPositionKeys[0].mTime = 0.f;
504 					break;
505 				}
506 
507 				unsigned int ticksPerFull = 15;
508 				anim->mNumPositionKeys = (unsigned int)(ticksPerFull * fps);
509 				anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
510 
511 				for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) {
512 					aiVectorKey &key = anim->mPositionKeys[i];
513 
514 					const ai_real dt = (i * in.speed * ai_real(0.001));
515 					const ai_real u = dt - std::floor(dt);
516 					const int idx = (int)std::floor(dt) % size;
517 
518 					// get the 4 current points to evaluate the spline
519 					const aiVector3D &p0 = in.splineKeys[ClampSpline(idx - 1, size)].mValue;
520 					const aiVector3D &p1 = in.splineKeys[ClampSpline(idx + 0, size)].mValue;
521 					const aiVector3D &p2 = in.splineKeys[ClampSpline(idx + 1, size)].mValue;
522 					const aiVector3D &p3 = in.splineKeys[ClampSpline(idx + 2, size)].mValue;
523 
524 					// compute polynomials
525 					const ai_real u2 = u * u;
526 					const ai_real u3 = u2 * 2;
527 
528 					const ai_real h1 = ai_real(2.0) * u3 - ai_real(3.0) * u2 + ai_real(1.0);
529 					const ai_real h2 = ai_real(-2.0) * u3 + ai_real(3.0) * u3;
530 					const ai_real h3 = u3 - ai_real(2.0) * u3;
531 					const ai_real h4 = u3 - u2;
532 
533 					// compute the spline tangents
534 					const aiVector3D t1 = (p2 - p0) * in.tightness;
535 					aiVector3D t2 = (p3 - p1) * in.tightness;
536 
537 					// and use them to get the interpolated point
538 					t2 = (h1 * p1 + p2 * h2 + t1 * h3 + h4 * t2);
539 
540 					// build a simple translation matrix from it
541 					key.mValue = t2;
542 					key.mTime = (double)i;
543 				}
544 			} break;
545 			default:
546 				// UNKNOWN , OTHER
547 				break;
548 		};
549 		if (anim) {
550 			anims.push_back(anim);
551 			++total;
552 		}
553 	}
554 }
555 
556 // ------------------------------------------------------------------------------------------------
557 // This function is maybe more generic than we'd need it here
SetupMapping(aiMaterial * mat,aiTextureMapping mode,const aiVector3D & axis=aiVector3D (0.f,0.f,-1.f))558 void SetupMapping(aiMaterial *mat, aiTextureMapping mode, const aiVector3D &axis = aiVector3D(0.f, 0.f, -1.f)) {
559 	if (nullptr == mat) {
560 		return;
561 	}
562 
563     // Check whether there are texture properties defined - setup
564 	// the desired texture mapping mode for all of them and ignore
565 	// all UV settings we might encounter. WE HAVE NO UVS!
566 
567 	std::vector<aiMaterialProperty *> p;
568 	p.reserve(mat->mNumProperties + 1);
569 
570 	for (unsigned int i = 0; i < mat->mNumProperties; ++i) {
571 		aiMaterialProperty *prop = mat->mProperties[i];
572 		if (!::strcmp(prop->mKey.data, "$tex.file")) {
573 			// Setup the mapping key
574 			aiMaterialProperty *m = new aiMaterialProperty();
575 			m->mKey.Set("$tex.mapping");
576 			m->mIndex = prop->mIndex;
577 			m->mSemantic = prop->mSemantic;
578 			m->mType = aiPTI_Integer;
579 
580 			m->mDataLength = 4;
581 			m->mData = new char[4];
582 			*((int *)m->mData) = mode;
583 
584 			p.push_back(prop);
585 			p.push_back(m);
586 
587 			// Setup the mapping axis
588 			if (mode == aiTextureMapping_CYLINDER || mode == aiTextureMapping_PLANE || mode == aiTextureMapping_SPHERE) {
589 				m = new aiMaterialProperty();
590 				m->mKey.Set("$tex.mapaxis");
591 				m->mIndex = prop->mIndex;
592 				m->mSemantic = prop->mSemantic;
593 				m->mType = aiPTI_Float;
594 
595 				m->mDataLength = 12;
596 				m->mData = new char[12];
597 				*((aiVector3D *)m->mData) = axis;
598 				p.push_back(m);
599 			}
600 		} else if (!::strcmp(prop->mKey.data, "$tex.uvwsrc")) {
601 			delete mat->mProperties[i];
602 		} else
603 			p.push_back(prop);
604 	}
605 
606 	if (p.empty()) return;
607 
608 	// rebuild the output array
609 	if (p.size() > mat->mNumAllocated) {
610 		delete[] mat->mProperties;
611 		mat->mProperties = new aiMaterialProperty *[p.size() * 2];
612 
613 		mat->mNumAllocated = static_cast<unsigned int>(p.size() * 2);
614 	}
615 	mat->mNumProperties = (unsigned int)p.size();
616 	::memcpy(mat->mProperties, &p[0], sizeof(void *) * mat->mNumProperties);
617 }
618 
619 // ------------------------------------------------------------------------------------------------
GenerateGraph(Node * root,aiNode * rootOut,aiScene * scene,BatchLoader & batch,std::vector<aiMesh * > & meshes,std::vector<aiNodeAnim * > & anims,std::vector<AttachmentInfo> & attach,std::vector<aiMaterial * > & materials,unsigned int & defMatIdx)620 void IRRImporter::GenerateGraph(Node *root, aiNode *rootOut, aiScene *scene,
621 		BatchLoader &batch,
622 		std::vector<aiMesh *> &meshes,
623 		std::vector<aiNodeAnim *> &anims,
624 		std::vector<AttachmentInfo> &attach,
625 		std::vector<aiMaterial *> &materials,
626 		unsigned int &defMatIdx) {
627 	unsigned int oldMeshSize = (unsigned int)meshes.size();
628 	//unsigned int meshTrafoAssign = 0;
629 
630 	// Now determine the type of the node
631 	switch (root->type) {
632 		case Node::ANIMMESH:
633 		case Node::MESH: {
634 			if (!root->meshPath.length())
635 				break;
636 
637 			// Get the loaded mesh from the scene and add it to
638 			// the list of all scenes to be attached to the
639 			// graph we're currently building
640 			aiScene *localScene = batch.GetImport(root->id);
641 			if (!localScene) {
642 				ASSIMP_LOG_ERROR("IRR: Unable to load external file: ", root->meshPath);
643 				break;
644 			}
645 			attach.push_back(AttachmentInfo(localScene, rootOut));
646 
647 			// Now combine the material we've loaded for this mesh
648 			// with the real materials we got from the file. As we
649 			// don't execute any pp-steps on the file, the numbers
650 			// should be equal. If they are not, we can impossibly
651 			// do this  ...
652 			if (root->materials.size() != (unsigned int)localScene->mNumMaterials) {
653 				ASSIMP_LOG_WARN("IRR: Failed to match imported materials "
654 								"with the materials found in the IRR scene file");
655 
656 				break;
657 			}
658 			for (unsigned int i = 0; i < localScene->mNumMaterials; ++i) {
659 				// Delete the old material, we don't need it anymore
660 				delete localScene->mMaterials[i];
661 
662 				std::pair<aiMaterial *, unsigned int> &src = root->materials[i];
663 				localScene->mMaterials[i] = src.first;
664 			}
665 
666 			// NOTE: Each mesh should have exactly one material assigned,
667 			// but we do it in a separate loop if this behavior changes
668 			// in future.
669 			for (unsigned int i = 0; i < localScene->mNumMeshes; ++i) {
670 				// Process material flags
671 				aiMesh *mesh = localScene->mMeshes[i];
672 
673 				// If "trans_vertex_alpha" mode is enabled, search all vertex colors
674 				// and check whether they have a common alpha value. This is quite
675 				// often the case so we can simply extract it to a shared oacity
676 				// value.
677 				std::pair<aiMaterial *, unsigned int> &src = root->materials[mesh->mMaterialIndex];
678 				aiMaterial *mat = (aiMaterial *)src.first;
679 
680 				if (mesh->HasVertexColors(0) && src.second & AI_IRRMESH_MAT_trans_vertex_alpha) {
681 					bool bdo = true;
682 					for (unsigned int a = 1; a < mesh->mNumVertices; ++a) {
683 
684 						if (mesh->mColors[0][a].a != mesh->mColors[0][a - 1].a) {
685 							bdo = false;
686 							break;
687 						}
688 					}
689 					if (bdo) {
690 						ASSIMP_LOG_INFO("IRR: Replacing mesh vertex alpha with common opacity");
691 
692 						for (unsigned int a = 0; a < mesh->mNumVertices; ++a)
693 							mesh->mColors[0][a].a = 1.f;
694 
695 						mat->AddProperty(&mesh->mColors[0][0].a, 1, AI_MATKEY_OPACITY);
696 					}
697 				}
698 
699 				// If we have a second texture coordinate set and a second texture
700 				// (either light-map, normal-map, 2layered material) we need to
701 				// setup the correct UV index for it. The texture can either
702 				// be diffuse (light-map & 2layer) or a normal map (normal & parallax)
703 				if (mesh->HasTextureCoords(1)) {
704 
705 					int idx = 1;
706 					if (src.second & (AI_IRRMESH_MAT_solid_2layer | AI_IRRMESH_MAT_lightmap)) {
707 						mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(0));
708 					} else if (src.second & AI_IRRMESH_MAT_normalmap_solid) {
709 						mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0));
710 					}
711 				}
712 			}
713 		} break;
714 
715 		case Node::LIGHT:
716 		case Node::CAMERA:
717 
718 			// We're already finished with lights and cameras
719 			break;
720 
721 		case Node::SPHERE: {
722 			// Generate the sphere model. Our input parameter to
723 			// the sphere generation algorithm is the number of
724 			// subdivisions of each triangle - but here we have
725 			// the number of polygons on a specific axis. Just
726 			// use some hard-coded limits to approximate this ...
727 			unsigned int mul = root->spherePolyCountX * root->spherePolyCountY;
728 			if (mul < 100)
729 				mul = 2;
730 			else if (mul < 300)
731 				mul = 3;
732 			else
733 				mul = 4;
734 
735 			meshes.push_back(StandardShapes::MakeMesh(mul,
736 					&StandardShapes::MakeSphere));
737 
738 			// Adjust scaling
739 			root->scaling *= root->sphereRadius / 2;
740 
741 			// Copy one output material
742 			CopyMaterial(materials, root->materials, defMatIdx, meshes.back());
743 
744 			// Now adjust this output material - if there is a first texture
745 			// set, setup spherical UV mapping around the Y axis.
746 			SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_SPHERE);
747 		} break;
748 
749 		case Node::CUBE: {
750 			// Generate an unit cube first
751 			meshes.push_back(StandardShapes::MakeMesh(
752 					&StandardShapes::MakeHexahedron));
753 
754 			// Adjust scaling
755 			root->scaling *= root->sphereRadius;
756 
757 			// Copy one output material
758 			CopyMaterial(materials, root->materials, defMatIdx, meshes.back());
759 
760 			// Now adjust this output material - if there is a first texture
761 			// set, setup cubic UV mapping
762 			SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_BOX);
763 		} break;
764 
765 		case Node::SKYBOX: {
766 			// A sky-box is defined by six materials
767 			if (root->materials.size() < 6) {
768 				ASSIMP_LOG_ERROR("IRR: There should be six materials for a skybox");
769 				break;
770 			}
771 
772 			// copy those materials and generate 6 meshes for our new sky-box
773 			materials.reserve(materials.size() + 6);
774 			for (unsigned int i = 0; i < 6; ++i)
775 				materials.insert(materials.end(), root->materials[i].first);
776 
777 			BuildSkybox(meshes, materials);
778 
779 			// *************************************************************
780 			// Skyboxes will require a different code path for rendering,
781 			// so there must be a way for the user to add special support
782 			// for IRR skyboxes. We add a 'IRR.SkyBox_' prefix to the node.
783 			// *************************************************************
784 			root->name = "IRR.SkyBox_" + root->name;
785 			ASSIMP_LOG_INFO("IRR: Loading skybox, this will "
786 							"require special handling to be displayed correctly");
787 		} break;
788 
789 		case Node::TERRAIN: {
790 			// to support terrains, we'd need to have a texture decoder
791 			ASSIMP_LOG_ERROR("IRR: Unsupported node - TERRAIN");
792 		} break;
793 		default:
794 			// DUMMY
795 			break;
796 	};
797 
798 	// Check whether we added a mesh (or more than one ...). In this case
799 	// we'll also need to attach it to the node
800 	if (oldMeshSize != (unsigned int)meshes.size()) {
801 
802 		rootOut->mNumMeshes = (unsigned int)meshes.size() - oldMeshSize;
803 		rootOut->mMeshes = new unsigned int[rootOut->mNumMeshes];
804 
805 		for (unsigned int a = 0; a < rootOut->mNumMeshes; ++a) {
806 			rootOut->mMeshes[a] = oldMeshSize + a;
807 		}
808 	}
809 
810 	// Setup the name of this node
811 	rootOut->mName.Set(root->name);
812 
813 	// Now compute the final local transformation matrix of the
814 	// node from the given translation, rotation and scaling values.
815 	// (the rotation is given in Euler angles, XYZ order)
816 	//std::swap((float&)root->rotation.z,(float&)root->rotation.y);
817 	rootOut->mTransformation.FromEulerAnglesXYZ(AI_DEG_TO_RAD(root->rotation));
818 
819 	// apply scaling
820 	aiMatrix4x4 &mat = rootOut->mTransformation;
821 	mat.a1 *= root->scaling.x;
822 	mat.b1 *= root->scaling.x;
823 	mat.c1 *= root->scaling.x;
824 	mat.a2 *= root->scaling.y;
825 	mat.b2 *= root->scaling.y;
826 	mat.c2 *= root->scaling.y;
827 	mat.a3 *= root->scaling.z;
828 	mat.b3 *= root->scaling.z;
829 	mat.c3 *= root->scaling.z;
830 
831 	// apply translation
832 	mat.a4 += root->position.x;
833 	mat.b4 += root->position.y;
834 	mat.c4 += root->position.z;
835 
836 	// now compute animations for the node
837 	ComputeAnimations(root, rootOut, anims);
838 
839 	// Add all children recursively. First allocate enough storage
840 	// for them, then call us again
841 	rootOut->mNumChildren = (unsigned int)root->children.size();
842 	if (rootOut->mNumChildren) {
843 
844 		rootOut->mChildren = new aiNode *[rootOut->mNumChildren];
845 		for (unsigned int i = 0; i < rootOut->mNumChildren; ++i) {
846 
847 			aiNode *node = rootOut->mChildren[i] = new aiNode();
848 			node->mParent = rootOut;
849 			GenerateGraph(root->children[i], node, scene, batch, meshes,
850 					anims, attach, materials, defMatIdx);
851 		}
852 	}
853 }
854 
855 // ------------------------------------------------------------------------------------------------
856 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)857 void IRRImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
858 	std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
859 
860 	// Check whether we can read from the file
861 	if (file.get() == nullptr) {
862         throw DeadlyImportError("Failed to open IRR file ", pFile);
863 	}
864 
865 	// Construct the irrXML parser
866 	XmlParser st;
867     if (!st.parse( file.get() )) {
868         throw DeadlyImportError("XML parse error while loading IRR file ", pFile);
869     }
870     pugi::xml_node rootElement = st.getRootNode();
871 
872 	// The root node of the scene
873 	Node *root = new Node(Node::DUMMY);
874 	root->parent = nullptr;
875 	root->name = "<IRRSceneRoot>";
876 
877 	// Current node parent
878 	Node *curParent = root;
879 
880 	// Scene-graph node we're currently working on
881 	Node *curNode = nullptr;
882 
883 	// List of output cameras
884 	std::vector<aiCamera *> cameras;
885 
886 	// List of output lights
887 	std::vector<aiLight *> lights;
888 
889 	// Batch loader used to load external models
890 	BatchLoader batch(pIOHandler);
891 	//  batch.SetBasePath(pFile);
892 
893 	cameras.reserve(5);
894 	lights.reserve(5);
895 
896 	bool inMaterials = false, inAnimator = false;
897 	unsigned int guessedAnimCnt = 0, guessedMeshCnt = 0, guessedMatCnt = 0;
898 
899 	// Parse the XML file
900 
901 	//while (reader->read())  {
902 	for (pugi::xml_node child : rootElement.children())
903 		switch (child.type()) {
904 			case pugi::node_element:
905 				if (!ASSIMP_stricmp(child.name(), "node")) {
906 					// ***********************************************************************
907 					/*  What we're going to do with the node depends
908                      *  on its type:
909                      *
910                      *  "mesh" - Load a mesh from an external file
911                      *  "cube" - Generate a cube
912                      *  "skybox" - Generate a skybox
913                      *  "light" - A light source
914                      *  "sphere" - Generate a sphere mesh
915                      *  "animatedMesh" - Load an animated mesh from an external file
916                      *    and join its animation channels with ours.
917                      *  "empty" - A dummy node
918                      *  "camera" - A camera
919                      *  "terrain" - a terrain node (data comes from a heightmap)
920                      *  "billboard", ""
921                      *
922                      *  Each of these nodes can be animated and all can have multiple
923                      *  materials assigned (except lights, cameras and dummies, of course).
924                      */
925 					// ***********************************************************************
926 					//const char *sz = reader->getAttributeValueSafe("type");
927 					pugi::xml_attribute attrib = child.attribute("type");
928 					Node *nd;
929 					if (!ASSIMP_stricmp(attrib.name(), "mesh") || !ASSIMP_stricmp(attrib.name(), "octTree")) {
930 						// OctTree's and meshes are treated equally
931 						nd = new Node(Node::MESH);
932 					} else if (!ASSIMP_stricmp(attrib.name(), "cube")) {
933 						nd = new Node(Node::CUBE);
934 						++guessedMeshCnt;
935 					} else if (!ASSIMP_stricmp(attrib.name(), "skybox")) {
936 						nd = new Node(Node::SKYBOX);
937 						guessedMeshCnt += 6;
938 					} else if (!ASSIMP_stricmp(attrib.name(), "camera")) {
939 						nd = new Node(Node::CAMERA);
940 
941 						// Setup a temporary name for the camera
942 						aiCamera *cam = new aiCamera();
943 						cam->mName.Set(nd->name);
944 						cameras.push_back(cam);
945 					} else if (!ASSIMP_stricmp(attrib.name(), "light")) {
946 						nd = new Node(Node::LIGHT);
947 
948 						// Setup a temporary name for the light
949 						aiLight *cam = new aiLight();
950 						cam->mName.Set(nd->name);
951 						lights.push_back(cam);
952 					} else if (!ASSIMP_stricmp(attrib.name(), "sphere")) {
953 						nd = new Node(Node::SPHERE);
954 						++guessedMeshCnt;
955 					} else if (!ASSIMP_stricmp(attrib.name(), "animatedMesh")) {
956 						nd = new Node(Node::ANIMMESH);
957 					} else if (!ASSIMP_stricmp(attrib.name(), "empty")) {
958 						nd = new Node(Node::DUMMY);
959 					} else if (!ASSIMP_stricmp(attrib.name(), "terrain")) {
960 						nd = new Node(Node::TERRAIN);
961 					} else if (!ASSIMP_stricmp(attrib.name(), "billBoard")) {
962 						// We don't support billboards, so ignore them
963 						ASSIMP_LOG_ERROR("IRR: Billboards are not supported by Assimp");
964 						nd = new Node(Node::DUMMY);
965 					} else {
966 						ASSIMP_LOG_WARN("IRR: Found unknown node: ", attrib.name());
967 
968 						/*  We skip the contents of nodes we don't know.
969                          *  We parse the transformation and all animators
970                          *  and skip the rest.
971                          */
972 						nd = new Node(Node::DUMMY);
973 					}
974 
975 					/* Attach the newly created node to the scene-graph
976                      */
977 					curNode = nd;
978 					nd->parent = curParent;
979 					curParent->children.push_back(nd);
980 				} else if (!ASSIMP_stricmp(child.name(), "materials")) {
981 					inMaterials = true;
982 				} else if (!ASSIMP_stricmp(child.name(), "animators")) {
983 					inAnimator = true;
984 				} else if (!ASSIMP_stricmp(child.name(), "attributes")) {
985 					//  We should have a valid node here
986 					//  FIX: no ... the scene root node is also contained in an attributes block
987 					if (!curNode) {
988 						continue;
989 					}
990 
991 					Animator *curAnim = nullptr;
992 
993 					// Materials can occur for nearly any type of node
994 					if (inMaterials && curNode->type != Node::DUMMY) {
995 						//  This is a material description - parse it!
996 						curNode->materials.push_back(std::pair<aiMaterial *, unsigned int>());
997 						std::pair<aiMaterial *, unsigned int> &p = curNode->materials.back();
998 
999 						p.first = ParseMaterial(p.second);
1000 						++guessedMatCnt;
1001 						continue;
1002 					} else if (inAnimator) {
1003 						//  This is an animation path - add a new animator
1004 						//  to the list.
1005 						curNode->animators.push_back(Animator());
1006 						curAnim = &curNode->animators.back();
1007 
1008 						++guessedAnimCnt;
1009 					}
1010 
1011 					/*  Parse all elements in the attributes block
1012                      *  and process them.
1013                      */
1014 					//					while (reader->read()) {
1015 					for (pugi::xml_node attrib : child.children()) {
1016 						if (attrib.type() == pugi::node_element) {
1017 							//if (reader->getNodeType() == EXN_ELEMENT) {
1018 							//if (!ASSIMP_stricmp(reader->getNodeName(), "vector3d")) {
1019 							if (!ASSIMP_stricmp(attrib.name(), "vector3d")) {
1020 								VectorProperty prop;
1021 								ReadVectorProperty(prop);
1022 
1023 								if (inAnimator) {
1024 									if (curAnim->type == Animator::ROTATION && prop.name == "Rotation") {
1025 										// We store the rotation euler angles in 'direction'
1026 										curAnim->direction = prop.value;
1027 									} else if (curAnim->type == Animator::FOLLOW_SPLINE) {
1028 										// Check whether the vector follows the PointN naming scheme,
1029 										// here N is the ONE-based index of the point
1030 										if (prop.name.length() >= 6 && prop.name.substr(0, 5) == "Point") {
1031 											// Add a new key to the list
1032 											curAnim->splineKeys.push_back(aiVectorKey());
1033 											aiVectorKey &key = curAnim->splineKeys.back();
1034 
1035 											// and parse its properties
1036 											key.mValue = prop.value;
1037 											key.mTime = strtoul10(&prop.name[5]);
1038 										}
1039 									} else if (curAnim->type == Animator::FLY_CIRCLE) {
1040 										if (prop.name == "Center") {
1041 											curAnim->circleCenter = prop.value;
1042 										} else if (prop.name == "Direction") {
1043 											curAnim->direction = prop.value;
1044 
1045 											// From Irrlicht's source - a workaround for backward compatibility with Irrlicht 1.1
1046 											if (curAnim->direction == aiVector3D()) {
1047 												curAnim->direction = aiVector3D(0.f, 1.f, 0.f);
1048 											} else
1049 												curAnim->direction.Normalize();
1050 										}
1051 									} else if (curAnim->type == Animator::FLY_STRAIGHT) {
1052 										if (prop.name == "Start") {
1053 											// We reuse the field here
1054 											curAnim->circleCenter = prop.value;
1055 										} else if (prop.name == "End") {
1056 											// We reuse the field here
1057 											curAnim->direction = prop.value;
1058 										}
1059 									}
1060 								} else {
1061 									if (prop.name == "Position") {
1062 										curNode->position = prop.value;
1063 									} else if (prop.name == "Rotation") {
1064 										curNode->rotation = prop.value;
1065 									} else if (prop.name == "Scale") {
1066 										curNode->scaling = prop.value;
1067 									} else if (Node::CAMERA == curNode->type) {
1068 										aiCamera *cam = cameras.back();
1069 										if (prop.name == "Target") {
1070 											cam->mLookAt = prop.value;
1071 										} else if (prop.name == "UpVector") {
1072 											cam->mUp = prop.value;
1073 										}
1074 									}
1075 								}
1076 								//} else if (!ASSIMP_stricmp(reader->getNodeName(), "bool")) {
1077 							} else if (!ASSIMP_stricmp(attrib.name(), "bool")) {
1078 								BoolProperty prop;
1079 								ReadBoolProperty(prop);
1080 
1081 								if (inAnimator && curAnim->type == Animator::FLY_CIRCLE && prop.name == "Loop") {
1082 									curAnim->loop = prop.value;
1083 								}
1084 								//} else if (!ASSIMP_stricmp(reader->getNodeName(), "float")) {
1085 							} else if (!ASSIMP_stricmp(attrib.name(), "float")) {
1086 								FloatProperty prop;
1087 								ReadFloatProperty(prop);
1088 
1089 								if (inAnimator) {
1090 									// The speed property exists for several animators
1091 									if (prop.name == "Speed") {
1092 										curAnim->speed = prop.value;
1093 									} else if (curAnim->type == Animator::FLY_CIRCLE && prop.name == "Radius") {
1094 										curAnim->circleRadius = prop.value;
1095 									} else if (curAnim->type == Animator::FOLLOW_SPLINE && prop.name == "Tightness") {
1096 										curAnim->tightness = prop.value;
1097 									}
1098 								} else {
1099 									if (prop.name == "FramesPerSecond" && Node::ANIMMESH == curNode->type) {
1100 										curNode->framesPerSecond = prop.value;
1101 									} else if (Node::CAMERA == curNode->type) {
1102 										/*  This is the vertical, not the horizontal FOV.
1103                                     *  We need to compute the right FOV from the
1104                                     *  screen aspect which we don't know yet.
1105                                     */
1106 										if (prop.name == "Fovy") {
1107 											cameras.back()->mHorizontalFOV = prop.value;
1108 										} else if (prop.name == "Aspect") {
1109 											cameras.back()->mAspect = prop.value;
1110 										} else if (prop.name == "ZNear") {
1111 											cameras.back()->mClipPlaneNear = prop.value;
1112 										} else if (prop.name == "ZFar") {
1113 											cameras.back()->mClipPlaneFar = prop.value;
1114 										}
1115 									} else if (Node::LIGHT == curNode->type) {
1116 										/*  Additional light information
1117                                      */
1118 										if (prop.name == "Attenuation") {
1119 											lights.back()->mAttenuationLinear = prop.value;
1120 										} else if (prop.name == "OuterCone") {
1121 											lights.back()->mAngleOuterCone = AI_DEG_TO_RAD(prop.value);
1122 										} else if (prop.name == "InnerCone") {
1123 											lights.back()->mAngleInnerCone = AI_DEG_TO_RAD(prop.value);
1124 										}
1125 									}
1126 									// radius of the sphere to be generated -
1127 									// or alternatively, size of the cube
1128 									else if ((Node::SPHERE == curNode->type && prop.name == "Radius") || (Node::CUBE == curNode->type && prop.name == "Size")) {
1129 
1130 										curNode->sphereRadius = prop.value;
1131 									}
1132 								}
1133 								//} else if (!ASSIMP_stricmp(reader->getNodeName(), "int")) {
1134 							} else if (!ASSIMP_stricmp(attrib.name(), "int")) {
1135 								IntProperty prop;
1136 								ReadIntProperty(prop);
1137 
1138 								if (inAnimator) {
1139 									if (curAnim->type == Animator::FLY_STRAIGHT && prop.name == "TimeForWay") {
1140 										curAnim->timeForWay = prop.value;
1141 									}
1142 								} else {
1143 									// sphere polygon numbers in each direction
1144 									if (Node::SPHERE == curNode->type) {
1145 
1146 										if (prop.name == "PolyCountX") {
1147 											curNode->spherePolyCountX = prop.value;
1148 										} else if (prop.name == "PolyCountY") {
1149 											curNode->spherePolyCountY = prop.value;
1150 										}
1151 									}
1152 								}
1153 								//} else if (!ASSIMP_stricmp(reader->getNodeName(), "string") || !ASSIMP_stricmp(reader->getNodeName(), "enum")) {
1154 							} else if (!ASSIMP_stricmp(attrib.name(), "string") || !ASSIMP_stricmp(attrib.name(), "enum")) {
1155 								StringProperty prop;
1156 								ReadStringProperty(prop);
1157 								if (prop.value.length()) {
1158 									if (prop.name == "Name") {
1159 										curNode->name = prop.value;
1160 
1161 										/*  If we're either a camera or a light source
1162                                      *  we need to update the name in the aiLight/
1163                                      *  aiCamera structure, too.
1164                                      */
1165 										if (Node::CAMERA == curNode->type) {
1166 											cameras.back()->mName.Set(prop.value);
1167 										} else if (Node::LIGHT == curNode->type) {
1168 											lights.back()->mName.Set(prop.value);
1169 										}
1170 									} else if (Node::LIGHT == curNode->type && "LightType" == prop.name) {
1171 										if (prop.value == "Spot")
1172 											lights.back()->mType = aiLightSource_SPOT;
1173 										else if (prop.value == "Point")
1174 											lights.back()->mType = aiLightSource_POINT;
1175 										else if (prop.value == "Directional")
1176 											lights.back()->mType = aiLightSource_DIRECTIONAL;
1177 										else {
1178 											// We won't pass the validation with aiLightSourceType_UNDEFINED,
1179 											// so we remove the light and replace it with a silly dummy node
1180 											delete lights.back();
1181 											lights.pop_back();
1182 											curNode->type = Node::DUMMY;
1183 
1184 											ASSIMP_LOG_ERROR("Ignoring light of unknown type: ", prop.value);
1185 										}
1186 									} else if ((prop.name == "Mesh" && Node::MESH == curNode->type) ||
1187 											   Node::ANIMMESH == curNode->type) {
1188     								/*  This is the file name of the mesh - either
1189                                      *  animated or not. We need to make sure we setup
1190                                      *  the correct post-processing settings here.
1191                                      */
1192 										unsigned int pp = 0;
1193 										BatchLoader::PropertyMap map;
1194 
1195 										/* If the mesh is a static one remove all animations from the impor data
1196                                      */
1197 										if (Node::ANIMMESH != curNode->type) {
1198 											pp |= aiProcess_RemoveComponent;
1199 											SetGenericProperty<int>(map.ints, AI_CONFIG_PP_RVC_FLAGS,
1200 													aiComponent_ANIMATIONS | aiComponent_BONEWEIGHTS);
1201 										}
1202 
1203 										/*  TODO: maybe implement the protection against recursive
1204                                         *  loading calls directly in BatchLoader? The current
1205                                         *  implementation is not absolutely safe. A LWS and an IRR
1206                                         *  file referencing each other *could* cause the system to
1207                                         *  recurse forever.
1208                                         */
1209 
1210 										const std::string extension = GetExtension(prop.value);
1211 										if ("irr" == extension) {
1212 											ASSIMP_LOG_ERROR("IRR: Can't load another IRR file recursively");
1213 										} else {
1214 											curNode->id = batch.AddLoadRequest(prop.value, pp, &map);
1215 											curNode->meshPath = prop.value;
1216 										}
1217 									} else if (inAnimator && prop.name == "Type") {
1218 										// type of the animator
1219 										if (prop.value == "rotation") {
1220 											curAnim->type = Animator::ROTATION;
1221 										} else if (prop.value == "flyCircle") {
1222 											curAnim->type = Animator::FLY_CIRCLE;
1223 										} else if (prop.value == "flyStraight") {
1224 											curAnim->type = Animator::FLY_CIRCLE;
1225 										} else if (prop.value == "followSpline") {
1226 											curAnim->type = Animator::FOLLOW_SPLINE;
1227 										} else {
1228 											ASSIMP_LOG_WARN("IRR: Ignoring unknown animator: ", prop.value);
1229 
1230 											curAnim->type = Animator::UNKNOWN;
1231 										}
1232 									}
1233 								}
1234 							}
1235 							//} else if (reader->getNodeType() == EXN_ELEMENT_END && !ASSIMP_stricmp(reader->getNodeName(), "attributes")) {
1236 						} else if (attrib.type() == pugi::node_null && !ASSIMP_stricmp(attrib.name(), "attributes")) {
1237 							break;
1238 						}
1239 					}
1240 				}
1241 				break;
1242 
1243 				/*case EXN_ELEMENT_END:
1244 
1245 				// If we reached the end of a node, we need to continue processing its parent
1246 				if (!ASSIMP_stricmp(reader->getNodeName(), "node")) {
1247 					if (!curNode) {
1248 						// currently is no node set. We need to go
1249 						// back in the node hierarchy
1250 						if (!curParent) {
1251 							curParent = root;
1252 							ASSIMP_LOG_ERROR("IRR: Too many closing <node> elements");
1253 						} else
1254 							curParent = curParent->parent;
1255 					} else
1256 						curNode = nullptr;
1257 				}
1258 				// clear all flags
1259 				else if (!ASSIMP_stricmp(reader->getNodeName(), "materials")) {
1260 					inMaterials = false;
1261 				} else if (!ASSIMP_stricmp(reader->getNodeName(), "animators")) {
1262 					inAnimator = false;
1263 				}
1264 				break;*/
1265 
1266 			default:
1267 				// GCC complains that not all enumeration values are handled
1268 				break;
1269 		}
1270 	//}
1271 
1272 	//  Now iterate through all cameras and compute their final (horizontal) FOV
1273 	for (aiCamera *cam : cameras) {
1274 		// screen aspect could be missing
1275 		if (cam->mAspect) {
1276 			cam->mHorizontalFOV *= cam->mAspect;
1277 		} else {
1278 			ASSIMP_LOG_WARN("IRR: Camera aspect is not given, can't compute horizontal FOV");
1279 		}
1280 	}
1281 
1282 	batch.LoadAll();
1283 
1284 	// Allocate a temporary scene data structure
1285 	aiScene *tempScene = new aiScene();
1286 	tempScene->mRootNode = new aiNode();
1287 	tempScene->mRootNode->mName.Set("<IRRRoot>");
1288 
1289 	// Copy the cameras to the output array
1290 	if (!cameras.empty()) {
1291 		tempScene->mNumCameras = (unsigned int)cameras.size();
1292 		tempScene->mCameras = new aiCamera *[tempScene->mNumCameras];
1293 		::memcpy(tempScene->mCameras, &cameras[0], sizeof(void *) * tempScene->mNumCameras);
1294 	}
1295 
1296 	// Copy the light sources to the output array
1297 	if (!lights.empty()) {
1298 		tempScene->mNumLights = (unsigned int)lights.size();
1299 		tempScene->mLights = new aiLight *[tempScene->mNumLights];
1300 		::memcpy(tempScene->mLights, &lights[0], sizeof(void *) * tempScene->mNumLights);
1301 	}
1302 
1303 	// temporary data
1304 	std::vector<aiNodeAnim *> anims;
1305 	std::vector<aiMaterial *> materials;
1306 	std::vector<AttachmentInfo> attach;
1307 	std::vector<aiMesh *> meshes;
1308 
1309 	// try to guess how much storage we'll need
1310 	anims.reserve(guessedAnimCnt + (guessedAnimCnt >> 2));
1311 	meshes.reserve(guessedMeshCnt + (guessedMeshCnt >> 2));
1312 	materials.reserve(guessedMatCnt + (guessedMatCnt >> 2));
1313 
1314 	// Now process our scene-graph recursively: generate final
1315 	// meshes and generate animation channels for all nodes.
1316 	unsigned int defMatIdx = UINT_MAX;
1317 	GenerateGraph(root, tempScene->mRootNode, tempScene,
1318 			batch, meshes, anims, attach, materials, defMatIdx);
1319 
1320 	if (!anims.empty()) {
1321 		tempScene->mNumAnimations = 1;
1322 		tempScene->mAnimations = new aiAnimation *[tempScene->mNumAnimations];
1323 		aiAnimation *an = tempScene->mAnimations[0] = new aiAnimation();
1324 
1325 		// ***********************************************************
1326 		// This is only the global animation channel of the scene.
1327 		// If there are animated models, they will have separate
1328 		// animation channels in the scene. To display IRR scenes
1329 		// correctly, users will need to combine the global anim
1330 		// channel with all the local animations they want to play
1331 		// ***********************************************************
1332 		an->mName.Set("Irr_GlobalAnimChannel");
1333 
1334 		// copy all node animation channels to the global channel
1335 		an->mNumChannels = (unsigned int)anims.size();
1336 		an->mChannels = new aiNodeAnim *[an->mNumChannels];
1337 		::memcpy(an->mChannels, &anims[0], sizeof(void *) * an->mNumChannels);
1338 	}
1339 	if (!meshes.empty()) {
1340 		// copy all meshes to the temporary scene
1341 		tempScene->mNumMeshes = (unsigned int)meshes.size();
1342 		tempScene->mMeshes = new aiMesh *[tempScene->mNumMeshes];
1343 		::memcpy(tempScene->mMeshes, &meshes[0], tempScene->mNumMeshes * sizeof(void *));
1344 	}
1345 
1346 	// Copy all materials to the output array
1347 	if (!materials.empty()) {
1348 		tempScene->mNumMaterials = (unsigned int)materials.size();
1349 		tempScene->mMaterials = new aiMaterial *[tempScene->mNumMaterials];
1350 		::memcpy(tempScene->mMaterials, &materials[0], sizeof(void *) * tempScene->mNumMaterials);
1351 	}
1352 
1353 	//  Now merge all sub scenes and attach them to the correct
1354 	//  attachment points in the scenegraph.
1355 	SceneCombiner::MergeScenes(&pScene, tempScene, attach,
1356 			AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | (!configSpeedFlag ? (
1357 																			  AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) :
1358 																	  0));
1359 
1360 	// If we have no meshes | no materials now set the INCOMPLETE
1361 	// scene flag. This is necessary if we failed to load all
1362 	// models from external files
1363 	if (!pScene->mNumMeshes || !pScene->mNumMaterials) {
1364 		ASSIMP_LOG_WARN("IRR: No meshes loaded, setting AI_SCENE_FLAGS_INCOMPLETE");
1365 		pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
1366 	}
1367 
1368 	// Finished ... everything destructs automatically and all
1369 	// temporary scenes have already been deleted by MergeScenes()
1370 	delete root;
1371 }
1372 
1373 #endif // !! ASSIMP_BUILD_NO_IRR_IMPORTER
1374