1 /*
2 Open Asset Import Library (assimp)
3 ----------------------------------------------------------------------
4 
5 Copyright (c) 2006-2015, assimp team
6 All rights reserved.
7 
8 Redistribution and use of this software in source and binary forms,
9 with or without modification, are permitted provided that the
10 following conditions are met:
11 
12 * Redistributions of source code must retain the above
13   copyright notice, this list of conditions and the
14   following disclaimer.
15 
16 * Redistributions in binary form must reproduce the above
17   copyright notice, this list of conditions and the
18   following disclaimer in the documentation and/or other
19   materials provided with the distribution.
20 
21 * Neither the name of the assimp team, nor the names of its
22   contributors may be used to endorse or promote products
23   derived from this software without specific prior
24   written permission of the assimp team.
25 
26 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 
38 ----------------------------------------------------------------------
39 */
40 
41 
42 
43 #ifndef ASSIMP_BUILD_NO_EXPORT
44 #ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER
45 #include "ColladaExporter.h"
46 
47 #include "Bitmap.h"
48 #include "fast_atof.h"
49 #include "SceneCombiner.h"
50 #include "DefaultIOSystem.h"
51 #include "XMLTools.h"
52 #include "../include/assimp/IOSystem.hpp"
53 #include "../include/assimp/Exporter.hpp"
54 #include "../include/assimp/scene.h"
55 
56 #include "Exceptional.h"
57 
58 #include <boost/scoped_ptr.hpp>
59 #include <ctime>
60 #include <set>
61 
62 using namespace Assimp;
63 
64 namespace Assimp
65 {
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 * pProperties)69 void ExportSceneCollada(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties)
70 {
71     std::string path = DefaultIOSystem::absolutePath(std::string(pFile));
72     std::string file = DefaultIOSystem::completeBaseName(std::string(pFile));
73 
74     // invoke the exporter
75     ColladaExporter iDoTheExportThing( pScene, pIOSystem, path, file);
76 
77     // we're still here - export successfully completed. Write result to the given IOSYstem
78     boost::scoped_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
79     if(outfile == NULL) {
80         throw DeadlyExportError("could not open output .dae file: " + std::string(pFile));
81     }
82 
83     // XXX maybe use a small wrapper around IOStream that behaves like std::stringstream in order to avoid the extra copy.
84     outfile->Write( iDoTheExportThing.mOutput.str().c_str(), static_cast<size_t>(iDoTheExportThing.mOutput.tellp()),1);
85 }
86 
87 } // end of namespace Assimp
88 
89 
90 
91 // ------------------------------------------------------------------------------------------------
92 // Constructor for a specific scene to export
ColladaExporter(const aiScene * pScene,IOSystem * pIOSystem,const std::string & path,const std::string & file)93 ColladaExporter::ColladaExporter( const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file) : mIOSystem(pIOSystem), mPath(path), mFile(file)
94 {
95     // make sure that all formatting happens using the standard, C locale and not the user's current locale
96     mOutput.imbue( std::locale("C") );
97 
98     mScene = pScene;
99     mSceneOwned = false;
100 
101     // set up strings
102     endstr = "\n";
103 
104     // start writing
105     WriteFile();
106 }
107 
108 // ------------------------------------------------------------------------------------------------
109 // Destructor
~ColladaExporter()110 ColladaExporter::~ColladaExporter()
111 {
112     if(mSceneOwned) {
113         delete mScene;
114     }
115 }
116 
117 // ------------------------------------------------------------------------------------------------
118 // Starts writing the contents
WriteFile()119 void ColladaExporter::WriteFile()
120 {
121     // write the DTD
122     mOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>" << endstr;
123     // COLLADA element start
124     mOutput << "<COLLADA xmlns=\"http://www.collada.org/2005/11/COLLADASchema\" version=\"1.4.1\">" << endstr;
125     PushTag();
126 
127     WriteTextures();
128     WriteHeader();
129 
130     WriteCamerasLibrary();
131     WriteLightsLibrary();
132     WriteMaterials();
133     WriteGeometryLibrary();
134 
135     WriteSceneLibrary();
136 
137     // useless Collada fu at the end, just in case we haven't had enough indirections, yet.
138     mOutput << startstr << "<scene>" << endstr;
139     PushTag();
140     mOutput << startstr << "<instance_visual_scene url=\"#" + XMLEscape(mScene->mRootNode->mName.C_Str()) + "\" />" << endstr;
141     PopTag();
142     mOutput << startstr << "</scene>" << endstr;
143     PopTag();
144     mOutput << "</COLLADA>" << endstr;
145 }
146 
147 // ------------------------------------------------------------------------------------------------
148 // Writes the asset header
WriteHeader()149 void ColladaExporter::WriteHeader()
150 {
151     static const float epsilon = 0.00001f;
152     static const aiQuaternion x_rot(aiMatrix3x3(
153         0, -1,  0,
154         1,  0,  0,
155         0,  0,  1));
156     static const aiQuaternion y_rot(aiMatrix3x3(
157         1,  0,  0,
158         0,  1,  0,
159         0,  0,  1));
160     static const aiQuaternion z_rot(aiMatrix3x3(
161         1,  0,  0,
162         0,  0,  1,
163         0, -1,  0));
164 
165     static const unsigned int date_nb_chars = 20;
166     char date_str[date_nb_chars];
167     std::time_t date = std::time(NULL);
168     std::strftime(date_str, date_nb_chars, "%Y-%m-%dT%H:%M:%S", std::localtime(&date));
169 
170     aiVector3D scaling;
171     aiQuaternion rotation;
172     aiVector3D position;
173     mScene->mRootNode->mTransformation.Decompose(scaling, rotation, position);
174     rotation.Normalize();
175 
176     bool add_root_node = false;
177 
178     float scale = 1.0;
179     if(std::abs(scaling.x - scaling.y) <= epsilon && std::abs(scaling.x - scaling.z) <= epsilon && std::abs(scaling.y - scaling.z) <= epsilon) {
180         scale = (float) ((((double) scaling.x) + ((double) scaling.y) + ((double) scaling.z)) / 3.0);
181     } else {
182         add_root_node = true;
183     }
184 
185     std::string up_axis = "Y_UP";
186     if(rotation.Equal(x_rot, epsilon)) {
187         up_axis = "X_UP";
188     } else if(rotation.Equal(y_rot, epsilon)) {
189         up_axis = "Y_UP";
190     } else if(rotation.Equal(z_rot, epsilon)) {
191         up_axis = "Z_UP";
192     } else {
193         add_root_node = true;
194     }
195 
196     if(! position.Equal(aiVector3D(0, 0, 0))) {
197         add_root_node = true;
198     }
199 
200     if(mScene->mRootNode->mNumChildren == 0) {
201         add_root_node = true;
202     }
203 
204     if(add_root_node) {
205         aiScene* scene;
206         SceneCombiner::CopyScene(&scene, mScene);
207 
208         aiNode* root = new aiNode("Scene");
209 
210         root->mNumChildren = 1;
211         root->mChildren = new aiNode*[root->mNumChildren];
212 
213         root->mChildren[0] = scene->mRootNode;
214         scene->mRootNode->mParent = root;
215         scene->mRootNode = root;
216 
217         mScene = scene;
218         mSceneOwned = true;
219 
220         up_axis = "Y_UP";
221         scale = 1.0;
222     }
223 
224     mOutput << startstr << "<asset>" << endstr;
225     PushTag();
226     mOutput << startstr << "<contributor>" << endstr;
227     PushTag();
228 
229     aiMetadata* meta = mScene->mRootNode->mMetaData;
230     aiString value;
231     if (!meta || !meta->Get("Author", value))
232         mOutput << startstr << "<author>" << "Assimp" << "</author>" << endstr;
233     else
234         mOutput << startstr << "<author>" << XMLEscape(value.C_Str()) << "</author>" << endstr;
235 
236     if (!meta || !meta->Get("AuthoringTool", value))
237         mOutput << startstr << "<authoring_tool>" << "Assimp Exporter" << "</authoring_tool>" << endstr;
238     else
239         mOutput << startstr << "<authoring_tool>" << XMLEscape(value.C_Str()) << "</authoring_tool>" << endstr;
240 
241     //mOutput << startstr << "<author>" << mScene->author.C_Str() << "</author>" << endstr;
242     //mOutput << startstr << "<authoring_tool>" << mScene->authoringTool.C_Str() << "</authoring_tool>" << endstr;
243 
244     PopTag();
245     mOutput << startstr << "</contributor>" << endstr;
246     mOutput << startstr << "<created>" << date_str << "</created>" << endstr;
247     mOutput << startstr << "<modified>" << date_str << "</modified>" << endstr;
248     mOutput << startstr << "<unit name=\"meter\" meter=\"" << scale << "\" />" << endstr;
249     mOutput << startstr << "<up_axis>" << up_axis << "</up_axis>" << endstr;
250     PopTag();
251     mOutput << startstr << "</asset>" << endstr;
252 }
253 
254 // ------------------------------------------------------------------------------------------------
255 // Write the embedded textures
WriteTextures()256 void ColladaExporter::WriteTextures() {
257     static const unsigned int buffer_size = 1024;
258     char str[buffer_size];
259 
260     if(mScene->HasTextures()) {
261         for(unsigned int i = 0; i < mScene->mNumTextures; i++) {
262             // It would be great to be able to create a directory in portable standard C++, but it's not the case,
263             // so we just write the textures in the current directory.
264 
265             aiTexture* texture = mScene->mTextures[i];
266 
267             ASSIMP_itoa10(str, buffer_size, i + 1);
268 
269             std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char*) texture->achFormatHint);
270 
271             boost::scoped_ptr<IOStream> outfile(mIOSystem->Open(mPath + name, "wb"));
272             if(outfile == NULL) {
273                 throw DeadlyExportError("could not open output texture file: " + mPath + name);
274             }
275 
276             if(texture->mHeight == 0) {
277                 outfile->Write((void*) texture->pcData, texture->mWidth, 1);
278             } else {
279                 Bitmap::Save(texture, outfile.get());
280             }
281 
282             outfile->Flush();
283 
284             textures.insert(std::make_pair(i, name));
285         }
286     }
287 }
288 
289 // ------------------------------------------------------------------------------------------------
290 // Write the embedded textures
WriteCamerasLibrary()291 void ColladaExporter::WriteCamerasLibrary() {
292     if(mScene->HasCameras()) {
293 
294         mOutput << startstr << "<library_cameras>" << endstr;
295         PushTag();
296 
297         for( size_t a = 0; a < mScene->mNumCameras; ++a)
298             WriteCamera( a);
299 
300         PopTag();
301         mOutput << startstr << "</library_cameras>" << endstr;
302 
303     }
304 }
305 
WriteCamera(size_t pIndex)306 void ColladaExporter::WriteCamera(size_t pIndex){
307 
308     const aiCamera *cam = mScene->mCameras[pIndex];
309     const std::string idstrEscaped = XMLEscape(cam->mName.C_Str());
310 
311     mOutput << startstr << "<camera id=\"" << idstrEscaped << "-camera\" name=\"" << idstrEscaped << "_name\" >" << endstr;
312     PushTag();
313     mOutput << startstr << "<optics>" << endstr;
314     PushTag();
315     mOutput << startstr << "<technique_common>" << endstr;
316     PushTag();
317     //assimp doesn't support the import of orthographic cameras! se we write
318     //always perspective
319     mOutput << startstr << "<perspective>" << endstr;
320     PushTag();
321     mOutput << startstr << "<xfov sid=\"xfov\">"<<
322                                 AI_RAD_TO_DEG(cam->mHorizontalFOV)
323                         <<"</xfov>" << endstr;
324     mOutput << startstr << "<aspect_ratio>"
325                         <<      cam->mAspect
326                         << "</aspect_ratio>" << endstr;
327     mOutput << startstr << "<znear sid=\"znear\">"
328                         <<      cam->mClipPlaneNear
329                         <<  "</znear>" << endstr;
330     mOutput << startstr << "<zfar sid=\"zfar\">"
331                         <<      cam->mClipPlaneFar
332                         << "</zfar>" << endstr;
333     PopTag();
334     mOutput << startstr << "</perspective>" << endstr;
335     PopTag();
336     mOutput << startstr << "</technique_common>" << endstr;
337     PopTag();
338     mOutput << startstr << "</optics>" << endstr;
339     PopTag();
340     mOutput << startstr << "</camera>" << endstr;
341 
342 }
343 
344 
345 // ------------------------------------------------------------------------------------------------
346 // Write the embedded textures
WriteLightsLibrary()347 void ColladaExporter::WriteLightsLibrary() {
348     if(mScene->HasLights()) {
349 
350         mOutput << startstr << "<library_lights>" << endstr;
351         PushTag();
352 
353         for( size_t a = 0; a < mScene->mNumLights; ++a)
354             WriteLight( a);
355 
356         PopTag();
357         mOutput << startstr << "</library_lights>" << endstr;
358 
359     }
360 }
361 
WriteLight(size_t pIndex)362 void ColladaExporter::WriteLight(size_t pIndex){
363 
364     const aiLight *light = mScene->mLights[pIndex];
365     const std::string idstrEscaped = XMLEscape(light->mName.C_Str());
366 
367     mOutput << startstr << "<light id=\"" << idstrEscaped << "-light\" name=\""
368             << idstrEscaped << "_name\" >" << endstr;
369     PushTag();
370     mOutput << startstr << "<technique_common>" << endstr;
371     PushTag();
372     switch(light->mType){
373         case aiLightSource_AMBIENT:
374             WriteAmbienttLight(light);
375             break;
376         case aiLightSource_DIRECTIONAL:
377             WriteDirectionalLight(light);
378             break;
379         case aiLightSource_POINT:
380             WritePointLight(light);
381             break;
382         case aiLightSource_SPOT:
383             WriteSpotLight(light);
384             break;
385         case aiLightSource_UNDEFINED:
386         case _aiLightSource_Force32Bit:
387             break;
388     }
389     PopTag();
390     mOutput << startstr << "</technique_common>" << endstr;
391 
392     PopTag();
393     mOutput << startstr << "</light>" << endstr;
394 
395 }
396 
WritePointLight(const aiLight * const light)397 void ColladaExporter::WritePointLight(const aiLight *const light){
398     const aiColor3D &color=  light->mColorDiffuse;
399     mOutput << startstr << "<point>" << endstr;
400     PushTag();
401     mOutput << startstr << "<color sid=\"color\">"
402                             << color.r<<" "<<color.g<<" "<<color.b
403                         <<"</color>" << endstr;
404     mOutput << startstr << "<constant_attenuation>"
405                             << light->mAttenuationConstant
406                         <<"</constant_attenuation>" << endstr;
407     mOutput << startstr << "<linear_attenuation>"
408                             << light->mAttenuationLinear
409                         <<"</linear_attenuation>" << endstr;
410     mOutput << startstr << "<quadratic_attenuation>"
411                             << light->mAttenuationQuadratic
412                         <<"</quadratic_attenuation>" << endstr;
413 
414     PopTag();
415     mOutput << startstr << "</point>" << endstr;
416 
417 }
WriteDirectionalLight(const aiLight * const light)418 void ColladaExporter::WriteDirectionalLight(const aiLight *const light){
419     const aiColor3D &color=  light->mColorDiffuse;
420     mOutput << startstr << "<directional>" << endstr;
421     PushTag();
422     mOutput << startstr << "<color sid=\"color\">"
423                             << color.r<<" "<<color.g<<" "<<color.b
424                         <<"</color>" << endstr;
425 
426     PopTag();
427     mOutput << startstr << "</directional>" << endstr;
428 
429 }
WriteSpotLight(const aiLight * const light)430 void ColladaExporter::WriteSpotLight(const aiLight *const light){
431 
432     const aiColor3D &color=  light->mColorDiffuse;
433     mOutput << startstr << "<spot>" << endstr;
434     PushTag();
435     mOutput << startstr << "<color sid=\"color\">"
436                             << color.r<<" "<<color.g<<" "<<color.b
437                         <<"</color>" << endstr;
438     mOutput << startstr << "<constant_attenuation>"
439                                 << light->mAttenuationConstant
440                             <<"</constant_attenuation>" << endstr;
441     mOutput << startstr << "<linear_attenuation>"
442                             << light->mAttenuationLinear
443                         <<"</linear_attenuation>" << endstr;
444     mOutput << startstr << "<quadratic_attenuation>"
445                             << light->mAttenuationQuadratic
446                         <<"</quadratic_attenuation>" << endstr;
447     /*
448     out->mAngleOuterCone = AI_DEG_TO_RAD (std::acos(std::pow(0.1f,1.f/srcLight->mFalloffExponent))+
449                             srcLight->mFalloffAngle);
450     */
451 
452     const float fallOffAngle = AI_RAD_TO_DEG(light->mAngleInnerCone);
453     mOutput << startstr <<"<falloff_angle sid=\"fall_off_angle\">"
454                                 << fallOffAngle
455                         <<"</falloff_angle>" << endstr;
456     double temp = light->mAngleOuterCone-light->mAngleInnerCone;
457 
458     temp = std::cos(temp);
459     temp = std::log(temp)/std::log(0.1);
460     temp = 1/temp;
461     mOutput << startstr << "<falloff_exponent sid=\"fall_off_exponent\">"
462                             << temp
463                         <<"</falloff_exponent>" << endstr;
464 
465 
466     PopTag();
467     mOutput << startstr << "</spot>" << endstr;
468 
469 }
470 
WriteAmbienttLight(const aiLight * const light)471 void ColladaExporter::WriteAmbienttLight(const aiLight *const light){
472 
473     const aiColor3D &color=  light->mColorAmbient;
474     mOutput << startstr << "<ambient>" << endstr;
475     PushTag();
476     mOutput << startstr << "<color sid=\"color\">"
477                             << color.r<<" "<<color.g<<" "<<color.b
478                         <<"</color>" << endstr;
479 
480     PopTag();
481     mOutput << startstr << "</ambient>" << endstr;
482 }
483 
484 // ------------------------------------------------------------------------------------------------
485 // 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)486 void ColladaExporter::ReadMaterialSurface( Surface& poSurface, const aiMaterial* pSrcMat, aiTextureType pTexture, const char* pKey, size_t pType, size_t pIndex)
487 {
488   if( pSrcMat->GetTextureCount( pTexture) > 0 )
489   {
490     aiString texfile;
491     unsigned int uvChannel = 0;
492     pSrcMat->GetTexture( pTexture, 0, &texfile, NULL, &uvChannel);
493 
494     std::string index_str(texfile.C_Str());
495 
496     if(index_str.size() != 0 && index_str[0] == '*')
497     {
498         unsigned int index;
499 
500         index_str = index_str.substr(1, std::string::npos);
501 
502         try {
503             index = (unsigned int) strtoul10_64(index_str.c_str());
504         } catch(std::exception& error) {
505             throw DeadlyExportError(error.what());
506         }
507 
508         std::map<unsigned int, std::string>::const_iterator name = textures.find(index);
509 
510         if(name != textures.end()) {
511             poSurface.texture = name->second;
512         } else {
513             throw DeadlyExportError("could not find embedded texture at index " + index_str);
514         }
515     } else
516     {
517         poSurface.texture = texfile.C_Str();
518     }
519 
520     poSurface.channel = uvChannel;
521     poSurface.exist = true;
522   } else
523   {
524     if( pKey )
525       poSurface.exist = pSrcMat->Get( pKey, pType, pIndex, poSurface.color) == aiReturn_SUCCESS;
526   }
527 }
528 
529 // ------------------------------------------------------------------------------------------------
530 // Writes an image entry for the given surface
WriteImageEntry(const Surface & pSurface,const std::string & pNameAdd)531 void ColladaExporter::WriteImageEntry( const Surface& pSurface, const std::string& pNameAdd)
532 {
533   if( !pSurface.texture.empty() )
534   {
535     mOutput << startstr << "<image id=\"" << XMLEscape(pNameAdd) << "\">" << endstr;
536     PushTag();
537     mOutput << startstr << "<init_from>";
538 
539     // URL encode image file name first, then XML encode on top
540     std::stringstream imageUrlEncoded;
541     for( std::string::const_iterator it = pSurface.texture.begin(); it != pSurface.texture.end(); ++it )
542     {
543       if( isalnum( *it) || *it == '_' || *it == '.' || *it == '/' || *it == '\\' )
544         imageUrlEncoded << *it;
545       else
546         imageUrlEncoded << '%' << std::hex << size_t( (unsigned char) *it) << std::dec;
547     }
548     mOutput << XMLEscape(imageUrlEncoded.str());
549     mOutput << "</init_from>" << endstr;
550     PopTag();
551     mOutput << startstr << "</image>" << endstr;
552   }
553 }
554 
555 // ------------------------------------------------------------------------------------------------
556 // Writes a color-or-texture entry into an effect definition
WriteTextureColorEntry(const Surface & pSurface,const std::string & pTypeName,const std::string & pImageName)557 void ColladaExporter::WriteTextureColorEntry( const Surface& pSurface, const std::string& pTypeName, const std::string& pImageName)
558 {
559   if(pSurface.exist) {
560     mOutput << startstr << "<" << pTypeName << ">" << endstr;
561     PushTag();
562     if( pSurface.texture.empty() )
563     {
564       mOutput << startstr << "<color sid=\"" << pTypeName << "\">" << pSurface.color.r << "   " << pSurface.color.g << "   " << pSurface.color.b << "   " << pSurface.color.a << "</color>" << endstr;
565     }
566     else
567     {
568       mOutput << startstr << "<texture texture=\"" << XMLEscape(pImageName) << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
569     }
570     PopTag();
571     mOutput << startstr << "</" << pTypeName << ">" << endstr;
572   }
573 }
574 
575 // ------------------------------------------------------------------------------------------------
576 // Writes the two parameters necessary for referencing a texture in an effect entry
WriteTextureParamEntry(const Surface & pSurface,const std::string & pTypeName,const std::string & pMatName)577 void ColladaExporter::WriteTextureParamEntry( const Surface& pSurface, const std::string& pTypeName, const std::string& pMatName)
578 {
579   // if surface is a texture, write out the sampler and the surface parameters necessary to reference the texture
580   if( !pSurface.texture.empty() )
581   {
582     mOutput << startstr << "<newparam sid=\"" << XMLEscape(pMatName) << "-" << pTypeName << "-surface\">" << endstr;
583     PushTag();
584     mOutput << startstr << "<surface type=\"2D\">" << endstr;
585     PushTag();
586     mOutput << startstr << "<init_from>" << XMLEscape(pMatName) << "-" << pTypeName << "-image</init_from>" << endstr;
587     PopTag();
588     mOutput << startstr << "</surface>" << endstr;
589     PopTag();
590     mOutput << startstr << "</newparam>" << endstr;
591 
592     mOutput << startstr << "<newparam sid=\"" << XMLEscape(pMatName) << "-" << pTypeName << "-sampler\">" << endstr;
593     PushTag();
594     mOutput << startstr << "<sampler2D>" << endstr;
595     PushTag();
596     mOutput << startstr << "<source>" << XMLEscape(pMatName) << "-" << pTypeName << "-surface</source>" << endstr;
597     PopTag();
598     mOutput << startstr << "</sampler2D>" << endstr;
599     PopTag();
600     mOutput << startstr << "</newparam>" << endstr;
601   }
602 }
603 
604 // ------------------------------------------------------------------------------------------------
605 // Writes a scalar property
WriteFloatEntry(const Property & pProperty,const std::string & pTypeName)606 void ColladaExporter::WriteFloatEntry( const Property& pProperty, const std::string& pTypeName)
607 {
608     if(pProperty.exist) {
609         mOutput << startstr << "<" << pTypeName << ">" << endstr;
610         PushTag();
611         mOutput << startstr << "<float sid=\"" << pTypeName << "\">" << pProperty.value << "</float>" << endstr;
612         PopTag();
613         mOutput << startstr << "</" << pTypeName << ">" << endstr;
614     }
615 }
616 
617 // ------------------------------------------------------------------------------------------------
618 // Writes the material setup
WriteMaterials()619 void ColladaExporter::WriteMaterials()
620 {
621   materials.resize( mScene->mNumMaterials);
622 
623   /// collect all materials from the scene
624   size_t numTextures = 0;
625   for( size_t a = 0; a < mScene->mNumMaterials; ++a )
626   {
627     const aiMaterial* mat = mScene->mMaterials[a];
628 
629     aiString name;
630     if( mat->Get( AI_MATKEY_NAME, name) != aiReturn_SUCCESS )
631       name = "mat";
632     materials[a].name = std::string( "m") + boost::lexical_cast<std::string> (a) + name.C_Str();
633     for( std::string::iterator it = materials[a].name.begin(); it != materials[a].name.end(); ++it ) {
634         // isalnum on MSVC asserts for code points outside [0,255]. Thus prevent unwanted promotion
635         // of char to signed int and take the unsigned char value.
636       if( !isalnum( static_cast<uint8_t>(*it) ) ) {
637         *it = '_';
638       }
639     }
640 
641     aiShadingMode shading = aiShadingMode_Flat;
642     materials[a].shading_model = "phong";
643     if(mat->Get( AI_MATKEY_SHADING_MODEL, shading) == aiReturn_SUCCESS) {
644         if(shading == aiShadingMode_Phong) {
645             materials[a].shading_model = "phong";
646         } else if(shading == aiShadingMode_Blinn) {
647             materials[a].shading_model = "blinn";
648         } else if(shading == aiShadingMode_NoShading) {
649             materials[a].shading_model = "constant";
650         } else if(shading == aiShadingMode_Gouraud) {
651             materials[a].shading_model = "lambert";
652         }
653     }
654 
655     ReadMaterialSurface( materials[a].ambient, mat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT);
656     if( !materials[a].ambient.texture.empty() ) numTextures++;
657     ReadMaterialSurface( materials[a].diffuse, mat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE);
658     if( !materials[a].diffuse.texture.empty() ) numTextures++;
659     ReadMaterialSurface( materials[a].specular, mat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR);
660     if( !materials[a].specular.texture.empty() ) numTextures++;
661     ReadMaterialSurface( materials[a].emissive, mat, aiTextureType_EMISSIVE, AI_MATKEY_COLOR_EMISSIVE);
662     if( !materials[a].emissive.texture.empty() ) numTextures++;
663     ReadMaterialSurface( materials[a].reflective, mat, aiTextureType_REFLECTION, AI_MATKEY_COLOR_REFLECTIVE);
664     if( !materials[a].reflective.texture.empty() ) numTextures++;
665     ReadMaterialSurface( materials[a].transparent, mat, aiTextureType_OPACITY, AI_MATKEY_COLOR_TRANSPARENT);
666     if( !materials[a].transparent.texture.empty() ) numTextures++;
667     ReadMaterialSurface( materials[a].normal, mat, aiTextureType_NORMALS, NULL, 0, 0);
668     if( !materials[a].normal.texture.empty() ) numTextures++;
669 
670     materials[a].shininess.exist = mat->Get( AI_MATKEY_SHININESS, materials[a].shininess.value) == aiReturn_SUCCESS;
671     materials[a].transparency.exist = mat->Get( AI_MATKEY_OPACITY, materials[a].transparency.value) == aiReturn_SUCCESS;
672     materials[a].transparency.value = 1 - materials[a].transparency.value;
673     materials[a].index_refraction.exist = mat->Get( AI_MATKEY_REFRACTI, materials[a].index_refraction.value) == aiReturn_SUCCESS;
674   }
675 
676   // output textures if present
677   if( numTextures > 0 )
678   {
679     mOutput << startstr << "<library_images>" << endstr;
680     PushTag();
681     for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
682     {
683       const Material& mat = *it;
684       WriteImageEntry( mat.ambient, mat.name + "-ambient-image");
685       WriteImageEntry( mat.diffuse, mat.name + "-diffuse-image");
686       WriteImageEntry( mat.specular, mat.name + "-specular-image");
687       WriteImageEntry( mat.emissive, mat.name + "-emission-image");
688       WriteImageEntry( mat.reflective, mat.name + "-reflective-image");
689       WriteImageEntry( mat.transparent, mat.name + "-transparent-image");
690       WriteImageEntry( mat.normal, mat.name + "-normal-image");
691     }
692     PopTag();
693     mOutput << startstr << "</library_images>" << endstr;
694   }
695 
696   // output effects - those are the actual carriers of information
697   if( !materials.empty() )
698   {
699     mOutput << startstr << "<library_effects>" << endstr;
700     PushTag();
701     for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
702     {
703       const Material& mat = *it;
704       // this is so ridiculous it must be right
705       mOutput << startstr << "<effect id=\"" << XMLEscape(mat.name) << "-fx\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
706       PushTag();
707       mOutput << startstr << "<profile_COMMON>" << endstr;
708       PushTag();
709 
710       // write sampler- and surface params for the texture entries
711       WriteTextureParamEntry( mat.emissive, "emission", mat.name);
712       WriteTextureParamEntry( mat.ambient, "ambient", mat.name);
713       WriteTextureParamEntry( mat.diffuse, "diffuse", mat.name);
714       WriteTextureParamEntry( mat.specular, "specular", mat.name);
715       WriteTextureParamEntry( mat.reflective, "reflective", mat.name);
716       WriteTextureParamEntry( mat.transparent, "transparent", mat.name);
717       WriteTextureParamEntry( mat.normal, "normal", mat.name);
718 
719       mOutput << startstr << "<technique sid=\"standard\">" << endstr;
720       PushTag();
721       mOutput << startstr << "<" << mat.shading_model << ">" << endstr;
722       PushTag();
723 
724       WriteTextureColorEntry( mat.emissive, "emission", mat.name + "-emission-sampler");
725       WriteTextureColorEntry( mat.ambient, "ambient", mat.name + "-ambient-sampler");
726       WriteTextureColorEntry( mat.diffuse, "diffuse", mat.name + "-diffuse-sampler");
727       WriteTextureColorEntry( mat.specular, "specular", mat.name + "-specular-sampler");
728       WriteFloatEntry(mat.shininess, "shininess");
729       WriteTextureColorEntry( mat.reflective, "reflective", mat.name + "-reflective-sampler");
730       WriteTextureColorEntry( mat.transparent, "transparent", mat.name + "-transparent-sampler");
731       WriteFloatEntry(mat.transparency, "transparency");
732       WriteFloatEntry(mat.index_refraction, "index_of_refraction");
733 
734       if(! mat.normal.texture.empty()) {
735         WriteTextureColorEntry( mat.normal, "bump", mat.name + "-normal-sampler");
736       }
737 
738       PopTag();
739       mOutput << startstr << "</" << mat.shading_model << ">" << endstr;
740       PopTag();
741       mOutput << startstr << "</technique>" << endstr;
742       PopTag();
743       mOutput << startstr << "</profile_COMMON>" << endstr;
744       PopTag();
745       mOutput << startstr << "</effect>" << endstr;
746     }
747     PopTag();
748     mOutput << startstr << "</library_effects>" << endstr;
749 
750     // write materials - they're just effect references
751     mOutput << startstr << "<library_materials>" << endstr;
752     PushTag();
753     for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
754     {
755       const Material& mat = *it;
756       mOutput << startstr << "<material id=\"" << XMLEscape(mat.name) << "\" name=\"" << mat.name << "\">" << endstr;
757       PushTag();
758       mOutput << startstr << "<instance_effect url=\"#" << XMLEscape(mat.name) << "-fx\"/>" << endstr;
759       PopTag();
760       mOutput << startstr << "</material>" << endstr;
761     }
762     PopTag();
763     mOutput << startstr << "</library_materials>" << endstr;
764   }
765 }
766 
767 // ------------------------------------------------------------------------------------------------
768 // Writes the geometry library
WriteGeometryLibrary()769 void ColladaExporter::WriteGeometryLibrary()
770 {
771     mOutput << startstr << "<library_geometries>" << endstr;
772     PushTag();
773 
774     for( size_t a = 0; a < mScene->mNumMeshes; ++a)
775         WriteGeometry( a);
776 
777     PopTag();
778     mOutput << startstr << "</library_geometries>" << endstr;
779 }
780 
781 // ------------------------------------------------------------------------------------------------
782 // Writes the given mesh
WriteGeometry(size_t pIndex)783 void ColladaExporter::WriteGeometry( size_t pIndex)
784 {
785     const aiMesh* mesh = mScene->mMeshes[pIndex];
786     const std::string idstr = GetMeshId( pIndex);
787     const std::string idstrEscaped = XMLEscape(idstr);
788 
789   if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
790     return;
791 
792     // opening tag
793     mOutput << startstr << "<geometry id=\"" << idstrEscaped << "\" name=\"" << idstrEscaped << "_name\" >" << endstr;
794     PushTag();
795 
796     mOutput << startstr << "<mesh>" << endstr;
797     PushTag();
798 
799     // Positions
800     WriteFloatArray( idstr + "-positions", FloatType_Vector, (float*) mesh->mVertices, mesh->mNumVertices);
801     // Normals, if any
802     if( mesh->HasNormals() )
803         WriteFloatArray( idstr + "-normals", FloatType_Vector, (float*) mesh->mNormals, mesh->mNumVertices);
804 
805     // texture coords
806     for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a)
807     {
808         if( mesh->HasTextureCoords( a) )
809         {
810             WriteFloatArray( idstr + "-tex" + boost::lexical_cast<std::string> (a), mesh->mNumUVComponents[a] == 3 ? FloatType_TexCoord3 : FloatType_TexCoord2,
811                 (float*) mesh->mTextureCoords[a], mesh->mNumVertices);
812         }
813     }
814 
815     // vertex colors
816     for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a)
817     {
818         if( mesh->HasVertexColors( a) )
819             WriteFloatArray( idstr + "-color" + boost::lexical_cast<std::string> (a), FloatType_Color, (float*) mesh->mColors[a], mesh->mNumVertices);
820     }
821 
822     // assemble vertex structure
823     mOutput << startstr << "<vertices id=\"" << idstrEscaped << "-vertices" << "\">" << endstr;
824     PushTag();
825     mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << idstrEscaped << "-positions\" />" << endstr;
826     if( mesh->HasNormals() )
827         mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << idstrEscaped << "-normals\" />" << endstr;
828     for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
829     {
830         if( mesh->HasTextureCoords( a) )
831             mOutput << startstr << "<input semantic=\"TEXCOORD\" source=\"#" << idstrEscaped << "-tex" << a << "\" " /*<< "set=\"" << a << "\"" */ << " />" << endstr;
832     }
833     for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
834     {
835         if( mesh->HasVertexColors( a) )
836             mOutput << startstr << "<input semantic=\"COLOR\" source=\"#" << idstrEscaped << "-color" << a << "\" " /*<< set=\"" << a << "\"" */ << " />" << endstr;
837     }
838 
839     PopTag();
840     mOutput << startstr << "</vertices>" << endstr;
841 
842     // count the number of lines, triangles and polygon meshes
843     int countLines = 0;
844     int countPoly = 0;
845     for( size_t a = 0; a < mesh->mNumFaces; ++a )
846     {
847         if (mesh->mFaces[a].mNumIndices == 2) countLines++;
848         else if (mesh->mFaces[a].mNumIndices >= 3) countPoly++;
849     }
850 
851     // lines
852     if (countLines)
853     {
854         mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
855         PushTag();
856         mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << idstrEscaped << "-vertices\" />" << endstr;
857         mOutput << startstr << "<p>";
858         for( size_t a = 0; a < mesh->mNumFaces; ++a )
859         {
860             const aiFace& face = mesh->mFaces[a];
861             if (face.mNumIndices != 2) continue;
862             for( size_t b = 0; b < face.mNumIndices; ++b )
863                 mOutput << face.mIndices[b] << " ";
864         }
865         mOutput << "</p>" << endstr;
866         PopTag();
867         mOutput << startstr << "</lines>" << endstr;
868     }
869 
870     // triangle - dont use it, because compatibility problems
871 
872     // polygons
873     if (countPoly)
874     {
875         mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
876         PushTag();
877         mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << idstrEscaped << "-vertices\" />" << endstr;
878 
879         mOutput << startstr << "<vcount>";
880         for( size_t a = 0; a < mesh->mNumFaces; ++a )
881         {
882             if (mesh->mFaces[a].mNumIndices < 3) continue;
883             mOutput << mesh->mFaces[a].mNumIndices << " ";
884         }
885         mOutput << "</vcount>" << endstr;
886 
887         mOutput << startstr << "<p>";
888         for( size_t a = 0; a < mesh->mNumFaces; ++a )
889         {
890             const aiFace& face = mesh->mFaces[a];
891             if (face.mNumIndices < 3) continue;
892             for( size_t b = 0; b < face.mNumIndices; ++b )
893                 mOutput << face.mIndices[b] << " ";
894         }
895         mOutput << "</p>" << endstr;
896         PopTag();
897         mOutput << startstr << "</polylist>" << endstr;
898     }
899 
900     // closing tags
901     PopTag();
902     mOutput << startstr << "</mesh>" << endstr;
903     PopTag();
904     mOutput << startstr << "</geometry>" << endstr;
905 }
906 
907 // ------------------------------------------------------------------------------------------------
908 // Writes a float array of the given type
WriteFloatArray(const std::string & pIdString,FloatDataType pType,const float * pData,size_t pElementCount)909 void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataType pType, const float* pData, size_t pElementCount)
910 {
911     size_t floatsPerElement = 0;
912     switch( pType )
913     {
914         case FloatType_Vector: floatsPerElement = 3; break;
915         case FloatType_TexCoord2: floatsPerElement = 2; break;
916         case FloatType_TexCoord3: floatsPerElement = 3; break;
917         case FloatType_Color: floatsPerElement = 3; break;
918         default:
919             return;
920     }
921 
922     std::string arrayId = pIdString + "-array";
923 
924     mOutput << startstr << "<source id=\"" << XMLEscape(pIdString) << "\" name=\"" << XMLEscape(pIdString) << "\">" << endstr;
925     PushTag();
926 
927     // source array
928     mOutput << startstr << "<float_array id=\"" << XMLEscape(arrayId) << "\" count=\"" << pElementCount * floatsPerElement << "\"> ";
929     PushTag();
930 
931     if( pType == FloatType_TexCoord2 )
932     {
933         for( size_t a = 0; a < pElementCount; ++a )
934         {
935             mOutput << pData[a*3+0] << " ";
936             mOutput << pData[a*3+1] << " ";
937         }
938     }
939     else if( pType == FloatType_Color )
940     {
941         for( size_t a = 0; a < pElementCount; ++a )
942         {
943             mOutput << pData[a*4+0] << " ";
944             mOutput << pData[a*4+1] << " ";
945             mOutput << pData[a*4+2] << " ";
946         }
947     }
948     else
949     {
950         for( size_t a = 0; a < pElementCount * floatsPerElement; ++a )
951             mOutput << pData[a] << " ";
952     }
953     mOutput << "</float_array>" << endstr;
954     PopTag();
955 
956     // the usual Collada fun. Let's bloat it even more!
957     mOutput << startstr << "<technique_common>" << endstr;
958     PushTag();
959     mOutput << startstr << "<accessor count=\"" << pElementCount << "\" offset=\"0\" source=\"#" << arrayId << "\" stride=\"" << floatsPerElement << "\">" << endstr;
960     PushTag();
961 
962     switch( pType )
963     {
964         case FloatType_Vector:
965             mOutput << startstr << "<param name=\"X\" type=\"float\" />" << endstr;
966             mOutput << startstr << "<param name=\"Y\" type=\"float\" />" << endstr;
967             mOutput << startstr << "<param name=\"Z\" type=\"float\" />" << endstr;
968             break;
969 
970         case FloatType_TexCoord2:
971             mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr;
972             mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr;
973             break;
974 
975         case FloatType_TexCoord3:
976             mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr;
977             mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr;
978             mOutput << startstr << "<param name=\"P\" type=\"float\" />" << endstr;
979             break;
980 
981         case FloatType_Color:
982             mOutput << startstr << "<param name=\"R\" type=\"float\" />" << endstr;
983             mOutput << startstr << "<param name=\"G\" type=\"float\" />" << endstr;
984             mOutput << startstr << "<param name=\"B\" type=\"float\" />" << endstr;
985             break;
986     }
987 
988     PopTag();
989     mOutput << startstr << "</accessor>" << endstr;
990     PopTag();
991     mOutput << startstr << "</technique_common>" << endstr;
992     PopTag();
993     mOutput << startstr << "</source>" << endstr;
994 }
995 
996 // ------------------------------------------------------------------------------------------------
997 // Writes the scene library
WriteSceneLibrary()998 void ColladaExporter::WriteSceneLibrary()
999 {
1000     const std::string scene_name_escaped = XMLEscape(mScene->mRootNode->mName.C_Str());
1001 
1002     mOutput << startstr << "<library_visual_scenes>" << endstr;
1003     PushTag();
1004     mOutput << startstr << "<visual_scene id=\"" + scene_name_escaped + "\" name=\"" + scene_name_escaped + "\">" << endstr;
1005     PushTag();
1006 
1007     // start recursive write at the root node
1008     for( size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a )
1009         WriteNode( mScene->mRootNode->mChildren[a]);
1010 
1011     PopTag();
1012     mOutput << startstr << "</visual_scene>" << endstr;
1013     PopTag();
1014     mOutput << startstr << "</library_visual_scenes>" << endstr;
1015 }
1016 
1017 // ------------------------------------------------------------------------------------------------
1018 // Recursively writes the given node
WriteNode(aiNode * pNode)1019 void ColladaExporter::WriteNode(aiNode* pNode)
1020 {
1021     // the must have a name
1022     if (pNode->mName.length == 0)
1023     {
1024         std::stringstream ss;
1025         ss << "Node_" << pNode;
1026         pNode->mName.Set(ss.str());
1027     }
1028 
1029     const std::string node_name_escaped = XMLEscape(pNode->mName.data);
1030     mOutput << startstr << "<node id=\"" << node_name_escaped << "\" name=\"" << node_name_escaped << "\">" << endstr;
1031     PushTag();
1032 
1033     // write transformation - we can directly put the matrix there
1034     // TODO: (thom) decompose into scale - rot - quad to allow adressing it by animations afterwards
1035     const aiMatrix4x4& mat = pNode->mTransformation;
1036     mOutput << startstr << "<matrix>";
1037     mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << " ";
1038     mOutput << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << " ";
1039     mOutput << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << " ";
1040     mOutput << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4;
1041     mOutput << "</matrix>" << endstr;
1042 
1043     if(pNode->mNumMeshes==0){
1044         //check if it is a camera node
1045         for(size_t i=0; i<mScene->mNumCameras; i++){
1046             if(mScene->mCameras[i]->mName == pNode->mName){
1047                 mOutput << startstr <<"<instance_camera url=\"#" << node_name_escaped << "-camera\"/>" << endstr;
1048                 break;
1049             }
1050         }
1051         //check if it is a light node
1052         for(size_t i=0; i<mScene->mNumLights; i++){
1053             if(mScene->mLights[i]->mName == pNode->mName){
1054                 mOutput << startstr <<"<instance_light url=\"#" << node_name_escaped << "-light\"/>" << endstr;
1055                 break;
1056             }
1057         }
1058 
1059     }else
1060     // instance every geometry
1061     for( size_t a = 0; a < pNode->mNumMeshes; ++a )
1062     {
1063         const aiMesh* mesh = mScene->mMeshes[pNode->mMeshes[a]];
1064     // do not instanciate mesh if empty. I wonder how this could happen
1065     if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
1066         continue;
1067     mOutput << startstr << "<instance_geometry url=\"#" << XMLEscape(GetMeshId( pNode->mMeshes[a])) << "\">" << endstr;
1068     PushTag();
1069     mOutput << startstr << "<bind_material>" << endstr;
1070     PushTag();
1071     mOutput << startstr << "<technique_common>" << endstr;
1072     PushTag();
1073     mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << XMLEscape(materials[mesh->mMaterialIndex].name) << "\" />" << endstr;
1074         PopTag();
1075     mOutput << startstr << "</technique_common>" << endstr;
1076     PopTag();
1077     mOutput << startstr << "</bind_material>" << endstr;
1078     PopTag();
1079         mOutput << startstr << "</instance_geometry>" << endstr;
1080     }
1081 
1082     // recurse into subnodes
1083     for( size_t a = 0; a < pNode->mNumChildren; ++a )
1084         WriteNode( pNode->mChildren[a]);
1085 
1086     PopTag();
1087     mOutput << startstr << "</node>" << endstr;
1088 }
1089 
1090 #endif
1091 #endif
1092