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