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