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