Coverage Report

Created: 2026-03-12 06:25

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
0
static const aiNode *findSkeletonRootNode(const aiScene *scene, const aiMesh *mesh) {
66
0
    std::set<const aiNode *> topParentBoneNodes;
67
0
    if (mesh && mesh->mNumBones > 0) {
68
0
        for (unsigned int i = 0; i < mesh->mNumBones; ++i) {
69
0
            aiBone *bone = mesh->mBones[i];
70
71
0
            const aiNode *node = scene->mRootNode->findBoneNode(bone);
72
0
            if (node) {
73
0
                while (node->mParent && scene->findBone(node->mParent->mName) != nullptr) {
74
0
                    node = node->mParent;
75
0
                }
76
0
                topParentBoneNodes.insert(node);
77
0
            }
78
0
        }
79
0
    }
80
81
0
    if (!topParentBoneNodes.empty()) {
82
0
        const aiNode *parentBoneNode = *topParentBoneNodes.begin();
83
0
        if (topParentBoneNodes.size() == 1) {
84
0
            return parentBoneNode;
85
0
        } else {
86
0
            for (auto it : topParentBoneNodes) {
87
0
                if (it->mParent) return it->mParent;
88
0
            }
89
0
            return parentBoneNode;
90
0
        }
91
0
    }
92
93
0
    return nullptr;
94
0
}
95
96
// ------------------------------------------------------------------------------------------------
97
// Worker function for exporting a scene to Collada. Prototyped and registered in Exporter.cpp
98
0
void ExportSceneCollada(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) {
99
0
    std::string path = DefaultIOSystem::absolutePath(std::string(pFile));
100
0
    std::string file = DefaultIOSystem::completeBaseName(std::string(pFile));
101
102
    // invoke the exporter
103
0
    ColladaExporter iDoTheExportThing(pScene, pIOSystem, path, file);
104
105
0
    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
0
    std::unique_ptr<IOStream> outfile(pIOSystem->Open(pFile, "wt"));
111
0
    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
0
    outfile->Write(iDoTheExportThing.mOutput.str().c_str(), static_cast<size_t>(iDoTheExportThing.mOutput.tellp()), 1);
117
0
}
118
119
// ------------------------------------------------------------------------------------------------
120
// Encodes a string into a valid XML ID using the xsd:ID schema qualifications.
121
0
static const std::string XMLIDEncode(const std::string &name) {
122
0
    const char XML_ID_CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.";
123
0
    const unsigned int XML_ID_CHARS_COUNT = sizeof(XML_ID_CHARS) / sizeof(char) - 1;
124
125
0
    if (name.length() == 0) {
126
0
        return name;
127
0
    }
128
129
0
    std::stringstream idEncoded;
130
131
    // xsd:ID must start with letter or underscore
132
0
    if (!((name[0] >= 'A' && name[0] <= 'z') || name[0] == '_')) {
133
0
        idEncoded << '_';
134
0
    }
135
136
0
    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
0
        if (strchr(XML_ID_CHARS, *it) != nullptr) {
139
0
            idEncoded << *it;
140
0
        } else {
141
            // Select placeholder character based on invalid character to reduce ID collisions
142
0
            idEncoded << XML_ID_CHARS[(*it) % XML_ID_CHARS_COUNT];
143
0
        }
144
0
    }
145
146
0
    return idEncoded.str();
147
0
}
148
149
// ------------------------------------------------------------------------------------------------
150
// Helper functions to create unique ids
151
0
inline bool IsUniqueId(const std::unordered_set<std::string> &idSet, const std::string &idStr) {
152
0
    return (idSet.find(idStr) == idSet.end());
153
0
}
154
155
0
inline std::string MakeUniqueId(const std::unordered_set<std::string> &idSet, const std::string &idPrefix, const std::string &postfix) {
156
0
    std::string result(idPrefix + postfix);
157
0
    if (!IsUniqueId(idSet, result)) {
158
        // Select a number to append
159
0
        size_t idnum = 1;
160
0
        do {
161
0
            result = idPrefix + '_' + ai_to_string(idnum) + postfix;
162
0
            ++idnum;
163
0
        } while (!IsUniqueId(idSet, result));
164
0
    }
165
0
    return result;
166
0
}
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
0
        mIOSystem(pIOSystem),
172
0
        mPath(path),
173
0
        mFile(file),
174
0
        mScene(pScene),
175
0
        endstr("\n") {
176
    // make sure that all formatting happens using the standard, C locale and not the user's current locale
177
0
    mOutput.imbue(std::locale("C"));
178
0
    mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
179
180
    // start writing the file
181
0
    WriteFile();
182
0
}
183
184
// ------------------------------------------------------------------------------------------------
185
// Starts writing the contents
186
0
void ColladaExporter::WriteFile() {
187
    // write the DTD
188
0
    mOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>" << endstr;
189
    // COLLADA element start
190
0
    mOutput << "<COLLADA xmlns=\"http://www.collada.org/2005/11/COLLADASchema\" version=\"1.4.1\">" << endstr;
191
0
    PushTag();
192
193
0
    WriteTextures();
194
0
    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
0
    CreateNodeIds(mScene->mRootNode);
198
199
0
    WriteCamerasLibrary();
200
0
    WriteLightsLibrary();
201
0
    WriteMaterials();
202
0
    WriteGeometryLibrary();
203
0
    WriteControllerLibrary();
204
205
0
    WriteSceneLibrary();
206
207
    // customized, Writes the animation library
208
0
    WriteAnimationsLibrary();
209
210
    // instantiate the scene(s)
211
    // For Assimp there will only ever be one
212
0
    mOutput << startstr << "<scene>" << endstr;
213
0
    PushTag();
214
0
    mOutput << startstr << "<instance_visual_scene url=\"#" + mSceneId + "\" />" << endstr;
215
0
    PopTag();
216
0
    mOutput << startstr << "</scene>" << endstr;
217
0
    PopTag();
218
0
    mOutput << "</COLLADA>" << endstr;
219
0
}
220
221
// ------------------------------------------------------------------------------------------------
222
// Writes the asset header
223
0
void ColladaExporter::WriteHeader() {
224
0
    static const ai_real epsilon = Math::getEpsilon<ai_real>();
225
0
    static const aiQuaternion x_rot(aiMatrix3x3(
226
0
            0, -1, 0,
227
0
            1, 0, 0,
228
0
            0, 0, 1));
229
0
    static const aiQuaternion y_rot(aiMatrix3x3(
230
0
            1, 0, 0,
231
0
            0, 1, 0,
232
0
            0, 0, 1));
233
0
    static const aiQuaternion z_rot(aiMatrix3x3(
234
0
            1, 0, 0,
235
0
            0, 0, 1,
236
0
            0, -1, 0));
237
238
0
    static const unsigned int date_nb_chars = 20;
239
0
    char date_str[date_nb_chars];
240
0
    std::time_t date = std::time(nullptr);
241
0
    std::strftime(date_str, date_nb_chars, "%Y-%m-%dT%H:%M:%S", std::localtime(&date));
242
243
0
    aiVector3D scaling;
244
0
    aiQuaternion rotation;
245
0
    aiVector3D position;
246
0
    mScene->mRootNode->mTransformation.Decompose(scaling, rotation, position);
247
0
    rotation.Normalize();
248
249
0
    mAdd_root_node = false;
250
251
0
    ai_real scale = 1.0;
252
0
    if (std::abs(scaling.x - scaling.y) <= epsilon && std::abs(scaling.x - scaling.z) <= epsilon && std::abs(scaling.y - scaling.z) <= epsilon) {
253
0
        scale = (ai_real)((((double)scaling.x) + ((double)scaling.y) + ((double)scaling.z)) / 3.0);
254
0
    } else {
255
0
        mAdd_root_node = true;
256
0
    }
257
258
0
    std::string up_axis = "Y_UP";
259
0
    if (rotation.Equal(x_rot, epsilon)) {
260
0
        up_axis = "X_UP";
261
0
    } else if (rotation.Equal(y_rot, epsilon)) {
262
0
        up_axis = "Y_UP";
263
0
    } else if (rotation.Equal(z_rot, epsilon)) {
264
0
        up_axis = "Z_UP";
265
0
    } else {
266
0
        mAdd_root_node = true;
267
0
    }
268
269
0
    if (!position.Equal(aiVector3D(0, 0, 0))) {
270
0
        mAdd_root_node = true;
271
0
    }
272
273
    // Assimp root nodes can have meshes, Collada Scenes cannot
274
0
    if (mScene->mRootNode->mNumChildren == 0 || mScene->mRootNode->mMeshes != nullptr) {
275
0
        mAdd_root_node = true;
276
0
    }
277
278
0
    if (mAdd_root_node) {
279
0
        up_axis = "Y_UP";
280
0
        scale = 1.0;
281
0
    }
282
283
0
    mOutput << startstr << "<asset>" << endstr;
284
0
    PushTag();
285
0
    mOutput << startstr << "<contributor>" << endstr;
286
0
    PushTag();
287
288
    // If no Scene metadata, use root node metadata
289
0
    aiMetadata *meta = mScene->mMetaData;
290
0
    if (nullptr == meta) {
291
0
        meta = mScene->mRootNode->mMetaData;
292
0
    }
293
294
0
    aiString value;
295
0
    if (!meta || !meta->Get("Author", value)) {
296
0
        mOutput << startstr << "<author>"
297
0
                << "Assimp"
298
0
                << "</author>" << endstr;
299
0
    } else {
300
0
        mOutput << startstr << "<author>" << XMLEscape(value.C_Str()) << "</author>" << endstr;
301
0
    }
302
303
0
    if (nullptr == meta || !meta->Get(AI_METADATA_SOURCE_GENERATOR, value)) {
304
0
        mOutput << startstr << "<authoring_tool>"
305
0
                << "Assimp Exporter"
306
0
                << "</authoring_tool>" << endstr;
307
0
    } else {
308
0
        mOutput << startstr << "<authoring_tool>" << XMLEscape(value.C_Str()) << "</authoring_tool>" << endstr;
309
0
    }
310
311
0
    if (meta) {
312
0
        if (meta->Get("Comments", value)) {
313
0
            mOutput << startstr << "<comments>" << XMLEscape(value.C_Str()) << "</comments>" << endstr;
314
0
        }
315
0
        if (meta->Get(AI_METADATA_SOURCE_COPYRIGHT, value)) {
316
0
            mOutput << startstr << "<copyright>" << XMLEscape(value.C_Str()) << "</copyright>" << endstr;
317
0
        }
318
0
        if (meta->Get("SourceData", value)) {
319
0
            mOutput << startstr << "<source_data>" << XMLEscape(value.C_Str()) << "</source_data>" << endstr;
320
0
        }
321
0
    }
322
323
0
    PopTag();
324
0
    mOutput << startstr << "</contributor>" << endstr;
325
326
0
    if (nullptr == meta || !meta->Get("Created", value)) {
327
0
        mOutput << startstr << "<created>" << date_str << "</created>" << endstr;
328
0
    } else {
329
0
        mOutput << startstr << "<created>" << XMLEscape(value.C_Str()) << "</created>" << endstr;
330
0
    }
331
332
    // Modified date is always the date saved
333
0
    mOutput << startstr << "<modified>" << date_str << "</modified>" << endstr;
334
335
0
    if (meta) {
336
0
        if (meta->Get("Keywords", value)) {
337
0
            mOutput << startstr << "<keywords>" << XMLEscape(value.C_Str()) << "</keywords>" << endstr;
338
0
        }
339
0
        if (meta->Get("Revision", value)) {
340
0
            mOutput << startstr << "<revision>" << XMLEscape(value.C_Str()) << "</revision>" << endstr;
341
0
        }
342
0
        if (meta->Get("Subject", value)) {
343
0
            mOutput << startstr << "<subject>" << XMLEscape(value.C_Str()) << "</subject>" << endstr;
344
0
        }
345
0
        if (meta->Get("Title", value)) {
346
0
            mOutput << startstr << "<title>" << XMLEscape(value.C_Str()) << "</title>" << endstr;
347
0
        }
348
0
    }
349
350
0
    mOutput << startstr << "<unit name=\"meter\" meter=\"" << scale << "\" />" << endstr;
351
0
    mOutput << startstr << "<up_axis>" << up_axis << "</up_axis>" << endstr;
352
0
    PopTag();
353
0
    mOutput << startstr << "</asset>" << endstr;
354
0
}
355
356
// ------------------------------------------------------------------------------------------------
357
// Write the embedded textures
358
0
void ColladaExporter::WriteTextures() {
359
0
    static constexpr unsigned int buffer_size = 1024;
360
0
    char str[buffer_size] = {'\0'};
361
362
0
    if (!mScene->HasTextures()) {
363
0
        return;
364
0
    }
365
366
0
    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
0
        aiTexture *texture = mScene->mTextures[i];
371
0
        if (nullptr == texture) {
372
0
            continue;
373
0
        }
374
375
0
        ASSIMP_itoa10(str, buffer_size, i + 1);
376
377
0
        std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char *)texture->achFormatHint);
