Coverage Report

Created: 2026-07-30 07:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/Collada/ColladaExporter.cpp
Line
Count
Source
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2026, assimp team
6
7
All rights reserved.
8
9
Redistribution and use of this software in source and binary forms,
10
with or without modification, are permitted provided that the
11
following conditions are met:
12
13
* Redistributions of source code must retain the above
14
  copyright notice, this list of conditions and the
15
  following disclaimer.
16
17
* Redistributions in binary form must reproduce the above
18
  copyright notice, this list of conditions and the
19
  following disclaimer in the documentation and/or other
20
  materials provided with the distribution.
21
22
* Neither the name of the assimp team, nor the names of its
23
  contributors may be used to endorse or promote products
24
  derived from this software without specific prior
25
  written permission of the assimp team.
26
27
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
----------------------------------------------------------------------
39
*/
40
41
#ifndef ASSIMP_BUILD_NO_EXPORT
42
#ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER
43
44
#include "ColladaExporter.h"
45
46
#include <assimp/Bitmap.h>
47
#include <assimp/ColladaMetaData.h>
48
#include <assimp/DefaultIOSystem.h>
49
#include <assimp/Exceptional.h>
50
#include <assimp/MathFunctions.h>
51
#include <assimp/SceneCombiner.h>
52
#include <assimp/StringUtils.h>
53
#include <assimp/XMLTools.h>
54
#include <assimp/commonMetaData.h>
55
#include <assimp/fast_atof.h>
56
#include <assimp/scene.h>
57
#include <assimp/Exporter.hpp>
58
#include <assimp/IOSystem.hpp>
59
60
#include <ctime>
61
#include <memory>
62
63
namespace Assimp {
64
65
119
static const aiNode *findSkeletonRootNode(const aiScene *scene, const aiMesh *mesh) {
66
119
    std::set<const aiNode *> topParentBoneNodes;
67
119
    if (mesh && mesh->mNumBones > 0) {
68
547
        for (unsigned int i = 0; i < mesh->mNumBones; ++i) {
69
428
            aiBone *bone = mesh->mBones[i];
70
71
428
            const aiNode *node = scene->mRootNode->findBoneNode(bone);
72
428
            if (node) {
73
989
                while (node->mParent && scene->findBone(node->mParent->mName) != nullptr) {
74
561
                    node = node->mParent;
75
561
                }
76
428
                topParentBoneNodes.insert(node);
77
428
            }
78
428
        }
79
119
    }
80
81
119
    if (!topParentBoneNodes.empty()) {
82
119
        const aiNode *parentBoneNode = *topParentBoneNodes.begin();
83
119
        if (topParentBoneNodes.size() == 1) {
84
74
            return parentBoneNode;
85
74
        } else {
86
45
            for (auto it : topParentBoneNodes) {
87
45
                if (it->mParent) return it->mParent;
88
45
            }
89
0
            return parentBoneNode;
90
45
        }
91
119
    }
92
93
0
    return nullptr;
94
119
}
95
96
// ------------------------------------------------------------------------------------------------
97
// Worker function for exporting a scene to Collada. Prototyped and registered in Exporter.cpp
98
1.54k
void ExportSceneCollada(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) {
99
1.54k
    std::string path = DefaultIOSystem::absolutePath(std::string(pFile));
100
1.54k
    std::string file = DefaultIOSystem::completeBaseName(std::string(pFile));
101
102
    // invoke the exporter
103
1.54k
    ColladaExporter iDoTheExportThing(pScene, pIOSystem, path, file);
104
105
1.54k
    if (iDoTheExportThing.mOutput.fail()) {
106
0
        throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
107
0
    }
108
109
    // we're still here - export successfully completed. Write result to the given IOSYstem
110
1.54k
    std::unique_ptr<IOStream> outfile(pIOSystem->Open(pFile, "wt"));
111
1.54k
    if (outfile == nullptr) {
112
0
        throw DeadlyExportError("could not open output .dae file: " + std::string(pFile));
113
0
    }
114
115
    // XXX maybe use a small wrapper around IOStream that behaves like std::stringstream in order to avoid the extra copy.
116
1.54k
    outfile->Write(iDoTheExportThing.mOutput.str().c_str(), static_cast<size_t>(iDoTheExportThing.mOutput.tellp()), 1);
117
1.54k
}
118
119
// ------------------------------------------------------------------------------------------------
120
// Encodes a string into a valid XML ID using the xsd:ID schema qualifications.
121
44.2k
static const std::string XMLIDEncode(const std::string &name) {
122
44.2k
    const char XML_ID_CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.";
123
44.2k
    const unsigned int XML_ID_CHARS_COUNT = sizeof(XML_ID_CHARS) / sizeof(char) - 1;
124
125
44.2k
    if (name.length() == 0) {
126
0
        return name;
127
0
    }
128
129
44.2k
    std::stringstream idEncoded;
130
131
    // xsd:ID must start with letter or underscore
132
44.2k
    if (!((name[0] >= 'A' && name[0] <= 'z') || name[0] == '_')) {
133
3.37k
        idEncoded << '_';
134
3.37k
    }
135
136
806k
    for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) {
137
        // xsd:ID can only contain letters, digits, underscores, hyphens and periods
138
762k
        if (strchr(XML_ID_CHARS, *it) != nullptr) {
139
724k
            idEncoded << *it;
140
724k
        } else {
141
            // Select placeholder character based on invalid character to reduce ID collisions
142
37.7k
            idEncoded << XML_ID_CHARS[(*it) % XML_ID_CHARS_COUNT];
143
37.7k
        }
144
762k
    }
145
146
44.2k
    return idEncoded.str();
147
44.2k
}
148
149
// ------------------------------------------------------------------------------------------------
150
// Helper functions to create unique ids
151
325k
inline bool IsUniqueId(const std::unordered_set<std::string> &idSet, const std::string &idStr) {
152
325k
    return (idSet.find(idStr) == idSet.end());
153
325k
}
154
155
20.7k
inline std::string MakeUniqueId(const std::unordered_set<std::string> &idSet, const std::string &idPrefix, const std::string &postfix) {
156
20.7k
    std::string result(idPrefix + postfix);
157
20.7k
    if (!IsUniqueId(idSet, result)) {
158
        // Select a number to append
159
6.82k
        size_t idnum = 1;
160
305k
        do {
161
305k
            result = idPrefix + '_' + ai_to_string(idnum) + postfix;
162
305k
            ++idnum;
163
305k
        } while (!IsUniqueId(idSet, result));
164
6.82k
    }
165
20.7k
    return result;
166
20.7k
}
167
168
// ------------------------------------------------------------------------------------------------
169
// Constructor for a specific scene to export
170
ColladaExporter::ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, const std::string &path, const std::string &file) :
171
1.54k
        mIOSystem(pIOSystem),
172
1.54k
        mPath(path),
173
1.54k
        mFile(file),
174
1.54k
        mScene(pScene),
