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