1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2019, assimp team
7 
8 
9 
10 All rights reserved.
11 
12 Redistribution and use of this software in source and binary forms,
13 with or without modification, are permitted provided that the following
14 conditions are met:
15 
16 * Redistributions of source code must retain the above
17 copyright notice, this list of conditions and the
18 following disclaimer.
19 
20 * Redistributions in binary form must reproduce the above
21 copyright notice, this list of conditions and the
22 following disclaimer in the documentation and/or other
23 materials provided with the distribution.
24 
25 * Neither the name of the assimp team, nor the names of its
26 contributors may be used to endorse or promote products
27 derived from this software without specific prior
28 written permission of the assimp team.
29 
30 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 ---------------------------------------------------------------------------
42 */
43 
44 /** @file ColladaParser.cpp
45  *  @brief Implementation of the Collada parser helper
46  */
47 
48 #ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER
49 
50 #include <sstream>
51 #include <stdarg.h>
52 #include "ColladaParser.h"
53 #include <assimp/fast_atof.h>
54 #include <assimp/ParsingUtils.h>
55 #include <assimp/StringUtils.h>
56 #include <assimp/DefaultLogger.hpp>
57 #include <assimp/IOSystem.hpp>
58 #include <assimp/light.h>
59 #include <assimp/TinyFormatter.h>
60 #include <assimp/ZipArchiveIOSystem.h>
61 
62 #include <memory>
63 
64 using namespace Assimp;
65 using namespace Assimp::Collada;
66 using namespace Assimp::Formatter;
67 
68 // ------------------------------------------------------------------------------------------------
69 // Constructor to be privately used by Importer
ColladaParser(IOSystem * pIOHandler,const std::string & pFile)70 ColladaParser::ColladaParser(IOSystem* pIOHandler, const std::string& pFile)
71     : mFileName(pFile)
72     , mReader(nullptr)
73     , mDataLibrary()
74     , mAccessorLibrary()
75     , mMeshLibrary()
76     , mNodeLibrary()
77     , mImageLibrary()
78     , mEffectLibrary()
79     , mMaterialLibrary()
80     , mLightLibrary()
81     , mCameraLibrary()
82     , mControllerLibrary()
83     , mRootNode(nullptr)
84     , mAnims()
85     , mUnitSize(1.0f)
86     , mUpDirection(UP_Y)
87     , mFormat(FV_1_5_n)    // We assume the newest file format by default
88 {
89     // validate io-handler instance
90     if (nullptr == pIOHandler) {
91         throw DeadlyImportError("IOSystem is NULL.");
92     }
93 
94     std::unique_ptr<IOStream> daefile;
95     std::unique_ptr<ZipArchiveIOSystem> zip_archive;
96 
97     // Determine type
98     std::string extension = BaseImporter::GetExtension(pFile);
99     if (extension != "dae") {
100         zip_archive.reset(new ZipArchiveIOSystem(pIOHandler, pFile));
101     }
102 
103     if (zip_archive && zip_archive->isOpen()) {
104         std::string dae_filename = ReadZaeManifest(*zip_archive);
105 
106         if (dae_filename.empty()) {
107             ThrowException(std::string("Invalid ZAE"));
108         }
109 
110         daefile.reset(zip_archive->Open(dae_filename.c_str()));
111         if (daefile == nullptr) {
112             ThrowException(std::string("Invalid ZAE manifest: '") + std::string(dae_filename) + std::string("' is missing"));
113         }
114     }
115     else {
116         // attempt to open the file directly
117         daefile.reset(pIOHandler->Open(pFile));
118         if (daefile.get() == nullptr) {
119             throw DeadlyImportError("Failed to open file '" + pFile + "'.");
120         }
121     }
122 
123     // generate a XML reader for it
124     std::unique_ptr<CIrrXML_IOStreamReader> mIOWrapper(new CIrrXML_IOStreamReader(daefile.get()));
125     mReader = irr::io::createIrrXMLReader(mIOWrapper.get());
126     if (!mReader) {
127         ThrowException("Unable to read file, malformed XML");
128     }
129 
130     // start reading
131     ReadContents();
132 
133     // read embedded textures
134     if (zip_archive && zip_archive->isOpen()) {
135         ReadEmbeddedTextures(*zip_archive);
136     }
137 }
138 
139 // ------------------------------------------------------------------------------------------------
140 // Destructor, private as well
~ColladaParser()141 ColladaParser::~ColladaParser()
142 {
143     delete mReader;
144     for (NodeLibrary::iterator it = mNodeLibrary.begin(); it != mNodeLibrary.end(); ++it)
145         delete it->second;
146     for (MeshLibrary::iterator it = mMeshLibrary.begin(); it != mMeshLibrary.end(); ++it)
147         delete it->second;
148 }
149 
150 // ------------------------------------------------------------------------------------------------
151 // Read a ZAE manifest and return the filename to attempt to open
ReadZaeManifest(ZipArchiveIOSystem & zip_archive)152 std::string ColladaParser::ReadZaeManifest(ZipArchiveIOSystem &zip_archive) {
153     // Open the manifest
154     std::unique_ptr<IOStream> manifestfile(zip_archive.Open("manifest.xml"));
155     if (manifestfile == nullptr)
156     {
157         // No manifest, hope there is only one .DAE inside
158         std::vector<std::string> file_list;
159         zip_archive.getFileListExtension(file_list, "dae");
160 
161         if (file_list.empty())
162             return std::string();
163 
164         return file_list.front();
165     }
166 
167     std::unique_ptr<CIrrXML_IOStreamReader> mIOWrapper(new CIrrXML_IOStreamReader(manifestfile.get()));
168     std::unique_ptr<irr::io::IrrXMLReader> manifest_reader(irr::io::createIrrXMLReader(mIOWrapper.get()));
169 
170     while (manifest_reader->read())
171     {
172         // find the manifest "dae_root" element
173         if (manifest_reader->getNodeType() == irr::io::EXN_ELEMENT)
174         {
175             if (::strcmp(manifest_reader->getNodeName(), "dae_root") == 0)
176             {
177                 if (!manifest_reader->read())
178                     return std::string();
179                 if (manifest_reader->getNodeType() != irr::io::EXN_TEXT && manifest_reader->getNodeType() != irr::io::EXN_CDATA)
180                     return std::string();
181 
182                 const char* filepath = manifest_reader->getNodeData();
183                 if (filepath == nullptr)
184                     return std::string();
185 
186                 aiString ai_str(filepath);
187                 UriDecodePath(ai_str);
188 
189                 return std::string(ai_str.C_Str());
190             }
191         }
192     }
193     return std::string();
194 }
195 
196 // ------------------------------------------------------------------------------------------------
197 // Convert a path read from a collada file to the usual representation
UriDecodePath(aiString & ss)198 void ColladaParser::UriDecodePath(aiString& ss)
199 {
200     // TODO: collada spec, p 22. Handle URI correctly.
201     // For the moment we're just stripping the file:// away to make it work.
202     // Windows doesn't seem to be able to find stuff like
203     // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg'
204     if (0 == strncmp(ss.data, "file://", 7))
205     {
206         ss.length -= 7;
207         memmove(ss.data, ss.data + 7, ss.length);
208         ss.data[ss.length] = '\0';
209     }
210 
211     // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes...
212     // I need to filter it without destroying linux paths starting with "/somewhere"
213 #if defined( _MSC_VER )
214     if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') {
215 #else
216     if (ss.data[0] == '/' && isalpha(ss.data[1]) && ss.data[2] == ':') {
217 #endif
218         --ss.length;
219         ::memmove(ss.data, ss.data + 1, ss.length);
220         ss.data[ss.length] = 0;
221     }
222 
223     // find and convert all %xy special chars
224     char* out = ss.data;
225     for (const char* it = ss.data; it != ss.data + ss.length; /**/)
226     {
227         if (*it == '%' && (it + 3) < ss.data + ss.length)
228         {
229             // separate the number to avoid dragging in chars from behind into the parsing
230             char mychar[3] = { it[1], it[2], 0 };
231             size_t nbr = strtoul16(mychar);
232             it += 3;
233             *out++ = (char)(nbr & 0xFF);
234         }
235         else
236         {
237             *out++ = *it++;
238         }
239     }
240 
241     // adjust length and terminator of the shortened string
242     *out = 0;
243     ai_assert(out > ss.data);
244     ss.length = static_cast<ai_uint32>(out - ss.data);
245 }
246 
247 // ------------------------------------------------------------------------------------------------
248 // Read bool from text contents of current element
249 bool ColladaParser::ReadBoolFromTextContent()
250 {
251     const char* cur = GetTextContent();
252     return (!ASSIMP_strincmp(cur, "true", 4) || '0' != *cur);
253 }
254 
255 // ------------------------------------------------------------------------------------------------
256 // Read float from text contents of current element
257 ai_real ColladaParser::ReadFloatFromTextContent()
258 {
259     const char* cur = GetTextContent();
260     return fast_atof(cur);
261 }
262 
263 // ------------------------------------------------------------------------------------------------
264 // Reads the contents of the file
265 void ColladaParser::ReadContents()
266 {
267     while (mReader->read())
268     {
269         // handle the root element "COLLADA"
270         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
271         {
272             if (IsElement("COLLADA"))
273             {
274                 // check for 'version' attribute
275                 const int attrib = TestAttribute("version");
276                 if (attrib != -1) {
277                     const char* version = mReader->getAttributeValue(attrib);
278 
279                     if (!::strncmp(version, "1.5", 3)) {
280                         mFormat = FV_1_5_n;
281                         ASSIMP_LOG_DEBUG("Collada schema version is 1.5.n");
282                     }
283                     else if (!::strncmp(version, "1.4", 3)) {
284                         mFormat = FV_1_4_n;
285                         ASSIMP_LOG_DEBUG("Collada schema version is 1.4.n");
286                     }
287                     else if (!::strncmp(version, "1.3", 3)) {
288                         mFormat = FV_1_3_n;
289                         ASSIMP_LOG_DEBUG("Collada schema version is 1.3.n");
290                     }
291                 }
292 
293                 ReadStructure();
294             }
295             else
296             {
297                 ASSIMP_LOG_DEBUG_F("Ignoring global element <", mReader->getNodeName(), ">.");
298                 SkipElement();
299             }
300         }
301         else
302         {
303             // skip everything else silently
304         }
305     }
306 }
307 
308 // ------------------------------------------------------------------------------------------------
309 // Reads the structure of the file
310 void ColladaParser::ReadStructure()
311 {
312     while (mReader->read())
313     {
314         // beginning of elements
315         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
316         {
317             if (IsElement("asset"))
318                 ReadAssetInfo();
319             else if (IsElement("library_animations"))
320                 ReadAnimationLibrary();
321             else if (IsElement("library_animation_clips"))
322                 ReadAnimationClipLibrary();
323             else if (IsElement("library_controllers"))
324                 ReadControllerLibrary();
325             else if (IsElement("library_images"))
326                 ReadImageLibrary();
327             else if (IsElement("library_materials"))
328                 ReadMaterialLibrary();
329             else if (IsElement("library_effects"))
330                 ReadEffectLibrary();
331             else if (IsElement("library_geometries"))
332                 ReadGeometryLibrary();
333             else if (IsElement("library_visual_scenes"))
334                 ReadSceneLibrary();
335             else if (IsElement("library_lights"))
336                 ReadLightLibrary();
337             else if (IsElement("library_cameras"))
338                 ReadCameraLibrary();
339             else if (IsElement("library_nodes"))
340                 ReadSceneNode(NULL); /* some hacking to reuse this piece of code */
341             else if (IsElement("scene"))
342                 ReadScene();
343             else
344                 SkipElement();
345         }
346         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
347         {
348             break;
349         }
350     }
351 
352     PostProcessRootAnimations();
353     PostProcessControllers();
354 }
355 
356 // ------------------------------------------------------------------------------------------------
357 // Reads asset information such as coordinate system information and legal blah
358 void ColladaParser::ReadAssetInfo()
359 {
360     if (mReader->isEmptyElement())
361         return;
362 
363     while (mReader->read())
364     {
365         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
366         {
367             if (IsElement("unit"))
368             {
369                 // read unit data from the element's attributes
370                 const int attrIndex = TestAttribute("meter");
371                 if (attrIndex == -1) {
372                     mUnitSize = 1.f;
373                 }
374                 else {
375                     mUnitSize = mReader->getAttributeValueAsFloat(attrIndex);
376                 }
377 
378                 // consume the trailing stuff
379                 if (!mReader->isEmptyElement())
380                     SkipElement();
381             }
382             else if (IsElement("up_axis"))
383             {
384                 // read content, strip whitespace, compare
385                 const char* content = GetTextContent();
386                 if (strncmp(content, "X_UP", 4) == 0)
387                     mUpDirection = UP_X;
388                 else if (strncmp(content, "Z_UP", 4) == 0)
389                     mUpDirection = UP_Z;
390                 else
391                     mUpDirection = UP_Y;
392 
393                 // check element end
394                 TestClosing("up_axis");
395             }
396             else if (IsElement("contributor"))
397             {
398                 ReadContributorInfo();
399             }
400             else
401             {
402                 ReadMetaDataItem(mAssetMetaData);
403             }
404         }
405         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
406         {
407             if (strcmp(mReader->getNodeName(), "asset") != 0)
408                 ThrowException("Expected end of <asset> element.");
409 
410             break;
411         }
412     }
413 }
414 
415 // ------------------------------------------------------------------------------------------------
416 // Reads the contributor info
417 void ColladaParser::ReadContributorInfo()
418 {
419     if (mReader->isEmptyElement())
420         return;
421 
422     while (mReader->read())
423     {
424         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
425         {
426             ReadMetaDataItem(mAssetMetaData);
427         }
428         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
429         {
430             if (strcmp(mReader->getNodeName(), "contributor") != 0)
431                 ThrowException("Expected end of <contributor> element.");
432             break;
433         }
434     }
435 }
436 
437 // ------------------------------------------------------------------------------------------------
438 // Reads a single string metadata item
439 void ColladaParser::ReadMetaDataItem(StringMetaData &metadata)
440 {
441     // Metadata such as created, keywords, subject etc
442     const char* key_char = mReader->getNodeName();
443     if (key_char != nullptr)
444     {
445         const std::string key_str(key_char);
446         const char* value_char = TestTextContent();
447         if (value_char != nullptr)
448         {
449             std::string camel_key_str = key_str;
450             ToCamelCase(camel_key_str);
451             aiString aistr;
452             aistr.Set(value_char);
453             metadata.emplace(camel_key_str, aistr);
454         }
455         TestClosing(key_str.c_str());
456     }
457     else
458         SkipElement();
459 }
460 
461 // ------------------------------------------------------------------------------------------------
462 // Convert underscore_seperated to CamelCase: "authoring_tool" becomes "AuthoringTool"
463 void ColladaParser::ToCamelCase(std::string &text)
464 {
465     if (text.empty())
466         return;
467     // Capitalise first character
468     text[0] = ToUpper(text[0]);
469     for (auto it = text.begin(); it != text.end(); /*iterated below*/)
470     {
471         if ((*it) == '_')
472         {
473             it = text.erase(it);
474             if (it != text.end())
475                 (*it) = ToUpper(*it);
476         }
477         else
478             ++it;
479     }
480 }
481 
482 // ------------------------------------------------------------------------------------------------
483 // Reads the animation clips
484 void ColladaParser::ReadAnimationClipLibrary()
485 {
486     if (mReader->isEmptyElement())
487         return;
488 
489     while (mReader->read())
490     {
491         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
492         {
493             if (IsElement("animation_clip"))
494             {
495                 // optional name given as an attribute
496                 std::string animName;
497                 int indexName = TestAttribute("name");
498                 int indexID = TestAttribute("id");
499                 if (indexName >= 0)
500                     animName = mReader->getAttributeValue(indexName);
501                 else if (indexID >= 0)
502                     animName = mReader->getAttributeValue(indexID);
503                 else
504                     animName = std::string("animation_") + to_string(mAnimationClipLibrary.size());
505 
506                 std::pair<std::string, std::vector<std::string> > clip;
507 
508                 clip.first = animName;
509 
510                 while (mReader->read())
511                 {
512                     if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
513                     {
514                         if (IsElement("instance_animation"))
515                         {
516                             int indexUrl = TestAttribute("url");
517                             if (indexUrl >= 0)
518                             {
519                                 const char* url = mReader->getAttributeValue(indexUrl);
520                                 if (url[0] != '#')
521                                     ThrowException("Unknown reference format");
522 
523                                 url++;
524 
525                                 clip.second.push_back(url);
526                             }
527                         }
528                         else
529                         {
530                             // ignore the rest
531                             SkipElement();
532                         }
533                     }
534                     else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
535                     {
536                         if (strcmp(mReader->getNodeName(), "animation_clip") != 0)
537                             ThrowException("Expected end of <animation_clip> element.");
538 
539                         break;
540                     }
541                 }
542 
543                 if (clip.second.size() > 0)
544                 {
545                     mAnimationClipLibrary.push_back(clip);
546                 }
547             }
548             else
549             {
550                 // ignore the rest
551                 SkipElement();
552             }
553         }
554         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
555         {
556             if (strcmp(mReader->getNodeName(), "library_animation_clips") != 0)
557                 ThrowException("Expected end of <library_animation_clips> element.");
558 
559             break;
560         }
561     }
562 }
563 
564 void ColladaParser::PostProcessControllers()
565 {
566     std::string meshId;
567     for (ControllerLibrary::iterator it = mControllerLibrary.begin(); it != mControllerLibrary.end(); ++it) {
568         meshId = it->second.mMeshId;
569         ControllerLibrary::iterator findItr = mControllerLibrary.find(meshId);
570         while (findItr != mControllerLibrary.end()) {
571             meshId = findItr->second.mMeshId;
572             findItr = mControllerLibrary.find(meshId);
573         }
574 
575         it->second.mMeshId = meshId;
576     }
577 }
578 
579 // ------------------------------------------------------------------------------------------------
580 // Re-build animations from animation clip library, if present, otherwise combine single-channel animations
581 void ColladaParser::PostProcessRootAnimations()
582 {
583     if (mAnimationClipLibrary.size() > 0)
584     {
585         Animation temp;
586 
587         for (AnimationClipLibrary::iterator it = mAnimationClipLibrary.begin(); it != mAnimationClipLibrary.end(); ++it)
588         {
589             std::string clipName = it->first;
590 
591             Animation *clip = new Animation();
592             clip->mName = clipName;
593 
594             temp.mSubAnims.push_back(clip);
595 
596             for (std::vector<std::string>::iterator a = it->second.begin(); a != it->second.end(); ++a)
597             {
598                 std::string animationID = *a;
599 
600                 AnimationLibrary::iterator animation = mAnimationLibrary.find(animationID);
601 
602                 if (animation != mAnimationLibrary.end())
603                 {
604                     Animation *pSourceAnimation = animation->second;
605 
606                     pSourceAnimation->CollectChannelsRecursively(clip->mChannels);
607                 }
608             }
609         }
610 
611         mAnims = temp;
612 
613         // Ensure no double deletes.
614         temp.mSubAnims.clear();
615     }
616     else
617     {
618         mAnims.CombineSingleChannelAnimations();
619     }
620 }
621 
622 // ------------------------------------------------------------------------------------------------
623 // Reads the animation library
624 void ColladaParser::ReadAnimationLibrary()
625 {
626     if (mReader->isEmptyElement())
627         return;
628 
629     while (mReader->read())
630     {
631         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
632         {
633             if (IsElement("animation"))
634             {
635                 // delegate the reading. Depending on the inner elements it will be a container or a anim channel
636                 ReadAnimation(&mAnims);
637             }
638             else
639             {
640                 // ignore the rest
641                 SkipElement();
642             }
643         }
644         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
645         {
646             if (strcmp(mReader->getNodeName(), "library_animations") != 0)
647                 ThrowException("Expected end of <library_animations> element.");
648 
649             break;
650         }
651     }
652 }
653 
654 // ------------------------------------------------------------------------------------------------
655 // Reads an animation into the given parent structure
656 void ColladaParser::ReadAnimation(Collada::Animation* pParent)
657 {
658     if (mReader->isEmptyElement())
659         return;
660 
661     // an <animation> element may be a container for grouping sub-elements or an animation channel
662     // this is the channel collection by ID, in case it has channels
663     typedef std::map<std::string, AnimationChannel> ChannelMap;
664     ChannelMap channels;
665     // this is the anim container in case we're a container
666     Animation* anim = NULL;
667 
668     // optional name given as an attribute
669     std::string animName;
670     std::string animID;
671     int indexName = TestAttribute("name");
672     int indexID = TestAttribute("id");
673 
674     if (indexID >= 0)
675         animID = mReader->getAttributeValue(indexID);
676 
677     if (indexName >= 0)
678         animName = mReader->getAttributeValue(indexName);
679     else if (indexID >= 0)
680         animName = animID;
681     else
682         animName = "animation";
683 
684     while (mReader->read())
685     {
686         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
687         {
688             // we have subanimations
689             if (IsElement("animation"))
690             {
691                 // create container from our element
692                 if (!anim)
693                 {
694                     anim = new Animation;
695                     anim->mName = animName;
696                     pParent->mSubAnims.push_back(anim);
697                 }
698 
699                 // recurse into the subelement
700                 ReadAnimation(anim);
701             }
702             else if (IsElement("source"))
703             {
704                 // possible animation data - we'll never know. Better store it
705                 ReadSource();
706             }
707             else if (IsElement("sampler"))
708             {
709                 // read the ID to assign the corresponding collada channel afterwards.
710                 int indexID = GetAttribute("id");
711                 std::string id = mReader->getAttributeValue(indexID);
712                 ChannelMap::iterator newChannel = channels.insert(std::make_pair(id, AnimationChannel())).first;
713 
714                 // have it read into a channel
715                 ReadAnimationSampler(newChannel->second);
716             }
717             else if (IsElement("channel"))
718             {
719                 // the binding element whose whole purpose is to provide the target to animate
720                 // Thanks, Collada! A directly posted information would have been too simple, I guess.
721                 // Better add another indirection to that! Can't have enough of those.
722                 int indexTarget = GetAttribute("target");
723                 int indexSource = GetAttribute("source");
724                 const char* sourceId = mReader->getAttributeValue(indexSource);
725                 if (sourceId[0] == '#')
726                     sourceId++;
727                 ChannelMap::iterator cit = channels.find(sourceId);
728                 if (cit != channels.end())
729                     cit->second.mTarget = mReader->getAttributeValue(indexTarget);
730 
731                 if (!mReader->isEmptyElement())
732                     SkipElement();
733             }
734             else
735             {
736                 // ignore the rest
737                 SkipElement();
738             }
739         }
740         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
741         {
742             if (strcmp(mReader->getNodeName(), "animation") != 0)
743                 ThrowException("Expected end of <animation> element.");
744 
745             break;
746         }
747     }
748 
749     // it turned out to have channels - add them
750     if (!channels.empty())
751     {
752         // FIXME: Is this essentially doing the same as "single-anim-node" codepath in
753         //        ColladaLoader::StoreAnimations? For now, this has been deferred to after
754         //        all animations and all clips have been read. Due to handling of
755         //        <library_animation_clips> this cannot be done here, as the channel owner
756         //        is lost, and some exporters make up animations by referring to multiple
757         //        single-channel animations from an <instance_animation>.
758 /*
759         // special filtering for stupid exporters packing each channel into a separate animation
760         if( channels.size() == 1)
761         {
762             pParent->mChannels.push_back( channels.begin()->second);
763         } else
764 */
765         {
766             // else create the animation, if not done yet, and store the channels
767             if (!anim)
768             {
769                 anim = new Animation;
770                 anim->mName = animName;
771                 pParent->mSubAnims.push_back(anim);
772             }
773             for (ChannelMap::const_iterator it = channels.begin(); it != channels.end(); ++it)
774                 anim->mChannels.push_back(it->second);
775 
776             if (indexID >= 0)
777             {
778                 mAnimationLibrary[animID] = anim;
779             }
780         }
781     }
782 }
783 
784 // ------------------------------------------------------------------------------------------------
785 // Reads an animation sampler into the given anim channel
786 void ColladaParser::ReadAnimationSampler(Collada::AnimationChannel& pChannel)
787 {
788     while (mReader->read())
789     {
790         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
791         {
792             if (IsElement("input"))
793             {
794                 int indexSemantic = GetAttribute("semantic");
795                 const char* semantic = mReader->getAttributeValue(indexSemantic);
796                 int indexSource = GetAttribute("source");
797                 const char* source = mReader->getAttributeValue(indexSource);
798                 if (source[0] != '#')
799                     ThrowException("Unsupported URL format");
800                 source++;
801 
802                 if (strcmp(semantic, "INPUT") == 0)
803                     pChannel.mSourceTimes = source;
804                 else if (strcmp(semantic, "OUTPUT") == 0)
805                     pChannel.mSourceValues = source;
806                 else if (strcmp(semantic, "IN_TANGENT") == 0)
807                     pChannel.mInTanValues = source;
808                 else if (strcmp(semantic, "OUT_TANGENT") == 0)
809                     pChannel.mOutTanValues = source;
810                 else if (strcmp(semantic, "INTERPOLATION") == 0)
811                     pChannel.mInterpolationValues = source;
812 
813                 if (!mReader->isEmptyElement())
814                     SkipElement();
815             }
816             else
817             {
818                 // ignore the rest
819                 SkipElement();
820             }
821         }
822         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
823         {
824             if (strcmp(mReader->getNodeName(), "sampler") != 0)
825                 ThrowException("Expected end of <sampler> element.");
826 
827             break;
828         }
829     }
830 }
831 
832 // ------------------------------------------------------------------------------------------------
833 // Reads the skeleton controller library
834 void ColladaParser::ReadControllerLibrary()
835 {
836     if (mReader->isEmptyElement())
837         return;
838 
839     while (mReader->read())
840     {
841         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
842         {
843             if (IsElement("controller"))
844             {
845                 // read ID. Ask the spec if it's necessary or optional... you might be surprised.
846                 int attrID = GetAttribute("id");
847                 std::string id = mReader->getAttributeValue(attrID);
848 
849                 // create an entry and store it in the library under its ID
850                 mControllerLibrary[id] = Controller();
851 
852                 // read on from there
853                 ReadController(mControllerLibrary[id]);
854             }
855             else
856             {
857                 // ignore the rest
858                 SkipElement();
859             }
860         }
861         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
862         {
863             if (strcmp(mReader->getNodeName(), "library_controllers") != 0)
864                 ThrowException("Expected end of <library_controllers> element.");
865 
866             break;
867         }
868     }
869 }
870 
871 // ------------------------------------------------------------------------------------------------
872 // Reads a controller into the given mesh structure
873 void ColladaParser::ReadController(Collada::Controller& pController)
874 {
875     // initial values
876     pController.mType = Skin;
877     pController.mMethod = Normalized;
878     while (mReader->read())
879     {
880         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
881         {
882             // two types of controllers: "skin" and "morph". Only the first one is relevant, we skip the other
883             if (IsElement("morph"))
884             {
885                 pController.mType = Morph;
886                 int baseIndex = GetAttribute("source");
887                 pController.mMeshId = mReader->getAttributeValue(baseIndex) + 1;
888                 int methodIndex = GetAttribute("method");
889                 if (methodIndex > 0) {
890                     const char *method = mReader->getAttributeValue(methodIndex);
891                     if (strcmp(method, "RELATIVE") == 0)
892                         pController.mMethod = Relative;
893                 }
894             }
895             else if (IsElement("skin"))
896             {
897                 // read the mesh it refers to. According to the spec this could also be another
898                 // controller, but I refuse to implement every single idea they've come up with
899                 int sourceIndex = GetAttribute("source");
900                 pController.mMeshId = mReader->getAttributeValue(sourceIndex) + 1;
901             }
902             else if (IsElement("bind_shape_matrix"))
903             {
904                 // content is 16 floats to define a matrix... it seems to be important for some models
905                 const char* content = GetTextContent();
906 
907                 // read the 16 floats
908                 for (unsigned int a = 0; a < 16; a++)
909                 {
910                     // read a number
911                     content = fast_atoreal_move<ai_real>(content, pController.mBindShapeMatrix[a]);
912                     // skip whitespace after it
913                     SkipSpacesAndLineEnd(&content);
914                 }
915 
916                 TestClosing("bind_shape_matrix");
917             }
918             else if (IsElement("source"))
919             {
920                 // data array - we have specialists to handle this
921                 ReadSource();
922             }
923             else if (IsElement("joints"))
924             {
925                 ReadControllerJoints(pController);
926             }
927             else if (IsElement("vertex_weights"))
928             {
929                 ReadControllerWeights(pController);
930             }
931             else if (IsElement("targets"))
932             {
933                 while (mReader->read()) {
934                     if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
935                         if (IsElement("input")) {
936                             int semanticsIndex = GetAttribute("semantic");
937                             int sourceIndex = GetAttribute("source");
938 
939                             const char *semantics = mReader->getAttributeValue(semanticsIndex);
940                             const char *source = mReader->getAttributeValue(sourceIndex);
941                             if (strcmp(semantics, "MORPH_TARGET") == 0) {
942                                 pController.mMorphTarget = source + 1;
943                             }
944                             else if (strcmp(semantics, "MORPH_WEIGHT") == 0)
945                             {
946                                 pController.mMorphWeight = source + 1;
947                             }
948                         }
949                     }
950                     else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
951                         if (strcmp(mReader->getNodeName(), "targets") == 0)
952                             break;
953                         else
954                             ThrowException("Expected end of <targets> element.");
955                     }
956                 }
957             }
958             else
959             {
960                 // ignore the rest
961                 SkipElement();
962             }
963         }
964         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
965         {
966             if (strcmp(mReader->getNodeName(), "controller") == 0)
967                 break;
968             else if (strcmp(mReader->getNodeName(), "skin") != 0 && strcmp(mReader->getNodeName(), "morph") != 0)
969                 ThrowException("Expected end of <controller> element.");
970         }
971     }
972 }
973 
974 // ------------------------------------------------------------------------------------------------
975 // Reads the joint definitions for the given controller
976 void ColladaParser::ReadControllerJoints(Collada::Controller& pController)
977 {
978     while (mReader->read())
979     {
980         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
981         {
982             // Input channels for joint data. Two possible semantics: "JOINT" and "INV_BIND_MATRIX"
983             if (IsElement("input"))
984             {
985                 int indexSemantic = GetAttribute("semantic");
986                 const char* attrSemantic = mReader->getAttributeValue(indexSemantic);
987                 int indexSource = GetAttribute("source");
988                 const char* attrSource = mReader->getAttributeValue(indexSource);
989 
990                 // local URLS always start with a '#'. We don't support global URLs
991                 if (attrSource[0] != '#')
992                     ThrowException(format() << "Unsupported URL format in \"" << attrSource << "\" in source attribute of <joints> data <input> element");
993                 attrSource++;
994 
995                 // parse source URL to corresponding source
996                 if (strcmp(attrSemantic, "JOINT") == 0)
997                     pController.mJointNameSource = attrSource;
998                 else if (strcmp(attrSemantic, "INV_BIND_MATRIX") == 0)
999                     pController.mJointOffsetMatrixSource = attrSource;
1000                 else
1001                     ThrowException(format() << "Unknown semantic \"" << attrSemantic << "\" in <joints> data <input> element");
1002 
1003                 // skip inner data, if present
1004                 if (!mReader->isEmptyElement())
1005                     SkipElement();
1006             }
1007             else
1008             {
1009                 // ignore the rest
1010                 SkipElement();
1011             }
1012         }
1013         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
1014         {
1015             if (strcmp(mReader->getNodeName(), "joints") != 0)
1016                 ThrowException("Expected end of <joints> element.");
1017 
1018             break;
1019         }
1020     }
1021 }
1022 
1023 // ------------------------------------------------------------------------------------------------
1024 // Reads the joint weights for the given controller
1025 void ColladaParser::ReadControllerWeights(Collada::Controller& pController)
1026 {
1027     // read vertex count from attributes and resize the array accordingly
1028     int indexCount = GetAttribute("count");
1029     size_t vertexCount = (size_t)mReader->getAttributeValueAsInt(indexCount);
1030     pController.mWeightCounts.resize(vertexCount);
1031 
1032     while (mReader->read())
1033     {
1034         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
1035         {
1036             // Input channels for weight data. Two possible semantics: "JOINT" and "WEIGHT"
1037             if (IsElement("input") && vertexCount > 0)
1038             {
1039                 InputChannel channel;
1040 
1041                 int indexSemantic = GetAttribute("semantic");
1042                 const char* attrSemantic = mReader->getAttributeValue(indexSemantic);
1043                 int indexSource = GetAttribute("source");
1044                 const char* attrSource = mReader->getAttributeValue(indexSource);
1045                 int indexOffset = TestAttribute("offset");
1046                 if (indexOffset >= 0)
1047                     channel.mOffset = mReader->getAttributeValueAsInt(indexOffset);
1048 
1049                 // local URLS always start with a '#'. We don't support global URLs
1050                 if (attrSource[0] != '#')
1051                     ThrowException(format() << "Unsupported URL format in \"" << attrSource << "\" in source attribute of <vertex_weights> data <input> element");
1052                 channel.mAccessor = attrSource + 1;
1053 
1054                 // parse source URL to corresponding source
1055                 if (strcmp(attrSemantic, "JOINT") == 0)
1056                     pController.mWeightInputJoints = channel;
1057                 else if (strcmp(attrSemantic, "WEIGHT") == 0)
1058                     pController.mWeightInputWeights = channel;
1059                 else
1060                     ThrowException(format() << "Unknown semantic \"" << attrSemantic << "\" in <vertex_weights> data <input> element");
1061 
1062                 // skip inner data, if present
1063                 if (!mReader->isEmptyElement())
1064                     SkipElement();
1065             }
1066             else if (IsElement("vcount") && vertexCount > 0)
1067             {
1068                 // read weight count per vertex
1069                 const char* text = GetTextContent();
1070                 size_t numWeights = 0;
1071                 for (std::vector<size_t>::iterator it = pController.mWeightCounts.begin(); it != pController.mWeightCounts.end(); ++it)
1072                 {
1073                     if (*text == 0)
1074                         ThrowException("Out of data while reading <vcount>");
1075 
1076                     *it = strtoul10(text, &text);
1077                     numWeights += *it;
1078                     SkipSpacesAndLineEnd(&text);
1079                 }
1080 
1081                 TestClosing("vcount");
1082 
1083                 // reserve weight count
1084                 pController.mWeights.resize(numWeights);
1085             }
1086             else if (IsElement("v") && vertexCount > 0)
1087             {
1088                 // read JointIndex - WeightIndex pairs
1089                 const char* text = GetTextContent();
1090 
1091                 for (std::vector< std::pair<size_t, size_t> >::iterator it = pController.mWeights.begin(); it != pController.mWeights.end(); ++it)
1092                 {
1093                     if (*text == 0)
1094                         ThrowException("Out of data while reading <vertex_weights>");
1095                     it->first = strtoul10(text, &text);
1096                     SkipSpacesAndLineEnd(&text);
1097                     if (*text == 0)
1098                         ThrowException("Out of data while reading <vertex_weights>");
1099                     it->second = strtoul10(text, &text);
1100                     SkipSpacesAndLineEnd(&text);
1101                 }
1102 
1103                 TestClosing("v");
1104             }
1105             else
1106             {
1107                 // ignore the rest
1108                 SkipElement();
1109             }
1110         }
1111         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
1112         {
1113             if (strcmp(mReader->getNodeName(), "vertex_weights") != 0)
1114                 ThrowException("Expected end of <vertex_weights> element.");
1115 
1116             break;
1117         }
1118     }
1119 }
1120 
1121 // ------------------------------------------------------------------------------------------------
1122 // Reads the image library contents
1123 void ColladaParser::ReadImageLibrary()
1124 {
1125     if (mReader->isEmptyElement())
1126         return;
1127 
1128     while (mReader->read())
1129     {
1130         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1131             if (IsElement("image"))
1132             {
1133                 // read ID. Another entry which is "optional" by design but obligatory in reality
1134                 int attrID = GetAttribute("id");
1135                 std::string id = mReader->getAttributeValue(attrID);
1136 
1137                 // create an entry and store it in the library under its ID
1138                 mImageLibrary[id] = Image();
1139 
1140                 // read on from there
1141                 ReadImage(mImageLibrary[id]);
1142             }
1143             else
1144             {
1145                 // ignore the rest
1146                 SkipElement();
1147             }
1148         }
1149         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1150             if (strcmp(mReader->getNodeName(), "library_images") != 0)
1151                 ThrowException("Expected end of <library_images> element.");
1152 
1153             break;
1154         }
1155     }
1156 }
1157 
1158 // ------------------------------------------------------------------------------------------------
1159 // Reads an image entry into the given image
1160 void ColladaParser::ReadImage(Collada::Image& pImage)
1161 {
1162     while (mReader->read())
1163     {
1164         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1165             // Need to run different code paths here, depending on the Collada XSD version
1166             if (IsElement("image")) {
1167                 SkipElement();
1168             }
1169             else if (IsElement("init_from"))
1170             {
1171                 if (mFormat == FV_1_4_n)
1172                 {
1173                     // FIX: C4D exporter writes empty <init_from/> tags
1174                     if (!mReader->isEmptyElement()) {
1175                         // element content is filename - hopefully
1176                         const char* sz = TestTextContent();
1177                         if (sz)
1178                         {
1179                             aiString filepath(sz);
1180                             UriDecodePath(filepath);
1181                             pImage.mFileName = filepath.C_Str();
1182                         }
1183                         TestClosing("init_from");
1184                     }
1185                     if (!pImage.mFileName.length()) {
1186                         pImage.mFileName = "unknown_texture";
1187                     }
1188                 }
1189                 else if (mFormat == FV_1_5_n)
1190                 {
1191                     // make sure we skip over mip and array initializations, which
1192                     // we don't support, but which could confuse the loader if
1193                     // they're not skipped.
1194                     int attrib = TestAttribute("array_index");
1195                     if (attrib != -1 && mReader->getAttributeValueAsInt(attrib) > 0) {
1196                         ASSIMP_LOG_WARN("Collada: Ignoring texture array index");
1197                         continue;
1198                     }
1199 
1200                     attrib = TestAttribute("mip_index");
1201                     if (attrib != -1 && mReader->getAttributeValueAsInt(attrib) > 0) {
1202                         ASSIMP_LOG_WARN("Collada: Ignoring MIP map layer");
1203                         continue;
1204                     }
1205 
1206                     // TODO: correctly jump over cube and volume maps?
1207                 }
1208             }
1209             else if (mFormat == FV_1_5_n)
1210             {
1211                 if (IsElement("ref"))
1212                 {
1213                     // element content is filename - hopefully
1214                     const char* sz = TestTextContent();
1215                     if (sz)
1216                     {
1217                         aiString filepath(sz);
1218                         UriDecodePath(filepath);
1219                         pImage.mFileName = filepath.C_Str();
1220                     }
1221                     TestClosing("ref");
1222                 }
1223                 else if (IsElement("hex") && !pImage.mFileName.length())
1224                 {
1225                     // embedded image. get format
1226                     const int attrib = TestAttribute("format");
1227                     if (-1 == attrib)
1228                         ASSIMP_LOG_WARN("Collada: Unknown image file format");
1229                     else pImage.mEmbeddedFormat = mReader->getAttributeValue(attrib);
1230 
1231                     const char* data = GetTextContent();
1232 
1233                     // hexadecimal-encoded binary octets. First of all, find the
1234                     // required buffer size to reserve enough storage.
1235                     const char* cur = data;
1236                     while (!IsSpaceOrNewLine(*cur)) cur++;
1237 
1238                     const unsigned int size = (unsigned int)(cur - data) * 2;
1239                     pImage.mImageData.resize(size);
1240                     for (unsigned int i = 0; i < size; ++i)
1241                         pImage.mImageData[i] = HexOctetToDecimal(data + (i << 1));
1242 
1243                     TestClosing("hex");
1244                 }
1245             }
1246             else
1247             {
1248                 // ignore the rest
1249                 SkipElement();
1250             }
1251         }
1252         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1253             if (strcmp(mReader->getNodeName(), "image") == 0)
1254                 break;
1255         }
1256     }
1257 }
1258 
1259 // ------------------------------------------------------------------------------------------------
1260 // Reads the material library
1261 void ColladaParser::ReadMaterialLibrary()
1262 {
1263     if (mReader->isEmptyElement())
1264         return;
1265 
1266     std::map<std::string, int> names;
1267     while (mReader->read())
1268     {
1269         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
1270         {
1271             if (IsElement("material"))
1272             {
1273                 // read ID. By now you probably know my opinion about this "specification"
1274                 int attrID = GetAttribute("id");
1275                 std::string id = mReader->getAttributeValue(attrID);
1276 
1277                 std::string name;
1278                 int attrName = TestAttribute("name");
1279                 if (attrName >= 0)
1280                     name = mReader->getAttributeValue(attrName);
1281 
1282                 // create an entry and store it in the library under its ID
1283                 mMaterialLibrary[id] = Material();
1284 
1285                 if (!name.empty())
1286                 {
1287                     std::map<std::string, int>::iterator it = names.find(name);
1288                     if (it != names.end())
1289                     {
1290                         std::ostringstream strStream;
1291                         strStream << ++it->second;
1292                         name.append(" " + strStream.str());
1293                     }
1294                     else
1295                     {
1296                         names[name] = 0;
1297                     }
1298 
1299                     mMaterialLibrary[id].mName = name;
1300                 }
1301 
1302                 ReadMaterial(mMaterialLibrary[id]);
1303             }
1304             else
1305             {
1306                 // ignore the rest
1307                 SkipElement();
1308             }
1309         }
1310         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
1311         {
1312             if (strcmp(mReader->getNodeName(), "library_materials") != 0)
1313                 ThrowException("Expected end of <library_materials> element.");
1314 
1315             break;
1316         }
1317     }
1318 }
1319 
1320 // ------------------------------------------------------------------------------------------------
1321 // Reads the light library
1322 void ColladaParser::ReadLightLibrary()
1323 {
1324     if (mReader->isEmptyElement())
1325         return;
1326 
1327     while (mReader->read())
1328     {
1329         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1330             if (IsElement("light"))
1331             {
1332                 // read ID. By now you probably know my opinion about this "specification"
1333                 int attrID = GetAttribute("id");
1334                 std::string id = mReader->getAttributeValue(attrID);
1335 
1336                 // create an entry and store it in the library under its ID
1337                 ReadLight(mLightLibrary[id] = Light());
1338 
1339             }
1340             else
1341             {
1342                 // ignore the rest
1343                 SkipElement();
1344             }
1345         }
1346         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1347             if (strcmp(mReader->getNodeName(), "library_lights") != 0)
1348                 ThrowException("Expected end of <library_lights> element.");
1349 
1350             break;
1351         }
1352     }
1353 }
1354 
1355 // ------------------------------------------------------------------------------------------------
1356 // Reads the camera library
1357 void ColladaParser::ReadCameraLibrary()
1358 {
1359     if (mReader->isEmptyElement())
1360         return;
1361 
1362     while (mReader->read())
1363     {
1364         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1365             if (IsElement("camera"))
1366             {
1367                 // read ID. By now you probably know my opinion about this "specification"
1368                 int attrID = GetAttribute("id");
1369                 std::string id = mReader->getAttributeValue(attrID);
1370 
1371                 // create an entry and store it in the library under its ID
1372                 Camera& cam = mCameraLibrary[id];
1373                 attrID = TestAttribute("name");
1374                 if (attrID != -1)
1375                     cam.mName = mReader->getAttributeValue(attrID);
1376 
1377                 ReadCamera(cam);
1378 
1379             }
1380             else
1381             {
1382                 // ignore the rest
1383                 SkipElement();
1384             }
1385         }
1386         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1387             if (strcmp(mReader->getNodeName(), "library_cameras") != 0)
1388                 ThrowException("Expected end of <library_cameras> element.");
1389 
1390             break;
1391         }
1392     }
1393 }
1394 
1395 // ------------------------------------------------------------------------------------------------
1396 // Reads a material entry into the given material
1397 void ColladaParser::ReadMaterial(Collada::Material& pMaterial)
1398 {
1399     while (mReader->read())
1400     {
1401         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1402             if (IsElement("material")) {
1403                 SkipElement();
1404             }
1405             else if (IsElement("instance_effect"))
1406             {
1407                 // referred effect by URL
1408                 int attrUrl = GetAttribute("url");
1409                 const char* url = mReader->getAttributeValue(attrUrl);
1410                 if (url[0] != '#')
1411                     ThrowException("Unknown reference format");
1412 
1413                 pMaterial.mEffect = url + 1;
1414 
1415                 SkipElement();
1416             }
1417             else
1418             {
1419                 // ignore the rest
1420                 SkipElement();
1421             }
1422         }
1423         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1424             if (strcmp(mReader->getNodeName(), "material") != 0)
1425                 ThrowException("Expected end of <material> element.");
1426 
1427             break;
1428         }
1429     }
1430 }
1431 
1432 // ------------------------------------------------------------------------------------------------
1433 // Reads a light entry into the given light
1434 void ColladaParser::ReadLight(Collada::Light& pLight)
1435 {
1436     while (mReader->read())
1437     {
1438         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1439             if (IsElement("light")) {
1440                 SkipElement();
1441             }
1442             else if (IsElement("spot")) {
1443                 pLight.mType = aiLightSource_SPOT;
1444             }
1445             else if (IsElement("ambient")) {
1446                 pLight.mType = aiLightSource_AMBIENT;
1447             }
1448             else if (IsElement("directional")) {
1449                 pLight.mType = aiLightSource_DIRECTIONAL;
1450             }
1451             else if (IsElement("point")) {
1452                 pLight.mType = aiLightSource_POINT;
1453             }
1454             else if (IsElement("color")) {
1455                 // text content contains 3 floats
1456                 const char* content = GetTextContent();
1457 
1458                 content = fast_atoreal_move<ai_real>(content, (ai_real&)pLight.mColor.r);
1459                 SkipSpacesAndLineEnd(&content);
1460 
1461                 content = fast_atoreal_move<ai_real>(content, (ai_real&)pLight.mColor.g);
1462                 SkipSpacesAndLineEnd(&content);
1463 
1464                 content = fast_atoreal_move<ai_real>(content, (ai_real&)pLight.mColor.b);
1465                 SkipSpacesAndLineEnd(&content);
1466 
1467                 TestClosing("color");
1468             }
1469             else if (IsElement("constant_attenuation")) {
1470                 pLight.mAttConstant = ReadFloatFromTextContent();
1471                 TestClosing("constant_attenuation");
1472             }
1473             else if (IsElement("linear_attenuation")) {
1474                 pLight.mAttLinear = ReadFloatFromTextContent();
1475                 TestClosing("linear_attenuation");
1476             }
1477             else if (IsElement("quadratic_attenuation")) {
1478                 pLight.mAttQuadratic = ReadFloatFromTextContent();
1479                 TestClosing("quadratic_attenuation");
1480             }
1481             else if (IsElement("falloff_angle")) {
1482                 pLight.mFalloffAngle = ReadFloatFromTextContent();
1483                 TestClosing("falloff_angle");
1484             }
1485             else if (IsElement("falloff_exponent")) {
1486                 pLight.mFalloffExponent = ReadFloatFromTextContent();
1487                 TestClosing("falloff_exponent");
1488             }
1489             // FCOLLADA extensions
1490             // -------------------------------------------------------
1491             else if (IsElement("outer_cone")) {
1492                 pLight.mOuterAngle = ReadFloatFromTextContent();
1493                 TestClosing("outer_cone");
1494             }
1495             // ... and this one is even deprecated
1496             else if (IsElement("penumbra_angle")) {
1497                 pLight.mPenumbraAngle = ReadFloatFromTextContent();
1498                 TestClosing("penumbra_angle");
1499             }
1500             else if (IsElement("intensity")) {
1501                 pLight.mIntensity = ReadFloatFromTextContent();
1502                 TestClosing("intensity");
1503             }
1504             else if (IsElement("falloff")) {
1505                 pLight.mOuterAngle = ReadFloatFromTextContent();
1506                 TestClosing("falloff");
1507             }
1508             else if (IsElement("hotspot_beam")) {
1509                 pLight.mFalloffAngle = ReadFloatFromTextContent();
1510                 TestClosing("hotspot_beam");
1511             }
1512             // OpenCOLLADA extensions
1513             // -------------------------------------------------------
1514             else if (IsElement("decay_falloff")) {
1515                 pLight.mOuterAngle = ReadFloatFromTextContent();
1516                 TestClosing("decay_falloff");
1517             }
1518         }
1519         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1520             if (strcmp(mReader->getNodeName(), "light") == 0)
1521                 break;
1522         }
1523     }
1524 }
1525 
1526 // ------------------------------------------------------------------------------------------------
1527 // Reads a camera entry into the given light
1528 void ColladaParser::ReadCamera(Collada::Camera& pCamera)
1529 {
1530     while (mReader->read())
1531     {
1532         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1533             if (IsElement("camera")) {
1534                 SkipElement();
1535             }
1536             else if (IsElement("orthographic")) {
1537                 pCamera.mOrtho = true;
1538             }
1539             else if (IsElement("xfov") || IsElement("xmag")) {
1540                 pCamera.mHorFov = ReadFloatFromTextContent();
1541                 TestClosing((pCamera.mOrtho ? "xmag" : "xfov"));
1542             }
1543             else if (IsElement("yfov") || IsElement("ymag")) {
1544                 pCamera.mVerFov = ReadFloatFromTextContent();
1545                 TestClosing((pCamera.mOrtho ? "ymag" : "yfov"));
1546             }
1547             else if (IsElement("aspect_ratio")) {
1548                 pCamera.mAspect = ReadFloatFromTextContent();
1549                 TestClosing("aspect_ratio");
1550             }
1551             else if (IsElement("znear")) {
1552                 pCamera.mZNear = ReadFloatFromTextContent();
1553                 TestClosing("znear");
1554             }
1555             else if (IsElement("zfar")) {
1556                 pCamera.mZFar = ReadFloatFromTextContent();
1557                 TestClosing("zfar");
1558             }
1559         }
1560         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1561             if (strcmp(mReader->getNodeName(), "camera") == 0)
1562                 break;
1563         }
1564     }
1565 }
1566 
1567 // ------------------------------------------------------------------------------------------------
1568 // Reads the effect library
1569 void ColladaParser::ReadEffectLibrary()
1570 {
1571     if (mReader->isEmptyElement()) {
1572         return;
1573     }
1574 
1575     while (mReader->read())
1576     {
1577         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1578             if (IsElement("effect"))
1579             {
1580                 // read ID. Do I have to repeat my ranting about "optional" attributes?
1581                 int attrID = GetAttribute("id");
1582                 std::string id = mReader->getAttributeValue(attrID);
1583 
1584                 // create an entry and store it in the library under its ID
1585                 mEffectLibrary[id] = Effect();
1586                 // read on from there
1587                 ReadEffect(mEffectLibrary[id]);
1588             }
1589             else
1590             {
1591                 // ignore the rest
1592                 SkipElement();
1593             }
1594         }
1595         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1596             if (strcmp(mReader->getNodeName(), "library_effects") != 0)
1597                 ThrowException("Expected end of <library_effects> element.");
1598 
1599             break;
1600         }
1601     }
1602 }
1603 
1604 // ------------------------------------------------------------------------------------------------
1605 // Reads an effect entry into the given effect
1606 void ColladaParser::ReadEffect(Collada::Effect& pEffect)
1607 {
1608     // for the moment we don't support any other type of effect.
1609     while (mReader->read())
1610     {
1611         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
1612         {
1613             if (IsElement("profile_COMMON"))
1614                 ReadEffectProfileCommon(pEffect);
1615             else
1616                 SkipElement();
1617         }
1618         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
1619         {
1620             if (strcmp(mReader->getNodeName(), "effect") != 0)
1621                 ThrowException("Expected end of <effect> element.");
1622 
1623             break;
1624         }
1625     }
1626 }
1627 
1628 // ------------------------------------------------------------------------------------------------
1629 // Reads an COMMON effect profile
1630 void ColladaParser::ReadEffectProfileCommon(Collada::Effect& pEffect)
1631 {
1632     while (mReader->read())
1633     {
1634         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
1635         {
1636             if (IsElement("newparam")) {
1637                 // save ID
1638                 int attrSID = GetAttribute("sid");
1639                 std::string sid = mReader->getAttributeValue(attrSID);
1640                 pEffect.mParams[sid] = EffectParam();
1641                 ReadEffectParam(pEffect.mParams[sid]);
1642             }
1643             else if (IsElement("technique") || IsElement("extra"))
1644             {
1645                 // just syntactic sugar
1646             }
1647 
1648             else if (mFormat == FV_1_4_n && IsElement("image"))
1649             {
1650                 // read ID. Another entry which is "optional" by design but obligatory in reality
1651                 int attrID = GetAttribute("id");
1652                 std::string id = mReader->getAttributeValue(attrID);
1653 
1654                 // create an entry and store it in the library under its ID
1655                 mImageLibrary[id] = Image();
1656 
1657                 // read on from there
1658                 ReadImage(mImageLibrary[id]);
1659             }
1660 
1661             /* Shading modes */
1662             else if (IsElement("phong"))
1663                 pEffect.mShadeType = Shade_Phong;
1664             else if (IsElement("constant"))
1665                 pEffect.mShadeType = Shade_Constant;
1666             else if (IsElement("lambert"))
1667                 pEffect.mShadeType = Shade_Lambert;
1668             else if (IsElement("blinn"))
1669                 pEffect.mShadeType = Shade_Blinn;
1670 
1671             /* Color + texture properties */
1672             else if (IsElement("emission"))
1673                 ReadEffectColor(pEffect.mEmissive, pEffect.mTexEmissive);
1674             else if (IsElement("ambient"))
1675                 ReadEffectColor(pEffect.mAmbient, pEffect.mTexAmbient);
1676             else if (IsElement("diffuse"))
1677                 ReadEffectColor(pEffect.mDiffuse, pEffect.mTexDiffuse);
1678             else if (IsElement("specular"))
1679                 ReadEffectColor(pEffect.mSpecular, pEffect.mTexSpecular);
1680             else if (IsElement("reflective")) {
1681                 ReadEffectColor(pEffect.mReflective, pEffect.mTexReflective);
1682             }
1683             else if (IsElement("transparent")) {
1684                 pEffect.mHasTransparency = true;
1685 
1686                 const char* opaque = mReader->getAttributeValueSafe("opaque");
1687 
1688                 if (::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "RGB_ONE") == 0) {
1689                     pEffect.mRGBTransparency = true;
1690                 }
1691 
1692                 // In RGB_ZERO mode, the transparency is interpreted in reverse, go figure...
1693                 if (::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "A_ZERO") == 0) {
1694                     pEffect.mInvertTransparency = true;
1695                 }
1696 
1697                 ReadEffectColor(pEffect.mTransparent, pEffect.mTexTransparent);
1698             }
1699             else if (IsElement("shininess"))
1700                 ReadEffectFloat(pEffect.mShininess);
1701             else if (IsElement("reflectivity"))
1702                 ReadEffectFloat(pEffect.mReflectivity);
1703 
1704             /* Single scalar properties */
1705             else if (IsElement("transparency"))
1706                 ReadEffectFloat(pEffect.mTransparency);
1707             else if (IsElement("index_of_refraction"))
1708                 ReadEffectFloat(pEffect.mRefractIndex);
1709 
1710             // GOOGLEEARTH/OKINO extensions
1711             // -------------------------------------------------------
1712             else if (IsElement("double_sided"))
1713                 pEffect.mDoubleSided = ReadBoolFromTextContent();
1714 
1715             // FCOLLADA extensions
1716             // -------------------------------------------------------
1717             else if (IsElement("bump")) {
1718                 aiColor4D dummy;
1719                 ReadEffectColor(dummy, pEffect.mTexBump);
1720             }
1721 
1722             // MAX3D extensions
1723             // -------------------------------------------------------
1724             else if (IsElement("wireframe")) {
1725                 pEffect.mWireframe = ReadBoolFromTextContent();
1726                 TestClosing("wireframe");
1727             }
1728             else if (IsElement("faceted")) {
1729                 pEffect.mFaceted = ReadBoolFromTextContent();
1730                 TestClosing("faceted");
1731             }
1732             else
1733             {
1734                 // ignore the rest
1735                 SkipElement();
1736             }
1737         }
1738         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1739             if (strcmp(mReader->getNodeName(), "profile_COMMON") == 0)
1740             {
1741                 break;
1742             }
1743         }
1744     }
1745 }
1746 
1747 // ------------------------------------------------------------------------------------------------
1748 // Read texture wrapping + UV transform settings from a profile==Maya chunk
1749 void ColladaParser::ReadSamplerProperties(Sampler& out)
1750 {
1751     if (mReader->isEmptyElement()) {
1752         return;
1753     }
1754 
1755     while (mReader->read())
1756     {
1757         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1758 
1759             // MAYA extensions
1760             // -------------------------------------------------------
1761             if (IsElement("wrapU")) {
1762                 out.mWrapU = ReadBoolFromTextContent();
1763                 TestClosing("wrapU");
1764             }
1765             else if (IsElement("wrapV")) {
1766                 out.mWrapV = ReadBoolFromTextContent();
1767                 TestClosing("wrapV");
1768             }
1769             else if (IsElement("mirrorU")) {
1770                 out.mMirrorU = ReadBoolFromTextContent();
1771                 TestClosing("mirrorU");
1772             }
1773             else if (IsElement("mirrorV")) {
1774                 out.mMirrorV = ReadBoolFromTextContent();
1775                 TestClosing("mirrorV");
1776             }
1777             else if (IsElement("repeatU")) {
1778                 out.mTransform.mScaling.x = ReadFloatFromTextContent();
1779                 TestClosing("repeatU");
1780             }
1781             else if (IsElement("repeatV")) {
1782                 out.mTransform.mScaling.y = ReadFloatFromTextContent();
1783                 TestClosing("repeatV");
1784             }
1785             else if (IsElement("offsetU")) {
1786                 out.mTransform.mTranslation.x = ReadFloatFromTextContent();
1787                 TestClosing("offsetU");
1788             }
1789             else if (IsElement("offsetV")) {
1790                 out.mTransform.mTranslation.y = ReadFloatFromTextContent();
1791                 TestClosing("offsetV");
1792             }
1793             else if (IsElement("rotateUV")) {
1794                 out.mTransform.mRotation = ReadFloatFromTextContent();
1795                 TestClosing("rotateUV");
1796             }
1797             else if (IsElement("blend_mode")) {
1798 
1799                 const char* sz = GetTextContent();
1800                 // http://www.feelingsoftware.com/content/view/55/72/lang,en/
1801                 // NONE, OVER, IN, OUT, ADD, SUBTRACT, MULTIPLY, DIFFERENCE, LIGHTEN, DARKEN, SATURATE, DESATURATE and ILLUMINATE
1802                 if (0 == ASSIMP_strincmp(sz, "ADD", 3))
1803                     out.mOp = aiTextureOp_Add;
1804 
1805                 else if (0 == ASSIMP_strincmp(sz, "SUBTRACT", 8))
1806                     out.mOp = aiTextureOp_Subtract;
1807 
1808                 else if (0 == ASSIMP_strincmp(sz, "MULTIPLY", 8))
1809                     out.mOp = aiTextureOp_Multiply;
1810 
1811                 else {
1812                     ASSIMP_LOG_WARN("Collada: Unsupported MAYA texture blend mode");
1813                 }
1814                 TestClosing("blend_mode");
1815             }
1816             // OKINO extensions
1817             // -------------------------------------------------------
1818             else if (IsElement("weighting")) {
1819                 out.mWeighting = ReadFloatFromTextContent();
1820                 TestClosing("weighting");
1821             }
1822             else if (IsElement("mix_with_previous_layer")) {
1823                 out.mMixWithPrevious = ReadFloatFromTextContent();
1824                 TestClosing("mix_with_previous_layer");
1825             }
1826             // MAX3D extensions
1827             // -------------------------------------------------------
1828             else if (IsElement("amount")) {
1829                 out.mWeighting = ReadFloatFromTextContent();
1830                 TestClosing("amount");
1831             }
1832         }
1833         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1834             if (strcmp(mReader->getNodeName(), "technique") == 0)
1835                 break;
1836         }
1837     }
1838 }
1839 
1840 // ------------------------------------------------------------------------------------------------
1841 // Reads an effect entry containing a color or a texture defining that color
1842 void ColladaParser::ReadEffectColor(aiColor4D& pColor, Sampler& pSampler)
1843 {
1844     if (mReader->isEmptyElement())
1845         return;
1846 
1847     // Save current element name
1848     const std::string curElem = mReader->getNodeName();
1849 
1850     while (mReader->read())
1851     {
1852         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1853             if (IsElement("color"))
1854             {
1855                 // text content contains 4 floats
1856                 const char* content = GetTextContent();
1857 
1858                 content = fast_atoreal_move<ai_real>(content, (ai_real&)pColor.r);
1859                 SkipSpacesAndLineEnd(&content);
1860 
1861                 content = fast_atoreal_move<ai_real>(content, (ai_real&)pColor.g);
1862                 SkipSpacesAndLineEnd(&content);
1863 
1864                 content = fast_atoreal_move<ai_real>(content, (ai_real&)pColor.b);
1865                 SkipSpacesAndLineEnd(&content);
1866 
1867                 content = fast_atoreal_move<ai_real>(content, (ai_real&)pColor.a);
1868                 SkipSpacesAndLineEnd(&content);
1869                 TestClosing("color");
1870             }
1871             else if (IsElement("texture"))
1872             {
1873                 // get name of source texture/sampler
1874                 int attrTex = GetAttribute("texture");
1875                 pSampler.mName = mReader->getAttributeValue(attrTex);
1876 
1877                 // get name of UV source channel. Specification demands it to be there, but some exporters
1878                 // don't write it. It will be the default UV channel in case it's missing.
1879                 attrTex = TestAttribute("texcoord");
1880                 if (attrTex >= 0)
1881                     pSampler.mUVChannel = mReader->getAttributeValue(attrTex);
1882                 //SkipElement();
1883 
1884                 // as we've read texture, the color needs to be 1,1,1,1
1885                 pColor = aiColor4D(1.f, 1.f, 1.f, 1.f);
1886             }
1887             else if (IsElement("technique"))
1888             {
1889                 const int _profile = GetAttribute("profile");
1890                 const char* profile = mReader->getAttributeValue(_profile);
1891 
1892                 // Some extensions are quite useful ... ReadSamplerProperties processes
1893                 // several extensions in MAYA, OKINO and MAX3D profiles.
1894                 if (!::strcmp(profile, "MAYA") || !::strcmp(profile, "MAX3D") || !::strcmp(profile, "OKINO"))
1895                 {
1896                     // get more information on this sampler
1897                     ReadSamplerProperties(pSampler);
1898                 }
1899                 else SkipElement();
1900             }
1901             else if (!IsElement("extra"))
1902             {
1903                 // ignore the rest
1904                 SkipElement();
1905             }
1906         }
1907         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1908             if (mReader->getNodeName() == curElem)
1909                 break;
1910         }
1911     }
1912 }
1913 
1914 // ------------------------------------------------------------------------------------------------
1915 // Reads an effect entry containing a float
1916 void ColladaParser::ReadEffectFloat(ai_real& pFloat)
1917 {
1918     while (mReader->read())
1919     {
1920         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1921             if (IsElement("float"))
1922             {
1923                 // text content contains a single floats
1924                 const char* content = GetTextContent();
1925                 content = fast_atoreal_move<ai_real>(content, pFloat);
1926                 SkipSpacesAndLineEnd(&content);
1927 
1928                 TestClosing("float");
1929             }
1930             else
1931             {
1932                 // ignore the rest
1933                 SkipElement();
1934             }
1935         }
1936         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1937             break;
1938         }
1939     }
1940 }
1941 
1942 // ------------------------------------------------------------------------------------------------
1943 // Reads an effect parameter specification of any kind
1944 void ColladaParser::ReadEffectParam(Collada::EffectParam& pParam)
1945 {
1946     while (mReader->read())
1947     {
1948         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
1949             if (IsElement("surface"))
1950             {
1951                 // image ID given inside <init_from> tags
1952                 TestOpening("init_from");
1953                 const char* content = GetTextContent();
1954                 pParam.mType = Param_Surface;
1955                 pParam.mReference = content;
1956                 TestClosing("init_from");
1957 
1958                 // don't care for remaining stuff
1959                 SkipElement("surface");
1960             }
1961             else if (IsElement("sampler2D") && (FV_1_4_n == mFormat || FV_1_3_n == mFormat))
1962             {
1963                 // surface ID is given inside <source> tags
1964                 TestOpening("source");
1965                 const char* content = GetTextContent();
1966                 pParam.mType = Param_Sampler;
1967                 pParam.mReference = content;
1968                 TestClosing("source");
1969 
1970                 // don't care for remaining stuff
1971                 SkipElement("sampler2D");
1972             }
1973             else if (IsElement("sampler2D"))
1974             {
1975                 // surface ID is given inside <instance_image> tags
1976                 TestOpening("instance_image");
1977                 int attrURL = GetAttribute("url");
1978                 const char* url = mReader->getAttributeValue(attrURL);
1979                 if (url[0] != '#')
1980                     ThrowException("Unsupported URL format in instance_image");
1981                 url++;
1982                 pParam.mType = Param_Sampler;
1983                 pParam.mReference = url;
1984                 SkipElement("sampler2D");
1985             }
1986             else
1987             {
1988                 // ignore unknown element
1989                 SkipElement();
1990             }
1991         }
1992         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
1993             break;
1994         }
1995     }
1996 }
1997 
1998 // ------------------------------------------------------------------------------------------------
1999 // Reads the geometry library contents
2000 void ColladaParser::ReadGeometryLibrary()
2001 {
2002     if (mReader->isEmptyElement())
2003         return;
2004 
2005     while (mReader->read())
2006     {
2007         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
2008         {
2009             if (IsElement("geometry"))
2010             {
2011                 // read ID. Another entry which is "optional" by design but obligatory in reality
2012                 int indexID = GetAttribute("id");
2013                 std::string id = mReader->getAttributeValue(indexID);
2014 
2015                 // TODO: (thom) support SIDs
2016                 // ai_assert( TestAttribute( "sid") == -1);
2017 
2018                 // create a mesh and store it in the library under its ID
2019                 Mesh* mesh = new Mesh;
2020                 mMeshLibrary[id] = mesh;
2021 
2022                 // read the mesh name if it exists
2023                 const int nameIndex = TestAttribute("name");
2024                 if (nameIndex != -1)
2025                 {
2026                     mesh->mName = mReader->getAttributeValue(nameIndex);
2027                 }
2028 
2029                 // read on from there
2030                 ReadGeometry(mesh);
2031             }
2032             else
2033             {
2034                 // ignore the rest
2035                 SkipElement();
2036             }
2037         }
2038         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
2039         {
2040             if (strcmp(mReader->getNodeName(), "library_geometries") != 0)
2041                 ThrowException("Expected end of <library_geometries> element.");
2042 
2043             break;
2044         }
2045     }
2046 }
2047 
2048 // ------------------------------------------------------------------------------------------------
2049 // Reads a geometry from the geometry library.
2050 void ColladaParser::ReadGeometry(Collada::Mesh* pMesh)
2051 {
2052     if (mReader->isEmptyElement())
2053         return;
2054 
2055     while (mReader->read())
2056     {
2057         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
2058         {
2059             if (IsElement("mesh"))
2060             {
2061                 // read on from there
2062                 ReadMesh(pMesh);
2063             }
2064             else
2065             {
2066                 // ignore the rest
2067                 SkipElement();
2068             }
2069         }
2070         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
2071         {
2072             if (strcmp(mReader->getNodeName(), "geometry") != 0)
2073                 ThrowException("Expected end of <geometry> element.");
2074 
2075             break;
2076         }
2077     }
2078 }
2079 
2080 // ------------------------------------------------------------------------------------------------
2081 // Reads a mesh from the geometry library
2082 void ColladaParser::ReadMesh(Mesh* pMesh)
2083 {
2084     if (mReader->isEmptyElement())
2085         return;
2086 
2087     while (mReader->read())
2088     {
2089         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
2090         {
2091             if (IsElement("source"))
2092             {
2093                 // we have professionals dealing with this
2094                 ReadSource();
2095             }
2096             else if (IsElement("vertices"))
2097             {
2098                 // read per-vertex mesh data
2099                 ReadVertexData(pMesh);
2100             }
2101             else if (IsElement("triangles") || IsElement("lines") || IsElement("linestrips")
2102                 || IsElement("polygons") || IsElement("polylist") || IsElement("trifans") || IsElement("tristrips"))
2103             {
2104                 // read per-index mesh data and faces setup
2105                 ReadIndexData(pMesh);
2106             }
2107             else
2108             {
2109                 // ignore the restf
2110                 SkipElement();
2111             }
2112         }
2113         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
2114         {
2115             if (strcmp(mReader->getNodeName(), "technique_common") == 0)
2116             {
2117                 // end of another meaningless element - read over it
2118             }
2119             else if (strcmp(mReader->getNodeName(), "mesh") == 0)
2120             {
2121                 // end of <mesh> element - we're done here
2122                 break;
2123             }
2124             else
2125             {
2126                 // everything else should be punished
2127                 ThrowException("Expected end of <mesh> element.");
2128             }
2129         }
2130     }
2131 }
2132 
2133 // ------------------------------------------------------------------------------------------------
2134 // Reads a source element
2135 void ColladaParser::ReadSource()
2136 {
2137     int indexID = GetAttribute("id");
2138     std::string sourceID = mReader->getAttributeValue(indexID);
2139 
2140     while (mReader->read())
2141     {
2142         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
2143         {
2144             if (IsElement("float_array") || IsElement("IDREF_array") || IsElement("Name_array"))
2145             {
2146                 ReadDataArray();
2147             }
2148             else if (IsElement("technique_common"))
2149             {
2150                 // I don't care for your profiles
2151             }
2152             else if (IsElement("accessor"))
2153             {
2154                 ReadAccessor(sourceID);
2155             }
2156             else
2157             {
2158                 // ignore the rest
2159                 SkipElement();
2160             }
2161         }
2162         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
2163         {
2164             if (strcmp(mReader->getNodeName(), "source") == 0)
2165             {
2166                 // end of <source> - we're done
2167                 break;
2168             }
2169             else if (strcmp(mReader->getNodeName(), "technique_common") == 0)
2170             {
2171                 // end of another meaningless element - read over it
2172             }
2173             else
2174             {
2175                 // everything else should be punished
2176                 ThrowException("Expected end of <source> element.");
2177             }
2178         }
2179     }
2180 }
2181 
2182 // ------------------------------------------------------------------------------------------------
2183 // Reads a data array holding a number of floats, and stores it in the global library
2184 void ColladaParser::ReadDataArray()
2185 {
2186     std::string elmName = mReader->getNodeName();
2187     bool isStringArray = (elmName == "IDREF_array" || elmName == "Name_array");
2188     bool isEmptyElement = mReader->isEmptyElement();
2189 
2190     // read attributes
2191     int indexID = GetAttribute("id");
2192     std::string id = mReader->getAttributeValue(indexID);
2193     int indexCount = GetAttribute("count");
2194     unsigned int count = (unsigned int)mReader->getAttributeValueAsInt(indexCount);
2195     const char* content = TestTextContent();
2196 
2197     // read values and store inside an array in the data library
2198     mDataLibrary[id] = Data();
2199     Data& data = mDataLibrary[id];
2200     data.mIsStringArray = isStringArray;
2201 
2202     // some exporters write empty data arrays, but we need to conserve them anyways because others might reference them
2203     if (content)
2204     {
2205         if (isStringArray)
2206         {
2207             data.mStrings.reserve(count);
2208             std::string s;
2209 
2210             for (unsigned int a = 0; a < count; a++)
2211             {
2212                 if (*content == 0)
2213                     ThrowException("Expected more values while reading IDREF_array contents.");
2214 
2215                 s.clear();
2216                 while (!IsSpaceOrNewLine(*content))
2217                     s += *content++;
2218                 data.mStrings.push_back(s);
2219 
2220                 SkipSpacesAndLineEnd(&content);
2221             }
2222         }
2223         else
2224         {
2225             data.mValues.reserve(count);
2226 
2227             for (unsigned int a = 0; a < count; a++)
2228             {
2229                 if (*content == 0)
2230                     ThrowException("Expected more values while reading float_array contents.");
2231 
2232                 ai_real value;
2233                 // read a number
2234                 content = fast_atoreal_move<ai_real>(content, value);
2235                 data.mValues.push_back(value);
2236                 // skip whitespace after it
2237                 SkipSpacesAndLineEnd(&content);
2238             }
2239         }
2240     }
2241 
2242     // test for closing tag
2243     if (!isEmptyElement)
2244         TestClosing(elmName.c_str());
2245 }
2246 
2247 // ------------------------------------------------------------------------------------------------
2248 // Reads an accessor and stores it in the global library
2249 void ColladaParser::ReadAccessor(const std::string& pID)
2250 {
2251     // read accessor attributes
2252     int attrSource = GetAttribute("source");
2253     const char* source = mReader->getAttributeValue(attrSource);
2254     if (source[0] != '#')
2255         ThrowException(format() << "Unknown reference format in url \"" << source << "\" in source attribute of <accessor> element.");
2256     int attrCount = GetAttribute("count");
2257     unsigned int count = (unsigned int)mReader->getAttributeValueAsInt(attrCount);
2258     int attrOffset = TestAttribute("offset");
2259     unsigned int offset = 0;
2260     if (attrOffset > -1)
2261         offset = (unsigned int)mReader->getAttributeValueAsInt(attrOffset);
2262     int attrStride = TestAttribute("stride");
2263     unsigned int stride = 1;
2264     if (attrStride > -1)
2265         stride = (unsigned int)mReader->getAttributeValueAsInt(attrStride);
2266 
2267     // store in the library under the given ID
2268     mAccessorLibrary[pID] = Accessor();
2269     Accessor& acc = mAccessorLibrary[pID];
2270     acc.mCount = count;
2271     acc.mOffset = offset;
2272     acc.mStride = stride;
2273     acc.mSource = source + 1; // ignore the leading '#'
2274     acc.mSize = 0; // gets incremented with every param
2275 
2276     // and read the components
2277     while (mReader->read())
2278     {
2279         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
2280         {
2281             if (IsElement("param"))
2282             {
2283                 // read data param
2284                 int attrName = TestAttribute("name");
2285                 std::string name;
2286                 if (attrName > -1)
2287                 {
2288                     name = mReader->getAttributeValue(attrName);
2289 
2290                     // analyse for common type components and store it's sub-offset in the corresponding field
2291 
2292                     /* Cartesian coordinates */
2293                     if (name == "X") acc.mSubOffset[0] = acc.mParams.size();
2294                     else if (name == "Y") acc.mSubOffset[1] = acc.mParams.size();
2295                     else if (name == "Z") acc.mSubOffset[2] = acc.mParams.size();
2296 
2297                     /* RGBA colors */
2298                     else if (name == "R") acc.mSubOffset[0] = acc.mParams.size();
2299                     else if (name == "G") acc.mSubOffset[1] = acc.mParams.size();
2300                     else if (name == "B") acc.mSubOffset[2] = acc.mParams.size();
2301                     else if (name == "A") acc.mSubOffset[3] = acc.mParams.size();
2302 
2303                     /* UVWQ (STPQ) texture coordinates */
2304                     else if (name == "S") acc.mSubOffset[0] = acc.mParams.size();
2305                     else if (name == "T") acc.mSubOffset[1] = acc.mParams.size();
2306                     else if (name == "P") acc.mSubOffset[2] = acc.mParams.size();
2307                     //  else if( name == "Q") acc.mSubOffset[3] = acc.mParams.size();
2308                         /* 4D uv coordinates are not supported in Assimp */
2309 
2310                         /* Generic extra data, interpreted as UV data, too*/
2311                     else if (name == "U") acc.mSubOffset[0] = acc.mParams.size();
2312                     else if (name == "V") acc.mSubOffset[1] = acc.mParams.size();
2313                     //else
2314                     //  DefaultLogger::get()->warn( format() << "Unknown accessor parameter \"" << name << "\". Ignoring data channel." );
2315                 }
2316 
2317                 // read data type
2318                 int attrType = TestAttribute("type");
2319                 if (attrType > -1)
2320                 {
2321                     // for the moment we only distinguish between a 4x4 matrix and anything else.
2322                     // TODO: (thom) I don't have a spec here at work. Check if there are other multi-value types
2323                     // which should be tested for here.
2324                     std::string type = mReader->getAttributeValue(attrType);
2325                     if (type == "float4x4")
2326                         acc.mSize += 16;
2327                     else
2328                         acc.mSize += 1;
2329                 }
2330 
2331                 acc.mParams.push_back(name);
2332 
2333                 // skip remaining stuff of this element, if any
2334                 SkipElement();
2335             }
2336             else
2337             {
2338                 ThrowException(format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag <accessor>");
2339             }
2340         }
2341         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
2342         {
2343             if (strcmp(mReader->getNodeName(), "accessor") != 0)
2344                 ThrowException("Expected end of <accessor> element.");
2345             break;
2346         }
2347     }
2348 }
2349 
2350 // ------------------------------------------------------------------------------------------------
2351 // Reads input declarations of per-vertex mesh data into the given mesh
2352 void ColladaParser::ReadVertexData(Mesh* pMesh)
2353 {
2354     // extract the ID of the <vertices> element. Not that we care, but to catch strange referencing schemes we should warn about
2355     int attrID = GetAttribute("id");
2356     pMesh->mVertexID = mReader->getAttributeValue(attrID);
2357 
2358     // a number of <input> elements
2359     while (mReader->read())
2360     {
2361         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
2362         {
2363             if (IsElement("input"))
2364             {
2365                 ReadInputChannel(pMesh->mPerVertexData);
2366             }
2367             else
2368             {
2369                 ThrowException(format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag <vertices>");
2370             }
2371         }
2372         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
2373         {
2374             if (strcmp(mReader->getNodeName(), "vertices") != 0)
2375                 ThrowException("Expected end of <vertices> element.");
2376 
2377             break;
2378         }
2379     }
2380 }
2381 
2382 // ------------------------------------------------------------------------------------------------
2383 // Reads input declarations of per-index mesh data into the given mesh
2384 void ColladaParser::ReadIndexData(Mesh* pMesh)
2385 {
2386     std::vector<size_t> vcount;
2387     std::vector<InputChannel> perIndexData;
2388 
2389     // read primitive count from the attribute
2390     int attrCount = GetAttribute("count");
2391     size_t numPrimitives = (size_t)mReader->getAttributeValueAsInt(attrCount);
2392     // some mesh types (e.g. tristrips) don't specify primitive count upfront,
2393     // so we need to sum up the actual number of primitives while we read the <p>-tags
2394     size_t actualPrimitives = 0;
2395 
2396     // material subgroup
2397     int attrMaterial = TestAttribute("material");
2398     SubMesh subgroup;
2399     if (attrMaterial > -1)
2400         subgroup.mMaterial = mReader->getAttributeValue(attrMaterial);
2401 
2402     // distinguish between polys and triangles
2403     std::string elementName = mReader->getNodeName();
2404     PrimitiveType primType = Prim_Invalid;
2405     if (IsElement("lines"))
2406         primType = Prim_Lines;
2407     else if (IsElement("linestrips"))
2408         primType = Prim_LineStrip;
2409     else if (IsElement("polygons"))
2410         primType = Prim_Polygon;
2411     else if (IsElement("polylist"))
2412         primType = Prim_Polylist;
2413     else if (IsElement("triangles"))
2414         primType = Prim_Triangles;
2415     else if (IsElement("trifans"))
2416         primType = Prim_TriFans;
2417     else if (IsElement("tristrips"))
2418         primType = Prim_TriStrips;
2419 
2420     ai_assert(primType != Prim_Invalid);
2421 
2422     // also a number of <input> elements, but in addition a <p> primitive collection and probably index counts for all primitives
2423     while (mReader->read())
2424     {
2425         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
2426         {
2427             if (IsElement("input"))
2428             {
2429                 ReadInputChannel(perIndexData);
2430             }
2431             else if (IsElement("vcount"))
2432             {
2433                 if (!mReader->isEmptyElement())
2434                 {
2435                     if (numPrimitives)  // It is possible to define a mesh without any primitives
2436                     {
2437                         // case <polylist> - specifies the number of indices for each polygon
2438                         const char* content = GetTextContent();
2439                         vcount.reserve(numPrimitives);
2440                         for (unsigned int a = 0; a < numPrimitives; a++)
2441                         {
2442                             if (*content == 0)
2443                                 ThrowException("Expected more values while reading <vcount> contents.");
2444                             // read a number
2445                             vcount.push_back((size_t)strtoul10(content, &content));
2446                             // skip whitespace after it
2447                             SkipSpacesAndLineEnd(&content);
2448                         }
2449                     }
2450 
2451                     TestClosing("vcount");
2452                 }
2453             }
2454             else if (IsElement("p"))
2455             {
2456                 if (!mReader->isEmptyElement())
2457                 {
2458                     // now here the actual fun starts - these are the indices to construct the mesh data from
2459                     actualPrimitives += ReadPrimitives(pMesh, perIndexData, numPrimitives, vcount, primType);
2460                 }
2461             }
2462             else if (IsElement("extra"))
2463             {
2464                 SkipElement("extra");
2465             }
2466             else if (IsElement("ph")) {
2467                 SkipElement("ph");
2468             }
2469             else {
2470                 ThrowException(format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag <" << elementName << ">");
2471             }
2472         }
2473         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
2474         {
2475             if (mReader->getNodeName() != elementName)
2476                 ThrowException(format() << "Expected end of <" << elementName << "> element.");
2477 
2478             break;
2479         }
2480     }
2481 
2482 #ifdef ASSIMP_BUILD_DEBUG
2483     if (primType != Prim_TriFans && primType != Prim_TriStrips && primType != Prim_LineStrip &&
2484         primType != Prim_Lines) { // this is ONLY to workaround a bug in SketchUp 15.3.331 where it writes the wrong 'count' when it writes out the 'lines'.
2485         ai_assert(actualPrimitives == numPrimitives);
2486     }
2487 #endif
2488 
2489     // only when we're done reading all <p> tags (and thus know the final vertex count) can we commit the submesh
2490     subgroup.mNumFaces = actualPrimitives;
2491     pMesh->mSubMeshes.push_back(subgroup);
2492 }
2493 
2494 // ------------------------------------------------------------------------------------------------
2495 // Reads a single input channel element and stores it in the given array, if valid
2496 void ColladaParser::ReadInputChannel(std::vector<InputChannel>& poChannels)
2497 {
2498     InputChannel channel;
2499 
2500     // read semantic
2501     int attrSemantic = GetAttribute("semantic");
2502     std::string semantic = mReader->getAttributeValue(attrSemantic);
2503     channel.mType = GetTypeForSemantic(semantic);
2504 
2505     // read source
2506     int attrSource = GetAttribute("source");
2507     const char* source = mReader->getAttributeValue(attrSource);
2508     if (source[0] != '#')
2509         ThrowException(format() << "Unknown reference format in url \"" << source << "\" in source attribute of <input> element.");
2510     channel.mAccessor = source + 1; // skipping the leading #, hopefully the remaining text is the accessor ID only
2511 
2512     // read index offset, if per-index <input>
2513     int attrOffset = TestAttribute("offset");
2514     if (attrOffset > -1)
2515         channel.mOffset = mReader->getAttributeValueAsInt(attrOffset);
2516 
2517     // read set if texture coordinates
2518     if (channel.mType == IT_Texcoord || channel.mType == IT_Color) {
2519         int attrSet = TestAttribute("set");
2520         if (attrSet > -1) {
2521             attrSet = mReader->getAttributeValueAsInt(attrSet);
2522             if (attrSet < 0)
2523                 ThrowException(format() << "Invalid index \"" << (attrSet) << "\" in set attribute of <input> element");
2524 
2525             channel.mIndex = attrSet;
2526         }
2527     }
2528 
2529     // store, if valid type
2530     if (channel.mType != IT_Invalid)
2531         poChannels.push_back(channel);
2532 
2533     // skip remaining stuff of this element, if any
2534     SkipElement();
2535 }
2536 
2537 // ------------------------------------------------------------------------------------------------
2538 // Reads a <p> primitive index list and assembles the mesh data into the given mesh
2539 size_t ColladaParser::ReadPrimitives(Mesh* pMesh, std::vector<InputChannel>& pPerIndexChannels,
2540     size_t pNumPrimitives, const std::vector<size_t>& pVCount, PrimitiveType pPrimType)
2541 {
2542     // determine number of indices coming per vertex
2543     // find the offset index for all per-vertex channels
2544     size_t numOffsets = 1;
2545     size_t perVertexOffset = SIZE_MAX; // invalid value
2546     for (const InputChannel& channel : pPerIndexChannels)
2547     {
2548         numOffsets = std::max(numOffsets, channel.mOffset + 1);
2549         if (channel.mType == IT_Vertex)
2550             perVertexOffset = channel.mOffset;
2551     }
2552 
2553     // determine the expected number of indices
2554     size_t expectedPointCount = 0;
2555     switch (pPrimType)
2556     {
2557     case Prim_Polylist:
2558     {
2559         for (size_t i : pVCount)
2560             expectedPointCount += i;
2561         break;
2562     }
2563     case Prim_Lines:
2564         expectedPointCount = 2 * pNumPrimitives;
2565         break;
2566     case Prim_Triangles:
2567         expectedPointCount = 3 * pNumPrimitives;
2568         break;
2569     default:
2570         // other primitive types don't state the index count upfront... we need to guess
2571         break;
2572     }
2573 
2574     // and read all indices into a temporary array
2575     std::vector<size_t> indices;
2576     if (expectedPointCount > 0)
2577         indices.reserve(expectedPointCount * numOffsets);
2578 
2579     if (pNumPrimitives > 0) // It is possible to not contain any indices
2580     {
2581         const char* content = GetTextContent();
2582         while (*content != 0)
2583         {
2584             // read a value.
2585             // Hack: (thom) Some exporters put negative indices sometimes. We just try to carry on anyways.
2586             int value = std::max(0, strtol10(content, &content));
2587             indices.push_back(size_t(value));
2588             // skip whitespace after it
2589             SkipSpacesAndLineEnd(&content);
2590         }
2591     }
2592 
2593     // complain if the index count doesn't fit
2594     if (expectedPointCount > 0 && indices.size() != expectedPointCount * numOffsets) {
2595         if (pPrimType == Prim_Lines) {
2596             // HACK: We just fix this number since SketchUp 15.3.331 writes the wrong 'count' for 'lines'
2597             ReportWarning("Expected different index count in <p> element, %zu instead of %zu.", indices.size(), expectedPointCount * numOffsets);
2598             pNumPrimitives = (indices.size() / numOffsets) / 2;
2599         }
2600         else
2601             ThrowException("Expected different index count in <p> element.");
2602 
2603     }
2604     else if (expectedPointCount == 0 && (indices.size() % numOffsets) != 0)
2605         ThrowException("Expected different index count in <p> element.");
2606 
2607     // find the data for all sources
2608     for (std::vector<InputChannel>::iterator it = pMesh->mPerVertexData.begin(); it != pMesh->mPerVertexData.end(); ++it)
2609     {
2610         InputChannel& input = *it;
2611         if (input.mResolved)
2612             continue;
2613 
2614         // find accessor
2615         input.mResolved = &ResolveLibraryReference(mAccessorLibrary, input.mAccessor);
2616         // resolve accessor's data pointer as well, if necessary
2617         const Accessor* acc = input.mResolved;
2618         if (!acc->mData)
2619             acc->mData = &ResolveLibraryReference(mDataLibrary, acc->mSource);
2620     }
2621     // and the same for the per-index channels
2622     for (std::vector<InputChannel>::iterator it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it)
2623     {
2624         InputChannel& input = *it;
2625         if (input.mResolved)
2626             continue;
2627 
2628         // ignore vertex pointer, it doesn't refer to an accessor
2629         if (input.mType == IT_Vertex)
2630         {
2631             // warn if the vertex channel does not refer to the <vertices> element in the same mesh
2632             if (input.mAccessor != pMesh->mVertexID)
2633                 ThrowException("Unsupported vertex referencing scheme.");
2634             continue;
2635         }
2636 
2637         // find accessor
2638         input.mResolved = &ResolveLibraryReference(mAccessorLibrary, input.mAccessor);
2639         // resolve accessor's data pointer as well, if necessary
2640         const Accessor* acc = input.mResolved;
2641         if (!acc->mData)
2642             acc->mData = &ResolveLibraryReference(mDataLibrary, acc->mSource);
2643     }
2644 
2645     // For continued primitives, the given count does not come all in one <p>, but only one primitive per <p>
2646     size_t numPrimitives = pNumPrimitives;
2647     if (pPrimType == Prim_TriFans || pPrimType == Prim_Polygon)
2648         numPrimitives = 1;
2649     // For continued primitives, the given count is actually the number of <p>'s inside the parent tag
2650     if (pPrimType == Prim_TriStrips) {
2651         size_t numberOfVertices = indices.size() / numOffsets;
2652         numPrimitives = numberOfVertices - 2;
2653     }
2654     if (pPrimType == Prim_LineStrip) {
2655         size_t numberOfVertices = indices.size() / numOffsets;
2656         numPrimitives = numberOfVertices - 1;
2657     }
2658 
2659     pMesh->mFaceSize.reserve(numPrimitives);
2660     pMesh->mFacePosIndices.reserve(indices.size() / numOffsets);
2661 
2662     size_t polylistStartVertex = 0;
2663     for (size_t currentPrimitive = 0; currentPrimitive < numPrimitives; currentPrimitive++)
2664     {
2665         // determine number of points for this primitive
2666         size_t numPoints = 0;
2667         switch (pPrimType)
2668         {
2669         case Prim_Lines:
2670             numPoints = 2;
2671             for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
2672                 CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
2673             break;
2674         case Prim_LineStrip:
2675             numPoints = 2;
2676             for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
2677                 CopyVertex(currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
2678             break;
2679         case Prim_Triangles:
2680             numPoints = 3;
2681             for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
2682                 CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
2683             break;
2684         case Prim_TriStrips:
2685             numPoints = 3;
2686             ReadPrimTriStrips(numOffsets, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
2687             break;
2688         case Prim_Polylist:
2689             numPoints = pVCount[currentPrimitive];
2690             for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
2691                 CopyVertex(polylistStartVertex + currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, 0, indices);
2692             polylistStartVertex += numPoints;
2693             break;
2694         case Prim_TriFans:
2695         case Prim_Polygon:
2696             numPoints = indices.size() / numOffsets;
2697             for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
2698                 CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
2699             break;
2700         default:
2701             // LineStrip is not supported due to expected index unmangling
2702             ThrowException("Unsupported primitive type.");
2703             break;
2704         }
2705 
2706         // store the face size to later reconstruct the face from
2707         pMesh->mFaceSize.push_back(numPoints);
2708     }
2709 
2710     // if I ever get my hands on that guy who invented this steaming pile of indirection...
2711     TestClosing("p");
2712     return numPrimitives;
2713 }
2714 
2715 ///@note This function willn't work correctly if both PerIndex and PerVertex channels have same channels.
2716 ///For example if TEXCOORD present in both <vertices> and <polylist> tags this function will create wrong uv coordinates.
2717 ///It's not clear from COLLADA documentation is this allowed or not. For now only exporter fixed to avoid such behavior
2718 void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, Mesh* pMesh, std::vector<InputChannel>& pPerIndexChannels, size_t currentPrimitive, const std::vector<size_t>& indices) {
2719     // calculate the base offset of the vertex whose attributes we ant to copy
2720     size_t baseOffset = currentPrimitive * numOffsets * numPoints + currentVertex * numOffsets;
2721 
2722     // don't overrun the boundaries of the index list
2723     ai_assert((baseOffset + numOffsets - 1) < indices.size());
2724 
2725     // extract per-vertex channels using the global per-vertex offset
2726     for (std::vector<InputChannel>::iterator it = pMesh->mPerVertexData.begin(); it != pMesh->mPerVertexData.end(); ++it)
2727         ExtractDataObjectFromChannel(*it, indices[baseOffset + perVertexOffset], pMesh);
2728     // and extract per-index channels using there specified offset
2729     for (std::vector<InputChannel>::iterator it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it)
2730         ExtractDataObjectFromChannel(*it, indices[baseOffset + it->mOffset], pMesh);
2731 
2732     // store the vertex-data index for later assignment of bone vertex weights
2733     pMesh->mFacePosIndices.push_back(indices[baseOffset + perVertexOffset]);
2734 }
2735 
2736 void ColladaParser::ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Mesh* pMesh, std::vector<InputChannel>& pPerIndexChannels, size_t currentPrimitive, const std::vector<size_t>& indices) {
2737     if (currentPrimitive % 2 != 0) {
2738         //odd tristrip triangles need their indices mangled, to preserve winding direction
2739         CopyVertex(1, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
2740         CopyVertex(0, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
2741         CopyVertex(2, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
2742     }
2743     else {//for non tristrips or even tristrip triangles
2744         CopyVertex(0, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
2745         CopyVertex(1, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
2746         CopyVertex(2, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
2747     }
2748 }
2749 
2750 // ------------------------------------------------------------------------------------------------
2751 // Extracts a single object from an input channel and stores it in the appropriate mesh data array
2752 void ColladaParser::ExtractDataObjectFromChannel(const InputChannel& pInput, size_t pLocalIndex, Mesh* pMesh)
2753 {
2754     // ignore vertex referrer - we handle them that separate
2755     if (pInput.mType == IT_Vertex)
2756         return;
2757 
2758     const Accessor& acc = *pInput.mResolved;
2759     if (pLocalIndex >= acc.mCount)
2760         ThrowException(format() << "Invalid data index (" << pLocalIndex << "/" << acc.mCount << ") in primitive specification");
2761 
2762     // get a pointer to the start of the data object referred to by the accessor and the local index
2763     const ai_real* dataObject = &(acc.mData->mValues[0]) + acc.mOffset + pLocalIndex * acc.mStride;
2764 
2765     // assemble according to the accessors component sub-offset list. We don't care, yet,
2766     // what kind of object exactly we're extracting here
2767     ai_real obj[4];
2768     for (size_t c = 0; c < 4; ++c)
2769         obj[c] = dataObject[acc.mSubOffset[c]];
2770 
2771     // now we reinterpret it according to the type we're reading here
2772     switch (pInput.mType)
2773     {
2774     case IT_Position: // ignore all position streams except 0 - there can be only one position
2775         if (pInput.mIndex == 0)
2776             pMesh->mPositions.push_back(aiVector3D(obj[0], obj[1], obj[2]));
2777         else
2778             ASSIMP_LOG_ERROR("Collada: just one vertex position stream supported");
2779         break;
2780     case IT_Normal:
2781         // pad to current vertex count if necessary
2782         if (pMesh->mNormals.size() < pMesh->mPositions.size() - 1)
2783             pMesh->mNormals.insert(pMesh->mNormals.end(), pMesh->mPositions.size() - pMesh->mNormals.size() - 1, aiVector3D(0, 1, 0));
2784 
2785         // ignore all normal streams except 0 - there can be only one normal
2786         if (pInput.mIndex == 0)
2787             pMesh->mNormals.push_back(aiVector3D(obj[0], obj[1], obj[2]));
2788         else
2789             ASSIMP_LOG_ERROR("Collada: just one vertex normal stream supported");
2790         break;
2791     case IT_Tangent:
2792         // pad to current vertex count if necessary
2793         if (pMesh->mTangents.size() < pMesh->mPositions.size() - 1)
2794             pMesh->mTangents.insert(pMesh->mTangents.end(), pMesh->mPositions.size() - pMesh->mTangents.size() - 1, aiVector3D(1, 0, 0));
2795 
2796         // ignore all tangent streams except 0 - there can be only one tangent
2797         if (pInput.mIndex == 0)
2798             pMesh->mTangents.push_back(aiVector3D(obj[0], obj[1], obj[2]));
2799         else
2800             ASSIMP_LOG_ERROR("Collada: just one vertex tangent stream supported");
2801         break;
2802     case IT_Bitangent:
2803         // pad to current vertex count if necessary
2804         if (pMesh->mBitangents.size() < pMesh->mPositions.size() - 1)
2805             pMesh->mBitangents.insert(pMesh->mBitangents.end(), pMesh->mPositions.size() - pMesh->mBitangents.size() - 1, aiVector3D(0, 0, 1));
2806 
2807         // ignore all bitangent streams except 0 - there can be only one bitangent
2808         if (pInput.mIndex == 0)
2809             pMesh->mBitangents.push_back(aiVector3D(obj[0], obj[1], obj[2]));
2810         else
2811             ASSIMP_LOG_ERROR("Collada: just one vertex bitangent stream supported");
2812         break;
2813     case IT_Texcoord:
2814         // up to 4 texture coord sets are fine, ignore the others
2815         if (pInput.mIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS)
2816         {
2817             // pad to current vertex count if necessary
2818             if (pMesh->mTexCoords[pInput.mIndex].size() < pMesh->mPositions.size() - 1)
2819                 pMesh->mTexCoords[pInput.mIndex].insert(pMesh->mTexCoords[pInput.mIndex].end(),
2820                     pMesh->mPositions.size() - pMesh->mTexCoords[pInput.mIndex].size() - 1, aiVector3D(0, 0, 0));
2821 
2822             pMesh->mTexCoords[pInput.mIndex].push_back(aiVector3D(obj[0], obj[1], obj[2]));
2823             if (0 != acc.mSubOffset[2] || 0 != acc.mSubOffset[3]) /* hack ... consider cleaner solution */
2824                 pMesh->mNumUVComponents[pInput.mIndex] = 3;
2825         }
2826         else
2827         {
2828             ASSIMP_LOG_ERROR("Collada: too many texture coordinate sets. Skipping.");
2829         }
2830         break;
2831     case IT_Color:
2832         // up to 4 color sets are fine, ignore the others
2833         if (pInput.mIndex < AI_MAX_NUMBER_OF_COLOR_SETS)
2834         {
2835             // pad to current vertex count if necessary
2836             if (pMesh->mColors[pInput.mIndex].size() < pMesh->mPositions.size() - 1)
2837                 pMesh->mColors[pInput.mIndex].insert(pMesh->mColors[pInput.mIndex].end(),
2838                     pMesh->mPositions.size() - pMesh->mColors[pInput.mIndex].size() - 1, aiColor4D(0, 0, 0, 1));
2839 
2840             aiColor4D result(0, 0, 0, 1);
2841             for (size_t i = 0; i < pInput.mResolved->mSize; ++i)
2842             {
2843                 result[static_cast<unsigned int>(i)] = obj[pInput.mResolved->mSubOffset[i]];
2844             }
2845             pMesh->mColors[pInput.mIndex].push_back(result);
2846         }
2847         else
2848         {
2849             ASSIMP_LOG_ERROR("Collada: too many vertex color sets. Skipping.");
2850         }
2851 
2852         break;
2853     default:
2854         // IT_Invalid and IT_Vertex
2855         ai_assert(false && "shouldn't ever get here");
2856     }
2857 }
2858 
2859 // ------------------------------------------------------------------------------------------------
2860 // Reads the library of node hierarchies and scene parts
2861 void ColladaParser::ReadSceneLibrary()
2862 {
2863     if (mReader->isEmptyElement())
2864         return;
2865 
2866     while (mReader->read())
2867     {
2868         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
2869         {
2870             // a visual scene - generate root node under its ID and let ReadNode() do the recursive work
2871             if (IsElement("visual_scene"))
2872             {
2873                 // read ID. Is optional according to the spec, but how on earth should a scene_instance refer to it then?
2874                 int indexID = GetAttribute("id");
2875                 const char* attrID = mReader->getAttributeValue(indexID);
2876 
2877                 // read name if given.
2878                 int indexName = TestAttribute("name");
2879                 const char* attrName = "unnamed";
2880                 if (indexName > -1)
2881                     attrName = mReader->getAttributeValue(indexName);
2882 
2883                 // create a node and store it in the library under its ID
2884                 Node* node = new Node;
2885                 node->mID = attrID;
2886                 node->mName = attrName;
2887                 mNodeLibrary[node->mID] = node;
2888 
2889                 ReadSceneNode(node);
2890             }
2891             else
2892             {
2893                 // ignore the rest
2894                 SkipElement();
2895             }
2896         }
2897         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
2898         {
2899             if (strcmp(mReader->getNodeName(), "library_visual_scenes") == 0)
2900                 //ThrowException( "Expected end of \"library_visual_scenes\" element.");
2901 
2902                 break;
2903         }
2904     }
2905 }
2906 
2907 // ------------------------------------------------------------------------------------------------
2908 // Reads a scene node's contents including children and stores it in the given node
2909 void ColladaParser::ReadSceneNode(Node* pNode)
2910 {
2911     // quit immediately on <bla/> elements
2912     if (mReader->isEmptyElement())
2913         return;
2914 
2915     while (mReader->read())
2916     {
2917         if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
2918         {
2919             if (IsElement("node"))
2920             {
2921                 Node* child = new Node;
2922                 int attrID = TestAttribute("id");
2923                 if (attrID > -1)
2924                     child->mID = mReader->getAttributeValue(attrID);
2925                 int attrSID = TestAttribute("sid");
2926                 if (attrSID > -1)
2927                     child->mSID = mReader->getAttributeValue(attrSID);
2928 
2929                 int attrName = TestAttribute("name");
2930                 if (attrName > -1)
2931                     child->mName = mReader->getAttributeValue(attrName);
2932 
2933                 // TODO: (thom) support SIDs
2934                 // ai_assert( TestAttribute( "sid") == -1);
2935 
2936                 if (pNode)
2937                 {
2938                     pNode->mChildren.push_back(child);
2939                     child->mParent = pNode;
2940                 }
2941                 else
2942                 {
2943                     // no parent node given, probably called from <library_nodes> element.
2944                     // create new node in node library
2945                     mNodeLibrary[child->mID] = child;
2946                 }
2947 
2948                 // read on recursively from there
2949                 ReadSceneNode(child);
2950                 continue;
2951             }
2952             // For any further stuff we need a valid node to work on
2953             else if (!pNode)
2954                 continue;
2955 
2956             if (IsElement("lookat"))
2957                 ReadNodeTransformation(pNode, TF_LOOKAT);
2958             else if (IsElement("matrix"))
2959                 ReadNodeTransformation(pNode, TF_MATRIX);
2960             else if (IsElement("rotate"))
2961                 ReadNodeTransformation(pNode, TF_ROTATE);
2962             else if (IsElement("scale"))
2963                 ReadNodeTransformation(pNode, TF_SCALE);
2964             else if (IsElement("skew"))
2965                 ReadNodeTransformation(pNode, TF_SKEW);
2966             else if (IsElement("translate"))
2967                 ReadNodeTransformation(pNode, TF_TRANSLATE);
2968             else if (IsElement("render") && pNode->mParent == NULL && 0 == pNode->mPrimaryCamera.length())
2969             {
2970                 // ... scene evaluation or, in other words, postprocessing pipeline,
2971                 // or, again in other words, a turing-complete description how to
2972                 // render a Collada scene. The only thing that is interesting for
2973                 // us is the primary camera.
2974                 int attrId = TestAttribute("camera_node");
2975                 if (-1 != attrId)
2976                 {
2977                     const char* s = mReader->getAttributeValue(attrId);
2978                     if (s[0] != '#')
2979                         ASSIMP_LOG_ERROR("Collada: Unresolved reference format of camera");
2980                     else
2981                         pNode->mPrimaryCamera = s + 1;
2982                 }
2983             }
2984             else if (IsElement("instance_node"))
2985             {
2986                 // find the node in the library
2987                 int attrID = TestAttribute("url");
2988                 if (attrID != -1)
2989                 {
2990                     const char* s = mReader->getAttributeValue(attrID);
2991                     if (s[0] != '#')
2992                         ASSIMP_LOG_ERROR("Collada: Unresolved reference format of node");
2993                     else
2994                     {
2995                         pNode->mNodeInstances.push_back(NodeInstance());
2996                         pNode->mNodeInstances.back().mNode = s + 1;
2997                     }
2998                 }
2999             }
3000             else if (IsElement("instance_geometry") || IsElement("instance_controller"))
3001             {
3002                 // Reference to a mesh or controller, with possible material associations
3003                 ReadNodeGeometry(pNode);
3004             }
3005             else if (IsElement("instance_light"))
3006             {
3007                 // Reference to a light, name given in 'url' attribute
3008                 int attrID = TestAttribute("url");
3009                 if (-1 == attrID)
3010                     ASSIMP_LOG_WARN("Collada: Expected url attribute in <instance_light> element");
3011                 else
3012                 {
3013                     const char* url = mReader->getAttributeValue(attrID);
3014                     if (url[0] != '#')
3015                         ThrowException("Unknown reference format in <instance_light> element");
3016 
3017                     pNode->mLights.push_back(LightInstance());
3018                     pNode->mLights.back().mLight = url + 1;
3019                 }
3020             }
3021             else if (IsElement("instance_camera"))
3022             {
3023                 // Reference to a camera, name given in 'url' attribute
3024                 int attrID = TestAttribute("url");
3025                 if (-1 == attrID)
3026                     ASSIMP_LOG_WARN("Collada: Expected url attribute in <instance_camera> element");
3027                 else
3028                 {
3029                     const char* url = mReader->getAttributeValue(attrID);
3030                     if (url[0] != '#')
3031                         ThrowException("Unknown reference format in <instance_camera> element");
3032 
3033                     pNode->mCameras.push_back(CameraInstance());
3034                     pNode->mCameras.back().mCamera = url + 1;
3035                 }
3036             }
3037             else
3038             {
3039                 // skip everything else for the moment
3040                 SkipElement();
3041             }
3042         }
3043         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
3044             break;
3045         }
3046     }
3047 }
3048 
3049 // ------------------------------------------------------------------------------------------------
3050 // Reads a node transformation entry of the given type and adds it to the given node's transformation list.
3051 void ColladaParser::ReadNodeTransformation(Node* pNode, TransformType pType)
3052 {
3053     if (mReader->isEmptyElement())
3054         return;
3055 
3056     std::string tagName = mReader->getNodeName();
3057 
3058     Transform tf;
3059     tf.mType = pType;
3060 
3061     // read SID
3062     int indexSID = TestAttribute("sid");
3063     if (indexSID >= 0)
3064         tf.mID = mReader->getAttributeValue(indexSID);
3065 
3066     // how many parameters to read per transformation type
3067     static const unsigned int sNumParameters[] = { 9, 4, 3, 3, 7, 16 };
3068     const char* content = GetTextContent();
3069 
3070     // read as many parameters and store in the transformation
3071     for (unsigned int a = 0; a < sNumParameters[pType]; a++)
3072     {
3073         // read a number
3074         content = fast_atoreal_move<ai_real>(content, tf.f[a]);
3075         // skip whitespace after it
3076         SkipSpacesAndLineEnd(&content);
3077     }
3078 
3079     // place the transformation at the queue of the node
3080     pNode->mTransforms.push_back(tf);
3081 
3082     // and consume the closing tag
3083     TestClosing(tagName.c_str());
3084 }
3085 
3086 // ------------------------------------------------------------------------------------------------
3087 // Processes bind_vertex_input and bind elements
3088 void ColladaParser::ReadMaterialVertexInputBinding(Collada::SemanticMappingTable& tbl)
3089 {
3090     while (mReader->read())
3091     {
3092         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
3093             if (IsElement("bind_vertex_input"))
3094             {
3095                 Collada::InputSemanticMapEntry vn;
3096 
3097                 // effect semantic
3098                 int n = GetAttribute("semantic");
3099                 std::string s = mReader->getAttributeValue(n);
3100 
3101                 // input semantic
3102                 n = GetAttribute("input_semantic");
3103                 vn.mType = GetTypeForSemantic(mReader->getAttributeValue(n));
3104 
3105                 // index of input set
3106                 n = TestAttribute("input_set");
3107                 if (-1 != n)
3108                     vn.mSet = mReader->getAttributeValueAsInt(n);
3109 
3110                 tbl.mMap[s] = vn;
3111             }
3112             else if (IsElement("bind")) {
3113                 ASSIMP_LOG_WARN("Collada: Found unsupported <bind> element");
3114             }
3115         }
3116         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
3117             if (strcmp(mReader->getNodeName(), "instance_material") == 0)
3118                 break;
3119         }
3120     }
3121 }
3122 
3123 void ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem& zip_archive)
3124 {
3125     // Attempt to load any undefined Collada::Image in ImageLibrary
3126     for (ImageLibrary::iterator it = mImageLibrary.begin(); it != mImageLibrary.end(); ++it) {
3127         Collada::Image &image = (*it).second;
3128 
3129         if (image.mImageData.empty()) {
3130             std::unique_ptr<IOStream> image_file(zip_archive.Open(image.mFileName.c_str()));
3131             if (image_file) {
3132                 image.mImageData.resize(image_file->FileSize());
3133                 image_file->Read(image.mImageData.data(), image_file->FileSize(), 1);
3134                 image.mEmbeddedFormat = BaseImporter::GetExtension(image.mFileName);
3135                 if (image.mEmbeddedFormat == "jpeg") {
3136                     image.mEmbeddedFormat = "jpg";
3137                 }
3138             }
3139         }
3140     }
3141 }
3142 
3143 // ------------------------------------------------------------------------------------------------
3144 // Reads a mesh reference in a node and adds it to the node's mesh list
3145 void ColladaParser::ReadNodeGeometry(Node* pNode)
3146 {
3147     // referred mesh is given as an attribute of the <instance_geometry> element
3148     int attrUrl = GetAttribute("url");
3149     const char* url = mReader->getAttributeValue(attrUrl);
3150     if (url[0] != '#')
3151         ThrowException("Unknown reference format");
3152 
3153     Collada::MeshInstance instance;
3154     instance.mMeshOrController = url + 1; // skipping the leading #
3155 
3156     if (!mReader->isEmptyElement())
3157     {
3158         // read material associations. Ignore additional elements in between
3159         while (mReader->read())
3160         {
3161             if (mReader->getNodeType() == irr::io::EXN_ELEMENT)
3162             {
3163                 if (IsElement("instance_material"))
3164                 {
3165                     // read ID of the geometry subgroup and the target material
3166                     int attrGroup = GetAttribute("symbol");
3167                     std::string group = mReader->getAttributeValue(attrGroup);
3168                     int attrMaterial = GetAttribute("target");
3169                     const char* urlMat = mReader->getAttributeValue(attrMaterial);
3170                     Collada::SemanticMappingTable s;
3171                     if (urlMat[0] == '#')
3172                         urlMat++;
3173 
3174                     s.mMatName = urlMat;
3175 
3176                     // resolve further material details + THIS UGLY AND NASTY semantic mapping stuff
3177                     if (!mReader->isEmptyElement())
3178                         ReadMaterialVertexInputBinding(s);
3179 
3180                     // store the association
3181                     instance.mMaterials[group] = s;
3182                 }
3183             }
3184             else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
3185             {
3186                 if (strcmp(mReader->getNodeName(), "instance_geometry") == 0
3187                     || strcmp(mReader->getNodeName(), "instance_controller") == 0)
3188                     break;
3189             }
3190         }
3191     }
3192 
3193     // store it
3194     pNode->mMeshes.push_back(instance);
3195 }
3196 
3197 // ------------------------------------------------------------------------------------------------
3198 // Reads the collada scene
3199 void ColladaParser::ReadScene()
3200 {
3201     if (mReader->isEmptyElement())
3202         return;
3203 
3204     while (mReader->read())
3205     {
3206         if (mReader->getNodeType() == irr::io::EXN_ELEMENT) {
3207             if (IsElement("instance_visual_scene"))
3208             {
3209                 // should be the first and only occurrence
3210                 if (mRootNode)
3211                     ThrowException("Invalid scene containing multiple root nodes in <instance_visual_scene> element");
3212 
3213                 // read the url of the scene to instance. Should be of format "#some_name"
3214                 int urlIndex = GetAttribute("url");
3215                 const char* url = mReader->getAttributeValue(urlIndex);
3216                 if (url[0] != '#')
3217                     ThrowException("Unknown reference format in <instance_visual_scene> element");
3218 
3219                 // find the referred scene, skip the leading #
3220                 NodeLibrary::const_iterator sit = mNodeLibrary.find(url + 1);
3221                 if (sit == mNodeLibrary.end())
3222                     ThrowException("Unable to resolve visual_scene reference \"" + std::string(url) + "\" in <instance_visual_scene> element.");
3223                 mRootNode = sit->second;
3224             }
3225             else {
3226                 SkipElement();
3227             }
3228         }
3229         else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
3230             break;
3231         }
3232     }
3233 }
3234 
3235 // ------------------------------------------------------------------------------------------------
3236 // Aborts the file reading with an exception
3237 AI_WONT_RETURN void ColladaParser::ThrowException(const std::string& pError) const {
3238     throw DeadlyImportError(format() << "Collada: " << mFileName << " - " << pError);
3239 }
3240 
3241 void ColladaParser::ReportWarning(const char* msg, ...) {
3242     ai_assert(nullptr != msg);
3243 
3244     va_list args;
3245     va_start(args, msg);
3246 
3247     char szBuffer[3000];
3248     const int iLen = vsprintf(szBuffer, msg, args);
3249     ai_assert(iLen > 0);
3250 
3251     va_end(args);
3252     ASSIMP_LOG_WARN_F("Validation warning: ", std::string(szBuffer, iLen));
3253 }
3254 
3255 // ------------------------------------------------------------------------------------------------
3256 // Skips all data until the end node of the current element
3257 void ColladaParser::SkipElement() {
3258     // nothing to skip if it's an <element />
3259     if (mReader->isEmptyElement()) {
3260         return;
3261     }
3262 
3263     // reroute
3264     SkipElement(mReader->getNodeName());
3265 }
3266 
3267 // ------------------------------------------------------------------------------------------------
3268 // Skips all data until the end node of the given element
3269 void ColladaParser::SkipElement(const char* pElement) {
3270     // copy the current node's name because it'a pointer to the reader's internal buffer,
3271     // which is going to change with the upcoming parsing
3272     std::string element = pElement;
3273     while (mReader->read()) {
3274         if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) {
3275             if (mReader->getNodeName() == element) {
3276                 break;
3277             }
3278         }
3279     }
3280 }
3281 
3282 // ------------------------------------------------------------------------------------------------
3283 // Tests for an opening element of the given name, throws an exception if not found
3284 void ColladaParser::TestOpening(const char* pName) {
3285     // read element start
3286     if (!mReader->read()) {
3287         ThrowException(format() << "Unexpected end of file while beginning of <" << pName << "> element.");
3288     }
3289     // whitespace in front is ok, just read again if found
3290     if (mReader->getNodeType() == irr::io::EXN_TEXT) {
3291         if (!mReader->read()) {
3292             ThrowException(format() << "Unexpected end of file while reading beginning of <" << pName << "> element.");
3293         }
3294     }
3295 
3296     if (mReader->getNodeType() != irr::io::EXN_ELEMENT || strcmp(mReader->getNodeName(), pName) != 0) {
3297         ThrowException(format() << "Expected start of <" << pName << "> element.");
3298     }
3299 }
3300 
3301 // ------------------------------------------------------------------------------------------------
3302 // Tests for the closing tag of the given element, throws an exception if not found
3303 void ColladaParser::TestClosing(const char* pName) {
3304     // check if we have an empty (self-closing) element
3305     if (mReader->isEmptyElement()) {
3306         return;
3307     }
3308 
3309     // check if we're already on the closing tag and return right away
3310     if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END && strcmp(mReader->getNodeName(), pName) == 0) {
3311         return;
3312     }
3313 
3314     // if not, read some more
3315     if (!mReader->read()) {
3316         ThrowException(format() << "Unexpected end of file while reading end of <" << pName << "> element.");
3317     }
3318     // whitespace in front is ok, just read again if found
3319     if (mReader->getNodeType() == irr::io::EXN_TEXT) {
3320         if (!mReader->read()) {
3321             ThrowException(format() << "Unexpected end of file while reading end of <" << pName << "> element.");
3322         }
3323     }
3324 
3325     // but this has the be the closing tag, or we're lost
3326     if (mReader->getNodeType() != irr::io::EXN_ELEMENT_END || strcmp(mReader->getNodeName(), pName) != 0) {
3327         ThrowException(format() << "Expected end of <" << pName << "> element.");
3328     }
3329 }
3330 
3331 // ------------------------------------------------------------------------------------------------
3332 // Returns the index of the named attribute or -1 if not found. Does not throw, therefore useful for optional attributes
3333 int ColladaParser::GetAttribute(const char* pAttr) const {
3334     int index = TestAttribute(pAttr);
3335     if (index != -1) {
3336         return index;
3337     }
3338 
3339     // attribute not found -> throw an exception
3340     ThrowException(format() << "Expected attribute \"" << pAttr << "\" for element <" << mReader->getNodeName() << ">.");
3341     return -1;
3342 }
3343 
3344 // ------------------------------------------------------------------------------------------------
3345 // Tests the present element for the presence of one attribute, returns its index or throws an exception if not found
3346 int ColladaParser::TestAttribute(const char* pAttr) const
3347 {
3348     for (int a = 0; a < mReader->getAttributeCount(); a++)
3349         if (strcmp(mReader->getAttributeName(a), pAttr) == 0)
3350             return a;
3351 
3352     return -1;
3353 }
3354 
3355 // ------------------------------------------------------------------------------------------------
3356 // Reads the text contents of an element, throws an exception if not given. Skips leading whitespace.
3357 const char* ColladaParser::GetTextContent()
3358 {
3359     const char* sz = TestTextContent();
3360     if (!sz) {
3361         ThrowException("Invalid contents in element \"n\".");
3362     }
3363     return sz;
3364 }
3365 
3366 // ------------------------------------------------------------------------------------------------
3367 // Reads the text contents of an element, returns NULL if not given. Skips leading whitespace.
3368 const char* ColladaParser::TestTextContent()
3369 {
3370     // present node should be the beginning of an element
3371     if (mReader->getNodeType() != irr::io::EXN_ELEMENT || mReader->isEmptyElement())
3372         return NULL;
3373 
3374     // read contents of the element
3375     if (!mReader->read())
3376         return NULL;
3377     if (mReader->getNodeType() != irr::io::EXN_TEXT && mReader->getNodeType() != irr::io::EXN_CDATA)
3378         return NULL;
3379 
3380     // skip leading whitespace
3381     const char* text = mReader->getNodeData();
3382     SkipSpacesAndLineEnd(&text);
3383 
3384     return text;
3385 }
3386 
3387 // ------------------------------------------------------------------------------------------------
3388 // Calculates the resulting transformation fromm all the given transform steps
3389 aiMatrix4x4 ColladaParser::CalculateResultTransform(const std::vector<Transform>& pTransforms) const
3390 {
3391     aiMatrix4x4 res;
3392 
3393     for (std::vector<Transform>::const_iterator it = pTransforms.begin(); it != pTransforms.end(); ++it)
3394     {
3395         const Transform& tf = *it;
3396         switch (tf.mType)
3397         {
3398         case TF_LOOKAT:
3399         {
3400             aiVector3D pos(tf.f[0], tf.f[1], tf.f[2]);
3401             aiVector3D dstPos(tf.f[3], tf.f[4], tf.f[5]);
3402             aiVector3D up = aiVector3D(tf.f[6], tf.f[7], tf.f[8]).Normalize();
3403             aiVector3D dir = aiVector3D(dstPos - pos).Normalize();
3404             aiVector3D right = (dir ^ up).Normalize();
3405 
3406             res *= aiMatrix4x4(
3407                 right.x, up.x, -dir.x, pos.x,
3408                 right.y, up.y, -dir.y, pos.y,
3409                 right.z, up.z, -dir.z, pos.z,
3410                 0, 0, 0, 1);
3411             break;
3412         }
3413         case TF_ROTATE:
3414         {
3415             aiMatrix4x4 rot;
3416             ai_real angle = tf.f[3] * ai_real(AI_MATH_PI) / ai_real(180.0);
3417             aiVector3D axis(tf.f[0], tf.f[1], tf.f[2]);
3418             aiMatrix4x4::Rotation(angle, axis, rot);
3419             res *= rot;
3420             break;
3421         }
3422         case TF_TRANSLATE:
3423         {
3424             aiMatrix4x4 trans;
3425             aiMatrix4x4::Translation(aiVector3D(tf.f[0], tf.f[1], tf.f[2]), trans);
3426             res *= trans;
3427             break;
3428         }
3429         case TF_SCALE:
3430         {
3431             aiMatrix4x4 scale(tf.f[0], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[1], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[2], 0.0f,
3432                 0.0f, 0.0f, 0.0f, 1.0f);
3433             res *= scale;
3434             break;
3435         }
3436         case TF_SKEW:
3437             // TODO: (thom)
3438             ai_assert(false);
3439             break;
3440         case TF_MATRIX:
3441         {
3442             aiMatrix4x4 mat(tf.f[0], tf.f[1], tf.f[2], tf.f[3], tf.f[4], tf.f[5], tf.f[6], tf.f[7],
3443                 tf.f[8], tf.f[9], tf.f[10], tf.f[11], tf.f[12], tf.f[13], tf.f[14], tf.f[15]);
3444             res *= mat;
3445             break;
3446         }
3447         default:
3448             ai_assert(false);
3449             break;
3450         }
3451     }
3452 
3453     return res;
3454 }
3455 
3456 // ------------------------------------------------------------------------------------------------
3457 // Determines the input data type for the given semantic string
3458 Collada::InputType ColladaParser::GetTypeForSemantic(const std::string& semantic)
3459 {
3460     if (semantic.empty()) {
3461         ASSIMP_LOG_WARN("Vertex input type is empty.");
3462         return IT_Invalid;
3463     }
3464 
3465     if (semantic == "POSITION")
3466         return IT_Position;
3467     else if (semantic == "TEXCOORD")
3468         return IT_Texcoord;
3469     else if (semantic == "NORMAL")
3470         return IT_Normal;
3471     else if (semantic == "COLOR")
3472         return IT_Color;
3473     else if (semantic == "VERTEX")
3474         return IT_Vertex;
3475     else if (semantic == "BINORMAL" || semantic == "TEXBINORMAL")
3476         return IT_Bitangent;
3477     else if (semantic == "TANGENT" || semantic == "TEXTANGENT")
3478         return IT_Tangent;
3479 
3480     ASSIMP_LOG_WARN_F("Unknown vertex input type \"", semantic, "\". Ignoring.");
3481     return IT_Invalid;
3482 }
3483 
3484 #endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER
3485