175
1.54k
        endstr("\n") {
176
    // make sure that all formatting happens using the standard, C locale and not the user's current locale
177
1.54k
    mOutput.imbue(std::locale("C"));
178
1.54k
    mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
179
180
    // start writing the file
181
1.54k
    WriteFile();
182
1.54k
}
183
184
// ------------------------------------------------------------------------------------------------
185
// Starts writing the contents
186
1.54k
void ColladaExporter::WriteFile() {
187
    // write the DTD
188
1.54k
    mOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>" << endstr;
189
    // COLLADA element start
190
1.54k
    mOutput << "<COLLADA xmlns=\"http://www.collada.org/2005/11/COLLADASchema\" version=\"1.4.1\">" << endstr;
191
1.54k
    PushTag();
192
193
1.54k
    WriteTextures();
194
1.54k
    WriteHeader();
195
196
    // Add node names to the unique id database first so they are most likely to use their names as unique ids
197
1.54k
    CreateNodeIds(mScene->mRootNode);
198
199
1.54k
    WriteCamerasLibrary();
200
1.54k
    WriteLightsLibrary();
201
1.54k
    WriteMaterials();
202
1.54k
    WriteGeometryLibrary();
203
1.54k
    WriteControllerLibrary();
204
205
1.54k
    WriteSceneLibrary();
206
207
    // customized, Writes the animation library
208
1.54k
    WriteAnimationsLibrary();
209
210
    // instantiate the scene(s)
211
    // For Assimp there will only ever be one
212
1.54k
    mOutput << startstr << "<scene>" << endstr;
213
1.54k
    PushTag();
214
1.54k
    mOutput << startstr << "<instance_visual_scene url=\"#" + mSceneId + "\" />" << endstr;
215
1.54k
    PopTag();
216
1.54k
    mOutput << startstr << "</scene>" << endstr;
217
1.54k
    PopTag();
218
1.54k
    mOutput << "</COLLADA>" << endstr;
219
1.54k
}
220
221
// ------------------------------------------------------------------------------------------------
222
// Writes the asset header
223
1.54k
void ColladaExporter::WriteHeader() {
224
1.54k
    static const ai_real epsilon = Math::getEpsilon<ai_real>();
225
1.54k
    static const aiQuaternion x_rot(aiMatrix3x3(
226
1.54k
            0, -1, 0,
227
1.54k
            1, 0, 0,
228
1.54k
            0, 0, 1));
229
1.54k
    static const aiQuaternion y_rot(aiMatrix3x3(
230
1.54k
            1, 0, 0,
231
1.54k
            0, 1, 0,
232
1.54k
            0, 0, 1));
233
1.54k
    static const aiQuaternion z_rot(aiMatrix3x3(
234
1.54k
            1, 0, 0,
235
1.54k
            0, 0, 1,
236
1.54k
            0, -1, 0));
237
238
1.54k
    static const unsigned int date_nb_chars = 20;
239
1.54k
    char date_str[date_nb_chars];
240
1.54k
    std::time_t date = std::time(nullptr);
241
1.54k
    std::strftime(date_str, date_nb_chars, "%Y-%m-%dT%H:%M:%S", std::localtime(&date));
242
243
1.54k
    aiVector3D scaling;
244
1.54k
    aiQuaternion rotation;
245
1.54k
    aiVector3D position;
246
1.54k
    mScene->mRootNode->mTransformation.Decompose(scaling, rotation, position);
247
1.54k
    rotation.Normalize();
248
249
1.54k
    mAdd_root_node = false;
250
251
1.54k
    ai_real scale = 1.0;
252
1.54k
    if (std::abs(scaling.x - scaling.y) <= epsilon && std::abs(scaling.x - scaling.z) <= epsilon && std::abs(scaling.y - scaling.z) <= epsilon) {
253
1.52k
        scale = (ai_real)((((double)scaling.x) + ((double)scaling.y) + ((double)scaling.z)) / 3.0);
254
1.52k
    } else {
255
19
        mAdd_root_node = true;
256
19
    }
257
258
1.54k
    std::string up_axis = "Y_UP";
259
1.54k
    if (rotation.Equal(x_rot, epsilon)) {
260
0
        up_axis = "X_UP";
261
1.54k
    } else if (rotation.Equal(y_rot, epsilon)) {
262
1.02k
        up_axis = "Y_UP";
263
1.02k
    } else if (rotation.Equal(z_rot, epsilon)) {
264
506
        up_axis = "Z_UP";
265
506
    } else {
266
13
        mAdd_root_node = true;
267
13
    }
268
269
1.54k
    if (!position.Equal(aiVector3D(0, 0, 0))) {
270
9
        mAdd_root_node = true;
271
9
    }
272
273
    // Assimp root nodes can have meshes, Collada Scenes cannot
274
1.54k
    if (mScene->mRootNode->mNumChildren == 0 || mScene->mRootNode->mMeshes != nullptr) {
275
1.54k
        mAdd_root_node = true;
276
1.54k
    }
277
278
1.54k
    if (mAdd_root_node) {
279
1.54k
        up_axis = "Y_UP";
280
1.54k
        scale = 1.0;
281
1.54k
    }
282
283
1.54k
    mOutput << startstr << "<asset>" << endstr;
284
1.54k
    PushTag();
285
1.54k
    mOutput << startstr << "<contributor>" << endstr;
286
1.54k
    PushTag();
287
288
    // If no Scene metadata, use root node metadata
289
1.54k
    aiMetadata *meta = mScene->mMetaData;
290
1.54k
    if (nullptr == meta) {
291
0
        meta = mScene->mRootNode->mMetaData;
292
0
    }
293
294
1.54k
    aiString value;
295
1.54k
    if (!meta || !meta->Get("Author", value)) {
296
1.48k
        mOutput << startstr << "<author>"
297
1.48k
                << "Assimp"
298
1.48k
                << "</author>" << endstr;
299
1.48k
    } else {
300
59
        mOutput << startstr << "<author>" << XMLEscape(value.C_Str()) << "</author>" << endstr;
301
59
    }
302
303
1.54k
    if (nullptr == meta || !meta->Get(AI_METADATA_SOURCE_GENERATOR, value)) {
304
978
        mOutput << startstr << "<authoring_tool>"
305
978
                << "Assimp Exporter"
306
978
                << "</authoring_tool>" << endstr;
307
978
    } else {
308
569
        mOutput << startstr << "<authoring_tool>" << XMLEscape(value.C_Str()) << "</authoring_tool>" << endstr;
309
569
    }
310
311
1.54k
    if (meta) {
312
1.54k
        if (meta->Get("Comments", value)) {
313
26
            mOutput << startstr << "<comments>" << XMLEscape(value.C_Str()) << "</comments>" << endstr;
314
26
        }
315
1.54k
        if (meta->Get(AI_METADATA_SOURCE_COPYRIGHT, value)) {
316
38
            mOutput << startstr << "<copyright>" << XMLEscape(value.C_Str()) << "</copyright>" << endstr;
317
38
        }
318
1.54k
        if (meta->Get("SourceData", value)) {
319
0
            mOutput << startstr << "<source_data>" << XMLEscape(value.C_Str()) << "</source_data>" << endstr;
320
0
        }
321
1.54k
    }
322
323
1.54k
    PopTag();
324
1.54k
    mOutput << startstr << "</contributor>" << endstr;
325
326
1.54k
    if (nullptr == meta || !meta->Get("Created", value)) {
327
1.48k
        mOutput << startstr << "<created>" << date_str << "</created>" << endstr;
328
1.48k
    } else {
329
65
        mOutput << startstr << "<created>" << XMLEscape(value.C_Str()) << "</created>" << endstr;
330
65
    }
331
332
    // Modified date is always the date saved
333
1.54k
    mOutput << startstr << "<modified>" << date_str << "</modified>" << endstr;
334
335
1.54k
    if (meta) {
336
1.54k
        if (meta->Get("Keywords", value)) {
337
11
            mOutput << startstr << "<keywords>" << XMLEscape(value.C_Str()) << "</keywords>" << endstr;
338
11
        }
339
1.54k
        if (meta->Get("Revision", value)) {
340
11
            mOutput << startstr << "<revision>" << XMLEscape(value.C_Str()) << "</revision>" << endstr;
341
11
        }
342
1.54k
        if (meta->Get("Subject", value)) {
343
11
            mOutput << startstr << "<subject>" << XMLEscape(value.C_Str()) << "</subject>" << endstr;
344
11
        }
345
1.54k
        if (meta->Get("Title", value)) {
346
11
            mOutput << startstr << "<title>" << XMLEscape(value.C_Str()) << "</title>" << endstr;
347
11
        }
348
1.54k
    }
349
350
1.54k
    mOutput << startstr << "<unit name=\"meter\" meter=\"" << scale << "\" />" << endstr;
351
1.54k
    mOutput << startstr << "<up_axis>" << up_axis << "</up_axis>" << endstr;
352
1.54k
    PopTag();
353
1.54k
    mOutput << startstr << "</asset>" << endstr;
354
1.54k
}
355
356
// ------------------------------------------------------------------------------------------------
357
// Write the embedded textures
358
1.54k
void ColladaExporter::WriteTextures() {
359
1.54k
    static constexpr unsigned int buffer_size = 1024;
360
1.54k
    char str[buffer_size] = {'\0'};
361
362
1.54k
    if (!mScene->HasTextures()) {
363
1.47k
        return;
364
1.47k
    }
365
366
150
    for (unsigned int i = 0; i < mScene->mNumTextures; i++) {
367
        // It would be great to be able to create a directory in portable standard C++, but it's not the case,
368
        // so we just write the textures in the current directory.
369
370
77
        aiTexture *texture = mScene->mTextures[i];
371
77
        if (nullptr == texture) {
372
0
            continue;
373
0
        }
374
375
77
        ASSIMP_itoa10(str, buffer_size, i + 1);
376
377
77
        std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char *)texture->achFormatHint);
378
379
77
        std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + mIOSystem->getOsSeparator() + name, "wb"));
380
77
        if (outfile == nullptr) {
381
0
            throw DeadlyExportError("could not open output texture file: " + mPath + name);
382
0
        }
383
384
77
        if (texture->mHeight == 0) {
385
59
            outfile->Write((void *)texture->pcData, texture->mWidth, 1);
386
59
        } else {
387
18
            Bitmap::Save(texture, outfile.get());
388
18
        }
389
390
77
        outfile->Flush();
391
392
77
        textures.insert(std::make_pair(i, name));
393
77
    }
