Coverage Report

Created: 2026-10-28 07:46

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