378
379
0
        std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + mIOSystem->getOsSeparator() + name, "wb"));
380
0
        if (outfile == nullptr) {
381
0
            throw DeadlyExportError("could not open output texture file: " + mPath + name);
382
0
        }
383
384
0
        if (texture->mHeight == 0) {
385
0
            outfile->Write((void *)texture->pcData, texture->mWidth, 1);
386
0
        } else {
387
0
            Bitmap::Save(texture, outfile.get());
388
0
        }
389
390
0
        outfile->Flush();
391
392
0
        textures.insert(std::make_pair(i, name));
393
0
    }
394
0
}
395
396
// ------------------------------------------------------------------------------------------------
397
// Write the embedded textures
398
0
void ColladaExporter::WriteCamerasLibrary() {
399
0
    if (!mScene->HasCameras()) {
400
0
        return;
401
0
    }
402
403
0
    mOutput << startstr << "<library_cameras>" << endstr;
404
0
    PushTag();
405
406
0
    for (size_t a = 0; a < mScene->mNumCameras; ++a) {
407
0
        WriteCamera(a);
408
0
    }
409
410
0
    PopTag();
411
0
    mOutput << startstr << "</library_cameras>" << endstr;
412
0
}
413
414
0
void ColladaExporter::WriteCamera(size_t pIndex) {
415
416
0
    const aiCamera *cam = mScene->mCameras[pIndex];
417
0
    if (cam == nullptr) {
418
0
        return;
419
0
    }
420
421
0
    const std::string cameraId = GetObjectUniqueId(AiObjectType::Camera, pIndex);
422
0
    const std::string cameraName = GetObjectName(AiObjectType::Camera, pIndex);
423
424
0
    mOutput << startstr << "<camera id=\"" << cameraId << "\" name=\"" << cameraName << "\" >" << endstr;
425
0
    PushTag();
426
0
    mOutput << startstr << "<optics>" << endstr;
427
0
    PushTag();
428
0
    mOutput << startstr << "<technique_common>" << endstr;
429
0
    PushTag();
430
    //assimp doesn't support the import of orthographic cameras! se we write
431
    //always perspective
432
0
    mOutput << startstr << "<perspective>" << endstr;
433
0
    PushTag();
434
0
    mOutput << startstr << "<xfov sid=\"xfov\">" << AI_RAD_TO_DEG(cam->mHorizontalFOV)
435
0
            << "</xfov>" << endstr;
436
0
    mOutput << startstr << "<aspect_ratio>"
437
0
            << cam->mAspect
438
0
            << "</aspect_ratio>" << endstr;
439
0
    mOutput << startstr << "<znear sid=\"znear\">"
440
0
            << cam->mClipPlaneNear
441
0
            << "</znear>" << endstr;
442
0
    mOutput << startstr << "<zfar sid=\"zfar\">"
443
0
            << cam->mClipPlaneFar
444
0
            << "</zfar>" << endstr;
445
0
    PopTag();
446
0
    mOutput << startstr << "</perspective>" << endstr;
447
0
    PopTag();
448
0
    mOutput << startstr << "</technique_common>" << endstr;
449
0
    PopTag();
450
0
    mOutput << startstr << "</optics>" << endstr;
451
0
    PopTag();
452
0
    mOutput << startstr << "</camera>" << endstr;
453
0
}
454
455
// ------------------------------------------------------------------------------------------------
456
// Write the embedded textures
457
0
void ColladaExporter::WriteLightsLibrary() {
458
0
    if (!mScene->HasLights()) {
459
0
        return;
460
0
    }
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
0
bool ColladaExporter::ReadMaterialSurface(Surface &poSurface, const aiMaterial &pSrcMat, aiTextureType pTexture, const char *pKey, size_t pType, size_t pIndex) {
597
0
    if (pSrcMat.GetTextureCount(pTexture) == 0) {
598
0
        if (pKey)
599
0
            poSurface.exist = pSrcMat.Get(pKey, static_cast<unsigned int>(pType), static_cast<unsigned int>(pIndex), poSurface.color) == aiReturn_SUCCESS;
600
0
        return poSurface.exist;
601
0
    }
602
603
0
    aiString texfile;
604
0
    unsigned int uvChannel = 0;
605
0
    pSrcMat.GetTexture(pTexture, 0, &texfile, nullptr, &uvChannel);
606
607
0
    std::string index_str(texfile.C_Str());
608
609
0
    if (index_str.size() != 0 && index_str[0] == '*') {
610
0
        unsigned int index;
611
612
0
        index_str = index_str.substr(1, std::string::npos);
613
614
0
        try {
615
0
            index = (unsigned int)strtoul10_64<DeadlyExportError>(index_str.c_str());
616
0
        } catch (std::exception &error) {
617
0
            throw DeadlyExportError(error.what());
618
0
        }
619
620
0
        std::map<unsigned int, std::string>::const_iterator name = textures.find(index);
621
622
0
        if (name != textures.end()) {
623
0
            poSurface.texture = name->second;
624
0
        } else {
625
0
            throw DeadlyExportError("could not find embedded texture at index " + index_str);
626
0
        }
627
0
    } else {
628
0
        poSurface.texture = texfile.C_Str();
629
0
    }
630
631
0
    poSurface.channel = uvChannel;
632
0
    poSurface.exist = true;
633
634
0
    return poSurface.exist;
635
0
}
636
637
// ------------------------------------------------------------------------------------------------
638
// Reimplementation of isalnum(,C locale), because AppVeyor does not see standard version.
639
0
static bool isalnum_C(char c) {
640
0
    return (nullptr != strchr("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", c));
641
0
}
642
643
// ------------------------------------------------------------------------------------------------
644
// Writes an image entry for the given surface
645
0
void ColladaExporter::WriteImageEntry(const Surface &pSurface, const std::string &imageId) {
646
0
    if (pSurface.texture.empty()) {
647
0
        return;
648
0
    }
649
650
0
    mOutput << startstr << "<image id=\"" << imageId << "\">" << endstr;
651
0
    PushTag();
652
0
    mOutput << startstr << "<init_from>";
653
654
    // URL encode image file name first, then XML encode on top
655
0
    std::stringstream imageUrlEncoded;
656
0
    for (std::string::const_iterator it = pSurface.texture.begin(); it != pSurface.texture.end(); ++it) {
657
0
        if (isalnum_C((unsigned char)*it) || *it == ':' || *it == '_' || *it == '-' || *it == '.' || *it == '/' || *it == '\\')
658
0
            imageUrlEncoded << *it;
659
0
        else
660
0
            imageUrlEncoded << '%' << std::hex << size_t((unsigned char)*it) << std::dec;
661
0
    }
662
0
    mOutput << XMLEscape(imageUrlEncoded.str());
663
0
    mOutput << "</init_from>" << endstr;
664
0
    PopTag();
665
0
    mOutput << startstr << "</image>" << endstr;
666
0
}
667
668
// ------------------------------------------------------------------------------------------------
669
// Writes a color-or-texture entry into an effect definition
670
0
void ColladaExporter::WriteTextureColorEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &imageId) {
671
0
    if (!pSurface.exist) {
672
0
        return;
673
0
    }
674
675
0
    mOutput << startstr << "<" << pTypeName << ">" << endstr;
676
0
    PushTag();
677
0
    if (pSurface.texture.empty()) {
678
0
        mOutput << startstr << "<color sid=\"" << pTypeName << "\">" << pSurface.color.r << "   " << pSurface.color.g << "   " << pSurface.color.b << "   " << pSurface.color.a << "</color>" << endstr;
679
0
    } else {
680
0
        mOutput << startstr << "<texture texture=\"" << imageId << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
681
0
    }
682
0
    PopTag();
683
0
    mOutput << startstr << "</" << pTypeName << ">" << endstr;
684
0
}
685
686
// ------------------------------------------------------------------------------------------------
687
// Writes the two parameters necessary for referencing a texture in an effect entry
688
0
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
0
    if (pSurface.texture.empty()) {
691
0
        return;
692
0
    }