394
73
}
395
396
// ------------------------------------------------------------------------------------------------
397
// Write the embedded textures
398
1.54k
void ColladaExporter::WriteCamerasLibrary() {
399
1.54k
    if (!mScene->HasCameras()) {
400
1.46k
        return;
401
1.46k
    }
402
403
78
    mOutput << startstr << "<library_cameras>" << endstr;
404
78
    PushTag();
405
406
163
    for (size_t a = 0; a < mScene->mNumCameras; ++a) {
407
85
        WriteCamera(a);
408
85
    }
409
410
78
    PopTag();
411
78
    mOutput << startstr << "</library_cameras>" << endstr;
412
78
}
413
414
85
void ColladaExporter::WriteCamera(size_t pIndex) {
415
416
85
    const aiCamera *cam = mScene->mCameras[pIndex];
417
85
    if (cam == nullptr) {
418
0
        return;
419
0
    }
420
421
85
    const std::string cameraId = GetObjectUniqueId(AiObjectType::Camera, pIndex);
422
85
    const std::string cameraName = GetObjectName(AiObjectType::Camera, pIndex);
423
424
85
    mOutput << startstr << "<camera id=\"" << cameraId << "\" name=\"" << cameraName << "\" >" << endstr;
425
85
    PushTag();
426
85
    mOutput << startstr << "<optics>" << endstr;
427
85
    PushTag();
428
85
    mOutput << startstr << "<technique_common>" << endstr;
429
85
    PushTag();
430
    //assimp doesn't support the import of orthographic cameras! se we write
431
    //always perspective
432
85
    mOutput << startstr << "<perspective>" << endstr;
433
85
    PushTag();
434
85
    mOutput << startstr << "<xfov sid=\"xfov\">" << AI_RAD_TO_DEG(cam->mHorizontalFOV)
435
85
            << "</xfov>" << endstr;
436
85
    mOutput << startstr << "<aspect_ratio>"
437
85
            << cam->mAspect
438
85
            << "</aspect_ratio>" << endstr;
439
85
    mOutput << startstr << "<znear sid=\"znear\">"
440
85
            << cam->mClipPlaneNear
441
85
            << "</znear>" << endstr;
442
85
    mOutput << startstr << "<zfar sid=\"zfar\">"
443
85
            << cam->mClipPlaneFar
444
85
            << "</zfar>" << endstr;
445
85
    PopTag();
446
85
    mOutput << startstr << "</perspective>" << endstr;
447
85
    PopTag();
448
85
    mOutput << startstr << "</technique_common>" << endstr;
449
85
    PopTag();
450
85
    mOutput << startstr << "</optics>" << endstr;
451
85
    PopTag();
452
85
    mOutput << startstr << "</camera>" << endstr;
453
85
}
454
455
// ------------------------------------------------------------------------------------------------
456
// Write the embedded textures
457
1.54k
void ColladaExporter::WriteLightsLibrary() {
458
1.54k
    if (!mScene->HasLights()) {
459
1.54k
        return;
460
1.54k
    }
461
462
0
    mOutput << startstr << "<library_lights>" << endstr;
463
0
    PushTag();
464
465
0
    for (size_t a = 0; a < mScene->mNumLights; ++a) {
466
0
        WriteLight(a);
467
0
    }
468
469
0
    PopTag();
470
0
    mOutput << startstr << "</library_lights>" << endstr;
471
0
}
472
473
0
void ColladaExporter::WriteLight(size_t pIndex) {
474
475
0
    const aiLight *light = mScene->mLights[pIndex];
476
0
    if (light == nullptr) {
477
0
        return;
478
0
    }
479
0
    const std::string lightId = GetObjectUniqueId(AiObjectType::Light, pIndex);
480
0
    const std::string lightName = GetObjectName(AiObjectType::Light, pIndex);
481
482
0
    mOutput << startstr << "<light id=\"" << lightId << "\" name=\""
483
0
            << lightName << "\" >" << endstr;
484
0
    PushTag();
485
0
    mOutput << startstr << "<technique_common>" << endstr;
486
0
    PushTag();
487
0
    switch (light->mType) {
488
0
    case aiLightSource_AMBIENT:
489
0
        WriteAmbientLight(light);
490
0
        break;
491
0
    case aiLightSource_DIRECTIONAL:
492
0
        WriteDirectionalLight(light);
493
0
        break;
494
0
    case aiLightSource_POINT:
495
0
        WritePointLight(light);
496
0
        break;
497
0
    case aiLightSource_SPOT:
498
0
        WriteSpotLight(light);
499
0
        break;
500
0
    case aiLightSource_AREA:
501
0
    case aiLightSource_UNDEFINED:
502
0
    case _aiLightSource_Force32Bit:
503
0
    default:
504
0
        break;
505
0
    }
506
0
    PopTag();
507
0
    mOutput << startstr << "</technique_common>" << endstr;
508
509
0
    PopTag();
510
0
    mOutput << startstr << "</light>" << endstr;
511
0
}
512
513
0
void ColladaExporter::WritePointLight(const aiLight *const light) {
514
0
    const aiColor3D &color = light->mColorDiffuse;
515
0
    mOutput << startstr << "<point>" << endstr;
516
0
    PushTag();
517
0
    mOutput << startstr << "<color sid=\"color\">"
518
0
            << color.r << " " << color.g << " " << color.b
519
0
            << "</color>" << endstr;
520
0
    mOutput << startstr << "<constant_attenuation>"
521
0
            << light->mAttenuationConstant
522
0
            << "</constant_attenuation>" << endstr;
523
0
    mOutput << startstr << "<linear_attenuation>"
524
0
            << light->mAttenuationLinear
525
0
            << "</linear_attenuation>" << endstr;
526
0
    mOutput << startstr << "<quadratic_attenuation>"
527
0
            << light->mAttenuationQuadratic
528
0
            << "</quadratic_attenuation>" << endstr;
529
530
0
    PopTag();
531
0
    mOutput << startstr << "</point>" << endstr;
532
0
}
533
534
0
void ColladaExporter::WriteDirectionalLight(const aiLight *const light) {
535
0
    const aiColor3D &color = light->mColorDiffuse;
536
0
    mOutput << startstr << "<directional>" << endstr;
537
0
    PushTag();
538
0
    mOutput << startstr << "<color sid=\"color\">"
539
0
            << color.r << " " << color.g << " " << color.b
540
0
            << "</color>" << endstr;
541
542
0
    PopTag();
543
0
    mOutput << startstr << "</directional>" << endstr;
544
0
}
545
546
0
void ColladaExporter::WriteSpotLight(const aiLight *const light) {
547
548
0
    const aiColor3D &color = light->mColorDiffuse;
549
0
    mOutput << startstr << "<spot>" << endstr;
550
0
    PushTag();
551
0
    mOutput << startstr << "<color sid=\"color\">"
552
0
            << color.r << " " << color.g << " " << color.b
553
0
            << "</color>" << endstr;
554
0
    mOutput << startstr << "<constant_attenuation>"
555
0
            << light->mAttenuationConstant
556
0
            << "</constant_attenuation>" << endstr;
557
0
    mOutput << startstr << "<linear_attenuation>"
558
0
            << light->mAttenuationLinear
559
0
            << "</linear_attenuation>" << endstr;
560
0
    mOutput << startstr << "<quadratic_attenuation>"
561
0
            << light->mAttenuationQuadratic
562
0
            << "</quadratic_attenuation>" << endstr;
563
564
0
    const ai_real fallOffAngle = AI_RAD_TO_DEG(light->mAngleInnerCone);
565
0
    mOutput << startstr << "<falloff_angle sid=\"fall_off_angle\">"
566
0
            << fallOffAngle
567
0
            << "</falloff_angle>" << endstr;
568
0
    double temp = light->mAngleOuterCone - light->mAngleInnerCone;
569
570
0
    temp = std::cos(temp);
571
0
    temp = std::log(temp) / std::log(0.1);
572
0
    temp = 1 / temp;
573
0
    mOutput << startstr << "<falloff_exponent sid=\"fall_off_exponent\">"
574
0
            << temp
575
0
            << "</falloff_exponent>" << endstr;
576
577
0
    PopTag();
578
0
    mOutput << startstr << "</spot>" << endstr;
579
0
}
580
581
0
void ColladaExporter::WriteAmbientLight(const aiLight *const light) {
582
583
0
    const aiColor3D &color = light->mColorAmbient;
584
0
    mOutput << startstr << "<ambient>" << endstr;
585
0
    PushTag();
586
0
    mOutput << startstr << "<color sid=\"color\">"
587
0
            << color.r << " " << color.g << " " << color.b
588
0
            << "</color>" << endstr;
589
590
0
    PopTag();
591
0
    mOutput << startstr << "</ambient>" << endstr;
592
0
}
593
594
// ------------------------------------------------------------------------------------------------
595
// Reads a single surface entry from the given material keys
596
11.5k
bool ColladaExporter::ReadMaterialSurface(Surface &poSurface, const aiMaterial &pSrcMat, aiTextureType pTexture, const char *pKey, size_t pType, size_t pIndex) {
597
11.5k
    if (pSrcMat.GetTextureCount(pTexture) == 0) {
598
11.1k
        if (pKey)
599
9.47k
            poSurface.exist = pSrcMat.Get(pKey, static_cast<unsigned int>(pType), static_cast<unsigned int>(pIndex), poSurface.color) == aiReturn_SUCCESS;
600
11.1k
        return poSurface.exist;
601
11.1k
    }
602
603
406
    aiString texfile;
604
406
    unsigned int uvChannel = 0;
605
406
    pSrcMat.GetTexture(pTexture, 0, &texfile, nullptr, &uvChannel);
606
607
406
    std::string index_str(texfile.C_Str());
608
609
406
    if (index_str.size() != 0 && index_str[0] == '*') {
610
74
        unsigned int index;
611
612
74
        index_str = index_str.substr(1, std::string::npos);
613
614
74
        try {
615
74
            index = (unsigned int)strtoul10_64<DeadlyExportError>(index_str.c_str());
616
74
        } catch (std::exception &error) {
617
0
            throw DeadlyExportError(error.what());
618
0
        }
619
620
74
        std::map<unsigned int, std::string>::const_iterator name = textures.find(index);
621
622
74
        if (name != textures.end()) {
623
74
            poSurface.texture = name->second;
624
74
        } else {
625
0
            throw DeadlyExportError("could not find embedded texture at index " + index_str);
626
0
        }
627
332
    } else {
628
332
        poSurface.texture = texfile.C_Str();
629
332
    }
630
631
406
    poSurface.channel = uvChannel;
632
406
    poSurface.exist = true;
633
634
406
    return poSurface.exist;
635
406
}
636
637
// ------------------------------------------------------------------------------------------------
638
// Reimplementation of isalnum(,C locale), because AppVeyor does not see standard version.
639
21.8k
static bool isalnum_C(char c) {
640
21.8k
    return (nullptr != strchr("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", c));
641
21.8k
}
642
643
// ------------------------------------------------------------------------------------------------
644
// Writes an image entry for the given surface
645
11.2k
void ColladaExporter::WriteImageEntry(const Surface &pSurface, const std::string &imageId) {
646
11.2k
    if (pSurface.texture.empty()) {
647
10.8k
        return;
648
10.8k
    }
649
650
397
    mOutput << startstr << "<image id=\"" << imageId << "\">" << endstr;
651
397
    PushTag();
652
397
    mOutput << startstr << "<init_from>";
653
654
    // URL encode image file name first, then XML encode on top
655
397
    std::stringstream imageUrlEncoded;
656
22.2k
    for (std::string::const_iterator it = pSurface.texture.begin(); it != pSurface.texture.end(); ++it) {
657
21.8k
        if (isalnum_C((unsigned char)*it) || *it == ':' || *it == '_' || *it == '-' || *it == '.' || *it == '/' || *it == '\\')
658
21.0k
            imageUrlEncoded << *it;
659
801
        else
660
801
            imageUrlEncoded << '%' << std::hex << size_t((unsigned char)*it) << std::dec;
661
21.8k
    }
662
397
    mOutput << XMLEscape(imageUrlEncoded.str());
663
397
    mOutput << "</init_from>" << endstr;
664
397
    PopTag();
665
397
    mOutput << startstr << "</image>" << endstr;
666
397
}
667
668
// ------------------------------------------------------------------------------------------------
669
// Writes a color-or-texture entry into an effect definition
670
9.87k
void ColladaExporter::WriteTextureColorEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &imageId) {
671
9.87k
    if (!pSurface.exist) {
672
3.86k
        return;
673
3.86k
    }
674
675
6.01k
    mOutput << startstr << "<" << pTypeName << ">" << endstr;
676
6.01k
    PushTag();
677
6.01k
    if (pSurface.texture.empty()) {
678
5.61k
        mOutput << startstr << "<color sid=\"" << pTypeName << "\">" << pSurface.color.r << "   " << pSurface.color.g << "   " << pSurface.color.b << "   " << pSurface.color.a << "</color>" << endstr;
679
5.61k
    } else {
680
397
        mOutput << startstr << "<texture texture=\"" << imageId << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
681
397
    }
682
6.01k
    PopTag();
683
6.01k
    mOutput << startstr << "</" << pTypeName << ">" << endstr;
684
6.01k
}
685
686
// ------------------------------------------------------------------------------------------------
687
// Writes the two parameters necessary for referencing a texture in an effect entry
688
11.5k
void ColladaExporter::WriteTextureParamEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &materialId) {
689
    // if surface is a texture, write out the sampler and the surface parameters necessary to reference the texture
690
11.5k
    if (pSurface.texture.empty()) {
691
11.1k
        return;
692
11.1k
    }
693
694
397
    mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-surface\">" << endstr;
695
397
    PushTag();
696
397
    mOutput << startstr << "<surface type=\"2D\">" << endstr;
697
397
    PushTag();
698
397
    mOutput << startstr << "<init_from>" << materialId << "-" << pTypeName << "-image</init_from>" << endstr;
699
397
    PopTag();
700
397
    mOutput << startstr << "</surface>" << endstr;
701
397
    PopTag();
702
397
    mOutput << startstr << "</newparam>" << endstr;
703
704
397
    mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-sampler\">" << endstr;
705
397
    PushTag();
706
397
    mOutput << startstr << "<sampler2D>" << endstr;
707
397
    PushTag();
708
397
    mOutput << startstr << "<source>" << materialId << "-" << pTypeName << "-surface</source>" << endstr;
709
397
    PopTag();
710
397
    mOutput << startstr << "</sampler2D>" << endstr;
711
397
    PopTag();
712
397
    mOutput << startstr << "</newparam>" << endstr;
713
397
}
714
715
// ------------------------------------------------------------------------------------------------
716
// Writes a scalar property
717
4.93k
void ColladaExporter::WriteFloatEntry(const Property &pProperty, const std::string &pTypeName) {
718
4.93k
    if (!pProperty.exist) {
719
2.62k
        return;
720
2.62k
    }
721
722
2.31k
    mOutput << startstr << "<" << pTypeName << ">" << endstr;
723
2.31k
    PushTag();
724
2.31k
    mOutput << startstr << "<float sid=\"" << pTypeName << "\">" << pProperty.value << "</float>" << endstr;
725
2.31k
    PopTag();
726
2.31k
    mOutput << startstr << "</" << pTypeName << ">" << endstr;
727
2.31k
}
728
729
// ------------------------------------------------------------------------------------------------
730
// Writes the material setup
731
1.54k
void ColladaExporter::WriteMaterials() {
732
1.54k
    std::vector<Material> materials;
733
1.54k
    materials.resize(mScene->mNumMaterials);
734
735
    /// collect all materials from the scene
736
1.54k
    size_t numTextures = 0;
737
3.19k
    for (size_t a = 0; a < mScene->mNumMaterials; ++a) {
738
1.64k
        Material &material = materials[a];
739
1.64k
        material.id = GetObjectUniqueId(AiObjectType::Material, a);
740
1.64k
        material.name = GetObjectName(AiObjectType::Material, a);
741
742
1.64k
        const aiMaterial &mat = *(mScene->mMaterials[a]);
743
1.64k
        aiShadingMode shading = aiShadingMode_Flat;
744
1.64k
        material.shading_model = "phong";
745
1.64k
        if (mat.Get(AI_MATKEY_SHADING_MODEL, shading) == aiReturn_SUCCESS) {
746
106
            if (shading == aiShadingMode_Phong) {
747
12
                material.shading_model = "phong";
748
94
            } else if (shading == aiShadingMode_Blinn) {
749
0
                material.shading_model = "blinn";
750
94
            } else if (shading == aiShadingMode_NoShading) {
751
0
                material.shading_model = "constant";
752
94
            } else if (shading == aiShadingMode_Gouraud) {
753
0
                material.shading_model = "lambert";
754
0
            }
755
106
        }
756
757
1.64k
        if (ReadMaterialSurface(material.ambient, mat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT))
758
1.27k
            ++numTextures;
759
1.64k
        if (ReadMaterialSurface(material.diffuse, mat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE))
760
1.60k
            ++numTextures;
761
1.64k
        if (ReadMaterialSurface(material.specular, mat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR))
762
1.27k
            ++numTextures;
763
1.64k
        if (ReadMaterialSurface(material.emissive, mat, aiTextureType_EMISSIVE, AI_MATKEY_COLOR_EMISSIVE))
764
1.16k
            ++numTextures;
765
1.64k
        if (ReadMaterialSurface(material.reflective, mat, aiTextureType_REFLECTION, AI_MATKEY_COLOR_REFLECTIVE))
766
106
            ++numTextures;
767
1.64k
        if (ReadMaterialSurface(material.transparent, mat, aiTextureType_OPACITY, AI_MATKEY_COLOR_TRANSPARENT))
768
575
            ++numTextures;
769
1.64k
        if (ReadMaterialSurface(material.normal, mat, aiTextureType_NORMALS, nullptr, 0, 0))
770
15
            ++numTextures;
771
772
1.64k
        material.shininess.exist = mat.Get(AI_MATKEY_SHININESS, material.shininess.value) == aiReturn_SUCCESS;
773
1.64k
        material.transparency.exist = mat.Get(AI_MATKEY_OPACITY, material.transparency.value) == aiReturn_SUCCESS;
774
1.64k
        material.index_refraction.exist = mat.Get(AI_MATKEY_REFRACTI, material.index_refraction.value) == aiReturn_SUCCESS;
775
1.64k
    }
776
777
    // output textures if present
778
1.54k
    if (numTextures > 0) {
779
1.24k
        mOutput << startstr << "<library_images>" << endstr;
780
1.24k
        PushTag();
781
1.60k
        for (const Material &mat : materials) {
782
1.60k
            WriteImageEntry(mat.ambient, mat.id + "-ambient-image");
783
1.60k
            WriteImageEntry(mat.diffuse, mat.id + "-diffuse-image");
784
1.60k
            WriteImageEntry(mat.specular, mat.id + "-specular-image");
785
1.60k
            WriteImageEntry(mat.emissive, mat.id + "-emission-image");
786
1.60k
            WriteImageEntry(mat.reflective, mat.id + "-reflective-image");
787
1.60k
            WriteImageEntry(mat.transparent, mat.id + "-transparent-image");
788
1.60k
            WriteImageEntry(mat.normal, mat.id + "-normal-image");
789
1.60k
        }
790
1.24k
        PopTag();
791
1.24k
        mOutput << startstr << "</library_images>" << endstr;
792
1.24k
    }
793
794
    // output effects - those are the actual carriers of information
795
1.54k
    if (!materials.empty()) {
796
1.27k
        mOutput << startstr << "<library_effects>" << endstr;
797
1.27k
        PushTag();
798
1.64k
        for (const Material &mat : materials) {
799
            // this is so ridiculous it must be right
800
1.64k
            mOutput << startstr << "<effect id=\"" << mat.id << "-fx\" name=\"" << mat.name << "\">" << endstr;
801
1.64k
            PushTag();
802
1.64k
            mOutput << startstr << "<profile_COMMON>" << endstr;
803
1.64k
            PushTag();
804
805
            // write sampler- and surface params for the texture entries
806
1.64k
            WriteTextureParamEntry(mat.emissive, "emission", mat.id);
807
1.64k
            WriteTextureParamEntry(mat.ambient, "ambient", mat.id);
808
1.64k
            WriteTextureParamEntry(mat.diffuse, "diffuse", mat.id);
809
1.64k
            WriteTextureParamEntry(mat.specular, "specular", mat.id);
810
1.64k
            WriteTextureParamEntry(mat.reflective, "reflective", mat.id);
811
1.64k
            WriteTextureParamEntry(mat.transparent, "transparent", mat.id);
812
1.64k
            WriteTextureParamEntry(mat.normal, "normal", mat.id);
813
814
1.64k
            mOutput << startstr << "<technique sid=\"standard\">" << endstr;
815
1.64k
            PushTag();
816
1.64k
            mOutput << startstr << "<" << mat.shading_model << ">" << endstr;
817
1.64k
            PushTag();
818
819
1.64k
            WriteTextureColorEntry(mat.emissive, "emission", mat.id + "-emission-sampler");
820
1.64k
            WriteTextureColorEntry(mat.ambient, "ambient", mat.id + "-ambient-sampler");
821
1.64k
            WriteTextureColorEntry(mat.diffuse, "diffuse", mat.id + "-diffuse-sampler");
822
1.64k
            WriteTextureColorEntry(mat.specular, "specular", mat.id + "-specular-sampler");
823
1.64k
            WriteFloatEntry(mat.shininess, "shininess");
824
1.64k
            WriteTextureColorEntry(mat.reflective, "reflective", mat.id + "-reflective-sampler");
825
1.64k
            WriteTextureColorEntry(mat.transparent, "transparent", mat.id + "-transparent-sampler");
826
1.64k
            WriteFloatEntry(mat.transparency, "transparency");
827
1.64k
            WriteFloatEntry(mat.index_refraction, "index_of_refraction");
828
829
1.64k
            if (!mat.normal.texture.empty()) {
830
15
                WriteTextureColorEntry(mat.normal, "bump", mat.id + "-normal-sampler");
831
15
            }
832
833
1.64k
            PopTag();
834
1.64k
            mOutput << startstr << "</" << mat.shading_model << ">" << endstr;
835
1.64k
            PopTag();
836
1.64k
            mOutput << startstr << "</technique>" << endstr;
837
1.64k
            PopTag();
838
1.64k
            mOutput << startstr << "</profile_COMMON>" << endstr;
839
1.64k
            PopTag();
840
1.64k
            mOutput << startstr << "</effect>" << endstr;
841
1.64k
        }
842
1.27k
        PopTag();
843
1.27k
        mOutput << startstr << "</library_effects>" << endstr;
844
845
        // write materials - they're just effect references
846
1.27k
        mOutput << startstr << "<library_materials>" << endstr;
847
1.27k
        PushTag();
848
2.92k
        for (std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it) {
849
1.64k
            const Material &mat = *it;
850
1.64k
            mOutput << startstr << "<material id=\"" << mat.id << "\" name=\"" << mat.name << "\">" << endstr;
851
1.64k
            PushTag();
852
1.64k
            mOutput << startstr << "<instance_effect url=\"#" << mat.id << "-fx\"/>" << endstr;
853
1.64k
            PopTag();
854
1.64k
            mOutput << startstr << "</material>" << endstr;
855
1.64k
        }
856
1.27k
        PopTag();
857
1.27k
        mOutput << startstr << "</library_materials>" << endstr;
858
1.27k
    }
859
1.54k
}
860
861
// ------------------------------------------------------------------------------------------------
862
// Writes the controller library
863
1.54k
void ColladaExporter::WriteControllerLibrary() {
864
1.54k
    mOutput << startstr << "<library_controllers>" << endstr;
865
1.54k
    PushTag();
866
867
6.17k
    for (size_t a = 0; a < mScene->mNumMeshes; ++a) {
868
4.62k
        WriteController(a);
869
4.62k
    }
870
871
1.54k
    PopTag();
872
1.54k
    mOutput << startstr << "</library_controllers>" << endstr;
873
1.54k
}
874
875
// ------------------------------------------------------------------------------------------------
876
// Writes a skin controller of the given mesh
877
4.62k
void ColladaExporter::WriteController(size_t pIndex) {
878
4.62k
    const aiMesh *mesh = mScene->mMeshes[pIndex];
879
    // Is there a skin controller?
880
4.62k
    if (mesh->mNumBones == 0 || mesh->mNumFaces == 0 || mesh->mNumVertices == 0) {
881
4.51k
        return;
882
4.51k
    }
883
884
118
    const std::string idstr = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
885
118
    const std::string namestr = GetObjectName(AiObjectType::Mesh, pIndex);
886
887
118
    mOutput << startstr << "<controller id=\"" << idstr << "-skin\" ";
888
118
    mOutput << "name=\"skinCluster" << pIndex << "\">" << endstr;
889
118
    PushTag();
890
891
118
    mOutput << startstr << "<skin source=\"#" << idstr << "\">" << endstr;
892
118
    PushTag();
893
894
    // bind pose matrix
895
118
    mOutput << startstr << "<bind_shape_matrix>" << endstr;
896
118
    PushTag();
897
898
    // I think it is identity in general cases.
899
118
    aiMatrix4x4 mat;
900
118
    mOutput << startstr << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << endstr;
901
118
    mOutput << startstr << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << endstr;
902
118
    mOutput << startstr << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << endstr;
903
118
    mOutput << startstr << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4 << endstr;
904
905
118
    PopTag();
906
118
    mOutput << startstr << "</bind_shape_matrix>" << endstr;
907
908
118
    mOutput << startstr << "<source id=\"" << idstr << "-skin-joints\" name=\"" << namestr << "-skin-joints\">" << endstr;
909
118
    PushTag();
910
911
118
    mOutput << startstr << "<Name_array id=\"" << idstr << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
912
913
539
    for (size_t i = 0; i < mesh->mNumBones; ++i) {
914
421
        mOutput << GetBoneUniqueId(mesh->mBones[i]) << ' ';
915
421
    }
916
917
118
    mOutput << "</Name_array>" << endstr;
918
919
118
    mOutput << startstr << "<technique_common>" << endstr;
920
118
    PushTag();
921
922
118
    mOutput << startstr << "<accessor source=\"#" << idstr << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\" stride=\"" << 1 << "\">" << endstr;
923
118
    PushTag();
924
925
118
    mOutput << startstr << "<param name=\"JOINT\" type=\"Name\"></param>" << endstr;
926
927
118
    PopTag();
928
118
    mOutput << startstr << "</accessor>" << endstr;
929
930
118
    PopTag();
931
118
    mOutput << startstr << "</technique_common>" << endstr;
932
933
118
    PopTag();
934
118
    mOutput << startstr << "</source>" << endstr;
935
936
118
    std::vector<ai_real> bind_poses;
937
118
    bind_poses.reserve(mesh->mNumBones * 16);
938
539
    for (unsigned int i = 0; i < mesh->mNumBones; ++i) {
939
2.10k
        for (unsigned int j = 0; j < 4; ++j) {
940
1.68k
            bind_poses.insert(bind_poses.end(), mesh->mBones[i]->mOffsetMatrix[j], mesh->mBones[i]->mOffsetMatrix[j] + 4);
941
1.68k
        }
942
421
    }
943
944
118
    WriteFloatArray(idstr + "-skin-bind_poses", FloatType_Mat4x4, (const ai_real *)bind_poses.data(), bind_poses.size() / 16);
945
946
118
    bind_poses.clear();
947
948
118
    std::vector<ai_real> skin_weights;
949
118
    skin_weights.reserve(mesh->mNumVertices * mesh->mNumBones);
950
539
    for (size_t i = 0; i < mesh->mNumBones; ++i) {
951
9.79k
        for (size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j) {
952
9.36k
            skin_weights.push_back(mesh->mBones[i]->mWeights[j].mWeight);
953
9.36k
        }
954
421
    }
955
956
118
    WriteFloatArray(idstr + "-skin-weights", FloatType_Weight, (const ai_real *)skin_weights.data(), skin_weights.size());
957
958
118
    skin_weights.clear();
959
960
118
    mOutput << startstr << "<joints>" << endstr;
961
118
    PushTag();
962
963
118
    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstr << "-skin-joints\"></input>" << endstr;
964
118
    mOutput << startstr << "<input semantic=\"INV_BIND_MATRIX\" source=\"#" << idstr << "-skin-bind_poses\"></input>" << endstr;
965
966
118
    PopTag();
967
118
    mOutput << startstr << "</joints>" << endstr;
968
969
118
    mOutput << startstr << "<vertex_weights count=\"" << mesh->mNumVertices << "\">" << endstr;
970
118
    PushTag();
971
972
118
    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstr << "-skin-joints\" offset=\"0\"></input>" << endstr;
973
118
    mOutput << startstr << "<input semantic=\"WEIGHT\" source=\"#" << idstr << "-skin-weights\" offset=\"1\"></input>" << endstr;
974
975
118
    mOutput << startstr << "<vcount>";
976
977
118
    std::vector<ai_uint> num_influences(mesh->mNumVertices, (ai_uint)0);
978
539
    for (size_t i = 0; i < mesh->mNumBones; ++i) {
979
9.79k
        for (size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j) {
980
9.36k
            ++num_influences[mesh->mBones[i]->mWeights[j].mVertexId];
981
9.36k
        }
982
421
    }
983
984
9.16k
    for (size_t i = 0; i < mesh->mNumVertices; ++i) {
985
9.04k
        mOutput << num_influences[i] << " ";
986
9.04k
    }
987
988
118
    mOutput << "</vcount>" << endstr;
989
990
118
    mOutput << startstr << "<v>";
991
992
118
    ai_uint joint_weight_indices_length = 0;
993
118
    std::vector<ai_uint> accum_influences;
994
118
    accum_influences.reserve(num_influences.size());
995
9.16k
    for (size_t i = 0; i < num_influences.size(); ++i) {
996
9.04k
        accum_influences.push_back(joint_weight_indices_length);
997
9.04k
        joint_weight_indices_length += num_influences[i];
998
9.04k
    }
999
1000
118
    ai_uint weight_index = 0;
1001
118
    std::vector<ai_int> joint_weight_indices(2 * joint_weight_indices_length, (ai_int)-1);
1002
539
    for (unsigned int i = 0; i < mesh->mNumBones; ++i) {
1003
9.79k
        for (unsigned j = 0; j < mesh->mBones[i]->mNumWeights; ++j) {
1004
9.36k
            unsigned int vId = mesh->mBones[i]->mWeights[j].mVertexId;
1005
9.80k
            for (ai_uint k = 0; k < num_influences[vId]; ++k) {
1006
9.80k
                if (joint_weight_indices[2 * (accum_influences[vId] + k)] == -1) {
1007
9.36k
                    joint_weight_indices[2 * (accum_influences[vId] + k)] = i;
1008
9.36k
                    joint_weight_indices[2 * (accum_influences[vId] + k) + 1] = weight_index;
1009
9.36k
                    break;
1010
9.36k
                }
1011
9.80k
            }
1012
9.36k
            ++weight_index;
1013
9.36k
        }
1014
421
    }
1015
1016
18.8k
    for (size_t i = 0; i < joint_weight_indices.size(); ++i) {
1017
18.7k
        mOutput << joint_weight_indices[i] << " ";
1018
18.7k
    }
1019
1020
118
    num_influences.clear();
1021
118
    accum_influences.clear();
1022
118
    joint_weight_indices.clear();
1023
1024
118
    mOutput << "</v>" << endstr;
1025
1026
118
    PopTag();
1027
118
    mOutput << startstr << "</vertex_weights>" << endstr;
1028
1029
118
    PopTag();
1030
118
    mOutput << startstr << "</skin>" << endstr;
1031
1032
118
    PopTag();
1033
118
    mOutput << startstr << "</controller>" << endstr;
1034
118
}
1035
1036
// ------------------------------------------------------------------------------------------------
1037
// Writes the geometry library
1038
1.54k
void ColladaExporter::WriteGeometryLibrary() {
1039
1.54k
    mOutput << startstr << "<library_geometries>" << endstr;
1040
1.54k
    PushTag();
1041
1042
6.17k
    for (size_t a = 0; a < mScene->mNumMeshes; ++a) {
1043
4.62k
        WriteGeometry(a);
1044
4.62k
    }
1045
1046
1.54k
    PopTag();
1047
1.54k
    mOutput << startstr << "</library_geometries>" << endstr;
1048
1.54k
}
1049
1050
// ------------------------------------------------------------------------------------------------
1051
// Writes the given mesh
1052
4.62k
void ColladaExporter::WriteGeometry(size_t pIndex) {
1053
4.62k
    const aiMesh *mesh = mScene->mMeshes[pIndex];
1054
4.62k
    const std::string geometryId = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
1055
4.62k
    const std::string geometryName = GetObjectName(AiObjectType::Mesh, pIndex);
1056
1057
4.62k
    if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0) {
1058
0
        return;
1059
0
    }
