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