693
694
0
    mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-surface\">" << endstr;
695
0
    PushTag();
696
0
    mOutput << startstr << "<surface type=\"2D\">" << endstr;
697
0
    PushTag();
698
0
    mOutput << startstr << "<init_from>" << materialId << "-" << pTypeName << "-image</init_from>" << endstr;
699
0
    PopTag();
700
0
    mOutput << startstr << "</surface>" << endstr;
701
0
    PopTag();
702
0
    mOutput << startstr << "</newparam>" << endstr;
703
704
0
    mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-sampler\">" << endstr;
705
0
    PushTag();
706
0
    mOutput << startstr << "<sampler2D>" << endstr;
707
0
    PushTag();
708
0
    mOutput << startstr << "<source>" << materialId << "-" << pTypeName << "-surface</source>" << endstr;
709
0
    PopTag();
710
0
    mOutput << startstr << "</sampler2D>" << endstr;
711
0
    PopTag();
712
0
    mOutput << startstr << "</newparam>" << endstr;
713
0
}
714
715
// ------------------------------------------------------------------------------------------------
716
// Writes a scalar property
717
0
void ColladaExporter::WriteFloatEntry(const Property &pProperty, const std::string &pTypeName) {
718
0
    if (!pProperty.exist) {
719
0
        return;
720
0
    }
721
722
0
    mOutput << startstr << "<" << pTypeName << ">" << endstr;
723
0
    PushTag();
724
0
    mOutput << startstr << "<float sid=\"" << pTypeName << "\">" << pProperty.value << "</float>" << endstr;
725
0
    PopTag();
726
0
    mOutput << startstr << "</" << pTypeName << ">" << endstr;
727
0
}
728
729
// ------------------------------------------------------------------------------------------------
730
// Writes the material setup
731
0
void ColladaExporter::WriteMaterials() {
732
0
    std::vector<Material> materials;
733
0
    materials.resize(mScene->mNumMaterials);
734
735
    /// collect all materials from the scene
736
0
    size_t numTextures = 0;
737
0
    for (size_t a = 0; a < mScene->mNumMaterials; ++a) {
738
0
        Material &material = materials[a];
739
0
        material.id = GetObjectUniqueId(AiObjectType::Material, a);
740
0
        material.name = GetObjectName(AiObjectType::Material, a);
741
742
0
        const aiMaterial &mat = *(mScene->mMaterials[a]);
743
0
        aiShadingMode shading = aiShadingMode_Flat;
744
0
        material.shading_model = "phong";
745
0
        if (mat.Get(AI_MATKEY_SHADING_MODEL, shading) == aiReturn_SUCCESS) {
746
0
            if (shading == aiShadingMode_Phong) {
747
0
                material.shading_model = "phong";
748
0
            } else if (shading == aiShadingMode_Blinn) {
749
0
                material.shading_model = "blinn";
750
0
            } else if (shading == aiShadingMode_NoShading) {
751
0
                material.shading_model = "constant";
752
0
            } else if (shading == aiShadingMode_Gouraud) {
753
0
                material.shading_model = "lambert";
754
0
            }
755
0
        }
756
757
0
        if (ReadMaterialSurface(material.ambient, mat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT))
758
0
            ++numTextures;
759
0
        if (ReadMaterialSurface(material.diffuse, mat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE))
760
0
            ++numTextures;
761
0
        if (ReadMaterialSurface(material.specular, mat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR))
762
0
            ++numTextures;
763
0
        if (ReadMaterialSurface(material.emissive, mat, aiTextureType_EMISSIVE, AI_MATKEY_COLOR_EMISSIVE))
764
0
            ++numTextures;
765
0
        if (ReadMaterialSurface(material.reflective, mat, aiTextureType_REFLECTION, AI_MATKEY_COLOR_REFLECTIVE))
766
0
            ++numTextures;
767
0
        if (ReadMaterialSurface(material.transparent, mat, aiTextureType_OPACITY, AI_MATKEY_COLOR_TRANSPARENT))
768
0
            ++numTextures;
769
0
        if (ReadMaterialSurface(material.normal, mat, aiTextureType_NORMALS, nullptr, 0, 0))
770
0
            ++numTextures;
771
772
0
        material.shininess.exist = mat.Get(AI_MATKEY_SHININESS, material.shininess.value) == aiReturn_SUCCESS;
773
0
        material.transparency.exist = mat.Get(AI_MATKEY_OPACITY, material.transparency.value) == aiReturn_SUCCESS;
774
0
        material.index_refraction.exist = mat.Get(AI_MATKEY_REFRACTI, material.index_refraction.value) == aiReturn_SUCCESS;
775
0
    }
776
777
    // output textures if present
778
0
    if (numTextures > 0) {
779
0
        mOutput << startstr << "<library_images>" << endstr;
780
0
        PushTag();
781
0
        for (const Material &mat : materials) {
782
0
            WriteImageEntry(mat.ambient, mat.id + "-ambient-image");
783
0
            WriteImageEntry(mat.diffuse, mat.id + "-diffuse-image");
784
0
            WriteImageEntry(mat.specular, mat.id + "-specular-image");
785
0
            WriteImageEntry(mat.emissive, mat.id + "-emission-image");
786
0
            WriteImageEntry(mat.reflective, mat.id + "-reflective-image");
787
0
            WriteImageEntry(mat.transparent, mat.id + "-transparent-image");
788
0
            WriteImageEntry(mat.normal, mat.id + "-normal-image");
789
0
        }
790
0
        PopTag();
791
0
        mOutput << startstr << "</library_images>" << endstr;
792
0
    }
793
794
    // output effects - those are the actual carriers of information
795
0
    if (!materials.empty()) {
796
0
        mOutput << startstr << "<library_effects>" << endstr;
797
0
        PushTag();
798
0
        for (const Material &mat : materials) {
799
            // this is so ridiculous it must be right
800
0
            mOutput << startstr << "<effect id=\"" << mat.id << "-fx\" name=\"" << mat.name << "\">" << endstr;
801
0
            PushTag();
802
0
            mOutput << startstr << "<profile_COMMON>" << endstr;
803
0
            PushTag();
804
805
            // write sampler- and surface params for the texture entries
806
0
            WriteTextureParamEntry(mat.emissive, "emission", mat.id);
807
0
            WriteTextureParamEntry(mat.ambient, "ambient", mat.id);
808
0
            WriteTextureParamEntry(mat.diffuse, "diffuse", mat.id);
809
0
            WriteTextureParamEntry(mat.specular, "specular", mat.id);
810
0
            WriteTextureParamEntry(mat.reflective, "reflective", mat.id);
811
0
            WriteTextureParamEntry(mat.transparent, "transparent", mat.id);
812
0
            WriteTextureParamEntry(mat.normal, "normal", mat.id);
813
814
0
            mOutput << startstr << "<technique sid=\"standard\">" << endstr;
815
0
            PushTag();
816
0
            mOutput << startstr << "<" << mat.shading_model << ">" << endstr;
817
0
            PushTag();
818
819
0
            WriteTextureColorEntry(mat.emissive, "emission", mat.id + "-emission-sampler");
820
0
            WriteTextureColorEntry(mat.ambient, "ambient", mat.id + "-ambient-sampler");
821
0
            WriteTextureColorEntry(mat.diffuse, "diffuse", mat.id + "-diffuse-sampler");
822
0
            WriteTextureColorEntry(mat.specular, "specular", mat.id + "-specular-sampler");
823
0
            WriteFloatEntry(mat.shininess, "shininess");
824
0
            WriteTextureColorEntry(mat.reflective, "reflective", mat.id + "-reflective-sampler");
825
0
            WriteTextureColorEntry(mat.transparent, "transparent", mat.id + "-transparent-sampler");
826
0
            WriteFloatEntry(mat.transparency, "transparency");
827
0
            WriteFloatEntry(mat.index_refraction, "index_of_refraction");
828
829
0
            if (!mat.normal.texture.empty()) {
830
0
                WriteTextureColorEntry(mat.normal, "bump", mat.id + "-normal-sampler");
831
0
            }
832
833
0
            PopTag();
834
0
            mOutput << startstr << "</" << mat.shading_model << ">" << endstr;
835
0
            PopTag();
836
0
            mOutput << startstr << "</technique>" << endstr;
837
0
            PopTag();
838
0
            mOutput << startstr << "</profile_COMMON>" << endstr;
839
0
            PopTag();
840
0
            mOutput << startstr << "</effect>" << endstr;
841
0
        }
842
0
        PopTag();
843
0
        mOutput << startstr << "</library_effects>" << endstr;
844
845
        // write materials - they're just effect references
846
0
        mOutput << startstr << "<library_materials>" << endstr;
847
0
        PushTag();
848
0
        for (std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it) {
849
0
            const Material &mat = *it;
850
0
            mOutput << startstr << "<material id=\"" << mat.id << "\" name=\"" << mat.name << "\">" << endstr;
851
0
            PushTag();
852
0
            mOutput << startstr << "<instance_effect url=\"#" << mat.id << "-fx\"/>" << endstr;
853
0
            PopTag();
854
0
            mOutput << startstr << "</material>" << endstr;
855
0
        }
856
0
        PopTag();
857
0
        mOutput << startstr << "</library_materials>" << endstr;
858
0
    }
859
0
}
860
861
// ------------------------------------------------------------------------------------------------
862
// Writes the controller library
863
0
void ColladaExporter::WriteControllerLibrary() {
864
0
    mOutput << startstr << "<library_controllers>" << endstr;
865
0
    PushTag();
866
867
0
    for (size_t a = 0; a < mScene->mNumMeshes; ++a) {
868
0
        WriteController(a);
869
0
    }
870
871
0
    PopTag();
872
0
    mOutput << startstr << "</library_controllers>" << endstr;
873
0
}
874
875
// ------------------------------------------------------------------------------------------------
876
// Writes a skin controller of the given mesh
877
0
void ColladaExporter::WriteController(size_t pIndex) {
878
0
    const aiMesh *mesh = mScene->mMeshes[pIndex];
879
    // Is there a skin controller?
880
0
    if (mesh->mNumBones == 0 || mesh->mNumFaces == 0 || mesh->mNumVertices == 0) {
881
0
        return;
882
0
    }
883
884
0
    const std::string idstr = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
885
0
    const std::string namestr = GetObjectName(AiObjectType::Mesh, pIndex);
886
887
0
    mOutput << startstr << "<controller id=\"" << idstr << "-skin\" ";
888
0
    mOutput << "name=\"skinCluster" << pIndex << "\">" << endstr;
889
0
    PushTag();
890
891
0
    mOutput << startstr << "<skin source=\"#" << idstr << "\">" << endstr;
892
0
    PushTag();
893
894
    // bind pose matrix
895
0
    mOutput << startstr << "<bind_shape_matrix>" << endstr;
896
0
    PushTag();
897
898
    // I think it is identity in general cases.
899
0
    aiMatrix4x4 mat;
900
0
    mOutput << startstr << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << endstr;
901
0
    mOutput << startstr << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << endstr;
902
0
    mOutput << startstr << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << endstr;
903
0
    mOutput << startstr << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4 << endstr;
904
905
0
    PopTag();
906
0
    mOutput << startstr << "</bind_shape_matrix>" << endstr;
907
908
0
    mOutput << startstr << "<source id=\"" << idstr << "-skin-joints\" name=\"" << namestr << "-skin-joints\">" << endstr;
909
0
    PushTag();
910
911
0
    mOutput << startstr << "<Name_array id=\"" << idstr << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
912
913
0
    for (size_t i = 0; i < mesh->mNumBones; ++i) {
914
0
        mOutput << GetBoneUniqueId(mesh->mBones[i]) << ' ';
915
0
    }
916
917
0
    mOutput << "</Name_array>" << endstr;
918
919
0
    mOutput << startstr << "<technique_common>" << endstr;
920
0
    PushTag();
921
922
0
    mOutput << startstr << "<accessor source=\"#" << idstr << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\" stride=\"" << 1 << "\">" << endstr;
923
0
    PushTag();
924
925
0
    mOutput << startstr << "<param name=\"JOINT\" type=\"Name\"></param>" << endstr;
926
927
0
    PopTag();
928
0
    mOutput << startstr << "</accessor>" << endstr;
929
930
0
    PopTag();
931
0
    mOutput << startstr << "</technique_common>" << endstr;
932
933
0
    PopTag();
934
0
    mOutput << startstr << "</source>" << endstr;
935
936
0
    std::vector<ai_real> bind_poses;
937
0
    bind_poses.reserve(mesh->mNumBones * 16);
938
0
    for (unsigned int i = 0; i < mesh->mNumBones; ++i) {
939
0
        for (unsigned int j = 0; j < 4; ++j) {
940
0
            bind_poses.insert(bind_poses.end(), mesh->mBones[i]->mOffsetMatrix[j], mesh->mBones[i]->mOffsetMatrix[j] + 4);
941
0
        }
942
0
    }
943
944
0
    WriteFloatArray(idstr + "-skin-bind_poses", FloatType_Mat4x4, (const ai_real *)bind_poses.data(), bind_poses.size() / 16);
945
946
0
    bind_poses.clear();
947
948
0
    std::vector<ai_real> skin_weights;
949
0
    skin_weights.reserve(mesh->mNumVertices * mesh->mNumBones);
950
0
    for (size_t i = 0; i < mesh->mNumBones; ++i) {
951
0
        for (size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j) {
952
0
            skin_weights.push_back(mesh->mBones[i]->mWeights[j].mWeight);
953
0
        }
954
0
    }
955
956
0
    WriteFloatArray(idstr + "-skin-weights", FloatType_Weight, (const ai_real *)skin_weights.data(), skin_weights.size());
957
958
0
    skin_weights.clear();
959
960
0
    mOutput << startstr << "<joints>" << endstr;
961
0
    PushTag();
962
963
0
    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstr << "-skin-joints\"></input>" << endstr;
964
0
    mOutput << startstr << "<input semantic=\"INV_BIND_MATRIX\" source=\"#" << idstr << "-skin-bind_poses\"></input>" << endstr;
965
966
0
    PopTag();
967
0
    mOutput << startstr << "</joints>" << endstr;
968
969
0
    mOutput << startstr << "<vertex_weights count=\"" << mesh->mNumVertices << "\">" << endstr;
970
0
    PushTag();
971
972
0
    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstr << "-skin-joints\" offset=\"0\"></input>" << endstr;
973
0
    mOutput << startstr << "<input semantic=\"WEIGHT\" source=\"#" << idstr << "-skin-weights\" offset=\"1\"></input>" << endstr;
974
975
0
    mOutput << startstr << "<vcount>";
976
977
0
    std::vector<ai_uint> num_influences(mesh->mNumVertices, (ai_uint)0);
978
0
    for (size_t i = 0; i < mesh->mNumBones; ++i) {
979
0
        for (size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j) {
980
0
            ++num_influences[mesh->mBones[i]->mWeights[j].mVertexId];
981
0
        }
982
0
    }
983
984
0
    for (size_t i = 0; i < mesh->mNumVertices; ++i) {
985
0
        mOutput << num_influences[i] << " ";
986
0
    }
987
988
0
    mOutput << "</vcount>" << endstr;
989
990
0
    mOutput << startstr << "<v>";
991
992
0
    ai_uint joint_weight_indices_length = 0;
993
0
    std::vector<ai_uint> accum_influences;
994
0
    accum_influences.reserve(num_influences.size());
995
0
    for (size_t i = 0; i < num_influences.size(); ++i) {
996
0
        accum_influences.push_back(joint_weight_indices_length);
997
0
        joint_weight_indices_length += num_influences[i];
998
0
    }
999
1000
0
    ai_uint weight_index = 0;
1001
0
    std::vector<ai_int> joint_weight_indices(2 * joint_weight_indices_length, (ai_int)-1);
1002
0
    for (unsigned int i = 0; i < mesh->mNumBones; ++i) {
1003
0
        for (unsigned j = 0; j < mesh->mBones[i]->mNumWeights; ++j) {
1004
0
            unsigned int vId = mesh->mBones[i]->mWeights[j].mVertexId;
1005
0
            for (ai_uint k = 0; k < num_influences[vId]; ++k) {
1006
0
                if (joint_weight_indices[2 * (accum_influences[vId] + k)] == -1) {
1007
0
                    joint_weight_indices[2 * (accum_influences[vId] + k)] = i;
1008
0
                    joint_weight_indices[2 * (accum_influences[vId] + k) + 1] = weight_index;
1009
0
                    break;
1010
0
                }
1011
0
            }
1012
0
            ++weight_index;
1013
0
        }
1014
0
    }
1015
1016
0
    for (size_t i = 0; i < joint_weight_indices.size(); ++i) {
1017
0
        mOutput << joint_weight_indices[i] << " ";
1018
0
    }
1019
1020
0
    num_influences.clear();
1021
0
    accum_influences.clear();
1022
0
    joint_weight_indices.clear();
1023
1024
0
    mOutput << "</v>" << endstr;
1025
1026
0
    PopTag();
1027
0
    mOutput << startstr << "</vertex_weights>" << endstr;
1028
1029
0
    PopTag();
1030
0
    mOutput << startstr << "</skin>" << endstr;
1031
1032
0
    PopTag();
1033
0
    mOutput << startstr << "</controller>" << endstr;
1034
0
}
1035
1036
// ------------------------------------------------------------------------------------------------
1037
// Writes the geometry library
1038
0
void ColladaExporter::WriteGeometryLibrary() {
1039
0
    mOutput << startstr << "<library_geometries>" << endstr;
1040
0
    PushTag();
1041
1042
0
    for (size_t a = 0; a < mScene->mNumMeshes; ++a) {
1043
0
        WriteGeometry(a);
1044
0
    }
1045
1046
0
    PopTag();
1047
0
    mOutput << startstr << "</library_geometries>" << endstr;
1048
0
}
1049
1050
// ------------------------------------------------------------------------------------------------
1051
// Writes the given mesh
1052
0
void ColladaExporter::WriteGeometry(size_t pIndex) {
1053
0
    const aiMesh *mesh = mScene->mMeshes[pIndex];
1054
0
    const std::string geometryId = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
1055
0
    const std::string geometryName = GetObjectName(AiObjectType::Mesh, pIndex);
1056
1057
0
    if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0) {
1058
0
        return;
1059
0
    }