1060
1061
    // opening tag
1062
4.62k
    mOutput << startstr << "<geometry id=\"" << geometryId << "\" name=\"" << geometryName << "\" >" << endstr;
1063
4.62k
    PushTag();
1064
1065
4.62k
    mOutput << startstr << "<mesh>" << endstr;
1066
4.62k
    PushTag();
1067
1068
    // Positions
1069
4.62k
    WriteFloatArray(geometryId + "-positions", FloatType_Vector, (ai_real *)mesh->mVertices, mesh->mNumVertices);
1070
    // Normals, if any
1071
4.62k
    if (mesh->HasNormals()) {
1072
4.57k
        WriteFloatArray(geometryId + "-normals", FloatType_Vector, (ai_real *)mesh->mNormals, mesh->mNumVertices);
1073
4.57k
    }
1074
1075
    // texture coords
1076
41.6k
    for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
1077
37.0k
        if (mesh->HasTextureCoords(static_cast<unsigned int>(a))) {
1078
1.43k
            WriteFloatArray(geometryId + "-tex" + ai_to_string(a), mesh->mNumUVComponents[a] == 3 ? FloatType_TexCoord3 : FloatType_TexCoord2,
1079
1.43k
                    (ai_real *)mesh->mTextureCoords[a], mesh->mNumVertices);
1080
1.43k
        }
