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