1060
1061
    // opening tag
1062
0
    mOutput << startstr << "<geometry id=\"" << geometryId << "\" name=\"" << geometryName << "\" >" << endstr;
1063
0
    PushTag();
1064
1065
0
    mOutput << startstr << "<mesh>" << endstr;
1066
0
    PushTag();
1067
1068
    // Positions
1069
0
    WriteFloatArray(geometryId + "-positions", FloatType_Vector, (ai_real *)mesh->mVertices, mesh->mNumVertices);
1070
    // Normals, if any
1071
0
    if (mesh->HasNormals()) {
1072
0
        WriteFloatArray(geometryId + "-normals", FloatType_Vector, (ai_real *)mesh->mNormals, mesh->mNumVertices);
1073
0
    }
1074
1075
    // texture coords
1076
0
    for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
1077
0
        if (mesh->HasTextureCoords(static_cast<unsigned int>(a))) {
1078
0
            WriteFloatArray(geometryId + "-tex" + ai_to_string(a), mesh->mNumUVComponents[a] == 3 ? FloatType_TexCoord3 : FloatType_TexCoord2,
1079
0
                    (ai_real *)mesh->mTextureCoords[a], mesh->mNumVertices);
1080
0
        }
1081
0
    }
1082
1083
    // vertex colors