1081
37.0k
    }
1082
1083
    // vertex colors
1084
41.6k
    for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
1085
37.0k
        if (mesh->HasVertexColors(static_cast<unsigned int>(a)))
1086
331
            WriteFloatArray(geometryId + "-color" + ai_to_string(a), FloatType_Color, (ai_real *)mesh->mColors[a], mesh->mNumVertices);
1087
37.0k
    }
1088
1089
    // assemble vertex structure
1090
    // Only write input for POSITION since we will write other as shared inputs in polygon definition
1091
4.62k
    mOutput << startstr << "<vertices id=\"" << geometryId << "-vertices"
1092
4.62k
            << "\">" << endstr;
1093
4.62k
    PushTag();
1094
4.62k
    mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << geometryId << "-positions\" />" << endstr;
1095
4.62k
    PopTag();
1096
4.62k
    mOutput << startstr << "</vertices>" << endstr;
1097
1098
    // count the number of lines, triangles and polygon meshes
1099
4.62k
    int countLines = 0;
1100
4.62k
    int countPoly = 0;
1101
557k
    for (size_t a = 0; a < mesh->mNumFaces; ++a) {
1102
552k
        if (mesh->mFaces[a].mNumIndices == 2) {
1103
82.4k
            countLines++;
1104
469k
        } else if (mesh->mFaces[a].mNumIndices >= 3) {
1105
427k
            countPoly++;
1106
427k
        }
1107
552k
    }
1108
1109
    // lines
1110
4.62k
    if (countLines) {
1111
270
        mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
1112
270
        PushTag();
1113
270
        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
1114
270
        if (mesh->HasNormals()) {
1115
225
            mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
1116
225
        }
1117
2.43k
        for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
1118
2.16k
            if (mesh->HasTextureCoords(static_cast<unsigned int>(a))) {
1119
34
                mOutput << startstr
1120
34
                        << "<input semantic=\"TEXCOORD\" source=\"#"
1121
34
                        << geometryId
1122
34
                        << "-tex" << a << "\" "
1123
34
                        << "set=\"" << a << "\""
1124
34
                        << " />" << endstr;
1125
34
            }
1126
2.16k
        }
1127
2.43k
        for (size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) {
1128
2.16k
            if (mesh->HasVertexColors(static_cast<unsigned int>(a)))
1129
27
                mOutput << startstr << "<input semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" "
1130
27
                        << "set=\"" << a << "\""
1131
27
                        << " />" << endstr;
1132
2.16k
        }
1133
1134
270
        mOutput << startstr << "<p>";
1135
82.7k
        for (size_t a = 0; a < mesh->mNumFaces; ++a) {
1136
82.4k
            const aiFace &face = mesh->mFaces[a];
1137
82.4k
            if (face.mNumIndices != 2) continue;
1138
247k
            for (size_t b = 0; b < face.mNumIndices; ++b) {
1139
164k
                mOutput << face.mIndices[b] << " ";
1140
164k
            }
1141
82.4k
        }
1142
270
        mOutput << "</p>" << endstr;
1143
270
        PopTag();
1144
270
        mOutput << startstr << "</lines>" << endstr;
1145
270
    }
1146
1147
    // triangle - don't use it, because compatibility problems
