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