1084
0
    for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
1085
0
        if (mesh->HasVertexColors(static_cast<unsigned int>(a)))
1086
0
            WriteFloatArray(geometryId + "-color" + ai_to_string(a), FloatType_Color, (ai_real *)mesh->mColors[a], mesh->mNumVertices);
1087
0
    }
1088
1089
    // assemble vertex structure
1090
    // Only write input for POSITION since we will write other as shared inputs in polygon definition
1091
0
    mOutput << startstr << "<vertices id=\"" << geometryId << "-vertices"
1092
0
            << "\">" << endstr;
1093
0
    PushTag();
1094
0
    mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << geometryId << "-positions\" />" << endstr;
1095
0
    PopTag();
1096
0
    mOutput << startstr << "</vertices>" << endstr;
1097
1098
    // count the number of lines, triangles and polygon meshes
1099
0
    int countLines = 0;
1100
0
    int countPoly = 0;
1101
0
    for (size_t a = 0; a < mesh->mNumFaces; ++a) {
1102
0
        if (mesh->mFaces[a].mNumIndices == 2) {
1103
0
            countLines++;
1104
0
        } else if (mesh->mFaces[a].mNumIndices >= 3) {
1105
0
            countPoly++;
1106
0
        }
1107
0
    }
1108
1109
    // lines
1110
0
    if (countLines) {
1111
0
        mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
1112
0
        PushTag();
1113
0
        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
1114
0
        if (mesh->HasNormals()) {
1115
0
            mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
1116
0
        }
1117
0
        for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
1118
0
            if (mesh->HasTextureCoords(static_cast<unsigned int>(a))) {
1119
0
                mOutput << startstr
1120
0
                        << "<input semantic=\"TEXCOORD\" source=\"#"
1121
0
                        << geometryId
1122
0
                        << "-tex" << a << "\" "
1123
0
                        << "set=\"" << a << "\""
1124
0
                        << " />" << endstr;
1125
0
            }
1126
0
        }
1127
0
        for (size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) {
1128
0
            if (mesh->HasVertexColors(static_cast<unsigned int>(a)))
1129
0
                mOutput << startstr << "<input semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" "
1130
0
                        << "set=\"" << a << "\""
1131
0
                        << " />" << endstr;
1132
0
        }
1133
1134
0
        mOutput << startstr << "<p>";
1135
0
        for (size_t a = 0; a < mesh->mNumFaces; ++a) {
1136
0
            const aiFace &face = mesh->mFaces[a];
1137
0
            if (face.mNumIndices != 2) continue;
1138
0
            for (size_t b = 0; b < face.mNumIndices; ++b) {
1139
0
                mOutput << face.mIndices[b] << " ";
1140
0
            }
1141
0
        }
1142
0
        mOutput << "</p>" << endstr;
1143
0
        PopTag();
1144
0
        mOutput << startstr << "</lines>" << endstr;
1145
0
    }
1146
1147
    // triangle - don't use it, because compatibility problems
1148
1149
    // polygons
1150
0
    if (countPoly) {
1151
0
        mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
1152
0
        PushTag();
1153
0
        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
1154
0
        if (mesh->HasNormals()) {
1155
0
            mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
1156
0
        }
1157
0
        for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
1158
0
            if (mesh->HasTextureCoords(static_cast<unsigned int>(a)))
1159
0
                mOutput << startstr << "<input offset=\"0\" semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" "
1160
0
                        << "set=\"" << a << "\""
1161
0
                        << " />" << endstr;
1162
0
        }
1163
0
        for (size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) {
1164
0
            if (mesh->HasVertexColors(static_cast<unsigned int>(a)))
1165
0
                mOutput << startstr << "<input offset=\"0\" semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" "
1166
0
                        << "set=\"" << a << "\""
1167
0
                        << " />" << endstr;
1168
0
        }
1169
1170
0
        mOutput << startstr << "<vcount>";
1171
0
        for (size_t a = 0; a < mesh->mNumFaces; ++a) {
1172
0
            if (mesh->mFaces[a].mNumIndices < 3) continue;
1173
0
            mOutput << mesh->mFaces[a].mNumIndices << " ";
1174
0
        }
1175
0
        mOutput << "</vcount>" << endstr;
1176
1177
0
        mOutput << startstr << "<p>";