1148
1149
    // polygons
1150
4.62k
    if (countPoly) {
1151
4.18k
        mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
1152
4.18k
        PushTag();
1153
4.18k
        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
1154
4.18k
        if (mesh->HasNormals()) {
1155
4.18k
            mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
1156
4.18k
        }
1157
37.7k
        for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
1158
33.5k
            if (mesh->HasTextureCoords(static_cast<unsigned int>(a)))
1159
1.37k
                mOutput << startstr << "<input offset=\"0\" semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" "
1160
1.37k
                        << "set=\"" << a << "\""
1161
1.37k
                        << " />" << endstr;
1162
33.5k
        }
1163
37.7k
        for (size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) {
1164
33.5k
            if (mesh->HasVertexColors(static_cast<unsigned int>(a)))
1165
303
                mOutput << startstr << "<input offset=\"0\" semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" "
1166
303
                        << "set=\"" << a << "\""
1167
303
                        << " />" << endstr;
1168
33.5k
        }
1169
1170
4.18k
        mOutput << startstr << "<vcount>";
1171
431k
        for (size_t a = 0; a < mesh->mNumFaces; ++a) {
1172
427k
            if (mesh->mFaces[a].mNumIndices < 3) continue;
1173
427k
            mOutput << mesh->mFaces[a].mNumIndices << " ";
1174
427k
        }
1175
4.18k
        mOutput << "</vcount>" << endstr;
1176
1177
4.18k
        mOutput << startstr << "<p>";
1178
431k
        for (size_t a = 0; a < mesh->mNumFaces; ++a) {
1179
427k
            const aiFace &face = mesh->mFaces[a];
1180
427k
            if (face.mNumIndices < 3) continue;
1181
1.70M
            for (size_t b = 0; b < face.mNumIndices; ++b) {
1182
1.28M
                mOutput << face.mIndices[b] << " ";
1183
1.28M
            }
1184
427k
        }
1185
4.18k
        mOutput << "</p>" << endstr;
1186
4.18k
        PopTag();
1187
4.18k
        mOutput << startstr << "</polylist>" << endstr;
1188
4.18k
    }
1189
1190
    // closing tags
1191
4.62k
    PopTag();
1192
4.62k
    mOutput << startstr << "</mesh>" << endstr;
1193
4.62k
    PopTag();
1194
4.62k
    mOutput << startstr << "</geometry>" << endstr;
1195
4.62k
}
1196
1197
// ------------------------------------------------------------------------------------------------
1198
// Writes a float array of the given type
1199
11.6k
void ColladaExporter::WriteFloatArray(const std::string &pIdString, FloatDataType pType, const ai_real *pData, size_t pElementCount) {
1200
11.6k
    size_t floatsPerElement = 0;
1201
11.6k
    switch (pType) {
1202
9.20k
    case FloatType_Vector:
1203
9.20k
        floatsPerElement = 3;
1204
9.20k
        break;
1205
1.42k
    case FloatType_TexCoord2:
1206
1.42k
        floatsPerElement = 2;
1207
1.42k
        break;
1208
8
    case FloatType_TexCoord3:
1209
8
        floatsPerElement = 3;
1210
8
        break;
1211
331
    case FloatType_Color:
1212
331
        floatsPerElement = 3;
1213
331
        break;
1214
336
    case FloatType_Mat4x4:
1215
336
        floatsPerElement = 16;
1216
336
        break;
1217
118
    case FloatType_Weight:
1218
118
        floatsPerElement = 1;
1219
118
        break;
1220
218
    case FloatType_Time:
1221
218
        floatsPerElement = 1;
1222
218
        break;
1223
0
    default:
1224
0
        return;
1225
11.6k
    }
1226
1227
11.6k
    std::string arrayId = XMLIDEncode(pIdString) + "-array";
1228
1229
11.6k
    mOutput << startstr << "<source id=\"" << XMLIDEncode(pIdString) << "\" name=\"" << XMLEscape(pIdString) << "\">" << endstr;
1230
11.6k
    PushTag();
1231
1232
    // source array
1233
11.6k
    mOutput << startstr << "<float_array id=\"" << arrayId << "\" count=\"" << pElementCount * floatsPerElement << "\"> ";
1234
11.6k
    PushTag();
1235
1236
11.6k
    if (pType == FloatType_TexCoord2) {
1237
223k
        for (size_t a = 0; a < pElementCount; ++a) {
1238
221k
            mOutput << pData[a * 3 + 0] << " ";
1239
221k
            mOutput << pData[a * 3 + 1] << " ";
1240
221k
        }
1241
10.2k
    } else if (pType == FloatType_Color) {
1242
22.2k
        for (size_t a = 0; a < pElementCount; ++a) {
1243
21.9k
            mOutput << pData[a * 4 + 0] << " ";
1244
21.9k
            mOutput << pData[a * 4 + 1] << " ";
1245
21.9k
            mOutput << pData[a * 4 + 2] << " ";
1246
21.9k
        }
1247
9.88k
    } else {
1248
2.46M
        for (size_t a = 0; a < pElementCount * floatsPerElement; ++a) {
1249
2.45M
            mOutput << pData[a] << " ";
1250
2.45M
        }
1251
9.88k
    }
1252
11.6k
    mOutput << "</float_array>" << endstr;
1253
11.6k
    PopTag();
1254
1255
    // the usual Collada fun. Let's bloat it even more!
1256
11.6k
    mOutput << startstr << "<technique_common>" << endstr;
1257
11.6k
    PushTag();
1258
11.6k
    mOutput << startstr << "<accessor count=\"" << pElementCount << "\" offset=\"0\" source=\"#" << arrayId << "\" stride=\"" << floatsPerElement << "\">" << endstr;
1259
11.6k
    PushTag();
1260
1261
11.6k
    switch (pType) {
1262
9.20k
    case FloatType_Vector:
1263
9.20k
        mOutput << startstr << "<param name=\"X\" type=\"float\" />" << endstr;
1264
9.20k
        mOutput << startstr << "<param name=\"Y\" type=\"float\" />" << endstr;
1265
9.20k
        mOutput << startstr << "<param name=\"Z\" type=\"float\" />" << endstr;
1266
9.20k
        break;
1267
1268
1.42k
    case FloatType_TexCoord2:
1269
1.42k
        mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr;
1270
1.42k
        mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr;
1271
1.42k
        break;
1272
1273
8
    case FloatType_TexCoord3:
1274
8
        mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr;
1275
8
        mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr;
1276
8
        mOutput << startstr << "<param name=\"P\" type=\"float\" />" << endstr;
1277
8
        break;
1278
1279
331
    case FloatType_Color:
1280
331
        mOutput << startstr << "<param name=\"R\" type=\"float\" />" << endstr;
1281
331
        mOutput << startstr << "<param name=\"G\" type=\"float\" />" << endstr;
1282
331
        mOutput << startstr << "<param name=\"B\" type=\"float\" />" << endstr;
1283
331
        break;
1284
1285
336
    case FloatType_Mat4x4:
1286
336
        mOutput << startstr << "<param name=\"TRANSFORM\" type=\"float4x4\" />" << endstr;
1287
336
        break;
1288
1289
118
    case FloatType_Weight:
1290
118
        mOutput << startstr << "<param name=\"WEIGHT\" type=\"float\" />" << endstr;
1291
118
        break;
1292
1293
    // customized, add animation related
1294
218
    case FloatType_Time:
1295
218
        mOutput << startstr << "<param name=\"TIME\" type=\"float\" />" << endstr;
1296
218
        break;
1297
11.6k
    }
1298
1299
11.6k
    PopTag();
1300
11.6k
    mOutput << startstr << "</accessor>" << endstr;
1301
11.6k
    PopTag();
1302
11.6k
    mOutput << startstr << "</technique_common>" << endstr;
1303
11.6k
    PopTag();
1304
11.6k
    mOutput << startstr << "</source>" << endstr;
1305
11.6k
}
1306
1307
// ------------------------------------------------------------------------------------------------
1308
// Writes the scene library
1309
1.54k
void ColladaExporter::WriteSceneLibrary() {
1310
    // Determine if we are using the aiScene root or our own
1311
1.54k
    std::string sceneName("Scene");
1312
1.54k
    if (mAdd_root_node) {
1313
1.54k
        mSceneId = MakeUniqueId(mUniqueIds, sceneName, std::string());
1314
1.54k
        mUniqueIds.insert(mSceneId);
1315
1.54k
    } else {
1316
0
        mSceneId = GetNodeUniqueId(mScene->mRootNode);
1317
0
        sceneName = GetNodeName(mScene->mRootNode);
1318
0
    }
1319
1320
1.54k
    mOutput << startstr << "<library_visual_scenes>" << endstr;
1321
1.54k
    PushTag();
1322
1.54k
    mOutput << startstr << "<visual_scene id=\"" + mSceneId + "\" name=\"" + sceneName + "\">" << endstr;
1323
1.54k
    PushTag();
1324
1325
1.54k
    if (mAdd_root_node) {
1326
        // Export the root node
1327
1.54k
        WriteNode(mScene->mRootNode);
1328
1.54k
    } else {
1329
        // Have already exported the root node
1330
0
        for (size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a)
1331
0
            WriteNode(mScene->mRootNode->mChildren[a]);
1332
0
    }
1333
1334
1.54k
    PopTag();
1335
1.54k
    mOutput << startstr << "</visual_scene>" << endstr;
1336
1.54k
    PopTag();
1337
1.54k
    mOutput << startstr << "</library_visual_scenes>" << endstr;
1338
1.54k
}
1339
// ------------------------------------------------------------------------------------------------
1340
173
void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
1341
173
    const aiAnimation *anim = mScene->mAnimations[pIndex];
1342
173
    if (anim == nullptr) {
1343
0
        return;
1344
0
    }
1345
1346
173
    if (anim->mNumChannels == 0 && anim->mNumMeshChannels == 0 && anim->mNumMorphMeshChannels == 0) {
1347
0
        return;
1348
0
    }
1349
1350
173
    const std::string animationNameEscaped = GetObjectName(AiObjectType::Animation, pIndex);
1351
173
    const std::string idstrEscaped = GetObjectUniqueId(AiObjectType::Animation, pIndex);
1352
1353
173
    mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animationNameEscaped + "\">" << endstr;
1354
173
    PushTag();
1355
1356
173
    std::string cur_node_idstr;
