1/*
2---------------------------------------------------------------------------
3Open Asset Import Library (assimp)
4---------------------------------------------------------------------------
5
6Copyright (c) 2006-2025, assimp team
7
8All rights reserved.
9
10Redistribution and use of this software in source and binary forms,
11with or without modification, are permitted provided that the following
12conditions are met:
13
14* Redistributions of source code must retain the above
15copyright notice, this list of conditions and the
16following disclaimer.
17
18* Redistributions in binary form must reproduce the above
19copyright notice, this list of conditions and the
20following disclaimer in the documentation and/or other
21materials provided with the distribution.
22
23* Neither the name of the assimp team, nor the names of its
24contributors may be used to endorse or promote products
25derived from this software without specific prior
26written permission of the assimp team.
27
28THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39---------------------------------------------------------------------------
40*/
41
42/** @file ColladaParser.cpp
43 * @brief Implementation of the Collada parser helper
44 */
45
46#ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER
47
48#include "ColladaParser.h"
49#include <assimp/ParsingUtils.h>
50#include <assimp/StringUtils.h>
51#include <assimp/ZipArchiveIOSystem.h>
52#include <assimp/commonMetaData.h>
53#include <assimp/fast_atof.h>
54#include <assimp/light.h>
55#include <assimp/DefaultLogger.hpp>
56#include <assimp/IOSystem.hpp>
57#include <memory>
58#include <utility>
59
60using namespace Assimp;
61using namespace Assimp::Collada;
62using namespace Assimp::Formatter;
63
64// ------------------------------------------------------------------------------------------------
65static void ReportWarning(const char *msg, ...) {
66 ai_assert(nullptr != msg);
67
68 va_list args;
69 va_start(args, msg);
70
71 char szBuffer[3000];
72 const int iLen = vsnprintf(s: szBuffer, maxlen: sizeof(szBuffer), format: msg, arg: args);
73 ai_assert(iLen > 0);
74
75 va_end(args);
76 ASSIMP_LOG_WARN("Validation warning: ", std::string(szBuffer, iLen));
77}
78
79// ------------------------------------------------------------------------------------------------
80static bool FindCommonKey(const std::string &collada_key, const MetaKeyPairVector &key_renaming, size_t &found_index) {
81 for (size_t i = 0; i < key_renaming.size(); ++i) {
82 if (key_renaming[i].first == collada_key) {
83 found_index = i;
84 return true;
85 }
86 }
87 found_index = std::numeric_limits<size_t>::max();
88
89 return false;
90}
91
92// ------------------------------------------------------------------------------------------------
93static void readUrlAttribute(XmlNode &node, std::string &url) {
94 url.clear();
95 if (!XmlParser::getStdStrAttribute(xmlNode&: node, name: "url", val&: url)) {
96 return;
97 }
98 if (url[0] != '#') {
99 throw DeadlyImportError("Unknown reference format");
100 }
101 url = url.c_str() + 1;
102}
103
104// ------------------------------------------------------------------------------------------------
105// Reads a node transformation entry of the given type and adds it to the given node's transformation list.
106static void ReadNodeTransformation(XmlNode &node, Node *pNode, TransformType pType) {
107 if (node.empty()) {
108 return;
109 }
110
111 std::string tagName = node.name();
112
113 Transform tf;
114 tf.mType = pType;
115
116 // read SID
117 if (XmlParser::hasAttribute(xmlNode&: node, name: "sid")) {
118 XmlParser::getStdStrAttribute(xmlNode&: node, name: "sid", val&: tf.mID);
119 }
120
121 // how many parameters to read per transformation type
122 static constexpr unsigned int sNumParameters[] = { 9, 4, 3, 3, 7, 16 };
123 std::string value;
124 XmlParser::getValueAsString(node, text&: value);
125 const char *content = value.c_str();
126 const char *end = value.c_str() + value.size();
127 // read as many parameters and store in the transformation
128 for (unsigned int a = 0; a < sNumParameters[pType]; a++) {
129 // skip whitespace before the number
130 SkipSpacesAndLineEnd(inout: &content, end);
131 // read a number
132 content = fast_atoreal_move<ai_real>(c: content, out&: tf.f[a]);
133 }
134
135 // place the transformation at the queue of the node
136 pNode->mTransforms.push_back(x: tf);
137}
138
139// ------------------------------------------------------------------------------------------------
140// Reads a single string metadata item
141static void ReadMetaDataItem(XmlNode &node, ColladaParser::StringMetaData &metadata) {
142 const MetaKeyPairVector &key_renaming = GetColladaAssimpMetaKeysCamelCase();
143 const std::string name = node.name();
144 if (name.empty()) {
145 return;
146 }
147
148 std::string v;
149 if (!XmlParser::getValueAsString(node, text&: v)) {
150 return;
151 }
152
153 v = ai_trim(s&: v);
154 aiString aistr;
155 aistr.Set(v);
156
157 std::string camel_key_str(name);
158 ToCamelCase(text&: camel_key_str);
159
160 size_t found_index;
161 if (FindCommonKey(collada_key: camel_key_str, key_renaming, found_index)) {
162 metadata.emplace(args: key_renaming[found_index].second, args&: aistr);
163 } else {
164 metadata.emplace(args&: camel_key_str, args&: aistr);
165 }
166}
167
168// ------------------------------------------------------------------------------------------------
169// Reads an animation sampler into the given anim channel
170static void ReadAnimationSampler(const XmlNode &node, AnimationChannel &pChannel) {
171 for (XmlNode &currentNode : node.children()) {
172 const std::string &currentName = currentNode.name();
173 if (currentName == "input") {
174 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "semantic")) {
175 std::string semantic, sourceAttr;
176 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "semantic", val&: semantic);
177 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "source")) {
178 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "source", val&: sourceAttr);
179 const char *source = sourceAttr.c_str();
180 if (source[0] != '#') {
181 throw DeadlyImportError("Unsupported URL format");
182 }
183 source++;
184
185 if (semantic == "INPUT") {
186 pChannel.mSourceTimes = source;
187 } else if (semantic == "OUTPUT") {
188 pChannel.mSourceValues = source;
189 } else if (semantic == "IN_TANGENT") {
190 pChannel.mInTanValues = source;
191 } else if (semantic == "OUT_TANGENT") {
192 pChannel.mOutTanValues = source;
193 } else if (semantic == "INTERPOLATION") {
194 pChannel.mInterpolationValues = source;
195 }
196 }
197 }
198 }
199 }
200}
201
202// ------------------------------------------------------------------------------------------------
203// Reads the joint definitions for the given controller
204static void ReadControllerJoints(const XmlNode &node, Controller &pController) {
205 for (XmlNode &currentNode : node.children()) {
206 const std::string &currentName = currentNode.name();
207 if (currentName == "input") {
208 const char *attrSemantic = currentNode.attribute(name: "semantic").as_string();
209 const char *attrSource = currentNode.attribute(name: "source").as_string();
210 if (attrSource[0] != '#') {
211 throw DeadlyImportError("Unsupported URL format in \"", attrSource, "\" in source attribute of <joints> data <input> element");
212 }
213 ++attrSource;
214 // parse source URL to corresponding source
215 if (strcmp(s1: attrSemantic, s2: "JOINT") == 0) {
216 pController.mJointNameSource = attrSource;
217 } else if (strcmp(s1: attrSemantic, s2: "INV_BIND_MATRIX") == 0) {
218 pController.mJointOffsetMatrixSource = attrSource;
219 } else {
220 throw DeadlyImportError("Unknown semantic \"", attrSemantic, "\" in <joints> data <input> element");
221 }
222 }
223 }
224}
225
226// ------------------------------------------------------------------------------------------------
227static void ReadControllerWeightsInput(const XmlNode &currentNode, Controller &pController) {
228 InputChannel channel;
229
230 const char *attrSemantic = currentNode.attribute(name: "semantic").as_string();
231 const char *attrSource = currentNode.attribute(name: "source").as_string();
232 channel.mOffset = currentNode.attribute(name: "offset").as_int();
233
234 // local URLS always start with a '#'. We don't support global URLs
235 if (attrSource[0] != '#') {
236 throw DeadlyImportError("Unsupported URL format in \"", attrSource, "\" in source attribute of <vertex_weights> data <input> element");
237 }
238 channel.mAccessor = attrSource + 1;
239
240 // parse source URL to corresponding source
241 if (strcmp(s1: attrSemantic, s2: "JOINT") == 0) {
242 pController.mWeightInputJoints = channel;
243 } else if (strcmp(s1: attrSemantic, s2: "WEIGHT") == 0) {
244 pController.mWeightInputWeights = channel;
245 } else {
246 throw DeadlyImportError("Unknown semantic \"", attrSemantic, "\" in <vertex_weights> data <input> element");
247 }
248}
249
250// ------------------------------------------------------------------------------------------------
251static void ReadControllerWeightsVCount(const XmlNode &currentNode, Controller &pController) {
252 const std::string stdText = currentNode.text().as_string();
253 const char *text = stdText.c_str();
254 const char *end = text + stdText.size();
255 size_t numWeights = 0;
256 for (auto it = pController.mWeightCounts.begin(); it != pController.mWeightCounts.end(); ++it) {
257 if (*text == 0) {
258 throw DeadlyImportError("Out of data while reading <vcount>");
259 }
260
261 *it = strtoul10(in: text, out: &text);
262 numWeights += *it;
263 SkipSpacesAndLineEnd(inout: &text, end);
264 }
265 // reserve weight count
266 pController.mWeights.resize(new_size: numWeights);
267}
268
269// ------------------------------------------------------------------------------------------------
270static void ReadControllerWeightsJoint2verts(XmlNode &currentNode, Controller &pController) {
271 // read JointIndex - WeightIndex pairs
272 std::string stdText;
273 XmlParser::getValueAsString(node&: currentNode, text&: stdText);
274 const char *text = stdText.c_str();
275 const char *end = text + stdText.size();
276 for (auto it = pController.mWeights.begin(); it != pController.mWeights.end(); ++it) {
277 if (text == nullptr) {
278 throw DeadlyImportError("Out of data while reading <vertex_weights>");
279 }
280 SkipSpacesAndLineEnd(inout: &text, end);
281 it->first = strtoul10(in: text, out: &text);
282 SkipSpacesAndLineEnd(inout: &text, end);
283 if (*text == 0) {
284 throw DeadlyImportError("Out of data while reading <vertex_weights>");
285 }
286 it->second = strtoul10(in: text, out: &text);
287 SkipSpacesAndLineEnd(inout: &text, end);
288 }
289
290}
291
292// ------------------------------------------------------------------------------------------------
293// Reads the joint weights for the given controller
294static void ReadControllerWeights(XmlNode &node, Controller &pController) {
295 // Read vertex count from attributes and resize the array accordingly
296 int vertexCount = 0;
297 XmlParser::getIntAttribute(xmlNode&: node, name: "count", val&: vertexCount);
298 pController.mWeightCounts.resize(new_size: vertexCount);
299
300 for (XmlNode &currentNode : node.children()) {
301 const std::string &currentName = currentNode.name();
302 if (currentName == "input") {
303 ReadControllerWeightsInput(currentNode, pController);
304 } else if (currentName == "vcount" && vertexCount > 0) {
305 ReadControllerWeightsVCount(currentNode, pController);
306 } else if (currentName == "v" && vertexCount > 0) {
307 ReadControllerWeightsJoint2verts(currentNode, pController);
308 }
309 }
310}
311
312// ------------------------------------------------------------------------------------------------
313// Reads a material entry into the given material
314static void ReadMaterial(const XmlNode &node, Material &pMaterial) {
315 for (XmlNode &currentNode : node.children()) {
316 const std::string &currentName = currentNode.name();
317 if (currentName == "instance_effect") {
318 std::string url;
319 readUrlAttribute(node&: currentNode, url);
320 pMaterial.mEffect = url;
321 }
322 }
323}
324
325// ------------------------------------------------------------------------------------------------
326// Reads a light entry into the given light
327static void ReadLight(XmlNode &node, Light &pLight) {
328 XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
329 XmlNode currentNode;
330 // TODO: Check the current technique and skip over unsupported extra techniques
331
332 while (xmlIt.getNext(next&: currentNode)) {
333 const std::string &currentName = currentNode.name();
334 if (currentName == "spot") {
335 pLight.mType = aiLightSource_SPOT;
336 } else if (currentName == "ambient") {
337 pLight.mType = aiLightSource_AMBIENT;
338 } else if (currentName == "directional") {
339 pLight.mType = aiLightSource_DIRECTIONAL;
340 } else if (currentName == "point") {
341 pLight.mType = aiLightSource_POINT;
342 } else if (currentName == "color") {
343 // text content contains 3 floats
344 std::string v;
345 XmlParser::getValueAsString(node&: currentNode, text&: v);
346 const char *content = v.c_str();
347 const char *end = content + v.size();
348
349 content = fast_atoreal_move<ai_real>(c: content, out&: (ai_real &)pLight.mColor.r);
350 SkipSpacesAndLineEnd(inout: &content, end);
351
352 content = fast_atoreal_move<ai_real>(c: content, out&: (ai_real &)pLight.mColor.g);
353 SkipSpacesAndLineEnd(inout: &content, end);
354
355 content = fast_atoreal_move<ai_real>(c: content, out&: (ai_real &)pLight.mColor.b);
356 SkipSpacesAndLineEnd(inout: &content, end);
357 } else if (currentName == "constant_attenuation") {
358 XmlParser::getValueAsReal(node&: currentNode, v&: pLight.mAttConstant);
359 } else if (currentName == "linear_attenuation") {
360 XmlParser::getValueAsReal(node&: currentNode, v&: pLight.mAttLinear);
361 } else if (currentName == "quadratic_attenuation") {
362 XmlParser::getValueAsReal(node&: currentNode, v&: pLight.mAttQuadratic);
363 } else if (currentName == "falloff_angle") {
364 XmlParser::getValueAsReal(node&: currentNode, v&: pLight.mFalloffAngle);
365 } else if (currentName == "falloff_exponent") {
366 XmlParser::getValueAsReal(node&: currentNode, v&: pLight.mFalloffExponent);
367 }
368 // FCOLLADA extensions
369 // -------------------------------------------------------
370 else if (currentName == "outer_cone") {
371 XmlParser::getValueAsReal(node&: currentNode, v&: pLight.mOuterAngle);
372 } else if (currentName == "penumbra_angle") { // this one is deprecated, now calculated using outer_cone
373 XmlParser::getValueAsReal(node&: currentNode, v&: pLight.mPenumbraAngle);
374 } else if (currentName == "intensity") {
375 XmlParser::getValueAsReal(node&: currentNode, v&: pLight.mIntensity);
376 } else if (currentName == "falloff") {
377 XmlParser::getValueAsReal(node&: currentNode, v&: pLight.mOuterAngle);
378 } else if (currentName == "hotspot_beam") {
379 XmlParser::getValueAsReal(node&: currentNode, v&: pLight.mFalloffAngle);
380 }
381 // OpenCOLLADA extensions
382 // -------------------------------------------------------
383 else if (currentName == "decay_falloff") {
384 XmlParser::getValueAsReal(node&: currentNode, v&: pLight.mOuterAngle);
385 }
386 }
387}
388
389// ------------------------------------------------------------------------------------------------
390// Reads a camera entry into the given light
391static void ReadCamera(XmlNode &node, Camera &camera) {
392 XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
393 XmlNode currentNode;
394 while (xmlIt.getNext(next&: currentNode)) {
395 const std::string &currentName = currentNode.name();
396 if (currentName == "orthographic") {
397 camera.mOrtho = true;
398 } else if (currentName == "xfov" || currentName == "xmag") {
399 XmlParser::getValueAsReal(node&: currentNode, v&: camera.mHorFov);
400 } else if (currentName == "yfov" || currentName == "ymag") {
401 XmlParser::getValueAsReal(node&: currentNode, v&: camera.mVerFov);
402 } else if (currentName == "aspect_ratio") {
403 XmlParser::getValueAsReal(node&: currentNode, v&: camera.mAspect);
404 } else if (currentName == "znear") {
405 XmlParser::getValueAsReal(node&: currentNode, v&: camera.mZNear);
406 } else if (currentName == "zfar") {
407 XmlParser::getValueAsReal(node&: currentNode, v&: camera.mZFar);
408 }
409 }
410}
411
412// ------------------------------------------------------------------------------------------------
413// Constructor to be privately used by Importer
414ColladaParser::ColladaParser(IOSystem *pIOHandler, const std::string &pFile) :
415 mFileName(pFile),
416 mRootNode(nullptr),
417 mUnitSize(1.0f),
418 mUpDirection(UP_Y),
419 mFormat(FV_1_5_n) {
420 if (nullptr == pIOHandler) {
421 throw DeadlyImportError("IOSystem is nullptr.");
422 }
423
424 std::unique_ptr<IOStream> daeFile;
425 std::unique_ptr<ZipArchiveIOSystem> zip_archive;
426
427 // Determine type
428 const std::string extension = BaseImporter::GetExtension(pFile);
429 if (extension != "dae") {
430 zip_archive = std::make_unique<ZipArchiveIOSystem>(args&: pIOHandler, args: pFile);
431 }
432
433 if (zip_archive && zip_archive->isOpen()) {
434 std::string dae_filename = ReadZaeManifest(zip_archive&: *zip_archive);
435
436 if (dae_filename.empty()) {
437 throw DeadlyImportError("Invalid ZAE");
438 }
439
440 daeFile.reset(p: zip_archive->Open(pFilename: dae_filename.c_str()));
441 if (daeFile == nullptr) {
442 throw DeadlyImportError("Invalid ZAE manifest: '", dae_filename, "' is missing");
443 }
444 } else {
445 // attempt to open the file directly
446 daeFile.reset(p: pIOHandler->Open(pFile));
447 if (daeFile == nullptr) {
448 throw DeadlyImportError("Failed to open file '", pFile, "'.");
449 }
450 }
451
452 // generate a XML reader for it
453 if (!mXmlParser.parse(stream: daeFile.get())) {
454 throw DeadlyImportError("Unable to read file, malformed XML");
455 }
456 // start reading
457 const XmlNode node = mXmlParser.getRootNode();
458 XmlNode colladaNode = node.child(name: "COLLADA");
459 if (colladaNode.empty()) {
460 return;
461 }
462
463 // Read content and embedded textures
464 ReadContents(node&: colladaNode);
465 if (zip_archive && zip_archive->isOpen()) {
466 ReadEmbeddedTextures(zip_archive&: *zip_archive);
467 }
468}
469
470// ------------------------------------------------------------------------------------------------
471// Destructor, private as well
472ColladaParser::~ColladaParser() {
473 for (auto &it : mNodeLibrary) {
474 delete it.second;
475 }
476 for (auto &it : mMeshLibrary) {
477 delete it.second;
478 }
479}
480
481// ------------------------------------------------------------------------------------------------
482// Read a ZAE manifest and return the filename to attempt to open
483std::string ColladaParser::ReadZaeManifest(ZipArchiveIOSystem &zip_archive) {
484 // Open the manifest
485 std::unique_ptr<IOStream> manifestfile(zip_archive.Open(pFilename: "manifest.xml"));
486 if (manifestfile == nullptr) {
487 // No manifest, hope there is only one .DAE inside
488 std::vector<std::string> file_list;
489 zip_archive.getFileListExtension(rFileList&: file_list, extension: "dae");
490
491 if (file_list.empty()) {
492 return {};
493 }
494
495 return file_list.front();
496 }
497 XmlParser manifestParser;
498 if (!manifestParser.parse(stream: manifestfile.get())) {
499 return {};
500 }
501
502 XmlNode root = manifestParser.getRootNode();
503 const std::string &name = root.name();
504 if (name != "dae_root") {
505 root = *manifestParser.findNode(name: "dae_root");
506 if (nullptr == root) {
507 return {};
508 }
509 std::string v;
510 XmlParser::getValueAsString(node&: root, text&: v);
511 aiString ai_str(v);
512 UriDecodePath(ss&: ai_str);
513 return std::string(ai_str.C_Str());
514 }
515
516 return {};
517}
518
519// ------------------------------------------------------------------------------------------------
520// Convert a path read from a collada file to the usual representation
521void ColladaParser::UriDecodePath(aiString &ss) {
522 // TODO: collada spec, p 22. Handle URI correctly.
523 // For the moment we're just stripping the file:// away to make it work.
524 // Windows doesn't seem to be able to find stuff like
525 // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg'
526 if (0 == strncmp(s1: ss.data, s2: "file://", n: 7)) {
527 ss.length -= 7;
528 memmove(dest: ss.data, src: ss.data + 7, n: ss.length);
529 ss.data[ss.length] = '\0';
530 }
531
532 // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes...
533 // I need to filter it without destroying linux paths starting with "/somewhere"
534 if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') {
535 --ss.length;
536 ::memmove(dest: ss.data, src: ss.data + 1, n: ss.length);
537 ss.data[ss.length] = 0;
538 }
539
540 // find and convert all %xy special chars
541 char *out = ss.data;
542 for (const char *it = ss.data; it != ss.data + ss.length; /**/) {
543 if (*it == '%' && (it + 3) < ss.data + ss.length) {
544 // separate the number to avoid dragging in chars from behind into the parsing
545 char mychar[3] = { it[1], it[2], 0 };
546 size_t nbr = strtoul16(in: mychar);
547 it += 3;
548 *out++ = static_cast<char>(nbr & 0xFF);
549 } else {
550 *out++ = *it++;
551 }
552 }
553
554 // adjust length and terminator of the shortened string
555 *out = 0;
556 ai_assert(out > ss.data);
557 ss.length = static_cast<ai_uint32>(out - ss.data);
558}
559
560// ------------------------------------------------------------------------------------------------
561// Reads the contents of the file
562void ColladaParser::ReadContents(XmlNode &node) {
563 if (const std::string name = node.name(); name == "COLLADA") {
564 std::string version;
565 if (XmlParser::getStdStrAttribute(xmlNode&: node, name: "version", val&: version)) {
566 aiString v;
567 v.Set(version);
568 mAssetMetaData.emplace(AI_METADATA_SOURCE_FORMAT_VERSION, args&: v);
569 if (!::strncmp(s1: version.c_str(), s2: "1.5", n: 3)) {
570 mFormat = FV_1_5_n;
571 ASSIMP_LOG_DEBUG("Collada schema version is 1.5.n");
572 } else if (!::strncmp(s1: version.c_str(), s2: "1.4", n: 3)) {
573 mFormat = FV_1_4_n;
574 ASSIMP_LOG_DEBUG("Collada schema version is 1.4.n");
575 } else if (!::strncmp(s1: version.c_str(), s2: "1.3", n: 3)) {
576 mFormat = FV_1_3_n;
577 ASSIMP_LOG_DEBUG("Collada schema version is 1.3.n");
578 }
579 }
580 ReadStructure(node);
581 }
582}
583
584// ------------------------------------------------------------------------------------------------
585// Reads the structure of the file
586void ColladaParser::ReadStructure(XmlNode &node) {
587 for (XmlNode &currentNode : node.children()) {
588 if (const std::string &currentName = currentNode.name(); currentName == "asset") {
589 ReadAssetInfo(node&: currentNode);
590 } else if (currentName == "library_animations") {
591 ReadAnimationLibrary(node&: currentNode);
592 } else if (currentName == "library_animation_clips") {
593 ReadAnimationClipLibrary(node&: currentNode);
594 } else if (currentName == "library_controllers") {
595 ReadControllerLibrary(node&: currentNode);
596 } else if (currentName == "library_images") {
597 ReadImageLibrary(node: currentNode);
598 } else if (currentName == "library_materials") {
599 ReadMaterialLibrary(node&: currentNode);
600 } else if (currentName == "library_effects") {
601 ReadEffectLibrary(node&: currentNode);
602 } else if (currentName == "library_geometries") {
603 ReadGeometryLibrary(node&: currentNode);
604 } else if (currentName == "library_visual_scenes") {
605 ReadSceneLibrary(node&: currentNode);
606 } else if (currentName == "library_lights") {
607 ReadLightLibrary(node&: currentNode);
608 } else if (currentName == "library_cameras") {
609 ReadCameraLibrary(node&: currentNode);
610 } else if (currentName == "library_nodes") {
611 ReadSceneNode(node&: currentNode, pNode: nullptr); /* some hacking to reuse this piece of code */
612 } else if (currentName == "scene") {
613 ReadScene(node&: currentNode);
614 }
615 }
616
617 PostProcessRootAnimations();
618 PostProcessControllers();
619}
620
621// ------------------------------------------------------------------------------------------------
622// Reads asset information such as coordinate system information and legal blah
623void ColladaParser::ReadAssetInfo(XmlNode &node) {
624 if (node.empty()) {
625 return;
626 }
627
628 for (XmlNode &currentNode : node.children()) {
629 if (const std::string &currentName = currentNode.name(); currentName == "unit") {
630 mUnitSize = 1.f;
631 std::string tUnitSizeString;
632 if (XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "meter", val&: tUnitSizeString)) {
633 try {
634 fast_atoreal_move<ai_real>(c: tUnitSizeString.data(), out&: mUnitSize);
635 } catch (const DeadlyImportError& die) {
636 std::string warning("Collada: Failed to parse meter parameter to real number. Exception:\n");
637 warning.append(s: die.what());
638 ASSIMP_LOG_WARN(warning.data());
639 }
640 }
641 } else if (currentName == "up_axis") {
642 std::string v;
643 if (!XmlParser::getValueAsString(node&: currentNode, text&: v)) {
644 continue;
645 }
646 if (v == "X_UP") {
647 mUpDirection = UP_X;
648 } else if (v == "Z_UP") {
649 mUpDirection = UP_Z;
650 } else {
651 mUpDirection = UP_Y;
652 }
653 } else if (currentName == "contributor") {
654 for (XmlNode currentChildNode : currentNode.children()) {
655 ReadMetaDataItem(node&: currentChildNode, metadata&: mAssetMetaData);
656 }
657 } else {
658 ReadMetaDataItem(node&: currentNode, metadata&: mAssetMetaData);
659 }
660 }
661}
662
663// ------------------------------------------------------------------------------------------------
664// Reads the animation clips
665void ColladaParser::ReadAnimationClipLibrary(XmlNode &node) {
666 if (node.empty()) {
667 return;
668 }
669
670 std::string animName;
671 if (!XmlParser::getStdStrAttribute(xmlNode&: node, name: "name", val&: animName)) {
672 if (!XmlParser::getStdStrAttribute(xmlNode&: node, name: "id", val&: animName)) {
673 animName = std::string("animation_") + ai_to_string(value: mAnimationClipLibrary.size());
674 }
675 }
676
677 std::pair<std::string, std::vector<std::string>> clip;
678 clip.first = animName;
679
680 for (XmlNode &currentNode : node.children()) {
681 const std::string &currentName = currentNode.name();
682 if (currentName == "instance_animation") {
683 std::string url;
684 readUrlAttribute(node&: currentNode, url);
685 clip.second.push_back(x: url);
686 }
687
688 if (clip.second.size() > 0) {
689 mAnimationClipLibrary.push_back(x: clip);
690 }
691 }
692}
693
694// ------------------------------------------------------------------------------------------------
695// The controller post processing step
696void ColladaParser::PostProcessControllers() {
697 for (auto &it : mControllerLibrary) {
698 std::string meshId = it.second.mMeshId;
699 if (meshId.empty()) {
700 continue;
701 }
702
703 auto findItr = mControllerLibrary.find(x: meshId);
704 while (findItr != mControllerLibrary.end()) {
705 meshId = findItr->second.mMeshId;
706 findItr = mControllerLibrary.find(x: meshId);
707 }
708
709 it.second.mMeshId = meshId;
710 }
711}
712
713// ------------------------------------------------------------------------------------------------
714// Re-build animations from animation clip library, if present, otherwise combine single-channel animations
715void ColladaParser::PostProcessRootAnimations() {
716 if (mAnimationClipLibrary.empty()) {
717 mAnims.CombineSingleChannelAnimations();
718 return;
719 }
720
721 Animation temp;
722 for (auto &it : mAnimationClipLibrary) {
723 std::string clipName = it.first;
724
725 auto *clip = new Animation();
726 clip->mName = clipName;
727
728 temp.mSubAnims.push_back(x: clip);
729
730 for (const std::string &animationID : it.second) {
731 auto animation = mAnimationLibrary.find(x: animationID);
732
733 if (animation != mAnimationLibrary.end()) {
734 Animation *pSourceAnimation = animation->second;
735 pSourceAnimation->CollectChannelsRecursively(channels&: clip->mChannels);
736 }
737 }
738 }
739
740 mAnims = temp;
741
742 // Ensure no double deletes.
743 temp.mSubAnims.clear();
744}
745
746// ------------------------------------------------------------------------------------------------
747// Reads the animation library
748void ColladaParser::ReadAnimationLibrary(XmlNode &node) {
749 if (node.empty()) {
750 return;
751 }
752
753 for (XmlNode &currentNode : node.children()) {
754 const std::string &currentName = currentNode.name();
755 if (currentName == "animation") {
756 ReadAnimation(node&: currentNode, pParent: &mAnims);
757 }
758 }
759}
760
761// ------------------------------------------------------------------------------------------------
762// Reads an animation into the given parent structure
763void ColladaParser::ReadAnimation(XmlNode &node, Collada::Animation *pParent) {
764 if (node.empty()) {
765 return;
766 }
767
768 // an <animation> element may be a container for grouping sub-elements or an animation channel
769 // this is the channel collection by ID, in case it has channels
770 using ChannelMap = std::map<std::string, AnimationChannel>;
771 ChannelMap channels;
772 // this is the anim container in case we're a container
773 Animation *anim = nullptr;
774
775 // optional name given as an attribute
776 std::string animName;
777 if (!XmlParser::getStdStrAttribute(xmlNode&: node, name: "name", val&: animName)) {
778 animName = "animation";
779 }
780
781 std::string animID;
782 pugi::xml_attribute idAttr = node.attribute(name: "id");
783 if (idAttr) {
784 animID = idAttr.as_string();
785 }
786
787 for (XmlNode &currentNode : node.children()) {
788 const std::string &currentName = currentNode.name();
789 if (currentName == "animation") {
790 if (!anim) {
791 anim = new Animation;
792 anim->mName = animName;
793 pParent->mSubAnims.push_back(x: anim);
794 }
795
796 // recurse into the sub-element
797 ReadAnimation(node&: currentNode, pParent: anim);
798 } else if (currentName == "source") {
799 ReadSource(node&: currentNode);
800 } else if (currentName == "sampler") {
801 std::string id;
802 if (XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "id", val&: id)) {
803 // have it read into a channel
804 auto newChannel = channels.insert(x: std::make_pair(x&: id, y: AnimationChannel())).first;
805 ReadAnimationSampler(node: currentNode, pChannel&: newChannel->second);
806 }
807 } else if (currentName == "channel") {
808 std::string source_name, target;
809 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "source", val&: source_name);
810 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "target", val&: target);
811 if (source_name[0] == '#') {
812 source_name = source_name.substr(pos: 1, n: source_name.size() - 1);
813 }
814 auto cit = channels.find(x: source_name);
815 if (cit != channels.end()) {
816 cit->second.mTarget = target;
817 }
818 }
819 }
820
821 // it turned out to have channels - add them
822 if (!channels.empty()) {
823 if (nullptr == anim) {
824 anim = new Animation;
825 anim->mName = animName;
826 pParent->mSubAnims.push_back(x: anim);
827 }
828
829 for (const auto &channel : channels) {
830 anim->mChannels.push_back(x: channel.second);
831 }
832
833 if (idAttr) {
834 mAnimationLibrary[animID] = anim;
835 }
836 }
837}
838
839// ------------------------------------------------------------------------------------------------
840// Reads the skeleton controller library
841void ColladaParser::ReadControllerLibrary(XmlNode &node) {
842 if (node.empty()) {
843 return;
844 }
845
846 for (XmlNode &currentNode : node.children()) {
847 const std::string &currentName = currentNode.name();
848 if (currentName != "controller") {
849 continue;
850 }
851 if (std::string id; XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "id", val&: id)) {
852 mControllerLibrary[id] = Controller();
853 ReadController(node&: currentNode, pController&: mControllerLibrary[id]);
854 }
855 }
856}
857
858// ------------------------------------------------------------------------------------------------
859// Reads a controller into the given mesh structure
860void ColladaParser::ReadController(XmlNode &node, Collada::Controller &controller) {
861 // initial values
862 controller.mType = Skin;
863 controller.mMethod = Normalized;
864
865 XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
866 XmlNode currentNode;
867 while (xmlIt.getNext(next&: currentNode)) {
868 if (const std::string &currentName = currentNode.name(); currentName == "morph") {
869 controller.mType = Morph;
870 std::string id = currentNode.attribute(name: "source").as_string();
871 controller.mMeshId = id.substr(pos: 1, n: id.size() - 1);
872 if (const int methodIndex = currentNode.attribute(name: "method").as_int(); methodIndex > 0) {
873 std::string method;
874 XmlParser::getValueAsString(node&: currentNode, text&: method);
875
876 if (method == "RELATIVE") {
877 controller.mMethod = Relative;
878 }
879 }
880 } else if (currentName == "skin") {
881 if (std::string id; XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "source", val&: id)) {
882 controller.mMeshId = id.substr(pos: 1, n: id.size() - 1);
883 }
884 } else if (currentName == "bind_shape_matrix") {
885 std::string v;
886 XmlParser::getValueAsString(node&: currentNode, text&: v);
887 const char *content = v.c_str();
888 const char *end = content + v.size();
889 for (auto & a : controller.mBindShapeMatrix) {
890 SkipSpacesAndLineEnd(inout: &content, end);
891 // read a number
892 content = fast_atoreal_move<ai_real>(c: content, out&: a);
893 // skip whitespace after it
894 SkipSpacesAndLineEnd(inout: &content, end);
895 }
896 } else if (currentName == "source") {
897 ReadSource(node&: currentNode);
898 } else if (currentName == "joints") {
899 ReadControllerJoints(node: currentNode, pController&: controller);
900 } else if (currentName == "vertex_weights") {
901 ReadControllerWeights(node&: currentNode, pController&: controller);
902 } else if (currentName == "targets") {
903 for (XmlNode currentChildNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) {
904 const std::string &currentChildName = currentChildNode.name();
905 if (currentChildName == "input") {
906 const char *semantics = currentChildNode.attribute(name: "semantic").as_string();
907 const char *source = currentChildNode.attribute(name: "source").as_string();
908 if (strcmp(s1: semantics, s2: "MORPH_TARGET") == 0) {
909 controller.mMorphTarget = source + 1;
910 } else if (strcmp(s1: semantics, s2: "MORPH_WEIGHT") == 0) {
911 controller.mMorphWeight = source + 1;
912 }
913 }
914 }
915 }
916 }
917}
918
919// ------------------------------------------------------------------------------------------------
920// Reads the image library contents
921void ColladaParser::ReadImageLibrary(const XmlNode &node) {
922 for (XmlNode &currentNode : node.children()) {
923 const std::string &currentName = currentNode.name();
924 if (currentName == "image") {
925 if (std::basic_string<char> id; XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "id", val&: id)) {
926 mImageLibrary[id] = Image();
927 // read on from there
928 ReadImage(node: currentNode, pImage&: mImageLibrary[id]);
929 }
930 }
931 }
932}
933
934// ------------------------------------------------------------------------------------------------
935// Reads an image entry into the given image
936void ColladaParser::ReadImage(const XmlNode &node, Collada::Image &pImage) const {
937 for (XmlNode &currentNode : node.children()) {
938 const std::string currentName = currentNode.name();
939 if (currentName == "image") {
940 // Ignore
941 continue;
942 } else if (currentName == "init_from") {
943 if (mFormat == FV_1_4_n) {
944 // FIX: C4D exporter writes empty <init_from/> tags
945 if (!currentNode.empty()) {
946 // element content is filename - hopefully
947 const char *sz = currentNode.text().as_string();
948 if (nullptr != sz) {
949 aiString filepath(sz);
950 UriDecodePath(ss&: filepath);
951 pImage.mFileName = filepath.C_Str();
952 }
953 }
954 if (!pImage.mFileName.length()) {
955 pImage.mFileName = "unknown_texture";
956 }
957 } else if (mFormat == FV_1_5_n) {
958 std::string value;
959 XmlNode refChild = currentNode.child(name: "ref");
960 XmlNode hexChild = currentNode.child(name: "hex");
961 if (refChild) {
962 // element content is filename - hopefully
963 if (XmlParser::getValueAsString(node&: refChild, text&: value)) {
964 aiString filepath(value);
965 UriDecodePath(ss&: filepath);
966 pImage.mFileName = filepath.C_Str();
967 }
968 } else if (hexChild && !pImage.mFileName.length()) {
969 // embedded image. get format
970 pImage.mEmbeddedFormat = hexChild.attribute(name: "format").as_string();
971 if (pImage.mEmbeddedFormat.empty()) {
972 ASSIMP_LOG_WARN("Collada: Unknown image file format");
973 }
974
975 XmlParser::getValueAsString(node&: hexChild, text&: value);
976 const char *data = value.c_str();
977 // hexadecimal-encoded binary octets. First of all, find the
978 // required buffer size to reserve enough storage.
979 const char *cur = data;
980 while (!IsSpaceOrNewLine(in: *cur)) {
981 ++cur;
982 }
983
984 const unsigned int size = (unsigned int)(cur - data) * 2;
985 pImage.mImageData.resize(new_size: size);
986 for (unsigned int i = 0; i < size; ++i) {
987 pImage.mImageData[i] = HexOctetToDecimal(in: data + (i << 1));
988 }
989 }
990 }
991 }
992 }
993}
994
995// ------------------------------------------------------------------------------------------------
996// Reads the material library
997void ColladaParser::ReadMaterialLibrary(XmlNode &node) {
998 std::map<std::string, int> names;
999 for (const XmlNode &currentNode : node.children()) {
1000 std::string id = currentNode.attribute(name: "id").as_string();
1001 std::string name = currentNode.attribute(name: "name").as_string();
1002 mMaterialLibrary[id] = Material();
1003
1004 if (!name.empty()) {
1005 auto it = names.find(x: name);
1006 if (it != names.end()) {
1007 std::ostringstream strStream;
1008 strStream << ++it->second;
1009 name.append(str: " " + strStream.str());
1010 } else {
1011 names[name] = 0;
1012 }
1013
1014 mMaterialLibrary[id].mName = name;
1015 }
1016
1017 ReadMaterial(node: currentNode, pMaterial&: mMaterialLibrary[id]);
1018 }
1019}
1020
1021// ------------------------------------------------------------------------------------------------
1022// Reads the light library
1023void ColladaParser::ReadLightLibrary(XmlNode &node) {
1024 for (XmlNode &currentNode : node.children()) {
1025 const std::string &currentName = currentNode.name();
1026 if (currentName == "light") {
1027 std::string id;
1028 if (XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "id", val&: id)) {
1029 ReadLight(node&: currentNode, pLight&: mLightLibrary[id] = Light());
1030 }
1031 }
1032 }
1033}
1034
1035// ------------------------------------------------------------------------------------------------
1036// Reads the camera library
1037void ColladaParser::ReadCameraLibrary(XmlNode &node) {
1038 for (XmlNode &currentNode : node.children()) {
1039 const std::string &currentName = currentNode.name();
1040 if (currentName == "camera") {
1041 std::string id;
1042 if (!XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "id", val&: id)) {
1043 continue;
1044 }
1045
1046 // create an entry and store it in the library under its ID
1047 Camera &cam = mCameraLibrary[id];
1048 std::string name;
1049 if (!XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "name", val&: name)) {
1050 continue;
1051 }
1052 if (!name.empty()) {
1053 cam.mName = name;
1054 }
1055 ReadCamera(node&: currentNode, camera&: cam);
1056 }
1057 }
1058}
1059
1060// ------------------------------------------------------------------------------------------------
1061// Reads the effect library
1062void ColladaParser::ReadEffectLibrary(XmlNode &node) {
1063 if (node.empty()) {
1064 return;
1065 }
1066
1067 for (XmlNode &currentNode : node.children()) {
1068 const std::string &currentName = currentNode.name();
1069 if (currentName == "effect") {
1070 // read ID. Do I have to repeat my ranting about "optional" attributes?
1071 std::string id;
1072 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "id", val&: id);
1073
1074 // create an entry and store it in the library under its ID
1075 mEffectLibrary[id] = Effect();
1076
1077 // read on from there
1078 ReadEffect(node&: currentNode, pEffect&: mEffectLibrary[id]);
1079 }
1080 }
1081}
1082
1083// ------------------------------------------------------------------------------------------------
1084// Reads an effect entry into the given effect
1085void ColladaParser::ReadEffect(XmlNode &node, Collada::Effect &pEffect) {
1086 for (XmlNode &currentNode : node.children()) {
1087 const std::string &currentName = currentNode.name();
1088 if (currentName == "profile_COMMON") {
1089 ReadEffectProfileCommon(node&: currentNode, pEffect);
1090 }
1091 }
1092}
1093
1094// ------------------------------------------------------------------------------------------------
1095// Reads an COMMON effect profile
1096void ColladaParser::ReadEffectProfileCommon(XmlNode &node, Collada::Effect &pEffect) {
1097 XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
1098 XmlNode currentNode;
1099 while (xmlIt.getNext(next&: currentNode)) {
1100 const std::string currentName = currentNode.name();
1101 if (currentName == "newparam") {
1102 // save ID
1103 std::string sid = currentNode.attribute(name: "sid").as_string();
1104 pEffect.mParams[sid] = EffectParam();
1105 ReadEffectParam(node&: currentNode, pParam&: pEffect.mParams[sid]);
1106 } else if (currentName == "technique" || currentName == "extra") {
1107 // just syntactic sugar
1108 } else if (mFormat == FV_1_4_n && currentName == "image") {
1109 // read ID. Another entry which is "optional" by design but obligatory in reality
1110 std::string id = currentNode.attribute(name: "id").as_string();
1111
1112 // create an entry and store it in the library under its ID
1113 mImageLibrary[id] = Image();
1114
1115 // read on from there
1116 ReadImage(node: currentNode, pImage&: mImageLibrary[id]);
1117 } else if (currentName == "phong")
1118 pEffect.mShadeType = Shade_Phong;
1119 else if (currentName == "constant")
1120 pEffect.mShadeType = Shade_Constant;
1121 else if (currentName == "lambert")
1122 pEffect.mShadeType = Shade_Lambert;
1123 else if (currentName == "blinn")
1124 pEffect.mShadeType = Shade_Blinn;
1125
1126 /* Color + texture properties */
1127 else if (currentName == "emission")
1128 ReadEffectColor(node&: currentNode, pColor&: pEffect.mEmissive, pSampler&: pEffect.mTexEmissive);
1129 else if (currentName == "ambient")
1130 ReadEffectColor(node&: currentNode, pColor&: pEffect.mAmbient, pSampler&: pEffect.mTexAmbient);
1131 else if (currentName == "diffuse")
1132 ReadEffectColor(node&: currentNode, pColor&: pEffect.mDiffuse, pSampler&: pEffect.mTexDiffuse);
1133 else if (currentName == "specular")
1134 ReadEffectColor(node&: currentNode, pColor&: pEffect.mSpecular, pSampler&: pEffect.mTexSpecular);
1135 else if (currentName == "reflective") {
1136 ReadEffectColor(node&: currentNode, pColor&: pEffect.mReflective, pSampler&: pEffect.mTexReflective);
1137 } else if (currentName == "transparent") {
1138 pEffect.mHasTransparency = true;
1139 const char *opaque = currentNode.attribute(name: "opaque").as_string();
1140 //const char *opaque = mReader->getAttributeValueSafe("opaque");
1141
1142 if (::strcmp(s1: opaque, s2: "RGB_ZERO") == 0 || ::strcmp(s1: opaque, s2: "RGB_ONE") == 0) {
1143 pEffect.mRGBTransparency = true;
1144 }
1145
1146 // In RGB_ZERO mode, the transparency is interpreted in reverse, go figure...
1147 if (::strcmp(s1: opaque, s2: "RGB_ZERO") == 0 || ::strcmp(s1: opaque, s2: "A_ZERO") == 0) {
1148 pEffect.mInvertTransparency = true;
1149 }
1150
1151 ReadEffectColor(node&: currentNode, pColor&: pEffect.mTransparent, pSampler&: pEffect.mTexTransparent);
1152 } else if (currentName == "shininess")
1153 ReadEffectFloat(node&: currentNode, pFloat&: pEffect.mShininess);
1154 else if (currentName == "reflectivity")
1155 ReadEffectFloat(node&: currentNode, pFloat&: pEffect.mReflectivity);
1156
1157 /* Single scalar properties */
1158 else if (currentName == "transparency")
1159 ReadEffectFloat(node&: currentNode, pFloat&: pEffect.mTransparency);
1160 else if (currentName == "index_of_refraction")
1161 ReadEffectFloat(node&: currentNode, pFloat&: pEffect.mRefractIndex);
1162
1163 // GOOGLEEARTH/OKINO extensions
1164 // -------------------------------------------------------
1165 else if (currentName == "double_sided")
1166 XmlParser::getValueAsBool(node&: currentNode, v&: pEffect.mDoubleSided);
1167
1168 // FCOLLADA extensions
1169 // -------------------------------------------------------
1170 else if (currentName == "bump") {
1171 aiColor4D dummy;
1172 ReadEffectColor(node&: currentNode, pColor&: dummy, pSampler&: pEffect.mTexBump);
1173 }
1174
1175 // MAX3D extensions
1176 // -------------------------------------------------------
1177 else if (currentName == "wireframe") {
1178 XmlParser::getValueAsBool(node&: currentNode, v&: pEffect.mWireframe);
1179 } else if (currentName == "faceted") {
1180 XmlParser::getValueAsBool(node&: currentNode, v&: pEffect.mFaceted);
1181 }
1182 }
1183}
1184
1185// ------------------------------------------------------------------------------------------------
1186// Read texture wrapping + UV transform settings from a profile==Maya chunk
1187void ColladaParser::ReadSamplerProperties(XmlNode &node, Sampler &out) {
1188 if (node.empty()) {
1189 return;
1190 }
1191
1192 XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
1193 XmlNode currentNode;
1194 while (xmlIt.getNext(next&: currentNode)) {
1195 const std::string &currentName = currentNode.name();
1196 // MAYA extensions
1197 // -------------------------------------------------------
1198 if (currentName == "wrapU") {
1199 XmlParser::getValueAsBool(node&: currentNode, v&: out.mWrapU);
1200 } else if (currentName == "wrapV") {
1201 XmlParser::getValueAsBool(node&: currentNode, v&: out.mWrapV);
1202 } else if (currentName == "mirrorU") {
1203 XmlParser::getValueAsBool(node&: currentNode, v&: out.mMirrorU);
1204 } else if (currentName == "mirrorV") {
1205 XmlParser::getValueAsBool(node&: currentNode, v&: out.mMirrorV);
1206 } else if (currentName == "repeatU") {
1207 XmlParser::getValueAsReal(node&: currentNode, v&: out.mTransform.mScaling.x);
1208 } else if (currentName == "repeatV") {
1209 XmlParser::getValueAsReal(node&: currentNode, v&: out.mTransform.mScaling.y);
1210 } else if (currentName == "offsetU") {
1211 XmlParser::getValueAsReal(node&: currentNode, v&: out.mTransform.mTranslation.x);
1212 } else if (currentName == "offsetV") {
1213 XmlParser::getValueAsReal(node&: currentNode, v&: out.mTransform.mTranslation.y);
1214 } else if (currentName == "rotateUV") {
1215 XmlParser::getValueAsReal(node&: currentNode, v&: out.mTransform.mRotation);
1216 } else if (currentName == "blend_mode") {
1217 std::string v;
1218 XmlParser::getValueAsString(node&: currentNode, text&: v);
1219 const char *sz = v.c_str();
1220 // http://www.feelingsoftware.com/content/view/55/72/lang,en/
1221 // NONE, OVER, IN, OUT, ADD, SUBTRACT, MULTIPLY, DIFFERENCE, LIGHTEN, DARKEN, SATURATE, DESATURATE and ILLUMINATE
1222 if (0 == ASSIMP_strincmp(s1: sz, s2: "ADD", n: 3))
1223 out.mOp = aiTextureOp_Add;
1224 else if (0 == ASSIMP_strincmp(s1: sz, s2: "SUBTRACT", n: 8))
1225 out.mOp = aiTextureOp_Subtract;
1226 else if (0 == ASSIMP_strincmp(s1: sz, s2: "MULTIPLY", n: 8))
1227 out.mOp = aiTextureOp_Multiply;
1228 else {
1229 ASSIMP_LOG_WARN("Collada: Unsupported MAYA texture blend mode");
1230 }
1231 }
1232 // OKINO extensions
1233 // -------------------------------------------------------
1234 else if (currentName == "weighting") {
1235 XmlParser::getValueAsReal(node&: currentNode, v&: out.mWeighting);
1236 } else if (currentName == "mix_with_previous_layer") {
1237 XmlParser::getValueAsReal(node&: currentNode, v&: out.mMixWithPrevious);
1238 }
1239 // MAX3D extensions
1240 // -------------------------------------------------------
1241 else if (currentName == "amount") {
1242 XmlParser::getValueAsReal(node&: currentNode, v&: out.mWeighting);
1243 }
1244 }
1245}
1246
1247// ------------------------------------------------------------------------------------------------
1248// Reads an effect entry containing a color or a texture defining that color
1249void ColladaParser::ReadEffectColor(XmlNode &node, aiColor4D &pColor, Sampler &pSampler) {
1250 if (node.empty()) {
1251 return;
1252 }
1253
1254 XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
1255 XmlNode currentNode;
1256 while (xmlIt.getNext(next&: currentNode)) {
1257 const std::string &currentName = currentNode.name();
1258 if (currentName == "color") {
1259 // text content contains 4 floats
1260 std::string v;
1261 XmlParser::getValueAsString(node&: currentNode, text&: v);
1262 const char *content = v.c_str();
1263 const char *end = v.c_str() + v.size() + 1;
1264
1265 content = fast_atoreal_move<ai_real>(c: content, out&: (ai_real &)pColor.r);
1266 SkipSpacesAndLineEnd(inout: &content, end);
1267
1268 content = fast_atoreal_move<ai_real>(c: content, out&: (ai_real &)pColor.g);
1269 SkipSpacesAndLineEnd(inout: &content, end);
1270
1271 content = fast_atoreal_move<ai_real>(c: content, out&: (ai_real &)pColor.b);
1272 SkipSpacesAndLineEnd(inout: &content, end);
1273
1274 content = fast_atoreal_move<ai_real>(c: content, out&: (ai_real &)pColor.a);
1275 SkipSpacesAndLineEnd(inout: &content, end);
1276 } else if (currentName == "texture") {
1277 // get name of source texture/sampler
1278 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "texture", val&: pSampler.mName);
1279
1280 // get name of UV source channel. Specification demands it to be there, but some exporters
1281 // don't write it. It will be the default UV channel in case it's missing.
1282 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "texcoord", val&: pSampler.mUVChannel);
1283
1284 // as we've read texture, the color needs to be 1,1,1,1
1285 pColor = aiColor4D(1.f, 1.f, 1.f, 1.f);
1286 } else if (currentName == "technique") {
1287 std::string profile;
1288 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "profile", val&: profile);
1289
1290 // Some extensions are quite useful ... ReadSamplerProperties processes
1291 // several extensions in MAYA, OKINO and MAX3D profiles.
1292 if (!::strcmp(s1: profile.c_str(), s2: "MAYA") || !::strcmp(s1: profile.c_str(), s2: "MAX3D") || !::strcmp(s1: profile.c_str(), s2: "OKINO")) {
1293 // get more information on this sampler
1294 ReadSamplerProperties(node&: currentNode, out&: pSampler);
1295 }
1296 }
1297 }
1298}
1299
1300// ------------------------------------------------------------------------------------------------
1301// Reads an effect entry containing a float
1302void ColladaParser::ReadEffectFloat(XmlNode &node, ai_real &pReal) {
1303 pReal = 0.f;
1304 XmlNode floatNode = node.child(name: "float");
1305 if (floatNode.empty()) {
1306 return;
1307 }
1308 XmlParser::getValueAsReal(node&: floatNode, v&: pReal);
1309}
1310
1311// ------------------------------------------------------------------------------------------------
1312// Reads an effect parameter specification of any kind
1313void ColladaParser::ReadEffectParam(XmlNode &node, Collada::EffectParam &pParam) {
1314 if (node.empty()) {
1315 return;
1316 }
1317
1318 for (XmlNode &currentNode : node.children()) {
1319 const std::string &currentName = currentNode.name();
1320 if (currentName == "surface") {
1321 // image ID given inside <init_from> tags
1322 XmlNode initNode = currentNode.child(name: "init_from");
1323 if (initNode) {
1324 std::string v;
1325 XmlParser::getValueAsString(node&: initNode, text&: v);
1326 pParam.mType = Param_Surface;
1327 pParam.mReference = v.c_str();
1328 }
1329 } else if (currentName == "sampler2D" && (FV_1_4_n == mFormat || FV_1_3_n == mFormat)) {
1330 // surface ID is given inside <source> tags
1331 XmlNode source = currentNode.child(name: "source");
1332 if (source) {
1333 std::string v;
1334 XmlParser::getValueAsString(node&: source, text&: v);
1335 pParam.mType = Param_Sampler;
1336 pParam.mReference = v.c_str();
1337 }
1338 } else if (currentName == "sampler2D") {
1339 // surface ID is given inside <instance_image> tags
1340 XmlNode instance_image = currentNode.child(name: "instance_image");
1341 if (instance_image) {
1342 std::string url;
1343 XmlParser::getStdStrAttribute(xmlNode&: instance_image, name: "url", val&: url);
1344 if (url[0] != '#') {
1345 throw DeadlyImportError("Unsupported URL format in instance_image");
1346 }
1347 pParam.mType = Param_Sampler;
1348 pParam.mReference = url.c_str() + 1;
1349 }
1350 }
1351 }
1352}
1353
1354// ------------------------------------------------------------------------------------------------
1355// Reads the geometry library contents
1356void ColladaParser::ReadGeometryLibrary(XmlNode &node) {
1357 if (node.empty()) {
1358 return;
1359 }
1360 for (XmlNode &currentNode : node.children()) {
1361 const std::string &currentName = currentNode.name();
1362 if (currentName == "geometry") {
1363 // read ID. Another entry which is "optional" by design but obligatory in reality
1364
1365 std::string id;
1366 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "id", val&: id);
1367 // create a mesh and store it in the library under its (resolved) ID
1368 // Skip and warn if ID is not unique
1369 if (mMeshLibrary.find(x: id) == mMeshLibrary.cend()) {
1370 std::unique_ptr<Mesh> mesh(new Mesh(id));
1371
1372 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "name", val&: mesh->mName);
1373
1374 // read on from there
1375 ReadGeometry(node&: currentNode, pMesh&: *mesh);
1376 // Read successfully, add to library
1377 mMeshLibrary.insert(x: { id, mesh.release() });
1378 }
1379 }
1380 }
1381}
1382
1383// ------------------------------------------------------------------------------------------------
1384// Reads a geometry from the geometry library.
1385void ColladaParser::ReadGeometry(XmlNode &node, Collada::Mesh &pMesh) {
1386 if (node.empty()) {
1387 return;
1388 }
1389
1390 for (XmlNode &currentNode : node.children()) {
1391 const std::string &currentName = currentNode.name();
1392 if (currentName == "mesh") {
1393 ReadMesh(node&: currentNode, pMesh);
1394 }
1395 }
1396}
1397
1398// ------------------------------------------------------------------------------------------------
1399// Reads a mesh from the geometry library
1400void ColladaParser::ReadMesh(XmlNode &node, Mesh &pMesh) {
1401 if (node.empty()) {
1402 return;
1403 }
1404
1405 XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
1406 XmlNode currentNode;
1407 while (xmlIt.getNext(next&: currentNode)) {
1408 const std::string &currentName = currentNode.name();
1409 if (currentName == "source") {
1410 ReadSource(node&: currentNode);
1411 } else if (currentName == "vertices") {
1412 ReadVertexData(node&: currentNode, pMesh);
1413 } else if (currentName == "triangles" || currentName == "lines" || currentName == "linestrips" ||
1414 currentName == "polygons" || currentName == "polylist" || currentName == "trifans" ||
1415 currentName == "tristrips") {
1416 ReadIndexData(node&: currentNode, pMesh);
1417 }
1418 }
1419}
1420
1421// ------------------------------------------------------------------------------------------------
1422// Reads a source element
1423void ColladaParser::ReadSource(XmlNode &node) {
1424 if (node.empty()) {
1425 return;
1426 }
1427
1428 std::string sourceID;
1429 XmlParser::getStdStrAttribute(xmlNode&: node, name: "id", val&: sourceID);
1430 XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
1431 XmlNode currentNode;
1432 while (xmlIt.getNext(next&: currentNode)) {
1433 const std::string &currentName = currentNode.name();
1434 if (currentName == "float_array" || currentName == "IDREF_array" || currentName == "Name_array") {
1435 ReadDataArray(node&: currentNode);
1436 } else if (currentName == "technique_common") {
1437 XmlNode technique = currentNode.child(name: "accessor");
1438 if (!technique.empty()) {
1439 ReadAccessor(node&: technique, pID: sourceID);
1440 }
1441 }
1442 }
1443}
1444
1445// ------------------------------------------------------------------------------------------------
1446// Reads a data array holding a number of floats, and stores it in the global library
1447void ColladaParser::ReadDataArray(XmlNode &node) {
1448 std::string name = node.name();
1449 bool isStringArray = (name == "IDREF_array" || name == "Name_array");
1450
1451 // read attributes
1452 std::string id;
1453 XmlParser::getStdStrAttribute(xmlNode&: node, name: "id", val&: id);
1454 unsigned int count = 0;
1455 XmlParser::getUIntAttribute(xmlNode&: node, name: "count", val&: count);
1456 std::string v;
1457 XmlParser::getValueAsString(node, text&: v);
1458 v = ai_trim(s&: v);
1459 const char *content = v.c_str();
1460 const char *end = content + v.size();
1461
1462 // read values and store inside an array in the data library
1463 mDataLibrary[id] = Data();
1464 Data &data = mDataLibrary[id];
1465 data.mIsStringArray = isStringArray;
1466
1467 // some exporters write empty data arrays, but we need to conserve them anyways because others might reference them
1468 if (content) {
1469 if (isStringArray) {
1470 data.mStrings.reserve(n: count);
1471 std::string s;
1472
1473 for (unsigned int a = 0; a < count; a++) {
1474 if (*content == 0) {
1475 throw DeadlyImportError("Expected more values while reading IDREF_array contents.");
1476 }
1477
1478 s.clear();
1479 while (!IsSpaceOrNewLine(in: *content)) {
1480 s += *content;
1481 content++;
1482 }
1483 data.mStrings.push_back(x: s);
1484
1485 SkipSpacesAndLineEnd(inout: &content, end);
1486 }
1487 } else {
1488 data.mValues.reserve(n: count);
1489
1490 for (unsigned int a = 0; a < count; a++) {
1491 if (*content == 0) {
1492 throw DeadlyImportError("Expected more values while reading float_array contents.");
1493 }
1494
1495 // read a number
1496 ai_real value;
1497 content = fast_atoreal_move<ai_real>(c: content, out&: value);
1498 data.mValues.push_back(x: value);
1499 // skip whitespace after it
1500 SkipSpacesAndLineEnd(inout: &content, end);
1501 }
1502 }
1503 }
1504}
1505
1506// ------------------------------------------------------------------------------------------------
1507// Reads an accessor and stores it in the global library
1508void ColladaParser::ReadAccessor(XmlNode &node, const std::string &pID) {
1509 // read accessor attributes
1510 std::string source;
1511 XmlParser::getStdStrAttribute(xmlNode&: node, name: "source", val&: source);
1512 if (source[0] != '#') {
1513 throw DeadlyImportError("Unknown reference format in url \"", source, "\" in source attribute of <accessor> element.");
1514 }
1515 int count = 0;
1516 XmlParser::getIntAttribute(xmlNode&: node, name: "count", val&: count);
1517
1518 unsigned int offset = 0;
1519 if (XmlParser::hasAttribute(xmlNode&: node, name: "offset")) {
1520 XmlParser::getUIntAttribute(xmlNode&: node, name: "offset", val&: offset);
1521 }
1522 unsigned int stride = 1;
1523 if (XmlParser::hasAttribute(xmlNode&: node, name: "stride")) {
1524 XmlParser::getUIntAttribute(xmlNode&: node, name: "stride", val&: stride);
1525 }
1526 // store in the library under the given ID
1527 mAccessorLibrary[pID] = Accessor();
1528 Accessor &acc = mAccessorLibrary[pID];
1529 acc.mCount = count;
1530 acc.mOffset = offset;
1531 acc.mStride = stride;
1532 acc.mSource = source.c_str() + 1; // ignore the leading '#'
1533 acc.mSize = 0; // gets incremented with every param
1534
1535 XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
1536 XmlNode currentNode;
1537 while (xmlIt.getNext(next&: currentNode)) {
1538 const std::string &currentName = currentNode.name();
1539 if (currentName == "param") {
1540 // read data param
1541 std::string name;
1542 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "name")) {
1543 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "name", val&: name);
1544
1545 // analyse for common type components and store it's sub-offset in the corresponding field
1546
1547 // Cartesian coordinates
1548 if (name == "X")
1549 acc.mSubOffset[0] = acc.mParams.size();
1550 else if (name == "Y")
1551 acc.mSubOffset[1] = acc.mParams.size();
1552 else if (name == "Z")
1553 acc.mSubOffset[2] = acc.mParams.size();
1554
1555 /* RGBA colors */
1556 else if (name == "R")
1557 acc.mSubOffset[0] = acc.mParams.size();
1558 else if (name == "G")
1559 acc.mSubOffset[1] = acc.mParams.size();
1560 else if (name == "B")
1561 acc.mSubOffset[2] = acc.mParams.size();
1562 else if (name == "A")
1563 acc.mSubOffset[3] = acc.mParams.size();
1564
1565 /* UVWQ (STPQ) texture coordinates */
1566 else if (name == "S")
1567 acc.mSubOffset[0] = acc.mParams.size();
1568 else if (name == "T")
1569 acc.mSubOffset[1] = acc.mParams.size();
1570 else if (name == "P")
1571 acc.mSubOffset[2] = acc.mParams.size();
1572 /* Generic extra data, interpreted as UV data, too*/
1573 else if (name == "U")
1574 acc.mSubOffset[0] = acc.mParams.size();
1575 else if (name == "V")
1576 acc.mSubOffset[1] = acc.mParams.size();
1577 }
1578 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "type")) {
1579 // read data type
1580 // TODO: (thom) I don't have a spec here at work. Check if there are other multi-value types
1581 // which should be tested for here.
1582 std::string type;
1583
1584 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "type", val&: type);
1585 if (type == "float4x4")
1586 acc.mSize += 16;
1587 else
1588 acc.mSize += 1;
1589 }
1590
1591 acc.mParams.push_back(x: name);
1592 }
1593 }
1594}
1595
1596// ------------------------------------------------------------------------------------------------
1597// Reads input declarations of per-vertex mesh data into the given mesh
1598void ColladaParser::ReadVertexData(XmlNode &node, Mesh &pMesh) {
1599 // extract the ID of the <vertices> element. Not that we care, but to catch strange referencing schemes we should warn about
1600 XmlParser::getStdStrAttribute(xmlNode&: node, name: "id", val&: pMesh.mVertexID);
1601 for (XmlNode &currentNode : node.children()) {
1602 const std::string &currentName = currentNode.name();
1603 if (currentName == "input") {
1604 ReadInputChannel(node&: currentNode, poChannels&: pMesh.mPerVertexData);
1605 } else {
1606 throw DeadlyImportError("Unexpected sub element <", currentName, "> in tag <vertices>");
1607 }
1608 }
1609}
1610
1611// ------------------------------------------------------------------------------------------------
1612// Reads input declarations of per-index mesh data into the given mesh
1613void ColladaParser::ReadIndexData(XmlNode &node, Mesh &pMesh) {
1614 std::vector<size_t> vcount;
1615 std::vector<InputChannel> perIndexData;
1616
1617 unsigned int numPrimitives = 0;
1618 XmlParser::getUIntAttribute(xmlNode&: node, name: "count", val&: numPrimitives);
1619 // read primitive count from the attribute
1620 //int attrCount = GetAttribute("count");
1621 //size_t numPrimitives = (size_t)mReader->getAttributeValueAsInt(attrCount);
1622 // some mesh types (e.g. tristrips) don't specify primitive count upfront,
1623 // so we need to sum up the actual number of primitives while we read the <p>-tags
1624 size_t actualPrimitives = 0;
1625 SubMesh subgroup;
1626 if (XmlParser::hasAttribute(xmlNode&: node, name: "material")) {
1627 XmlParser::getStdStrAttribute(xmlNode&: node, name: "material", val&: subgroup.mMaterial);
1628 }
1629
1630 // distinguish between polys and triangles
1631 std::string elementName = node.name();
1632 PrimitiveType primType = Prim_Invalid;
1633 if (elementName == "lines")
1634 primType = Prim_Lines;
1635 else if (elementName == "linestrips")
1636 primType = Prim_LineStrip;
1637 else if (elementName == "polygons")
1638 primType = Prim_Polygon;
1639 else if (elementName == "polylist")
1640 primType = Prim_Polylist;
1641 else if (elementName == "triangles")
1642 primType = Prim_Triangles;
1643 else if (elementName == "trifans")
1644 primType = Prim_TriFans;
1645 else if (elementName == "tristrips")
1646 primType = Prim_TriStrips;
1647
1648 ai_assert(primType != Prim_Invalid);
1649
1650 // also a number of <input> elements, but in addition a <p> primitive collection and probably index counts for all primitives
1651 XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
1652 XmlNode currentNode;
1653 while (xmlIt.getNext(next&: currentNode)) {
1654 const std::string &currentName = currentNode.name();
1655 if (currentName == "input") {
1656 ReadInputChannel(node&: currentNode, poChannels&: perIndexData);
1657 } else if (currentName == "vcount") {
1658 if (!currentNode.empty()) {
1659 if (numPrimitives) // It is possible to define a mesh without any primitives
1660 {
1661 // case <polylist> - specifies the number of indices for each polygon
1662 std::string v;
1663 XmlParser::getValueAsString(node&: currentNode, text&: v);
1664 const char *content = v.c_str();
1665 const char *end = content + v.size();
1666
1667 vcount.reserve(n: numPrimitives);
1668 SkipSpacesAndLineEnd(inout: &content, end);
1669 for (unsigned int a = 0; a < numPrimitives; a++) {
1670 if (*content == 0) {
1671 throw DeadlyImportError("Expected more values while reading <vcount> contents.");
1672 }
1673 // read a number
1674 vcount.push_back(x: (size_t)strtoul10(in: content, out: &content));
1675 // skip whitespace after it
1676 SkipSpacesAndLineEnd(inout: &content, end);
1677 }
1678 }
1679 }
1680 } else if (currentName == "p") {
1681 if (!currentNode.empty()) {
1682 // now here the actual fun starts - these are the indices to construct the mesh data from
1683 actualPrimitives += ReadPrimitives(node&: currentNode, pMesh, pPerIndexChannels&: perIndexData, pNumPrimitives: numPrimitives, pVCount: vcount, pPrimType: primType);
1684 }
1685 } else if (currentName == "extra") {
1686 // skip
1687 } else if (currentName == "ph") {
1688 // skip
1689 } else {
1690 throw DeadlyImportError("Unexpected sub element <", currentName, "> in tag <", elementName, ">");
1691 }
1692 }
1693
1694#ifdef ASSIMP_BUILD_DEBUG
1695 if (primType != Prim_TriFans && primType != Prim_TriStrips && primType != Prim_LineStrip &&
1696 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'.
1697 ai_assert(actualPrimitives == numPrimitives);
1698 }
1699#endif
1700
1701 // only when we're done reading all <p> tags (and thus know the final vertex count) can we commit the submesh
1702 subgroup.mNumFaces = actualPrimitives;
1703 pMesh.mSubMeshes.push_back(x: subgroup);
1704}
1705
1706// ------------------------------------------------------------------------------------------------
1707// Reads a single input channel element and stores it in the given array, if valid
1708void ColladaParser::ReadInputChannel(XmlNode &node, std::vector<InputChannel> &poChannels) {
1709 InputChannel channel;
1710
1711 // read semantic
1712 std::string semantic;
1713 XmlParser::getStdStrAttribute(xmlNode&: node, name: "semantic", val&: semantic);
1714 channel.mType = GetTypeForSemantic(pSemantic: semantic);
1715
1716 // read source
1717 std::string source;
1718 XmlParser::getStdStrAttribute(xmlNode&: node, name: "source", val&: source);
1719 if (source[0] != '#') {
1720 throw DeadlyImportError("Unknown reference format in url \"", source, "\" in source attribute of <input> element.");
1721 }
1722 channel.mAccessor = source.c_str() + 1; // skipping the leading #, hopefully the remaining text is the accessor ID only
1723
1724 // read index offset, if per-index <input>
1725 if (XmlParser::hasAttribute(xmlNode&: node, name: "offset")) {
1726 XmlParser::getUIntAttribute(xmlNode&: node, name: "offset", val&: (unsigned int &)channel.mOffset);
1727 }
1728
1729 // read set if texture coordinates
1730 if (channel.mType == IT_Texcoord || channel.mType == IT_Color) {
1731 unsigned int attrSet = 0;
1732 if (XmlParser::getUIntAttribute(xmlNode&: node, name: "set", val&: attrSet))
1733 channel.mIndex = attrSet;
1734 }
1735
1736 // store, if valid type
1737 if (channel.mType != IT_Invalid)
1738 poChannels.push_back(x: channel);
1739}
1740
1741// ------------------------------------------------------------------------------------------------
1742// Reads a <p> primitive index list and assembles the mesh data into the given mesh
1743size_t ColladaParser::ReadPrimitives(XmlNode &node, Mesh &pMesh, std::vector<InputChannel> &pPerIndexChannels,
1744 size_t pNumPrimitives, const std::vector<size_t> &pVCount, PrimitiveType pPrimType) {
1745 // determine number of indices coming per vertex
1746 // find the offset index for all per-vertex channels
1747 size_t numOffsets = 1;
1748 size_t perVertexOffset = SIZE_MAX; // invalid value
1749 for (const InputChannel &channel : pPerIndexChannels) {
1750 numOffsets = std::max(a: numOffsets, b: channel.mOffset + 1);
1751 if (channel.mType == IT_Vertex)
1752 perVertexOffset = channel.mOffset;
1753 }
1754
1755 // determine the expected number of indices
1756 size_t expectedPointCount = 0;
1757 switch (pPrimType) {
1758 case Prim_Polylist: {
1759 for (size_t i : pVCount)
1760 expectedPointCount += i;
1761 break;
1762 }
1763 case Prim_Lines:
1764 expectedPointCount = 2 * pNumPrimitives;
1765 break;
1766 case Prim_Triangles:
1767 expectedPointCount = 3 * pNumPrimitives;
1768 break;
1769 default:
1770 break;
1771 }
1772
1773 // and read all indices into a temporary array
1774 std::vector<size_t> indices;
1775 if (expectedPointCount > 0) {
1776 indices.reserve(n: expectedPointCount * numOffsets);
1777 }
1778
1779 // It is possible to not contain any indices
1780 if (pNumPrimitives > 0) {
1781 std::string v;
1782 XmlParser::getValueAsString(node, text&: v);
1783 const char *content = v.c_str();
1784 const char *end = content + v.size();
1785
1786 SkipSpacesAndLineEnd(inout: &content, end);
1787 while (*content != 0) {
1788 // read a value.
1789 // Hack: (thom) Some exporters put negative indices sometimes. We just try to carry on anyways.
1790 int value = std::max(a: 0, b: strtol10(in: content, out: &content));
1791 indices.push_back(x: size_t(value));
1792 // skip whitespace after it
1793 SkipSpacesAndLineEnd(inout: &content, end);
1794 }
1795 }
1796
1797 // complain if the index count doesn't fit
1798 if (expectedPointCount > 0 && indices.size() != expectedPointCount * numOffsets) {
1799 if (pPrimType == Prim_Lines) {
1800 // HACK: We just fix this number since SketchUp 15.3.331 writes the wrong 'count' for 'lines'
1801 ReportWarning(msg: "Expected different index count in <p> element, %zu instead of %zu.", indices.size(), expectedPointCount * numOffsets);
1802 pNumPrimitives = (indices.size() / numOffsets) / 2;
1803 } else {
1804 throw DeadlyImportError("Expected different index count in <p> element.");
1805 }
1806 } else if (expectedPointCount == 0 && (indices.size() % numOffsets) != 0) {
1807 throw DeadlyImportError("Expected different index count in <p> element.");
1808 }
1809
1810 // find the data for all sources
1811 for (auto it = pMesh.mPerVertexData.begin(); it != pMesh.mPerVertexData.end(); ++it) {
1812 InputChannel &input = *it;
1813 if (input.mResolved) {
1814 continue;
1815 }
1816
1817 // find accessor
1818 input.mResolved = &ResolveLibraryReference(pLibrary: mAccessorLibrary, pURL: input.mAccessor);
1819 // resolve accessor's data pointer as well, if necessary
1820 const Accessor *acc = input.mResolved;
1821 if (!acc->mData) {
1822 acc->mData = &ResolveLibraryReference(pLibrary: mDataLibrary, pURL: acc->mSource);
1823 const size_t dataSize = acc->mOffset + acc->mCount * acc->mStride;
1824 if (dataSize > acc->mData->mValues.size()) {
1825 throw DeadlyImportError("Not enough data for accessor");
1826 }
1827 }
1828 }
1829 // and the same for the per-index channels
1830 for (auto it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it) {
1831 InputChannel &input = *it;
1832 if (input.mResolved) {
1833 continue;
1834 }
1835
1836 // ignore vertex pointer, it doesn't refer to an accessor
1837 if (input.mType == IT_Vertex) {
1838 // warn if the vertex channel does not refer to the <vertices> element in the same mesh
1839 if (input.mAccessor != pMesh.mVertexID) {
1840 throw DeadlyImportError("Unsupported vertex referencing scheme.");
1841 }
1842 continue;
1843 }
1844
1845 // find accessor
1846 input.mResolved = &ResolveLibraryReference(pLibrary: mAccessorLibrary, pURL: input.mAccessor);
1847 // resolve accessor's data pointer as well, if necessary
1848 const Accessor *acc = input.mResolved;
1849 if (!acc->mData) {
1850 acc->mData = &ResolveLibraryReference(pLibrary: mDataLibrary, pURL: acc->mSource);
1851 const size_t dataSize = acc->mOffset + acc->mCount * acc->mStride;
1852 if (dataSize > acc->mData->mValues.size()) {
1853 throw DeadlyImportError("Not enough data for accessor");
1854 }
1855 }
1856 }
1857
1858 // For continued primitives, the given count does not come all in one <p>, but only one primitive per <p>
1859 size_t numPrimitives = pNumPrimitives;
1860 if (pPrimType == Prim_TriFans || pPrimType == Prim_Polygon) {
1861 numPrimitives = 1;
1862 }
1863
1864 // For continued primitives, the given count is actually the number of <p>'s inside the parent tag
1865 if (pPrimType == Prim_TriStrips) {
1866 size_t numberOfVertices = indices.size() / numOffsets;
1867 numPrimitives = numberOfVertices - 2;
1868 }
1869 if (pPrimType == Prim_LineStrip) {
1870 size_t numberOfVertices = indices.size() / numOffsets;
1871 numPrimitives = numberOfVertices - 1;
1872 }
1873
1874 pMesh.mFaceSize.reserve(n: numPrimitives);
1875 pMesh.mFacePosIndices.reserve(n: indices.size() / numOffsets);
1876
1877 size_t polylistStartVertex = 0;
1878 for (size_t currentPrimitive = 0; currentPrimitive < numPrimitives; currentPrimitive++) {
1879 // determine number of points for this primitive
1880 size_t numPoints = 0;
1881 switch (pPrimType) {
1882 case Prim_Lines:
1883 numPoints = 2;
1884 for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
1885 CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
1886 break;
1887 case Prim_LineStrip:
1888 numPoints = 2;
1889 for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
1890 CopyVertex(currentVertex, numOffsets, numPoints: 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
1891 break;
1892 case Prim_Triangles:
1893 numPoints = 3;
1894 for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
1895 CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
1896 break;
1897 case Prim_TriStrips:
1898 numPoints = 3;
1899 ReadPrimTriStrips(numOffsets, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
1900 break;
1901 case Prim_Polylist:
1902 numPoints = pVCount[currentPrimitive];
1903 for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
1904 CopyVertex(currentVertex: polylistStartVertex + currentVertex, numOffsets, numPoints: 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive: 0, indices);
1905 polylistStartVertex += numPoints;
1906 break;
1907 case Prim_TriFans:
1908 case Prim_Polygon:
1909 numPoints = indices.size() / numOffsets;
1910 for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
1911 CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
1912 break;
1913 default:
1914 // LineStrip is not supported due to expected index unmangling
1915 throw DeadlyImportError("Unsupported primitive type.");
1916 }
1917
1918 // store the face size to later reconstruct the face from
1919 pMesh.mFaceSize.push_back(x: numPoints);
1920 }
1921
1922 // if I ever get my hands on that guy who invented this steaming pile of indirection...
1923 return numPrimitives;
1924}
1925
1926///@note This function won't work correctly if both PerIndex and PerVertex channels have same channels.
1927///For example if TEXCOORD present in both <vertices> and <polylist> tags this function will create wrong uv coordinates.
1928///It's not clear from COLLADA documentation whether this is allowed or not. For now only exporter fixed to avoid such behavior
1929void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, Mesh &pMesh,
1930 std::vector<InputChannel> &pPerIndexChannels, size_t currentPrimitive, const std::vector<size_t> &indices) {
1931 // calculate the base offset of the vertex whose attributes we ant to copy
1932 size_t baseOffset = currentPrimitive * numOffsets * numPoints + currentVertex * numOffsets;
1933
1934 // don't overrun the boundaries of the index list
1935 ai_assert((baseOffset + numOffsets - 1) < indices.size());
1936
1937 // extract per-vertex channels using the global per-vertex offset
1938 for (auto it = pMesh.mPerVertexData.begin(); it != pMesh.mPerVertexData.end(); ++it) {
1939 ExtractDataObjectFromChannel(pInput: *it, pLocalIndex: indices[baseOffset + perVertexOffset], pMesh);
1940 }
1941 // and extract per-index channels using there specified offset
1942 for (auto it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it) {
1943 ExtractDataObjectFromChannel(pInput: *it, pLocalIndex: indices[baseOffset + it->mOffset], pMesh);
1944 }
1945
1946 // store the vertex-data index for later assignment of bone vertex weights
1947 pMesh.mFacePosIndices.push_back(x: indices[baseOffset + perVertexOffset]);
1948}
1949
1950void ColladaParser::ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Mesh &pMesh, std::vector<InputChannel> &pPerIndexChannels,
1951 size_t currentPrimitive, const std::vector<size_t> &indices) {
1952 if (currentPrimitive % 2 != 0) {
1953 //odd tristrip triangles need their indices mangled, to preserve winding direction
1954 CopyVertex(currentVertex: 1, numOffsets, numPoints: 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
1955 CopyVertex(currentVertex: 0, numOffsets, numPoints: 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
1956 CopyVertex(currentVertex: 2, numOffsets, numPoints: 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
1957 } else { //for non tristrips or even tristrip triangles
1958 CopyVertex(currentVertex: 0, numOffsets, numPoints: 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
1959 CopyVertex(currentVertex: 1, numOffsets, numPoints: 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
1960 CopyVertex(currentVertex: 2, numOffsets, numPoints: 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
1961 }
1962}
1963
1964// ------------------------------------------------------------------------------------------------
1965// Extracts a single object from an input channel and stores it in the appropriate mesh data array
1966void ColladaParser::ExtractDataObjectFromChannel(const InputChannel &pInput, size_t pLocalIndex, Mesh &pMesh) {
1967 // ignore vertex referrer - we handle them that separate
1968 if (pInput.mType == IT_Vertex) {
1969 return;
1970 }
1971
1972 const Accessor &acc = *pInput.mResolved;
1973 if (pLocalIndex >= acc.mCount) {
1974 throw DeadlyImportError("Invalid data index (", pLocalIndex, "/", acc.mCount, ") in primitive specification");
1975 }
1976
1977 // get a pointer to the start of the data object referred to by the accessor and the local index
1978 const ai_real *dataObject = &(acc.mData->mValues[0]) + acc.mOffset + pLocalIndex * acc.mStride;
1979
1980 // assemble according to the accessors component sub-offset list. We don't care, yet,
1981 // what kind of object exactly we're extracting here
1982 ai_real obj[4];
1983 for (size_t c = 0; c < 4; ++c) {
1984 obj[c] = dataObject[acc.mSubOffset[c]];
1985 }
1986
1987 // now we reinterpret it according to the type we're reading here
1988 switch (pInput.mType) {
1989 case IT_Position: // ignore all position streams except 0 - there can be only one position
1990 if (pInput.mIndex == 0) {
1991 pMesh.mPositions.emplace_back(args&: obj[0], args&: obj[1], args&: obj[2]);
1992 } else {
1993 ASSIMP_LOG_ERROR("Collada: just one vertex position stream supported");
1994 }
1995 break;
1996 case IT_Normal:
1997 // pad to current vertex count if necessary
1998 if (pMesh.mNormals.size() < pMesh.mPositions.size() - 1)
1999 pMesh.mNormals.insert(position: pMesh.mNormals.end(), n: pMesh.mPositions.size() - pMesh.mNormals.size() - 1, x: aiVector3D(0, 1, 0));
2000
2001 // ignore all normal streams except 0 - there can be only one normal
2002 if (pInput.mIndex == 0) {
2003 pMesh.mNormals.emplace_back(args&: obj[0], args&: obj[1], args&: obj[2]);
2004 } else {
2005 ASSIMP_LOG_ERROR("Collada: just one vertex normal stream supported");
2006 }
2007 break;
2008 case IT_Tangent:
2009 // pad to current vertex count if necessary
2010 if (pMesh.mTangents.size() < pMesh.mPositions.size() - 1)
2011 pMesh.mTangents.insert(position: pMesh.mTangents.end(), n: pMesh.mPositions.size() - pMesh.mTangents.size() - 1, x: aiVector3D(1, 0, 0));
2012
2013 // ignore all tangent streams except 0 - there can be only one tangent
2014 if (pInput.mIndex == 0) {
2015 pMesh.mTangents.emplace_back(args&: obj[0], args&: obj[1], args&: obj[2]);
2016 } else {
2017 ASSIMP_LOG_ERROR("Collada: just one vertex tangent stream supported");
2018 }
2019 break;
2020 case IT_Bitangent:
2021 // pad to current vertex count if necessary
2022 if (pMesh.mBitangents.size() < pMesh.mPositions.size() - 1) {
2023 pMesh.mBitangents.insert(position: pMesh.mBitangents.end(), n: pMesh.mPositions.size() - pMesh.mBitangents.size() - 1, x: aiVector3D(0, 0, 1));
2024 }
2025
2026 // ignore all bitangent streams except 0 - there can be only one bitangent
2027 if (pInput.mIndex == 0) {
2028 pMesh.mBitangents.emplace_back(args&: obj[0], args&: obj[1], args&: obj[2]);
2029 } else {
2030 ASSIMP_LOG_ERROR("Collada: just one vertex bitangent stream supported");
2031 }
2032 break;
2033 case IT_Texcoord:
2034 // up to 4 texture coord sets are fine, ignore the others
2035 if (pInput.mIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS) {
2036 // pad to current vertex count if necessary
2037 if (pMesh.mTexCoords[pInput.mIndex].size() < pMesh.mPositions.size() - 1)
2038 pMesh.mTexCoords[pInput.mIndex].insert(position: pMesh.mTexCoords[pInput.mIndex].end(),
2039 n: pMesh.mPositions.size() - pMesh.mTexCoords[pInput.mIndex].size() - 1, x: aiVector3D(0, 0, 0));
2040
2041 pMesh.mTexCoords[pInput.mIndex].emplace_back(args&: obj[0], args&: obj[1], args&: obj[2]);
2042 if (0 != acc.mSubOffset[2] || 0 != acc.mSubOffset[3]) {
2043 pMesh.mNumUVComponents[pInput.mIndex] = 3;
2044 }
2045 } else {
2046 ASSIMP_LOG_ERROR("Collada: too many texture coordinate sets. Skipping.");
2047 }
2048 break;
2049 case IT_Color:
2050 // up to 4 color sets are fine, ignore the others
2051 if (pInput.mIndex < AI_MAX_NUMBER_OF_COLOR_SETS) {
2052 // pad to current vertex count if necessary
2053 if (pMesh.mColors[pInput.mIndex].size() < pMesh.mPositions.size() - 1)
2054 pMesh.mColors[pInput.mIndex].insert(position: pMesh.mColors[pInput.mIndex].end(),
2055 n: pMesh.mPositions.size() - pMesh.mColors[pInput.mIndex].size() - 1, x: aiColor4D(0, 0, 0, 1));
2056
2057 aiColor4D result(0, 0, 0, 1);
2058 for (size_t i = 0; i < pInput.mResolved->mSize; ++i) {
2059 result[static_cast<unsigned int>(i)] = obj[pInput.mResolved->mSubOffset[i]];
2060 }
2061 pMesh.mColors[pInput.mIndex].push_back(x: result);
2062 } else {
2063 ASSIMP_LOG_ERROR("Collada: too many vertex color sets. Skipping.");
2064 }
2065
2066 break;
2067 default:
2068 // IT_Invalid and IT_Vertex
2069 ai_assert(false && "shouldn't ever get here");
2070 }
2071}
2072
2073// ------------------------------------------------------------------------------------------------
2074// Reads the library of node hierarchies and scene parts
2075void ColladaParser::ReadSceneLibrary(XmlNode &node) {
2076 if (node.empty()) {
2077 return;
2078 }
2079
2080 for (XmlNode &currentNode : node.children()) {
2081 const std::string &currentName = currentNode.name();
2082 if (currentName == "visual_scene") {
2083 // read ID. Is optional according to the spec, but how on earth should a scene_instance refer to it then?
2084 std::string id;
2085 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "id", val&: id);
2086
2087 // read name if given.
2088 std::string attrName = "Scene";
2089 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "name")) {
2090 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "name", val&: attrName);
2091 }
2092
2093 // create a node and store it in the library under its ID
2094 Node *sceneNode = new Node;
2095 sceneNode->mID = id;
2096 sceneNode->mName = attrName;
2097 mNodeLibrary[sceneNode->mID] = sceneNode;
2098
2099 ReadSceneNode(node&: currentNode, pNode: sceneNode);
2100 }
2101 }
2102}
2103
2104// ------------------------------------------------------------------------------------------------
2105// Reads a scene node's contents including children and stores it in the given node
2106void ColladaParser::ReadSceneNode(XmlNode &node, Node *pNode) {
2107 // quit immediately on <bla/> elements
2108 if (node.empty()) {
2109 return;
2110 }
2111
2112 for (XmlNode &currentNode : node.children()) {
2113 const std::string &currentName = currentNode.name();
2114 if (currentName == "node") {
2115 Node *child = new Node;
2116 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "id")) {
2117 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "id", val&: child->mID);
2118 }
2119 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "sid")) {
2120 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "sid", val&: child->mSID);
2121 }
2122 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "name")) {
2123 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "name", val&: child->mName);
2124 }
2125 if (pNode) {
2126 pNode->mChildren.push_back(x: child);
2127 child->mParent = pNode;
2128 } else {
2129 // no parent node given, probably called from <library_nodes> element.
2130 // create new node in node library
2131 mNodeLibrary[child->mID] = child;
2132 }
2133
2134 // read on recursively from there
2135 ReadSceneNode(node&: currentNode, pNode: child);
2136 continue;
2137 } else if (!pNode) {
2138 // For any further stuff we need a valid node to work on
2139 continue;
2140 }
2141 if (currentName == "lookat") {
2142 ReadNodeTransformation(node&: currentNode, pNode, pType: TF_LOOKAT);
2143 } else if (currentName == "matrix") {
2144 ReadNodeTransformation(node&: currentNode, pNode, pType: TF_MATRIX);
2145 } else if (currentName == "rotate") {
2146 ReadNodeTransformation(node&: currentNode, pNode, pType: TF_ROTATE);
2147 } else if (currentName == "scale") {
2148 ReadNodeTransformation(node&: currentNode, pNode, pType: TF_SCALE);
2149 } else if (currentName == "skew") {
2150 ReadNodeTransformation(node&: currentNode, pNode, pType: TF_SKEW);
2151 } else if (currentName == "translate") {
2152 ReadNodeTransformation(node&: currentNode, pNode, pType: TF_TRANSLATE);
2153 } else if (currentName == "render" && pNode->mParent == nullptr && 0 == pNode->mPrimaryCamera.length()) {
2154 // ... scene evaluation or, in other words, postprocessing pipeline,
2155 // or, again in other words, a turing-complete description how to
2156 // render a Collada scene. The only thing that is interesting for
2157 // us is the primary camera.
2158 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "camera_node")) {
2159 std::string s;
2160 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "camera_node", val&: s);
2161 if (s[0] != '#') {
2162 ASSIMP_LOG_ERROR("Collada: Unresolved reference format of camera");
2163 } else {
2164 pNode->mPrimaryCamera = s.c_str() + 1;
2165 }
2166 }
2167 } else if (currentName == "instance_node") {
2168 // find the node in the library
2169 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "url")) {
2170 std::string s;
2171 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "url", val&: s);
2172 if (s[0] != '#') {
2173 ASSIMP_LOG_ERROR("Collada: Unresolved reference format of node");
2174 } else {
2175 pNode->mNodeInstances.emplace_back();
2176 pNode->mNodeInstances.back().mNode = s.c_str() + 1;
2177 }
2178 }
2179 } else if (currentName == "instance_geometry" || currentName == "instance_controller") {
2180 // Reference to a mesh or controller, with possible material associations
2181 ReadNodeGeometry(node&: currentNode, pNode);
2182 } else if (currentName == "instance_light") {
2183 // Reference to a light, name given in 'url' attribute
2184 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "url")) {
2185 std::string url;
2186 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "url", val&: url);
2187 if (url[0] != '#') {
2188 throw DeadlyImportError("Unknown reference format in <instance_light> element");
2189 }
2190
2191 pNode->mLights.emplace_back();
2192 pNode->mLights.back().mLight = url.c_str() + 1;
2193 }
2194 } else if (currentName == "instance_camera") {
2195 // Reference to a camera, name given in 'url' attribute
2196 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "url")) {
2197 std::string url;
2198 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "url", val&: url);
2199 if (url[0] != '#') {
2200 throw DeadlyImportError("Unknown reference format in <instance_camera> element");
2201 }
2202 pNode->mCameras.emplace_back();
2203 pNode->mCameras.back().mCamera = url.c_str() + 1;
2204 }
2205 }
2206 }
2207}
2208
2209
2210// ------------------------------------------------------------------------------------------------
2211// Processes bind_vertex_input and bind elements
2212void ColladaParser::ReadMaterialVertexInputBinding(XmlNode &node, Collada::SemanticMappingTable &tbl) {
2213 std::string name = node.name();
2214 for (XmlNode &currentNode : node.children()) {
2215 const std::string &currentName = currentNode.name();
2216 if (currentName == "bind_vertex_input") {
2217 Collada::InputSemanticMapEntry vn;
2218
2219 // effect semantic
2220 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "semantic")) {
2221 std::string s;
2222 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "semantic", val&: s);
2223 XmlParser::getUIntAttribute(xmlNode&: currentNode, name: "input_semantic", val&: (unsigned int &)vn.mType);
2224 }
2225 std::string s;
2226 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "semantic", val&: s);
2227
2228 // input semantic
2229 XmlParser::getUIntAttribute(xmlNode&: currentNode, name: "input_semantic", val&: (unsigned int &)vn.mType);
2230
2231 // index of input set
2232 if (XmlParser::hasAttribute(xmlNode&: currentNode, name: "input_set")) {
2233 XmlParser::getUIntAttribute(xmlNode&: currentNode, name: "input_set", val&: vn.mSet);
2234 }
2235
2236 tbl.mMap[s] = vn;
2237 } else if (currentName == "bind") {
2238 ASSIMP_LOG_WARN("Collada: Found unsupported <bind> element");
2239 }
2240 }
2241}
2242
2243void ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem &zip_archive) {
2244 // Attempt to load any undefined Collada::Image in ImageLibrary
2245 for (auto &it : mImageLibrary) {
2246 if (Image &image = it.second; image.mImageData.empty()) {
2247 std::unique_ptr<IOStream> image_file(zip_archive.Open(pFilename: image.mFileName.c_str()));
2248 if (image_file) {
2249 image.mImageData.resize(new_size: image_file->FileSize());
2250 image_file->Read(pvBuffer: image.mImageData.data(), pSize: image_file->FileSize(), pCount: 1);
2251 image.mEmbeddedFormat = BaseImporter::GetExtension(pFile: image.mFileName);
2252 if (image.mEmbeddedFormat == "jpeg") {
2253 image.mEmbeddedFormat = "jpg";
2254 }
2255 }
2256 }
2257 }
2258}
2259
2260// ------------------------------------------------------------------------------------------------
2261// Reads a mesh reference in a node and adds it to the node's mesh list
2262void ColladaParser::ReadNodeGeometry(XmlNode &node, Node *pNode) {
2263 // referred mesh is given as an attribute of the <instance_geometry> element
2264 std::string url;
2265 XmlParser::getStdStrAttribute(xmlNode&: node, name: "url", val&: url);
2266 if (url[0] != '#') {
2267 throw DeadlyImportError("Unknown reference format");
2268 }
2269
2270 Collada::MeshInstance instance;
2271 instance.mMeshOrController = url.c_str() + 1; // skipping the leading #
2272
2273 for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) {
2274 const std::string &currentName = currentNode.name();
2275 if (currentName == "bind_material") {
2276 XmlNode techNode = currentNode.child(name: "technique_common");
2277 if (techNode) {
2278 for (XmlNode instanceMatNode = techNode.child(name: "instance_material"); instanceMatNode; instanceMatNode = instanceMatNode.next_sibling())
2279 {
2280 const std::string &instance_name = instanceMatNode.name();
2281 if (instance_name == "instance_material")
2282 {
2283 // read ID of the geometry subgroup and the target material
2284 std::string group;
2285 XmlParser::getStdStrAttribute(xmlNode&: instanceMatNode, name: "symbol", val&: group);
2286 XmlParser::getStdStrAttribute(xmlNode&: instanceMatNode, name: "target", val&: url);
2287 const char *urlMat = url.c_str();
2288 Collada::SemanticMappingTable s;
2289 if (urlMat[0] == '#')
2290 urlMat++;
2291
2292 s.mMatName = urlMat;
2293 ReadMaterialVertexInputBinding(node&: instanceMatNode, tbl&: s);
2294 // store the association
2295 instance.mMaterials[group] = s;
2296 }
2297 }
2298 }
2299 }
2300 }
2301
2302 // store it
2303 pNode->mMeshes.push_back(x: instance);
2304}
2305
2306// ------------------------------------------------------------------------------------------------
2307// Reads the collada scene
2308void ColladaParser::ReadScene(XmlNode &node) {
2309 if (node.empty()) {
2310 return;
2311 }
2312
2313 for (XmlNode &currentNode : node.children()) {
2314 const std::string &currentName = currentNode.name();
2315 if (currentName == "instance_visual_scene") {
2316 // should be the first and only occurrence
2317 if (mRootNode) {
2318 throw DeadlyImportError("Invalid scene containing multiple root nodes in <instance_visual_scene> element");
2319 }
2320
2321 // read the url of the scene to instance. Should be of format "#some_name"
2322 std::string url;
2323 XmlParser::getStdStrAttribute(xmlNode&: currentNode, name: "url", val&: url);
2324 if (url[0] != '#') {
2325 throw DeadlyImportError("Unknown reference format in <instance_visual_scene> element");
2326 }
2327
2328 // find the referred scene, skip the leading #
2329 auto sit = mNodeLibrary.find(x: url.c_str() + 1);
2330 if (sit == mNodeLibrary.end()) {
2331 throw DeadlyImportError("Unable to resolve visual_scene reference \"", std::string(std::move(url)), "\" in <instance_visual_scene> element.");
2332 }
2333 mRootNode = sit->second;
2334 }
2335 }
2336}
2337
2338// ------------------------------------------------------------------------------------------------
2339// Calculates the resulting transformation from all the given transform steps
2340aiMatrix4x4 ColladaParser::CalculateResultTransform(const std::vector<Transform> &pTransforms) const {
2341 aiMatrix4x4 res;
2342
2343 for (std::vector<Transform>::const_iterator it = pTransforms.begin(); it != pTransforms.end(); ++it) {
2344 const Transform &tf = *it;
2345 switch (tf.mType) {
2346 case TF_LOOKAT: {
2347 aiVector3D pos(tf.f[0], tf.f[1], tf.f[2]);
2348 aiVector3D dstPos(tf.f[3], tf.f[4], tf.f[5]);
2349 aiVector3D up = aiVector3D(tf.f[6], tf.f[7], tf.f[8]).Normalize();
2350 aiVector3D dir = aiVector3D(dstPos - pos).Normalize();
2351 aiVector3D right = (dir ^ up).Normalize();
2352
2353 res *= aiMatrix4x4(
2354 right.x, up.x, -dir.x, pos.x,
2355 right.y, up.y, -dir.y, pos.y,
2356 right.z, up.z, -dir.z, pos.z,
2357 0, 0, 0, 1);
2358 break;
2359 }
2360 case TF_ROTATE: {
2361 aiMatrix4x4 rot;
2362 ai_real angle = tf.f[3] * ai_real(AI_MATH_PI) / ai_real(180.0);
2363 aiVector3D axis(tf.f[0], tf.f[1], tf.f[2]);
2364 aiMatrix4x4::Rotation(a: angle, axis, out&: rot);
2365 res *= rot;
2366 break;
2367 }
2368 case TF_TRANSLATE: {
2369 aiMatrix4x4 trans;
2370 aiMatrix4x4::Translation(v: aiVector3D(tf.f[0], tf.f[1], tf.f[2]), out&: trans);
2371 res *= trans;
2372 break;
2373 }
2374 case TF_SCALE: {
2375 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,
2376 0.0f, 0.0f, 0.0f, 1.0f);
2377 res *= scale;
2378 break;
2379 }
2380 case TF_SKEW:
2381 // TODO: (thom)
2382 ai_assert(false);
2383 break;
2384 case TF_MATRIX: {
2385 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],
2386 tf.f[8], tf.f[9], tf.f[10], tf.f[11], tf.f[12], tf.f[13], tf.f[14], tf.f[15]);
2387 res *= mat;
2388 break;
2389 }
2390 default:
2391 ai_assert(false);
2392 break;
2393 }
2394 }
2395
2396 return res;
2397}
2398
2399// ------------------------------------------------------------------------------------------------
2400// Determines the input data type for the given semantic string
2401InputType ColladaParser::GetTypeForSemantic(const std::string &semantic) {
2402 if (semantic.empty()) {
2403 ASSIMP_LOG_WARN("Vertex input type is empty.");
2404 return IT_Invalid;
2405 }
2406
2407 if (semantic == "POSITION")
2408 return IT_Position;
2409 else if (semantic == "TEXCOORD")
2410 return IT_Texcoord;
2411 else if (semantic == "NORMAL")
2412 return IT_Normal;
2413 else if (semantic == "COLOR")
2414 return IT_Color;
2415 else if (semantic == "VERTEX")
2416 return IT_Vertex;
2417 else if (semantic == "BINORMAL" || semantic == "TEXBINORMAL")
2418 return IT_Bitangent;
2419 else if (semantic == "TANGENT" || semantic == "TEXTANGENT")
2420 return IT_Tangent;
2421
2422 ASSIMP_LOG_WARN("Unknown vertex input type \"", semantic, "\". Ignoring.");
2423 return IT_Invalid;
2424}
2425
2426#endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER
2427

source code of qtquick3d/src/3rdparty/assimp/src/code/AssetLib/Collada/ColladaParser.cpp