1178
0
        for (size_t a = 0; a < mesh->mNumFaces; ++a) {
1179
0
            const aiFace &face = mesh->mFaces[a];
1180
0
            if (face.mNumIndices < 3) continue;
1181
0
            for (size_t b = 0; b < face.mNumIndices; ++b) {
1182
0
                mOutput << face.mIndices[b] << " ";
1183
0
            }
1184
0
        }
1185
0
        mOutput << "</p>" << endstr;
1186
0
        PopTag();
1187
0
        mOutput << startstr << "</polylist>" << endstr;
1188
0
    }
1189
1190
    // closing tags
1191
0
    PopTag();
1192
0
    mOutput << startstr << "</mesh>" << endstr;
1193
0
    PopTag();
1194
0
    mOutput << startstr << "</geometry>" << endstr;
1195
0
}
1196
1197
// ------------------------------------------------------------------------------------------------
1198
// Writes a float array of the given type
1199
0
void ColladaExporter::WriteFloatArray(const std::string &pIdString, FloatDataType pType, const ai_real *pData, size_t pElementCount) {
1200
0
    size_t floatsPerElement = 0;
1201
0
    switch (pType) {
1202
0
    case FloatType_Vector:
1203
0
        floatsPerElement = 3;
1204
0
        break;
1205
0
    case FloatType_TexCoord2:
1206
0
        floatsPerElement = 2;
1207
0
        break;
1208
0
    case FloatType_TexCoord3:
1209
0
        floatsPerElement = 3;
1210
0
        break;
1211
0
    case FloatType_Color:
1212
0
        floatsPerElement = 3;
1213
0
        break;
1214
0
    case FloatType_Mat4x4:
1215
0
        floatsPerElement = 16;
1216
0
        break;
1217
0
    case FloatType_Weight:
1218
0
        floatsPerElement = 1;
1219
0
        break;
1220
0
    case FloatType_Time:
1221
0
        floatsPerElement = 1;
1222
0
        break;
1223
0
    default:
1224
0
        return;
1225
0
    }
1226
1227
0
    std::string arrayId = XMLIDEncode(pIdString) + "-array";
1228
1229
0
    mOutput << startstr << "<source id=\"" << XMLIDEncode(pIdString) << "\" name=\"" << XMLEscape(pIdString) << "\">" << endstr;
1230
0
    PushTag();
1231
1232
    // source array
1233
0
    mOutput << startstr << "<float_array id=\"" << arrayId << "\" count=\"" << pElementCount * floatsPerElement << "\"> ";
1234
0
    PushTag();
1235
1236
0
    if (pType == FloatType_TexCoord2) {
1237
0
        for (size_t a = 0; a < pElementCount; ++a) {
1238
0
            mOutput << pData[a * 3 + 0] << " ";
1239
0
            mOutput << pData[a * 3 + 1] << " ";
1240
0
        }
1241
0
    } else if (pType == FloatType_Color) {
1242
0
        for (size_t a = 0; a < pElementCount; ++a) {
1243
0
            mOutput << pData[a * 4 + 0] << " ";
1244
0
            mOutput << pData[a * 4 + 1] << " ";
1245
0
            mOutput << pData[a * 4 + 2] << " ";
1246
0
        }
1247
0
    } else {
1248
0
        for (size_t a = 0; a < pElementCount * floatsPerElement; ++a) {
1249
0
            mOutput << pData[a] << " ";
1250
0
        }
1251
0
    }
1252
0
    mOutput << "</float_array>" << endstr;
1253
0
    PopTag();
1254
1255
    // the usual Collada fun. Let's bloat it even more!
1256
0
    mOutput << startstr << "<technique_common>" << endstr;
1257
0
    PushTag();
1258
0
    mOutput << startstr << "<accessor count=\"" << pElementCount << "\" offset=\"0\" source=\"#" << arrayId << "\" stride=\"" << floatsPerElement << "\">" << endstr;
1259
0
    PushTag();
1260
1261
0
    switch (pType) {
1262
0
    case FloatType_Vector:
1263
0
        mOutput << startstr << "<param name=\"X\" type=\"float\" />" << endstr;
1264
0
        mOutput << startstr << "<param name=\"Y\" type=\"float\" />" << endstr;
1265
0
        mOutput << startstr << "<param name=\"Z\" type=\"float\" />" << endstr;
1266
0
        break;
1267
1268
0
    case FloatType_TexCoord2:
1269
0
        mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr;
1270
0
        mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr;
1271
0
        break;
1272
1273
0
    case FloatType_TexCoord3:
1274
0
        mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr;
1275
0
        mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr;
1276
0
        mOutput << startstr << "<param name=\"P\" type=\"float\" />" << endstr;
1277
0
        break;
1278
1279
0
    case FloatType_Color:
1280
0
        mOutput << startstr << "<param name=\"R\" type=\"float\" />" << endstr;
1281
0
        mOutput << startstr << "<param name=\"G\" type=\"float\" />" << endstr;
1282
0
        mOutput << startstr << "<param name=\"B\" type=\"float\" />" << endstr;
1283
0
        break;
1284
1285
0
    case FloatType_Mat4x4:
1286
0
        mOutput << startstr << "<param name=\"TRANSFORM\" type=\"float4x4\" />" << endstr;
1287
0
        break;
1288
1289
0
    case FloatType_Weight:
1290
0
        mOutput << startstr << "<param name=\"WEIGHT\" type=\"float\" />" << endstr;
1291
0
        break;
1292
1293
    // customized, add animation related
1294
0
    case FloatType_Time:
1295
0
        mOutput << startstr << "<param name=\"TIME\" type=\"float\" />" << endstr;
1296
0
        break;
1297
0
    }
1298
1299
0
    PopTag();
1300
0
    mOutput << startstr << "</accessor>" << endstr;
1301
0
    PopTag();
1302
0
    mOutput << startstr << "</technique_common>" << endstr;
1303
0
    PopTag();
1304
0
    mOutput << startstr << "</source>" << endstr;
1305
0
}
1306
1307
// ------------------------------------------------------------------------------------------------
1308
// Writes the scene library
1309
0
void ColladaExporter::WriteSceneLibrary() {
1310
    // Determine if we are using the aiScene root or our own
1311
0
    std::string sceneName("Scene");
1312
0
    if (mAdd_root_node) {
1313
0
        mSceneId = MakeUniqueId(mUniqueIds, sceneName, std::string());
1314
0
        mUniqueIds.insert(mSceneId);
1315
0
    } else {
1316
0
        mSceneId = GetNodeUniqueId(mScene->mRootNode);
1317
0
        sceneName = GetNodeName(mScene->mRootNode);
1318
0
    }
1319
1320
0
    mOutput << startstr << "<library_visual_scenes>" << endstr;
1321
0
    PushTag();
1322
0
    mOutput << startstr << "<visual_scene id=\"" + mSceneId + "\" name=\"" + sceneName + "\">" << endstr;
1323
0
    PushTag();
1324
1325
0
    if (mAdd_root_node) {
1326
        // Export the root node
1327
0
        WriteNode(mScene->mRootNode);
1328
0
    } 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
0
    PopTag();
1335
0
    mOutput << startstr << "</visual_scene>" << endstr;
1336
0
    PopTag();
1337
0
    mOutput << startstr << "</library_visual_scenes>" << endstr;
1338
0
}
1339
// ------------------------------------------------------------------------------------------------
1340
0
void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
1341
0
    const aiAnimation *anim = mScene->mAnimations[pIndex];
1342
0
    if (anim == nullptr) {
1343
0
        return;
1344
0
    }
1345
1346
0
    if (anim->mNumChannels == 0 && anim->mNumMeshChannels == 0 && anim->mNumMorphMeshChannels == 0) {
1347
0
        return;
1348
0
    }
1349
1350
0
    const std::string animationNameEscaped = GetObjectName(AiObjectType::Animation, pIndex);
1351
0
    const std::string idstrEscaped = GetObjectUniqueId(AiObjectType::Animation, pIndex);
1352
1353
0
    mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animationNameEscaped + "\">" << endstr;
1354
0
    PushTag();
1355
1356
0
    std::string cur_node_idstr;