1357
867
    for (size_t a = 0; a < anim->mNumChannels; ++a) {
1358
694
        const aiNodeAnim *nodeAnim = anim->mChannels[a];
1359
694
        if (nodeAnim == nullptr) {
1360
0
            continue;
1361
0
        }
1362
1363
        // sanity checks
1364
694
        if (nodeAnim->mNumPositionKeys != nodeAnim->mNumScalingKeys || nodeAnim->mNumPositionKeys != nodeAnim->mNumRotationKeys) {
1365
476
            continue;
1366
476
        }
1367
1368
218
        {
1369
218
            cur_node_idstr.clear();
1370
218
            cur_node_idstr += nodeAnim->mNodeName.data;
1371
218
            cur_node_idstr += std::string("_matrix-input");
1372
1373
218
            std::vector<ai_real> frames;
1374
1.47k
            for (size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
1375
1.25k
                frames.push_back(static_cast<ai_real>(nodeAnim->mPositionKeys[i].mTime));
1376
1.25k
            }
1377
1378
218
            WriteFloatArray(cur_node_idstr, FloatType_Time, (const ai_real *)frames.data(), frames.size());
1379
218
            frames.clear();
1380
218
        }
1381
1382
218
        {
1383
218
            cur_node_idstr.clear();
1384
1385
218
            cur_node_idstr += nodeAnim->mNodeName.data;
1386
218
            cur_node_idstr += std::string("_matrix-output");
1387
1388
218
            std::vector<ai_real> keyframes;
1389
218
            keyframes.reserve(nodeAnim->mNumPositionKeys * 16);
1390
1.47k
            for (size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
1391
1.25k
                aiVector3D Scaling = nodeAnim->mScalingKeys[i].mValue;
1392
1.25k
                aiMatrix4x4 ScalingM; // identity
1393
1.25k
                ScalingM[0][0] = Scaling.x;
1394
1.25k
                ScalingM[1][1] = Scaling.y;
1395
1.25k
                ScalingM[2][2] = Scaling.z;
1396
1397
1.25k
                aiQuaternion RotationQ = nodeAnim->mRotationKeys[i].mValue;
1398
1.25k
                aiMatrix4x4 s = aiMatrix4x4(RotationQ.GetMatrix());
1399
1.25k
                aiMatrix4x4 RotationM(s.a1, s.a2, s.a3, 0, s.b1, s.b2, s.b3, 0, s.c1, s.c2, s.c3, 0, 0, 0, 0, 1);
1400
1401
1.25k
                aiVector3D Translation = nodeAnim->mPositionKeys[i].mValue;
1402
1.25k
                aiMatrix4x4 TranslationM; // identity
1403
1.25k
                TranslationM[0][3] = Translation.x;
1404
1.25k
                TranslationM[1][3] = Translation.y;
1405
1.25k
                TranslationM[2][3] = Translation.z;
1406
1407
                // Combine the above transformations
1408
1.25k
                aiMatrix4x4 mat = TranslationM * RotationM * ScalingM;
1409
1410
6.27k
                for (unsigned int j = 0; j < 4; ++j) {
1411
5.02k
                    keyframes.insert(keyframes.end(), mat[j], mat[j] + 4);
1412
5.02k
                }
1413
1.25k
            }
1414
1415
218
            WriteFloatArray(cur_node_idstr, FloatType_Mat4x4, (const ai_real *)keyframes.data(), keyframes.size() / 16);
1416
218
        }
1417
1418
218
        {
1419
218
            std::vector<std::string> names;
1420
1.47k
            for (size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
1421
1.25k
                if (nodeAnim->mPreState == aiAnimBehaviour_DEFAULT || nodeAnim->mPreState == aiAnimBehaviour_LINEAR || nodeAnim->mPreState == aiAnimBehaviour_REPEAT) {
1422
1.25k
                    names.emplace_back("LINEAR");
1423
1.25k
                } else if (nodeAnim->mPostState == aiAnimBehaviour_CONSTANT) {
1424
0
                    names.emplace_back("STEP");
1425
0
                }
1426
1.25k
            }
1427
1428
218
            const std::string cur_node_idstr2 = nodeAnim->mNodeName.data + std::string("_matrix-interpolation");
1429
218
            std::string arrayId = XMLIDEncode(cur_node_idstr2) + "-array";
1430
1431
218
            mOutput << startstr << "<source id=\"" << XMLIDEncode(cur_node_idstr2) << "\">" << endstr;
1432
218
            PushTag();
1433
1434
            // source array
1435
218
            mOutput << startstr << "<Name_array id=\"" << arrayId << "\" count=\"" << names.size() << "\"> ";
1436
1.47k
            for (size_t aa = 0; aa < names.size(); ++aa) {
1437
1.25k
                mOutput << names[aa] << " ";
1438
1.25k
            }
1439
218
            mOutput << "</Name_array>" << endstr;
1440
1441
218
            mOutput << startstr << "<technique_common>" << endstr;
1442
218
            PushTag();
1443
1444
218
            mOutput << startstr << "<accessor source=\"#" << arrayId << "\" count=\"" << names.size() << "\" stride=\"" << 1 << "\">" << endstr;
1445
218
            PushTag();
1446
1447
218
            mOutput << startstr << "<param name=\"INTERPOLATION\" type=\"name\"></param>" << endstr;
1448
1449
218
            PopTag();
1450
218
            mOutput << startstr << "</accessor>" << endstr;
1451
1452
218
            PopTag();
1453
218
            mOutput << startstr << "</technique_common>" << endstr;
1454
1455
218
            PopTag();
1456
218
            mOutput << startstr << "</source>" << endstr;
1457
218
        }
1458
218
    }
1459
1460
867
    for (size_t a = 0; a < anim->mNumChannels; ++a) {
1461
694
        const aiNodeAnim *nodeAnim = anim->mChannels[a];
1462
694
        if (nodeAnim == nullptr) {
1463
0
            continue;
1464
0
        }
1465
1466
694
        {
1467
            // samplers
1468
694
            const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-sampler");
1469
694
            mOutput << startstr << "<sampler id=\"" << XMLIDEncode(node_idstr) << "\">" << endstr;
1470
694
            PushTag();
1471
1472
694
            mOutput << startstr << "<input semantic=\"INPUT\" source=\"#" << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-input")) << "\"/>" << endstr;
1473
694
            mOutput << startstr << "<input semantic=\"OUTPUT\" source=\"#" << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-output")) << "\"/>" << endstr;
1474
694
            mOutput << startstr << "<input semantic=\"INTERPOLATION\" source=\"#" << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-interpolation")) << "\"/>" << endstr;
1475
1476
694
            PopTag();
1477
694
            mOutput << startstr << "</sampler>" << endstr;
1478
694
        }
1479
694
    }
1480
1481
867
    for (size_t a = 0; a < anim->mNumChannels; ++a) {
1482
694
        const aiNodeAnim *nodeAnim = anim->mChannels[a];
1483
694
        if (nodeAnim == nullptr) {
1484
0
            continue;
1485
0
        }
1486
1487
694
        {
1488
            // channels
1489
694
            mOutput << startstr
1490
694
                    << "<channel source=\"#"
1491
694
                    << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-sampler"))
1492
694
                    << "\" target=\""
1493
694
                    << XMLIDEncode(nodeAnim->mNodeName.data)
1494
694
                    << "/matrix\"/>"
1495
694
                    << endstr;
1496
694
        }
1497
694
    }
1498
1499
173
    PopTag();
1500
173
    mOutput << startstr << "</animation>" << endstr;
1501
173
}
1502
1503
// ------------------------------------------------------------------------------------------------
1504
1.54k
void ColladaExporter::WriteAnimationsLibrary() {
1505
1.54k
    if (mScene->mNumAnimations == 0) {
1506
1.41k
        return;
1507
1.41k
    }
1508
1509
128
    mOutput << startstr << "<library_animations>" << endstr;
1510
128
    PushTag();
1511
1512
    // start recursive write at the root node
1513
301
    for (size_t a = 0; a < mScene->mNumAnimations; ++a) {
1514
173
        WriteAnimationLibrary(a);
1515
173
    }
1516
1517
128
    PopTag();
1518
128
    mOutput << startstr << "</library_animations>" << endstr;
1519
128
}
1520
1521
// ------------------------------------------------------------------------------------------------
1522
// Recursively writes the given node
1523
12.6k
void ColladaExporter::WriteNode(const aiNode *pNode) {
1524
    // If the node is associated with a bone, it is a joint node (JOINT)
1525
    // otherwise it is a normal node (NODE)
1526
    // Assimp-specific: nodes with no name cannot be associated with bones
1527
12.6k
    const char *node_type;
1528
12.6k
    bool is_joint, is_skeleton_root = false;
1529
12.6k
    if (pNode->mName.length == 0 || nullptr == mScene->findBone(pNode->mName)) {
1530
12.2k
        node_type = "NODE";
1531
12.2k
        is_joint = false;
1532
12.2k
    } else {
1533
390
        node_type = "JOINT";
1534
390
        is_joint = true;
1535
390
        if (!pNode->mParent || nullptr == mScene->findBone(pNode->mParent->mName)) {
1536
222
            is_skeleton_root = true;
1537
222
        }
1538
390
    }
1539
1540
12.6k
    const std::string node_id = GetNodeUniqueId(pNode);
1541
12.6k
    const std::string node_name = GetNodeName(pNode);
1542
12.6k
    mOutput << startstr << "<node ";
1543
12.6k
    if (is_skeleton_root) {
1544
222
        mFoundSkeletonRootNodeID = node_id; // For now, only support one skeleton in a scene.
1545
222
    }
1546
12.6k
    mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id + "\" " : "");
1547
12.6k
    mOutput << "name=\"" << node_name
1548
12.6k
            << "\" type=\"" << node_type
1549
12.6k
            << "\">" << endstr;
1550
12.6k
    PushTag();
1551
1552
    // write transformation - we can directly put the matrix there
1553
    // TODO: (thom) decompose into scale - rot - quad to allow addressing it by animations afterwards
1554
12.6k
    aiMatrix4x4 mat = pNode->mTransformation;
1555
1556
    // If this node is a Camera node, the camera coordinate system needs to be multiplied in.
1557
    // When importing from Collada, the mLookAt is set to 0, 0, -1, and the node transform is unchanged.
1558
    // When importing from a different format, mLookAt is set to 0, 0, 1. Therefore, the local camera
1559
    // coordinate system must be changed to matche the Collada specification.
1560
13.0k
    for (size_t i = 0; i < mScene->mNumCameras; i++) {
1561
446
        if (mScene->mCameras[i]->mName == pNode->mName) {
1562
85
            aiMatrix4x4 sourceView;
1563
85
            mScene->mCameras[i]->GetCameraMatrix(sourceView);
1564
1565
85
            aiMatrix4x4 colladaView;
1566
85
            colladaView.a1 = colladaView.c3 = -1; // move into -z space.
1567
85
            mat *= (sourceView * colladaView);
1568
85
            break;
1569
85
        }
1570
446
    }
1571
1572
    // customized, sid should be 'matrix' to match with loader code.
1573
12.6k
    mOutput << startstr << "<matrix sid=\"matrix\">";
1574
1575
12.6k
    mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << " ";
1576
12.6k
    mOutput << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << " ";
1577
12.6k
    mOutput << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << " ";
1578
12.6k
    mOutput << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4;
1579
12.6k
    mOutput << "</matrix>" << endstr;
