1/// \file X3DExporter.cpp
2/// \brief X3D-format files exporter for Assimp. Implementation.
3/// \date 2016
4/// \author smal.root@gmail.com
5
6#ifndef ASSIMP_BUILD_NO_EXPORT
7#ifndef ASSIMP_BUILD_NO_X3D_EXPORTER
8
9#include "X3DExporter.hpp"
10
11// Header files, Assimp.
12#include "Exceptional.h"
13#include "StringUtils.h"
14#include <assimp/Exporter.hpp>
15#include <assimp/IOSystem.hpp>
16
17using namespace std;
18
19namespace Assimp
20{
21
22void ExportSceneX3D(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties)
23{
24 X3DExporter exporter(pFile, pIOSystem, pScene, pProperties);
25}
26
27}// namespace Assimp
28
29namespace Assimp
30{
31
32void X3DExporter::IndentationStringSet(const size_t pNewLevel)
33{
34 if(pNewLevel > mIndentationString.size())
35 {
36 if(pNewLevel > mIndentationString.capacity()) mIndentationString.reserve(pNewLevel + 1);
37
38 for(size_t i = 0, i_e = pNewLevel - mIndentationString.size(); i < i_e; i++) mIndentationString.push_back('\t');
39 }
40 else if(pNewLevel < mIndentationString.size())
41 {
42 mIndentationString.resize(pNewLevel);
43 }
44}
45
46void X3DExporter::XML_Write(const string& pData)
47{
48 if(pData.size() == 0) return;
49 if(mOutFile->Write((void*)pData.data(), pData.length(), 1) != 1) throw DeadlyExportError("Failed to write scene data!");
50}
51
52aiMatrix4x4 X3DExporter::Matrix_GlobalToCurrent(const aiNode& pNode) const
53{
54aiNode* cur_node;
55std::list<aiMatrix4x4> matr;
56aiMatrix4x4 out_matr;
57
58 // starting walk from current element to root
59 matr.push_back(pNode.mTransformation);
60 cur_node = pNode.mParent;
61 if(cur_node != nullptr)
62 {
63 do
64 {
65 matr.push_back(cur_node->mTransformation);
66 cur_node = cur_node->mParent;
67 } while(cur_node != nullptr);
68 }
69
70 // multiplicate all matrices in reverse order
71 for(std::list<aiMatrix4x4>::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); rit++) out_matr = out_matr * (*rit);
72
73 return out_matr;
74}
75
76void X3DExporter::AttrHelper_FloatToString(const float pValue, std::string& pTargetString)
77{
78 pTargetString = to_string(pValue);
79 AttrHelper_CommaToPoint(pTargetString);
80}
81
82void X3DExporter::AttrHelper_Vec3DArrToString(const aiVector3D* pArray, const size_t pArray_Size, string& pTargetString)
83{
84 pTargetString.clear();
85 pTargetString.reserve(pArray_Size * 6);// (Number + space) * 3.
86 for(size_t idx = 0; idx < pArray_Size; idx++)
87 pTargetString.append(to_string(pArray[idx].x) + " " + to_string(pArray[idx].y) + " " + to_string(pArray[idx].z) + " ");
88
89 // remove last space symbol.
90 pTargetString.resize(pTargetString.length() - 1);
91 AttrHelper_CommaToPoint(pTargetString);
92}
93
94void X3DExporter::AttrHelper_Vec2DArrToString(const aiVector2D* pArray, const size_t pArray_Size, std::string& pTargetString)
95{
96 pTargetString.clear();
97 pTargetString.reserve(pArray_Size * 4);// (Number + space) * 2.
98 for(size_t idx = 0; idx < pArray_Size; idx++)
99 pTargetString.append(to_string(pArray[idx].x) + " " + to_string(pArray[idx].y) + " ");
100
101 // remove last space symbol.
102 pTargetString.resize(pTargetString.length() - 1);
103 AttrHelper_CommaToPoint(pTargetString);
104}
105
106void X3DExporter::AttrHelper_Vec3DAsVec2fArrToString(const aiVector3D* pArray, const size_t pArray_Size, string& pTargetString)
107{
108 pTargetString.clear();
109 pTargetString.reserve(pArray_Size * 4);// (Number + space) * 2.
110 for(size_t idx = 0; idx < pArray_Size; idx++)
111 pTargetString.append(to_string(pArray[idx].x) + " " + to_string(pArray[idx].y) + " ");
112
113 // remove last space symbol.
114 pTargetString.resize(pTargetString.length() - 1);
115 AttrHelper_CommaToPoint(pTargetString);
116}
117
118void X3DExporter::AttrHelper_Col4DArrToString(const aiColor4D* pArray, const size_t pArray_Size, string& pTargetString)
119{
120 pTargetString.clear();
121 pTargetString.reserve(pArray_Size * 8);// (Number + space) * 4.
122 for(size_t idx = 0; idx < pArray_Size; idx++)
123 pTargetString.append(to_string(pArray[idx].r) + " " + to_string(pArray[idx].g) + " " + to_string(pArray[idx].b) + " " +
124 to_string(pArray[idx].a) + " ");
125
126 // remove last space symbol.
127 pTargetString.resize(pTargetString.length() - 1);
128 AttrHelper_CommaToPoint(pTargetString);
129}
130
131void X3DExporter::AttrHelper_Col3DArrToString(const aiColor3D* pArray, const size_t pArray_Size, std::string& pTargetString)
132{
133 pTargetString.clear();
134 pTargetString.reserve(pArray_Size * 6);// (Number + space) * 3.
135 for(size_t idx = 0; idx < pArray_Size; idx++)
136 pTargetString.append(to_string(pArray[idx].r) + " " + to_string(pArray[idx].g) + " " + to_string(pArray[idx].b) + " ");
137
138 // remove last space symbol.
139 pTargetString.resize(pTargetString.length() - 1);
140 AttrHelper_CommaToPoint(pTargetString);
141}
142
143void X3DExporter::AttrHelper_Color3ToAttrList(std::list<SAttribute>& pList, const std::string& pName, const aiColor3D& pValue, const aiColor3D& pDefaultValue)
144{
145string tstr;
146
147 if(pValue == pDefaultValue) return;
148
149 AttrHelper_Col3DArrToString(&pValue, 1, tstr);
150 pList.push_back({pName, tstr});
151}
152
153void X3DExporter::AttrHelper_FloatToAttrList(std::list<SAttribute>& pList, const string& pName, const float pValue, const float pDefaultValue)
154{
155string tstr;
156
157 if(pValue == pDefaultValue) return;
158
159 AttrHelper_FloatToString(pValue, tstr);
160 pList.push_back({pName, tstr});
161}
162
163void X3DExporter::NodeHelper_OpenNode(const string& pNodeName, const size_t pTabLevel, const bool pEmptyElement, const list<SAttribute>& pAttrList)
164{
165 // Write indentation.
166 IndentationStringSet(pTabLevel);
167 XML_Write(mIndentationString);
168 // Begin of the element
169 XML_Write("<" + pNodeName);
170 // Write attributes
171 for(const SAttribute& attr: pAttrList) { XML_Write(" " + attr.Name + "='" + attr.Value + "'"); }
172
173 // End of the element
174 if(pEmptyElement)
175 {
176 XML_Write("/>\n");
177 }
178 else
179 {
180 XML_Write(">\n");
181 }
182}
183
184void X3DExporter::NodeHelper_OpenNode(const string& pNodeName, const size_t pTabLevel, const bool pEmptyElement)
185{
186const list<SAttribute> attr_list;
187
188 NodeHelper_OpenNode(pNodeName, pTabLevel, pEmptyElement, attr_list);
189}
190
191void X3DExporter::NodeHelper_CloseNode(const string& pNodeName, const size_t pTabLevel)
192{
193 // Write indentation.
194 IndentationStringSet(pTabLevel);
195 XML_Write(mIndentationString);
196 // Write element
197 XML_Write("</" + pNodeName + ">\n");
198}
199
200void X3DExporter::Export_Node(const aiNode *pNode, const size_t pTabLevel)
201{
202bool transform = false;
203list<SAttribute> attr_list;
204
205 // In Assimp lights is stored in next way: light source store in mScene->mLights and in node tree must present aiNode with name same as
206 // light source has. Considering it we must compare every aiNode name with light sources names. Why not to look where ligths is present
207 // and save them to fili? Because corresponding aiNode can be already written to file and we can only add information to file not to edit.
208 if(CheckAndExport_Light(*pNode, pTabLevel)) return;
209
210 // Check if need DEF.
211 if(pNode->mName.length) attr_list.push_back({"DEF", pNode->mName.C_Str()});
212
213 // Check if need <Transformation> node against <Group>.
214 if(!pNode->mTransformation.IsIdentity())
215 {
216 auto Vector2String = [this](const aiVector3D pVector) -> string
217 {
218 string tstr = to_string(pVector.x) + " " + to_string(pVector.y) + " " + to_string(pVector.z);
219
220 AttrHelper_CommaToPoint(tstr);
221
222 return tstr;
223 };
224
225 auto Rotation2String = [this](const aiVector3D pAxis, const ai_real pAngle) -> string
226 {
227 string tstr = to_string(pAxis.x) + " " + to_string(pAxis.y) + " " + to_string(pAxis.z) + " " + to_string(pAngle);
228
229 AttrHelper_CommaToPoint(tstr);
230
231 return tstr;
232 };
233
234 aiVector3D scale, translate, rotate_axis;
235 ai_real rotate_angle;
236
237 transform = true;
238 pNode->mTransformation.Decompose(scale, rotate_axis, rotate_angle, translate);
239 // Check if values different from default
240 if((rotate_angle != 0) && (rotate_axis.Length() > 0))
241 attr_list.push_back({"rotation", Rotation2String(rotate_axis, rotate_angle)});
242
243 if(!scale.Equal({1.0,1.0,1.0})) {
244 attr_list.push_back({"scale", Vector2String(scale)});
245 }
246 if(translate.Length() > 0) {
247 attr_list.push_back({"translation", Vector2String(translate)});
248 }
249 }
250
251 // Begin node if need.
252 if(transform)
253 NodeHelper_OpenNode("Transform", pTabLevel, false, attr_list);
254 else
255 NodeHelper_OpenNode("Group", pTabLevel);
256
257 // Export metadata
258 if(pNode->mMetaData != nullptr)
259 {
260 for(size_t idx_prop = 0; idx_prop < pNode->mMetaData->mNumProperties; idx_prop++)
261 {
262 const aiString* key;
263 const aiMetadataEntry* entry;
264
265 if(pNode->mMetaData->Get(idx_prop, key, entry))
266 {
267 switch(entry->mType)
268 {
269 case AI_BOOL:
270 Export_MetadataBoolean(*key, *static_cast<bool*>(entry->mData), pTabLevel + 1);
271 break;
272 case AI_DOUBLE:
273 Export_MetadataDouble(*key, *static_cast<double*>(entry->mData), pTabLevel + 1);
274 break;
275 case AI_FLOAT:
276 Export_MetadataFloat(*key, *static_cast<float*>(entry->mData), pTabLevel + 1);
277 break;
278 case AI_INT32:
279 Export_MetadataInteger(*key, *static_cast<int32_t*>(entry->mData), pTabLevel + 1);
280 break;
281 case AI_AISTRING:
282 Export_MetadataString(*key, *static_cast<aiString*>(entry->mData), pTabLevel + 1);
283 break;
284 default:
285 LogError("Unsupported metadata type: " + to_string(entry->mType));
286 break;
287 }// switch(entry->mType)
288 }
289 }
290 }// if(pNode->mMetaData != nullptr)
291
292 // Export meshes.
293 for(size_t idx_mesh = 0; idx_mesh < pNode->mNumMeshes; idx_mesh++) Export_Mesh(pNode->mMeshes[idx_mesh], pTabLevel + 1);
294 // Export children.
295 for(size_t idx_node = 0; idx_node < pNode->mNumChildren; idx_node++) Export_Node(pNode->mChildren[idx_node], pTabLevel + 1);
296
297 // End node if need.
298 if(transform)
299 NodeHelper_CloseNode("Transform", pTabLevel);
300 else
301 NodeHelper_CloseNode("Group", pTabLevel);
302}
303
304void X3DExporter::Export_Mesh(const size_t pIdxMesh, const size_t pTabLevel)
305{
306const char* NodeName_IFS = "IndexedFaceSet";
307const char* NodeName_Shape = "Shape";
308
309list<SAttribute> attr_list;
310aiMesh& mesh = *mScene->mMeshes[pIdxMesh];// create alias for conveniance.
311
312 // Check if mesh already defined early.
313 if(mDEF_Map_Mesh.find(pIdxMesh) != mDEF_Map_Mesh.end())
314 {
315 // Mesh already defined, just refer to it
316 attr_list.push_back({"USE", mDEF_Map_Mesh.at(pIdxMesh)});
317 NodeHelper_OpenNode(NodeName_Shape, pTabLevel, true, attr_list);
318
319 return;
320 }
321
322 string mesh_name(mesh.mName.C_Str() + string("_IDX_") + to_string(pIdxMesh));// Create mesh name
323
324 // Define mesh name.
325 attr_list.push_back({"DEF", mesh_name});
326 mDEF_Map_Mesh[pIdxMesh] = mesh_name;
327
328 //
329 // "Shape" node.
330 //
331 NodeHelper_OpenNode(NodeName_Shape, pTabLevel, false, attr_list);
332 attr_list.clear();
333
334 //
335 // "Appearance" node.
336 //
337 Export_Material(mesh.mMaterialIndex, pTabLevel + 1);
338
339 //
340 // "IndexedFaceSet" node.
341 //
342 // Fill attributes which differ from default. In Assimp for colors, vertices and normals used one indices set. So, only "coordIndex" must be set.
343 string coordIndex;
344
345 // fill coordinates index.
346 coordIndex.reserve(mesh.mNumVertices * 4);// Index + space + Face delimiter
347 for(size_t idx_face = 0; idx_face < mesh.mNumFaces; idx_face++)
348 {
349 const aiFace& face_cur = mesh.mFaces[idx_face];
350
351 for(size_t idx_vert = 0; idx_vert < face_cur.mNumIndices; idx_vert++)
352 {
353 coordIndex.append(to_string(face_cur.mIndices[idx_vert]) + " ");
354 }
355
356 coordIndex.append("-1 ");// face delimiter.
357 }
358
359 // remove last space symbol.
360 coordIndex.resize(coordIndex.length() - 1);
361 attr_list.push_back({"coordIndex", coordIndex});
362 // create node
363 NodeHelper_OpenNode(NodeName_IFS, pTabLevel + 1, false, attr_list);
364 attr_list.clear();
365 // Child nodes for "IndexedFaceSet" needed when used colors, textures or normals.
366 string attr_value;
367
368 // Export <Coordinate>
369 AttrHelper_Vec3DArrToString(mesh.mVertices, mesh.mNumVertices, attr_value);
370 attr_list.push_back({"point", attr_value});
371 NodeHelper_OpenNode("Coordinate", pTabLevel + 2, true, attr_list);
372 attr_list.clear();
373
374 // Export <ColorRGBA>
375 if(mesh.HasVertexColors(0))
376 {
377 AttrHelper_Col4DArrToString(mesh.mColors[0], mesh.mNumVertices, attr_value);
378 attr_list.push_back({"color", attr_value});
379 NodeHelper_OpenNode("ColorRGBA", pTabLevel + 2, true, attr_list);
380 attr_list.clear();
381 }
382
383 // Export <TextureCoordinate>
384 if(mesh.HasTextureCoords(0))
385 {
386 AttrHelper_Vec3DAsVec2fArrToString(mesh.mTextureCoords[0], mesh.mNumVertices, attr_value);
387 attr_list.push_back({"point", attr_value});
388 NodeHelper_OpenNode("TextureCoordinate", pTabLevel + 2, true, attr_list);
389 attr_list.clear();
390 }
391
392 // Export <Normal>
393 if(mesh.HasNormals())
394 {
395 AttrHelper_Vec3DArrToString(mesh.mNormals, mesh.mNumVertices, attr_value);
396 attr_list.push_back({"vector", attr_value});
397 NodeHelper_OpenNode("Normal", pTabLevel + 2, true, attr_list);
398 attr_list.clear();
399 }
400
401 //
402 // Close opened nodes.
403 //
404 NodeHelper_CloseNode(NodeName_IFS, pTabLevel + 1);
405 NodeHelper_CloseNode(NodeName_Shape, pTabLevel);
406}
407
408void X3DExporter::Export_Material(const size_t pIdxMaterial, const size_t pTabLevel)
409{
410const char* NodeName_A = "Appearance";
411
412list<SAttribute> attr_list;
413aiMaterial& material = *mScene->mMaterials[pIdxMaterial];// create alias for conveniance.
414
415 // Check if material already defined early.
416 if(mDEF_Map_Material.find(pIdxMaterial) != mDEF_Map_Material.end())
417 {
418 // Material already defined, just refer to it
419 attr_list.push_back({"USE", mDEF_Map_Material.at(pIdxMaterial)});
420 NodeHelper_OpenNode(NodeName_A, pTabLevel, true, attr_list);
421
422 return;
423 }
424
425 string material_name(string("_IDX_") + to_string(pIdxMaterial));// Create material name
426 aiString ai_mat_name;
427
428 if(material.Get(AI_MATKEY_NAME, ai_mat_name) == AI_SUCCESS) material_name.insert(0, ai_mat_name.C_Str());
429
430 // Define material name.
431 attr_list.push_back({"DEF", material_name});
432 mDEF_Map_Material[pIdxMaterial] = material_name;
433
434 //
435 // "Appearance" node.
436 //
437 NodeHelper_OpenNode(NodeName_A, pTabLevel, false, attr_list);
438 attr_list.clear();
439
440 //
441 // "Material" node.
442 //
443 {
444 auto Color4ToAttrList = [&](const string& pAttrName, const aiColor4D& pAttrValue, const aiColor3D& pAttrDefaultValue)
445 {
446 string tstr;
447
448 if(aiColor3D(pAttrValue.r, pAttrValue.g, pAttrValue.b) != pAttrDefaultValue)
449 {
450 AttrHelper_Col4DArrToString(&pAttrValue, 1, tstr);
451 attr_list.push_back({pAttrName, tstr});
452 }
453 };
454
455 float tvalf;
456 aiColor3D color3;
457 aiColor4D color4;
458
459 // ambientIntensity="0.2" SFFloat [inputOutput]
460 if(material.Get(AI_MATKEY_COLOR_AMBIENT, color3) == AI_SUCCESS)
461 AttrHelper_FloatToAttrList(attr_list, "ambientIntensity", (color3.r + color3.g + color3.b) / 3.0f, 0.2f);
462 else if(material.Get(AI_MATKEY_COLOR_AMBIENT, color4) == AI_SUCCESS)
463 AttrHelper_FloatToAttrList(attr_list, "ambientIntensity", (color4.r + color4.g + color4.b) / 3.0f, 0.2f);
464
465 // diffuseColor="0.8 0.8 0.8" SFColor [inputOutput]
466 if(material.Get(AI_MATKEY_COLOR_DIFFUSE, color3) == AI_SUCCESS)
467 AttrHelper_Color3ToAttrList(attr_list, "diffuseColor", color3, aiColor3D(0.8f, 0.8f, 0.8f));
468 else if(material.Get(AI_MATKEY_COLOR_DIFFUSE, color4) == AI_SUCCESS)
469 Color4ToAttrList("diffuseColor", color4, aiColor3D(0.8f, 0.8f, 0.8f));
470
471 // emissiveColor="0 0 0" SFColor [inputOutput]
472 if(material.Get(AI_MATKEY_COLOR_EMISSIVE, color3) == AI_SUCCESS)
473 AttrHelper_Color3ToAttrList(attr_list, "emissiveColor", color3, aiColor3D(0, 0, 0));
474 else if(material.Get(AI_MATKEY_COLOR_EMISSIVE, color4) == AI_SUCCESS)
475 Color4ToAttrList("emissiveColor", color4, aiColor3D(0, 0, 0));
476
477 // shininess="0.2" SFFloat [inputOutput]
478 if(material.Get(AI_MATKEY_SHININESS, tvalf) == AI_SUCCESS) AttrHelper_FloatToAttrList(attr_list, "shininess", tvalf, 0.2f);
479
480 // specularColor="0 0 0" SFColor [inputOutput]
481 if(material.Get(AI_MATKEY_COLOR_SPECULAR, color3) == AI_SUCCESS)
482 AttrHelper_Color3ToAttrList(attr_list, "specularColor", color3, aiColor3D(0, 0, 0));
483 else if(material.Get(AI_MATKEY_COLOR_SPECULAR, color4) == AI_SUCCESS)
484 Color4ToAttrList("specularColor", color4, aiColor3D(0, 0, 0));
485
486 // transparency="0" SFFloat [inputOutput]
487 if(material.Get(AI_MATKEY_OPACITY, tvalf) == AI_SUCCESS)
488 {
489 if(tvalf > 1) tvalf = 1;
490
491 tvalf = 1.0f - tvalf;
492 AttrHelper_FloatToAttrList(attr_list, "transparency", tvalf, 0);
493 }
494
495 NodeHelper_OpenNode("Material", pTabLevel + 1, true, attr_list);
496 attr_list.clear();
497 }// "Material" node. END.
498
499 //
500 // "ImageTexture" node.
501 //
502 {
503 auto RepeatToAttrList = [&](const string& pAttrName, const bool pAttrValue)
504 {
505 if(!pAttrValue) attr_list.push_back({pAttrName, "false"});
506 };
507
508 bool tvalb;
509 aiString tstring;
510
511 // url="" MFString
512 if(material.Get(AI_MATKEY_TEXTURE_DIFFUSE(0), tstring) == AI_SUCCESS)
513 {
514 if(strncmp(tstring.C_Str(), AI_EMBEDDED_TEXNAME_PREFIX, strlen(AI_EMBEDDED_TEXNAME_PREFIX)) == 0)
515 LogError("Embedded texture is not supported");
516 else
517 attr_list.push_back({"url", string("\"") + tstring.C_Str() + "\""});
518 }
519
520 // repeatS="true" SFBool
521 if(material.Get(AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0), tvalb) == AI_SUCCESS) RepeatToAttrList("repeatS", tvalb);
522
523 // repeatT="true" SFBool
524 if(material.Get(AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0), tvalb) == AI_SUCCESS) RepeatToAttrList("repeatT", tvalb);
525
526 NodeHelper_OpenNode("ImageTexture", pTabLevel + 1, true, attr_list);
527 attr_list.clear();
528 }// "ImageTexture" node. END.
529
530 //
531 // "TextureTransform" node.
532 //
533 {
534 auto Vec2ToAttrList = [&](const string& pAttrName, const aiVector2D& pAttrValue, const aiVector2D& pAttrDefaultValue)
535 {
536 string tstr;
537
538 if(pAttrValue != pAttrDefaultValue)
539 {
540 AttrHelper_Vec2DArrToString(&pAttrValue, 1, tstr);
541 attr_list.push_back({pAttrName, tstr});
542 }
543 };
544
545 aiUVTransform transform;
546
547 if(material.Get(AI_MATKEY_UVTRANSFORM_DIFFUSE(0), transform) == AI_SUCCESS)
548 {
549 Vec2ToAttrList("translation", transform.mTranslation, aiVector2D(0, 0));
550 AttrHelper_FloatToAttrList(attr_list, "rotation", transform.mRotation, 0);
551 Vec2ToAttrList("scale", transform.mScaling, aiVector2D(1, 1));
552
553 NodeHelper_OpenNode("TextureTransform", pTabLevel + 1, true, attr_list);
554 attr_list.clear();
555 }
556 }// "TextureTransform" node. END.
557
558 //
559 // Close opened nodes.
560 //
561 NodeHelper_CloseNode(NodeName_A, pTabLevel);
562
563}
564
565void X3DExporter::Export_MetadataBoolean(const aiString& pKey, const bool pValue, const size_t pTabLevel)
566{
567list<SAttribute> attr_list;
568
569 attr_list.push_back({"name", pKey.C_Str()});
570 attr_list.push_back({"value", pValue ? "true" : "false"});
571 NodeHelper_OpenNode("MetadataBoolean", pTabLevel, true, attr_list);
572}
573
574void X3DExporter::Export_MetadataDouble(const aiString& pKey, const double pValue, const size_t pTabLevel)
575{
576list<SAttribute> attr_list;
577
578 attr_list.push_back({"name", pKey.C_Str()});
579 attr_list.push_back({"value", to_string(pValue)});
580 NodeHelper_OpenNode("MetadataDouble", pTabLevel, true, attr_list);
581}
582
583void X3DExporter::Export_MetadataFloat(const aiString& pKey, const float pValue, const size_t pTabLevel)
584{
585list<SAttribute> attr_list;
586
587 attr_list.push_back({"name", pKey.C_Str()});
588 attr_list.push_back({"value", to_string(pValue)});
589 NodeHelper_OpenNode("MetadataFloat", pTabLevel, true, attr_list);
590}
591
592void X3DExporter::Export_MetadataInteger(const aiString& pKey, const int32_t pValue, const size_t pTabLevel)
593{
594list<SAttribute> attr_list;
595
596 attr_list.push_back({"name", pKey.C_Str()});
597 attr_list.push_back({"value", to_string(pValue)});
598 NodeHelper_OpenNode("MetadataInteger", pTabLevel, true, attr_list);
599}
600
601void X3DExporter::Export_MetadataString(const aiString& pKey, const aiString& pValue, const size_t pTabLevel)
602{
603list<SAttribute> attr_list;
604
605 attr_list.push_back({"name", pKey.C_Str()});
606 attr_list.push_back({"value", pValue.C_Str()});
607 NodeHelper_OpenNode("MetadataString", pTabLevel, true, attr_list);
608}
609
610bool X3DExporter::CheckAndExport_Light(const aiNode& pNode, const size_t pTabLevel)
611{
612list<SAttribute> attr_list;
613
614auto Vec3ToAttrList = [&](const string& pAttrName, const aiVector3D& pAttrValue, const aiVector3D& pAttrDefaultValue)
615{
616 string tstr;
617
618 if(pAttrValue != pAttrDefaultValue)
619 {
620 AttrHelper_Vec3DArrToString(&pAttrValue, 1, tstr);
621 attr_list.push_back({pAttrName, tstr});
622 }
623};
624
625size_t idx_light;
626bool found = false;
627
628 // Name of the light source can not be empty.
629 if(pNode.mName.length == 0) return false;
630
631 // search for light with name like node has.
632 for(idx_light = 0; mScene->mNumLights; idx_light++)
633 {
634 if(pNode.mName == mScene->mLights[idx_light]->mName)
635 {
636 found = true;
637 break;
638 }
639 }
640
641 if(!found) return false;
642
643 // Light source is found.
644 const aiLight& light = *mScene->mLights[idx_light];// Alias for conveniance.
645
646 aiMatrix4x4 trafo_mat = Matrix_GlobalToCurrent(pNode).Inverse();
647
648 attr_list.push_back({"DEF", light.mName.C_Str()});
649 attr_list.push_back({"global", "true"});// "false" is not supported.
650 // ambientIntensity="0" SFFloat [inputOutput]
651 AttrHelper_FloatToAttrList(attr_list, "ambientIntensity", aiVector3D(light.mColorAmbient.r, light.mColorAmbient.g, light.mColorAmbient.b).Length(), 0);
652 // color="1 1 1" SFColor [inputOutput]
653 AttrHelper_Color3ToAttrList(attr_list, "color", light.mColorDiffuse, aiColor3D(1, 1, 1));
654
655 switch(light.mType)
656 {
657 case aiLightSource_DIRECTIONAL:
658 {
659 aiVector3D direction = trafo_mat * light.mDirection;
660
661 Vec3ToAttrList("direction", direction, aiVector3D(0, 0, -1));
662 NodeHelper_OpenNode("DirectionalLight", pTabLevel, true, attr_list);
663 }
664
665 break;
666 case aiLightSource_POINT:
667 {
668 aiVector3D attenuation(light.mAttenuationConstant, light.mAttenuationLinear, light.mAttenuationQuadratic);
669 aiVector3D location = trafo_mat * light.mPosition;
670
671 Vec3ToAttrList("attenuation", attenuation, aiVector3D(1, 0, 0));
672 Vec3ToAttrList("location", location, aiVector3D(0, 0, 0));
673 NodeHelper_OpenNode("PointLight", pTabLevel, true, attr_list);
674 }
675
676 break;
677 case aiLightSource_SPOT:
678 {
679 aiVector3D attenuation(light.mAttenuationConstant, light.mAttenuationLinear, light.mAttenuationQuadratic);
680 aiVector3D location = trafo_mat * light.mPosition;
681 aiVector3D direction = trafo_mat * light.mDirection;
682
683 Vec3ToAttrList("attenuation", attenuation, aiVector3D(1, 0, 0));
684 Vec3ToAttrList("location", location, aiVector3D(0, 0, 0));
685 Vec3ToAttrList("direction", direction, aiVector3D(0, 0, -1));
686 AttrHelper_FloatToAttrList(attr_list, "beamWidth", light.mAngleInnerCone, 0.7854f);
687 AttrHelper_FloatToAttrList(attr_list, "cutOffAngle", light.mAngleOuterCone, 1.570796f);
688 NodeHelper_OpenNode("SpotLight", pTabLevel, true, attr_list);
689 }
690
691 break;
692 default:
693 throw DeadlyExportError("Unknown light type: " + to_string(light.mType));
694 }// switch(light.mType)
695
696 return true;
697}
698
699X3DExporter::X3DExporter(const char* pFileName, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/)
700 : mScene(pScene)
701{
702list<SAttribute> attr_list;
703
704 mOutFile = pIOSystem->Open(pFileName, "wt");
705 if(mOutFile == nullptr) throw DeadlyExportError("Could not open output .x3d file: " + string(pFileName));
706
707 // Begin document
708 XML_Write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
709 XML_Write("<!DOCTYPE X3D PUBLIC \"ISO//Web3D//DTD X3D 3.3//EN\" \"http://www.web3d.org/specifications/x3d-3.3.dtd\">\n");
710 // Root node
711 attr_list.push_back({"profile", "Interchange"});
712 attr_list.push_back({"version", "3.3"});
713 attr_list.push_back({"xmlns:xsd", "http://www.w3.org/2001/XMLSchema-instance"});
714 attr_list.push_back({"xsd:noNamespaceSchemaLocation", "http://www.web3d.org/specifications/x3d-3.3.xsd"});
715 NodeHelper_OpenNode("X3D", 0, false, attr_list);
716 attr_list.clear();
717 // <head>: meta data.
718 NodeHelper_OpenNode("head", 1);
719 XML_Write(mIndentationString + "<!-- All \"meta\" from this section tou will found in <Scene> node as MetadataString nodes. -->\n");
720 NodeHelper_CloseNode("head", 1);
721 // Scene node.
722 NodeHelper_OpenNode("Scene", 1);
723 Export_Node(mScene->mRootNode, 2);
724 NodeHelper_CloseNode("Scene", 1);
725 // Close Root node.
726 NodeHelper_CloseNode("X3D", 0);
727 // Cleanup
728 pIOSystem->Close(mOutFile);
729 mOutFile = nullptr;
730}
731
732}// namespace Assimp
733
734#endif // ASSIMP_BUILD_NO_X3D_EXPORTER
735#endif // ASSIMP_BUILD_NO_EXPORT
736