1357
0
    for (size_t a = 0; a < anim->mNumChannels; ++a) {
1358
0
        const aiNodeAnim *nodeAnim = anim->mChannels[a];
1359
0
        if (nodeAnim == nullptr) {
1360
0
            continue;
1361
0
        }
1362
1363
        // sanity checks
1364
0
        if (nodeAnim->mNumPositionKeys != nodeAnim->mNumScalingKeys || nodeAnim->mNumPositionKeys != nodeAnim->mNumRotationKeys) {
1365
0
            continue;
1366
0
        }
1367
1368
0
        {
1369
0
            cur_node_idstr.clear();
1370
0
            cur_node_idstr += nodeAnim->mNodeName.data;
1371
0
            cur_node_idstr += std::string("_matrix-input");
1372
1373
0
            std::vector<ai_real> frames;
1374
0
            for (size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
1375
0
                frames.push_back(static_cast<ai_real>(nodeAnim->mPositionKeys[i].mTime));
1376
0
            }
1377
1378
0
            WriteFloatArray(cur_node_idstr, FloatType_Time, (const ai_real *)frames.data(), frames.size());
1379
0
            frames.clear();
1380
0
        }
1381
1382
0
        {
1383
0
            cur_node_idstr.clear();
1384
1385
0
            cur_node_idstr += nodeAnim->mNodeName.data;
1386
0
            cur_node_idstr += std::string("_matrix-output");
1387
1388
0
            std::vector<ai_real> keyframes;
1389
0
            keyframes.reserve(nodeAnim->mNumPositionKeys * 16);
1390
0
            for (size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
1391
0
                aiVector3D Scaling = nodeAnim->mScalingKeys[i].mValue;
1392
0
                aiMatrix4x4 ScalingM; // identity
1393
0
                ScalingM[0][0] = Scaling.x;
1394
0
                ScalingM[1][1] = Scaling.y;
1395
0
                ScalingM[2][2] = Scaling.z;
1396
1397
0
                aiQuaternion RotationQ = nodeAnim->mRotationKeys[i].mValue;
1398
0
                aiMatrix4x4 s = aiMatrix4x4(RotationQ.GetMatrix());
1399
0
                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
0
                aiVector3D Translation = nodeAnim->mPositionKeys[i].mValue;
1402
0
                aiMatrix4x4 TranslationM; // identity
1403
0
                TranslationM[0][3] = Translation.x;
1404
0
                TranslationM[1][3] = Translation.y;
1405
0
                TranslationM[2][3] = Translation.z;
1406
1407
                // Combine the above transformations
1408
0
                aiMatrix4x4 mat = TranslationM * RotationM * ScalingM;
1409
1410
0
                for (unsigned int j = 0; j < 4; ++j) {
1411
0
                    keyframes.insert(keyframes.end(), mat[j], mat[j] + 4);
1412
0
                }
1413
0
            }
1414
1415
0
            WriteFloatArray(cur_node_idstr, FloatType_Mat4x4, (const ai_real *)keyframes.data(), keyframes.size() / 16);
1416
0
        }
1417
1418
0
        {
1419
0
            std::vector<std::string> names;
1420
0
            for (size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
1421
0
                if (nodeAnim->mPreState == aiAnimBehaviour_DEFAULT || nodeAnim->mPreState == aiAnimBehaviour_LINEAR || nodeAnim->mPreState == aiAnimBehaviour_REPEAT) {
1422
0
                    names.emplace_back("LINEAR");
1423
0
                } else if (nodeAnim->mPostState == aiAnimBehaviour_CONSTANT) {
1424
0
                    names.emplace_back("STEP");
1425
0
                }
1426
0
            }
1427
1428
0
            const std::string cur_node_idstr2 = nodeAnim->mNodeName.data + std::string("_matrix-interpolation");
1429
0
            std::string arrayId = XMLIDEncode(cur_node_idstr2) + "-array";
1430
1431
0
            mOutput << startstr << "<source id=\"" << XMLIDEncode(cur_node_idstr2) << "\">" << endstr;
1432
0
            PushTag();
1433
1434
            // source array
1435
0
            mOutput << startstr << "<Name_array id=\"" << arrayId << "\" count=\"" << names.size() << "\"> ";
1436
0
            for (size_t aa = 0; aa < names.size(); ++aa) {
1437
0
                mOutput << names[aa] << " ";
1438
0
            }
1439
0
            mOutput << "</Name_array>" << endstr;
1440
1441
0
            mOutput << startstr << "<technique_common>" << endstr;
1442
0
            PushTag();
1443
1444
0
            mOutput << startstr << "<accessor source=\"#" << arrayId << "\" count=\"" << names.size() << "\" stride=\"" << 1 << "\">" << endstr;
1445
0
            PushTag();
1446
1447
0
            mOutput << startstr << "<param name=\"INTERPOLATION\" type=\"name\"></param>" << endstr;
1448
1449
0
            PopTag();
1450
0
            mOutput << startstr << "</accessor>" << endstr;
1451
1452
0
            PopTag();
1453
0
            mOutput << startstr << "</technique_common>" << endstr;
1454
1455
0
            PopTag();
1456
0
            mOutput << startstr << "</source>" << endstr;
1457
0
        }
1458
0
    }
1459
1460
0
    for (size_t a = 0; a < anim->mNumChannels; ++a) {
1461
0
        const aiNodeAnim *nodeAnim = anim->mChannels[a];
1462
0
        if (nodeAnim == nullptr) {
1463
0
            continue;
1464
0
        }
1465
1466
0
        {
1467
            // samplers
1468
0
            const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-sampler");
1469
0
            mOutput << startstr << "<sampler id=\"" << XMLIDEncode(node_idstr) << "\">" << endstr;
1470
0
            PushTag();
1471
1472
0
            mOutput << startstr << "<input semantic=\"INPUT\" source=\"#" << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-input")) << "\"/>" << endstr;
1473
0
            mOutput << startstr << "<input semantic=\"OUTPUT\" source=\"#" << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-output")) << "\"/>" << endstr;
1474
0
            mOutput << startstr << "<input semantic=\"INTERPOLATION\" source=\"#" << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-interpolation")) << "\"/>" << endstr;
1475
1476
0
            PopTag();
1477
0
            mOutput << startstr << "</sampler>" << endstr;
1478
0
        }
1479
0
    }
1480
1481
0
    for (size_t a = 0; a < anim->mNumChannels; ++a) {
1482
0
        const aiNodeAnim *nodeAnim = anim->mChannels[a];
1483
0
        if (nodeAnim == nullptr) {
1484
0
            continue;
1485
0
        }
1486
1487
0
        {
1488
            // channels
1489
0
            mOutput << startstr
1490
0
                    << "<channel source=\"#"
1491
0
                    << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-sampler"))
1492
0
                    << "\" target=\""
1493
0
                    << XMLIDEncode(nodeAnim->mNodeName.data)
1494
0
                    << "/matrix\"/>"
1495
0
                    << endstr;
1496
0
        }
1497
0
    }
1498
1499
0
    PopTag();
1500
0
    mOutput << startstr << "</animation>" << endstr;
1501
0
}
1502
1503
// ------------------------------------------------------------------------------------------------
1504
0
void ColladaExporter::WriteAnimationsLibrary() {
1505
0
    if (mScene->mNumAnimations == 0) {
1506
0
        return;
1507
0
    }
1508
1509
0
    mOutput << startstr << "<library_animations>" << endstr;
1510
0
    PushTag();
1511
1512
    // start recursive write at the root node
1513
0
    for (size_t a = 0; a < mScene->mNumAnimations; ++a) {
1514
0
        WriteAnimationLibrary(a);
1515
0
    }
1516
1517
0
    PopTag();
1518
0
    mOutput << startstr << "</library_animations>" << endstr;
1519
0
}
1520
1521
// ------------------------------------------------------------------------------------------------
1522
// Recursively writes the given node
1523
0
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
0
    const char *node_type;
1528
0
    bool is_joint, is_skeleton_root = false;
1529
0
    if (pNode->mName.length == 0 || nullptr == mScene->findBone(pNode->mName)) {
1530
0
        node_type = "NODE";
1531
0
        is_joint = false;
1532
0
    } else {
1533
0
        node_type = "JOINT";
1534
0
        is_joint = true;
1535
0
        if (!pNode->mParent || nullptr == mScene->findBone(pNode->mParent->mName)) {
1536
0
            is_skeleton_root = true;
1537
0
        }
1538
0
    }
1539
1540
0
    const std::string node_id = GetNodeUniqueId(pNode);
1541
0
    const std::string node_name = GetNodeName(pNode);
1542
0
    mOutput << startstr << "<node ";
1543
0
    if (is_skeleton_root) {
1544
0
        mFoundSkeletonRootNodeID = node_id; // For now, only support one skeleton in a scene.
1545
0
    }
1546
0
    mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id + "\" " : "");
1547
0
    mOutput << "name=\"" << node_name
1548
0
            << "\" type=\"" << node_type
1549
0
            << "\">" << endstr;
1550
0
    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
0
    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
0
    for (size_t i = 0; i < mScene->mNumCameras; i++) {
1561
0
        if (mScene->mCameras[i]->mName == pNode->mName) {
1562
0
            aiMatrix4x4 sourceView;
1563
0
            mScene->mCameras[i]->GetCameraMatrix(sourceView);
1564
1565
0
            aiMatrix4x4 colladaView;
1566
0
            colladaView.a1 = colladaView.c3 = -1; // move into -z space.
1567
0
            mat *= (sourceView * colladaView);
1568
0
            break;
1569
0
        }
1570
0
    }
1571
1572
    // customized, sid should be 'matrix' to match with loader code.
1573
0
    mOutput << startstr << "<matrix sid=\"matrix\">";
1574
1575
0
    mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << " ";
1576
0
    mOutput << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << " ";
1577
0
    mOutput << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << " ";
1578
0
    mOutput << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4;
1579
0
    mOutput << "</matrix>" << endstr;