1580
1581
12.6k
    if (pNode->mNumMeshes == 0) {
1582
        //check if it is a camera node
1583
10.6k
        for (size_t i = 0; i < mScene->mNumCameras; i++) {
1584
326
            if (mScene->mCameras[i]->mName == pNode->mName) {
1585
85
                mOutput << startstr << "<instance_camera url=\"#" << GetObjectUniqueId(AiObjectType::Camera, i) << "\"/>" << endstr;
1586
85
                break;
1587
85
            }
1588
326
        }
1589
        //check if it is a light node
1590
10.4k
        for (size_t i = 0; i < mScene->mNumLights; i++) {
1591
0
            if (mScene->mLights[i]->mName == pNode->mName) {
1592
0
                mOutput << startstr << "<instance_light url=\"#" << GetObjectUniqueId(AiObjectType::Light, i) << "\"/>" << endstr;
1593
0
                break;
1594
0
            }
1595
0
        }
1596
10.4k
    } else
1597
        // instance every geometry
1598
6.87k
        for (size_t a = 0; a < pNode->mNumMeshes; ++a) {
1599
4.64k
            const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[a]];
1600
            // do not instantiate mesh if empty. I wonder how this could happen
1601
4.64k
            if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0)
1602
0
                continue;
1603
1604
4.64k
            const std::string meshId = GetObjectUniqueId(AiObjectType::Mesh, pNode->mMeshes[a]);
1605
1606
4.64k
            if (mesh->mNumBones == 0) {
1607
4.52k
                mOutput << startstr << "<instance_geometry url=\"#" << meshId << "\">" << endstr;
1608
4.52k
                PushTag();
1609
4.52k
            } else {
1610
119
                mOutput << startstr
1611
119
                        << "<instance_controller url=\"#" << meshId << "-skin\">"
1612
119
                        << endstr;
1613
119
                PushTag();
1614
1615
                // note! this mFoundSkeletonRootNodeID some how affects animation, it makes the mesh attaches to armature skeleton root node.
1616
                // use the first bone to find skeleton root
1617
119
                const aiNode *skeletonRootBoneNode = findSkeletonRootNode(mScene, mesh);
1618
119
                if (skeletonRootBoneNode) {
1619
119
                    mFoundSkeletonRootNodeID = GetNodeUniqueId(skeletonRootBoneNode);
1620
119
                }
1621
119
                mOutput << startstr << "<skeleton>#" << mFoundSkeletonRootNodeID << "</skeleton>" << endstr;
1622
119
            }
1623
4.64k
            mOutput << startstr << "<bind_material>" << endstr;
1624
4.64k
            PushTag();
1625
4.64k
            mOutput << startstr << "<technique_common>" << endstr;
1626
4.64k
            PushTag();
1627
4.64k
            mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << GetObjectUniqueId(AiObjectType::Material, mesh->mMaterialIndex) << "\">" << endstr;
1628
4.64k
            PushTag();
1629
41.7k
            for (size_t aa = 0; aa < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++aa) {
1630
37.1k
                if (mesh->HasTextureCoords(static_cast<unsigned int>(aa)))
1631
                    // semantic       as in <texture texcoord=...>
1632
                    // input_semantic as in <input semantic=...>
1633
                    // input_set      as in <input set=...>
1634
1.43k
                    mOutput << startstr << "<bind_vertex_input semantic=\"CHANNEL" << aa << "\" input_semantic=\"TEXCOORD\" input_set=\"" << aa << "\"/>" << endstr;
1635
37.1k
            }
1636
4.64k
            PopTag();
1637
4.64k
            mOutput << startstr << "</instance_material>" << endstr;
1638
4.64k
            PopTag();
1639
4.64k
            mOutput << startstr << "</technique_common>" << endstr;
1640
4.64k
            PopTag();
1641
4.64k
            mOutput << startstr << "</bind_material>" << endstr;
1642
1643
4.64k
            PopTag();
1644
4.64k
            if (mesh->mNumBones == 0)
1645
4.52k
                mOutput << startstr << "</instance_geometry>" << endstr;
1646
119
            else
1647
119
                mOutput << startstr << "</instance_controller>" << endstr;
1648
4.64k
        }
1649
1650
    // recurse into subnodes
1651
23.8k
    for (size_t a = 0; a < pNode->mNumChildren; ++a) {
1652
11.1k
        WriteNode(pNode->mChildren[a]);
1653
11.1k
    }
1654
1655
12.6k
    PopTag();
1656
12.6k
    mOutput << startstr << "</node>" << endstr;
1657
12.6k
}
1658
1659
12.6k
void ColladaExporter::CreateNodeIds(const aiNode *node) {
1660
12.6k
    GetNodeUniqueId(node);
1661
23.8k
    for (size_t a = 0; a < node->mNumChildren; ++a)
1662
11.1k
        CreateNodeIds(node->mChildren[a]);
1663
12.6k
}
1664
1665
25.9k
std::string ColladaExporter::GetNodeUniqueId(const aiNode *node) {
1666
    // Use the pointer as the key. This is safe because the scene is immutable.
1667
25.9k
    auto idIt = mNodeIdMap.find(node);
1668
25.9k
    if (idIt != mNodeIdMap.cend()) {
1669
13.2k
        return idIt->second;
1670
13.2k
    }
1671
1672
    // Prefer the requested Collada Id if extant
1673
12.6k
    std::string idStr;
1674
12.6k
    aiString origId;
1675
12.6k
    if (node->mMetaData && node->mMetaData->Get(AI_METADATA_COLLADA_ID, origId)) {
1676
0
        idStr = origId.C_Str();
1677
12.6k
    } else {
1678
12.6k
        idStr = node->mName.C_Str();
1679
12.6k
    }
1680
    // Make sure the requested id is valid
1681
12.6k
    if (idStr.empty()) {
1682
715
        idStr = "node";
1683
11.9k
    } else {
1684
11.9k
        idStr = XMLIDEncode(idStr);
1685
11.9k
    }
1686
1687
    // Ensure it's unique
1688
12.6k
    idStr = MakeUniqueId(mUniqueIds, idStr, std::string());
1689
12.6k
    mUniqueIds.insert(idStr);
1690
12.6k
    mNodeIdMap.insert(std::make_pair(node, idStr));
1691
1692
12.6k
    return idStr;
1693
25.9k
}
1694
1695
12.6k
std::string ColladaExporter::GetNodeName(const aiNode *node) {
1696
12.6k
    if (node == nullptr) {
1697
0
        return std::string();
1698
0
    }
1699
12.6k
    return XMLEscape(node->mName.C_Str());
1700
12.6k
}
1701
1702
421
std::string ColladaExporter::GetBoneUniqueId(const aiBone *bone) {
1703
    // Find the Node that is this Bone
1704
421
    const aiNode *boneNode = mScene->mRootNode->findBoneNode(bone);
1705
421
    if (boneNode == nullptr) {
1706
0
        return std::string();
1707
0
    }
1708
1709
421
    return GetNodeUniqueId(boneNode);
1710
421
}
1711
1712
16.0k
std::string ColladaExporter::GetObjectUniqueId(AiObjectType type, size_t pIndex) {
1713
16.0k
    auto idIt = GetObjectIdMap(type).find(pIndex);
1714
16.0k
    if (idIt != GetObjectIdMap(type).cend()) {
1715
9.65k
        return idIt->second;
1716
9.65k
    }
1717
1718
    // Not seen this object before, create and add
1719
6.35k
    NameIdPair result = AddObjectIndexToMaps(type, pIndex);
1720
6.35k
    return result.second;
1721
16.0k
}
1722
1723
6.64k
std::string ColladaExporter::GetObjectName(AiObjectType type, size_t pIndex) {
1724
6.64k
    auto objectName = GetObjectNameMap(type).find(pIndex);
1725
6.64k
    if (objectName != GetObjectNameMap(type).cend()) {
1726
6.47k
        return objectName->second;
1727
6.47k
    }
1728
1729
    // Not seen this object before, create and add
1730
173
    NameIdPair result = AddObjectIndexToMaps(type, pIndex);
1731
173
    return result.first;
1732
6.64k
}
1733
1734
// Determine unique id and add the name and id to the maps
1735
// @param type object type
1736
// @param index object index
1737
// @param name in/out. Caller to set the original name if known.
1738
// @param idStr in/out. Caller to set the preferred id if known.
1739
6.53k
ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType type, size_t index) {
1740
1741
6.53k
    std::string name;
1742
6.53k
    std::string idStr;
1743
6.53k
    std::string idPostfix;
1744
1745
    // Get the name and id postfix
1746
6.53k
    switch (type) {
1747
4.62k
    case AiObjectType::Mesh:
1748
4.62k
        name = mScene->mMeshes[index]->mName.C_Str();
1749
4.62k
        break;
1750
1.64k
    case AiObjectType::Material:
1751
1.64k
        name = mScene->mMaterials[index]->GetName().C_Str();
1752
1.64k
        break;
1753
173
    case AiObjectType::Animation:
1754
173
        name = mScene->mAnimations[index]->mName.C_Str();
1755
173
        break;
1756
0
    case AiObjectType::Light:
1757
0
        name = mScene->mLights[index]->mName.C_Str();
1758
0
        idPostfix = "-light";
1759
0
        break;
1760
85
    case AiObjectType::Camera:
1761
85
        name = mScene->mCameras[index]->mName.C_Str();
1762
85
        idPostfix = "-camera";
1763
85
        break;
1764
0
    case AiObjectType::Count:
1765
0
        throw std::logic_error("ColladaExporter::AiObjectType::Count is not an object type");
1766
6.53k
    }
1767
1768
6.53k
    if (name.empty()) {
1769
        // Default ids if empty name
1770
2.09k
        switch (type) {
1771
1.69k
        case AiObjectType::Mesh: idStr = std::string("mesh_"); break;
1772
307
        case AiObjectType::Material: idStr = std::string("material_"); break; // This one should never happen
1773
94
        case AiObjectType::Animation: idStr = std::string("animation_"); break;
1774
0
        case AiObjectType::Light: idStr = std::string("light_"); break;
1775
0
        case AiObjectType::Camera: idStr = std::string("camera_"); break;
1776
0
        case AiObjectType::Count: throw std::logic_error("ColladaExporter::AiObjectType::Count is not an object type");
1777
2.09k
        }
1778
2.09k
        idStr.append(ai_to_string(index));
1779
4.43k
    } else {
1780
4.43k
        idStr = XMLIDEncode(name);
1781
4.43k
    }
1782
1783
6.53k
    if (!name.empty()) {
1784
4.43k
        name = XMLEscape(name);
1785
4.43k
    }
1786
1787
6.53k
    idStr = MakeUniqueId(mUniqueIds, idStr, idPostfix);
1788
1789
    // Add to maps
1790
6.53k
    mUniqueIds.insert(idStr);
1791
6.53k
    GetObjectIdMap(type).insert(std::make_pair(index, idStr));
1792
6.53k
    GetObjectNameMap(type).insert(std::make_pair(index, name));
1793
1794
6.53k
    return std::make_pair(name, idStr);
1795
6.53k
}
1796
1797
} // end of namespace Assimp
1798
1799
#endif // ASSIMP_BUILD_NO_COLLADA_EXPORTER
1800
#endif // ASSIMP_BUILD_NO_EXPORT