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