1580
1581
0
    if (pNode->mNumMeshes == 0) {
1582
        //check if it is a camera node
1583
0
        for (size_t i = 0; i < mScene->mNumCameras; i++) {
1584
0
            if (mScene->mCameras[i]->mName == pNode->mName) {
1585
0
                mOutput << startstr << "<instance_camera url=\"#" << GetObjectUniqueId(AiObjectType::Camera, i) << "\"/>" << endstr;
1586
0
                break;
1587
0
            }
1588
0
        }
1589
        //check if it is a light node
1590
0
        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
0
    } else
1597
        // instance every geometry
1598
0
        for (size_t a = 0; a < pNode->mNumMeshes; ++a) {
1599
0
            const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[a]];
1600
            // do not instantiate mesh if empty. I wonder how this could happen
1601
0
            if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0)
1602
0
                continue;
1603
1604
0
            const std::string meshId = GetObjectUniqueId(AiObjectType::Mesh, pNode->mMeshes[a]);
1605
1606
0
            if (mesh->mNumBones == 0) {
1607
0
                mOutput << startstr << "<instance_geometry url=\"#" << meshId << "\">" << endstr;
1608
0
                PushTag();
1609
0
            } else {
1610
0
                mOutput << startstr
1611
0
                        << "<instance_controller url=\"#" << meshId << "-skin\">"
1612
0
                        << endstr;
1613
0
                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
0
                const aiNode *skeletonRootBoneNode = findSkeletonRootNode(mScene, mesh);
1618
0
                if (skeletonRootBoneNode) {
1619
0
                    mFoundSkeletonRootNodeID = GetNodeUniqueId(skeletonRootBoneNode);
1620
0
                }
1621
0
                mOutput << startstr << "<skeleton>#" << mFoundSkeletonRootNodeID << "</skeleton>" << endstr;
1622
0
            }
1623
0
            mOutput << startstr << "<bind_material>" << endstr;
1624
0
            PushTag();
1625
0
            mOutput << startstr << "<technique_common>" << endstr;
1626
0
            PushTag();
1627
0
            mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << GetObjectUniqueId(AiObjectType::Material, mesh->mMaterialIndex) << "\">" << endstr;
1628
0
            PushTag();
1629
0
            for (size_t aa = 0; aa < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++aa) {
1630
0
                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
0
                    mOutput << startstr << "<bind_vertex_input semantic=\"CHANNEL" << aa << "\" input_semantic=\"TEXCOORD\" input_set=\"" << aa << "\"/>" << endstr;
1635
0
            }
1636
0
            PopTag();
1637
0
            mOutput << startstr << "</instance_material>" << endstr;
1638
0
            PopTag();
1639
0
            mOutput << startstr << "</technique_common>" << endstr;
1640
0
            PopTag();
1641
0
            mOutput << startstr << "</bind_material>" << endstr;
1642
1643
0
            PopTag();
1644
0
            if (mesh->mNumBones == 0)
1645
0
                mOutput << startstr << "</instance_geometry>" << endstr;
1646
0
            else
1647
0
                mOutput << startstr << "</instance_controller>" << endstr;
1648
0
        }
1649
1650
    // recurse into subnodes
1651
0
    for (size_t a = 0; a < pNode->mNumChildren; ++a) {
1652
0
        WriteNode(pNode->mChildren[a]);
1653
0
    }
1654
1655
0
    PopTag();
1656
0
    mOutput << startstr << "</node>" << endstr;
1657
0
}
1658
1659
0
void ColladaExporter::CreateNodeIds(const aiNode *node) {
1660
0
    GetNodeUniqueId(node);
1661
0
    for (size_t a = 0; a < node->mNumChildren; ++a)
1662
0
        CreateNodeIds(node->mChildren[a]);
1663
0
}
1664
1665
0
std::string ColladaExporter::GetNodeUniqueId(const aiNode *node) {
1666
    // Use the pointer as the key. This is safe because the scene is immutable.
1667
0
    auto idIt = mNodeIdMap.find(node);
1668
0
    if (idIt != mNodeIdMap.cend()) {
1669
0
        return idIt->second;
1670
0
    }
1671
1672
    // Prefer the requested Collada Id if extant
1673
0
    std::string idStr;
1674
0
    aiString origId;
1675
0
    if (node->mMetaData && node->mMetaData->Get(AI_METADATA_COLLADA_ID, origId)) {
1676
0
        idStr = origId.C_Str();
1677
0
    } else {
1678
0
        idStr = node->mName.C_Str();
1679
0
    }
1680
    // Make sure the requested id is valid
1681
0
    if (idStr.empty()) {
1682
0
        idStr = "node";
1683
0
    } else {
1684
0
        idStr = XMLIDEncode(idStr);
1685
0
    }
1686
1687
    // Ensure it's unique
1688
0
    idStr = MakeUniqueId(mUniqueIds, idStr, std::string());
1689
0
    mUniqueIds.insert(idStr);
1690
0
    mNodeIdMap.insert(std::make_pair(node, idStr));
1691
1692
0
    return idStr;
1693
0
}
1694
1695
0
std::string ColladaExporter::GetNodeName(const aiNode *node) {
1696
0
    if (node == nullptr) {
1697
0
        return std::string();
1698
0
    }
1699
0
    return XMLEscape(node->mName.C_Str());
1700
0
}
1701
1702
0
std::string ColladaExporter::GetBoneUniqueId(const aiBone *bone) {
1703
    // Find the Node that is this Bone
1704
0
    const aiNode *boneNode = mScene->mRootNode->findBoneNode(bone);
1705
0
    if (boneNode == nullptr) {
1706
0
        return std::string();
1707
0
    }
1708
1709
0
    return GetNodeUniqueId(boneNode);
1710
0
}
1711
1712
0
std::string ColladaExporter::GetObjectUniqueId(AiObjectType type, size_t pIndex) {
1713
0
    auto idIt = GetObjectIdMap(type).find(pIndex);
1714
0
    if (idIt != GetObjectIdMap(type).cend()) {
1715
0
        return idIt->second;
1716
0
    }
1717
1718
    // Not seen this object before, create and add
1719
0
    NameIdPair result = AddObjectIndexToMaps(type, pIndex);
1720
0
    return result.second;
1721
0
}
1722
1723
0
std::string ColladaExporter::GetObjectName(AiObjectType type, size_t pIndex) {
1724
0
    auto objectName = GetObjectNameMap(type).find(pIndex);
1725
0
    if (objectName != GetObjectNameMap(type).cend()) {
1726
0
        return objectName->second;
1727
0
    }
1728
1729
    // Not seen this object before, create and add
1730
0
    NameIdPair result = AddObjectIndexToMaps(type, pIndex);
1731
0
    return result.first;
1732
0
}
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
0
ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType type, size_t index) {
1740
1741
0
    std::string name;
1742
0
    std::string idStr;
1743
0
    std::string idPostfix;
1744
1745
    // Get the name and id postfix
1746
0
    switch (type) {
1747
0
    case AiObjectType::Mesh:
1748
0
        name = mScene->mMeshes[index]->mName.C_Str();
1749
0
        break;
1750
0
    case AiObjectType::Material:
1751
0
        name = mScene->mMaterials[index]->GetName().C_Str();
1752
0
        break;
1753
0
    case AiObjectType::Animation:
1754
0
        name = mScene->mAnimations[index]->mName.C_Str();
1755
0
        break;
1756
0
    case AiObjectType::Light:
1757
0
        name = mScene->mLights[index]->mName.C_Str();
1758
0
        idPostfix = "-light";
1759
0
        break;
1760
0
    case AiObjectType::Camera:
1761
0
        name = mScene->mCameras[index]->mName.C_Str();
1762
0
        idPostfix = "-camera";
1763
0
        break;
1764
0
    case AiObjectType::Count:
1765
0
        throw std::logic_error("ColladaExporter::AiObjectType::Count is not an object type");
1766
0
    }
1767
1768
0
    if (name.empty()) {
1769
        // Default ids if empty name
1770
0
        switch (type) {
1771
0
        case AiObjectType::Mesh: idStr = std::string("mesh_"); break;
1772
0
        case AiObjectType::Material: idStr = std::string("material_"); break; // This one should never happen
1773
0
        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
0
        }
1778
0
        idStr.append(ai_to_string(index));
1779
0
    } else {
1780
0
        idStr = XMLIDEncode(name);
1781
0
    }
1782
1783
0
    if (!name.empty()) {
1784
0
        name = XMLEscape(name);
1785
0
    }
1786
1787
0
    idStr = MakeUniqueId(mUniqueIds, idStr, idPostfix);
1788
1789
    // Add to maps
1790
0
    mUniqueIds.insert(idStr);
1791
0
    GetObjectIdMap(type).insert(std::make_pair(index, idStr));
1792
0
    GetObjectNameMap(type).insert(std::make_pair(index, name));
1793
1794
0
    return std::make_pair(name, idStr);
1795
0
}
1796
1797
} // end of namespace Assimp
1798
1799
#endif // ASSIMP_BUILD_NO_COLLADA_EXPORTER
1800
#endif // ASSIMP_BUILD_NO_EXPORT