Coverage Report

Created: 2025-08-28 06:38

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