1/*
2---------------------------------------------------------------------------
3Open Asset Import Library (assimp)
4---------------------------------------------------------------------------
5
6Copyright (c) 2006-2017, assimp team
7
8
9All rights reserved.
10
11Redistribution and use of this software in source and binary forms,
12with or without modification, are permitted provided that the following
13conditions are met:
14
15* Redistributions of source code must retain the above
16copyright notice, this list of conditions and the
17following disclaimer.
18
19* Redistributions in binary form must reproduce the above
20copyright notice, this list of conditions and the
21following disclaimer in the documentation and/or other
22materials provided with the distribution.
23
24* Neither the name of the assimp team, nor the names of its
25contributors may be used to endorse or promote products
26derived from this software without specific prior
27written permission of the assimp team.
28
29THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40---------------------------------------------------------------------------
41*/
42
43/// \file AMFImporter_Postprocess.cpp
44/// \brief Convert built scenegraph and objects to Assimp scenegraph.
45/// \date 2016
46/// \author smal.root@gmail.com
47
48#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
49
50#include "AMFImporter.hpp"
51
52// Header files, Assimp.
53#include <assimp/SceneCombiner.h>
54#include "StandardShapes.h"
55#include "StringUtils.h"
56
57// Header files, stdlib.
58#include <iterator>
59
60namespace Assimp
61{
62
63aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /*pY*/, const float /*pZ*/) const
64{
65 aiColor4D tcol;
66
67 // Check if stored data are supported.
68 if(Composition.size() != 0)
69 {
70 throw DeadlyImportError("IME. GetColor for composition");
71 }
72 else if(Color->Composed)
73 {
74 throw DeadlyImportError("IME. GetColor, composed color");
75 }
76 else
77 {
78 tcol = Color->Color;
79 }
80
81 // Check if default color must be used
82 if((tcol.r == 0) && (tcol.g == 0) && (tcol.b == 0) && (tcol.a == 0))
83 {
84 tcol.r = 0.5f;
85 tcol.g = 0.5f;
86 tcol.b = 0.5f;
87 tcol.a = 1;
88 }
89
90 return tcol;
91}
92
93void AMFImporter::PostprocessHelper_CreateMeshDataArray(const CAMFImporter_NodeElement_Mesh& pNodeElement, std::vector<aiVector3D>& pVertexCoordinateArray,
94 std::vector<CAMFImporter_NodeElement_Color*>& pVertexColorArray) const
95{
96 CAMFImporter_NodeElement_Vertices* vn = nullptr;
97 size_t col_idx;
98
99 // All data stored in "vertices", search for it.
100 for(CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
101 {
102 if(ne_child->Type == CAMFImporter_NodeElement::ENET_Vertices) vn = (CAMFImporter_NodeElement_Vertices*)ne_child;
103 }
104
105 // If "vertices" not found then no work for us.
106 if(vn == nullptr) return;
107
108 pVertexCoordinateArray.reserve(vn->Child.size());// all coordinates stored as child and we need to reserve space for future push_back's.
109 pVertexColorArray.resize(vn->Child.size());// colors count equal vertices count.
110 col_idx = 0;
111 // Inside vertices collect all data and place to arrays
112 for(CAMFImporter_NodeElement* vn_child: vn->Child)
113 {
114 // vertices, colors
115 if(vn_child->Type == CAMFImporter_NodeElement::ENET_Vertex)
116 {
117 // by default clear color for current vertex
118 pVertexColorArray[col_idx] = nullptr;
119
120 for(CAMFImporter_NodeElement* vtx: vn_child->Child)
121 {
122 if(vtx->Type == CAMFImporter_NodeElement::ENET_Coordinates)
123 {
124 pVertexCoordinateArray.push_back(((CAMFImporter_NodeElement_Coordinates*)vtx)->Coordinate);
125
126 continue;
127 }
128
129 if(vtx->Type == CAMFImporter_NodeElement::ENET_Color)
130 {
131 pVertexColorArray[col_idx] = (CAMFImporter_NodeElement_Color*)vtx;
132
133 continue;
134 }
135 }// for(CAMFImporter_NodeElement* vtx: vn_child->Child)
136
137 col_idx++;
138 }// if(vn_child->Type == CAMFImporter_NodeElement::ENET_Vertex)
139 }// for(CAMFImporter_NodeElement* vn_child: vn->Child)
140}
141
142size_t AMFImporter::PostprocessHelper_GetTextureID_Or_Create(const std::string& pID_R, const std::string& pID_G, const std::string& pID_B,
143 const std::string& pID_A)
144{
145 size_t TextureConverted_Index;
146 std::string TextureConverted_ID;
147
148 // check input data
149 if(pID_R.empty() && pID_G.empty() && pID_B.empty() && pID_A.empty())
150 throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. At least one texture ID must be defined.");
151
152 // Create ID
153 TextureConverted_ID = pID_R + "_" + pID_G + "_" + pID_B + "_" + pID_A;
154 // Check if texture specified by set of IDs is converted already.
155 TextureConverted_Index = 0;
156 for(const SPP_Texture& tex_convd: mTexture_Converted)
157 {
158 if(tex_convd.ID == TextureConverted_ID)
159 return TextureConverted_Index;
160 else
161 TextureConverted_Index++;
162 }
163
164 //
165 // Converted texture not found, create it.
166 //
167 CAMFImporter_NodeElement_Texture* src_texture[4]{nullptr};
168 std::vector<CAMFImporter_NodeElement_Texture*> src_texture_4check;
169 SPP_Texture converted_texture;
170
171 {// find all specified source textures
172 CAMFImporter_NodeElement* t_tex;
173
174 // R
175 if(!pID_R.empty())
176 {
177 if(!Find_NodeElement(pID_R, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_R);
178
179 src_texture[0] = (CAMFImporter_NodeElement_Texture*)t_tex;
180 src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex);
181 }
182 else
183 {
184 src_texture[0] = nullptr;
185 }
186
187 // G
188 if(!pID_G.empty())
189 {
190 if(!Find_NodeElement(pID_G, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_G);
191
192 src_texture[1] = (CAMFImporter_NodeElement_Texture*)t_tex;
193 src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex);
194 }
195 else
196 {
197 src_texture[1] = nullptr;
198 }
199
200 // B
201 if(!pID_B.empty())
202 {
203 if(!Find_NodeElement(pID_B, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_B);
204
205 src_texture[2] = (CAMFImporter_NodeElement_Texture*)t_tex;
206 src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex);
207 }
208 else
209 {
210 src_texture[2] = nullptr;
211 }
212
213 // A
214 if(!pID_A.empty())
215 {
216 if(!Find_NodeElement(pID_A, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_A);
217
218 src_texture[3] = (CAMFImporter_NodeElement_Texture*)t_tex;
219 src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex);
220 }
221 else
222 {
223 src_texture[3] = nullptr;
224 }
225 }// END: find all specified source textures
226
227 // check that all textures has same size
228 if(src_texture_4check.size() > 1)
229 {
230 for (size_t i = 0, i_e = (src_texture_4check.size() - 1); i < i_e; i++)
231 {
232 if((src_texture_4check[i]->Width != src_texture_4check[i + 1]->Width) || (src_texture_4check[i]->Height != src_texture_4check[i + 1]->Height) ||
233 (src_texture_4check[i]->Depth != src_texture_4check[i + 1]->Depth))
234 {
235 throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. Source texture must has the same size.");
236 }
237 }
238 }// if(src_texture_4check.size() > 1)
239
240 // set texture attributes
241 converted_texture.Width = src_texture_4check[0]->Width;
242 converted_texture.Height = src_texture_4check[0]->Height;
243 converted_texture.Depth = src_texture_4check[0]->Depth;
244 // if one of source texture is tiled then converted texture is tiled too.
245 converted_texture.Tiled = false;
246 for(uint8_t i = 0; i < src_texture_4check.size(); i++) converted_texture.Tiled |= src_texture_4check[i]->Tiled;
247
248 // Create format hint.
249 strcpy(converted_texture.FormatHint, "rgba0000");// copy initial string.
250 if(!pID_R.empty()) converted_texture.FormatHint[4] = '8';
251 if(!pID_G.empty()) converted_texture.FormatHint[5] = '8';
252 if(!pID_B.empty()) converted_texture.FormatHint[6] = '8';
253 if(!pID_A.empty()) converted_texture.FormatHint[7] = '8';
254
255 //
256 // Сopy data of textures.
257 //
258 size_t tex_size = 0;
259 size_t step = 0;
260 size_t off_g = 0;
261 size_t off_b = 0;
262
263 // Calculate size of the target array and rule how data will be copied.
264 if(!pID_R.empty() && nullptr != src_texture[ 0 ] ) {
265 tex_size += src_texture[0]->Data.size(); step++, off_g++, off_b++;
266 }
267 if(!pID_G.empty() && nullptr != src_texture[ 1 ] ) {
268 tex_size += src_texture[1]->Data.size(); step++, off_b++;
269 }
270 if(!pID_B.empty() && nullptr != src_texture[ 2 ] ) {
271 tex_size += src_texture[2]->Data.size(); step++;
272 }
273 if(!pID_A.empty() && nullptr != src_texture[ 3 ] ) {
274 tex_size += src_texture[3]->Data.size(); step++;
275 }
276
277 // Create target array.
278 converted_texture.Data = new uint8_t[tex_size];
279 // And copy data
280 auto CopyTextureData = [&](const std::string& pID, const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void
281 {
282 if(!pID.empty())
283 {
284 for(size_t idx_target = pOffset, idx_src = 0; idx_target < tex_size; idx_target += pStep, idx_src++) {
285 CAMFImporter_NodeElement_Texture* tex = src_texture[pSrcTexNum];
286 ai_assert(tex);
287 converted_texture.Data[idx_target] = tex->Data.at(idx_src);
288 }
289 }
290 };// auto CopyTextureData = [&](const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void
291
292 CopyTextureData(pID_R, 0, step, 0);
293 CopyTextureData(pID_G, off_g, step, 1);
294 CopyTextureData(pID_B, off_b, step, 2);
295 CopyTextureData(pID_A, step - 1, step, 3);
296
297 // Store new converted texture ID
298 converted_texture.ID = TextureConverted_ID;
299 // Store new converted texture
300 mTexture_Converted.push_back(converted_texture);
301
302 return TextureConverted_Index;
303}
304
305void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace>& pInputList, std::list<std::list<SComplexFace> >& pOutputList_Separated)
306{
307 auto texmap_is_equal = [](const CAMFImporter_NodeElement_TexMap* pTexMap1, const CAMFImporter_NodeElement_TexMap* pTexMap2) -> bool
308 {
309 if((pTexMap1 == nullptr) && (pTexMap2 == nullptr)) return true;
310 if(pTexMap1 == nullptr) return false;
311 if(pTexMap2 == nullptr) return false;
312
313 if(pTexMap1->TextureID_R != pTexMap2->TextureID_R) return false;
314 if(pTexMap1->TextureID_G != pTexMap2->TextureID_G) return false;
315 if(pTexMap1->TextureID_B != pTexMap2->TextureID_B) return false;
316 if(pTexMap1->TextureID_A != pTexMap2->TextureID_A) return false;
317
318 return true;
319 };
320
321 pOutputList_Separated.clear();
322 if(pInputList.size() == 0) return;
323
324 do
325 {
326 SComplexFace face_start = pInputList.front();
327 std::list<SComplexFace> face_list_cur;
328
329 for(std::list<SComplexFace>::iterator it = pInputList.begin(), it_end = pInputList.end(); it != it_end;)
330 {
331 if(texmap_is_equal(face_start.TexMap, it->TexMap))
332 {
333 auto it_old = it;
334
335 it++;
336 face_list_cur.push_back(*it_old);
337 pInputList.erase(it_old);
338 }
339 else
340 {
341 it++;
342 }
343 }
344
345 if(face_list_cur.size() > 0) pOutputList_Separated.push_back(face_list_cur);
346
347 } while(pInputList.size() > 0);
348}
349
350void AMFImporter::Postprocess_AddMetadata(const std::list<CAMFImporter_NodeElement_Metadata*>& metadataList, aiNode& sceneNode) const
351{
352 if ( !metadataList.empty() )
353 {
354 if(sceneNode.mMetaData != nullptr) throw DeadlyImportError("Postprocess. MetaData member in node are not nullptr. Something went wrong.");
355
356 // copy collected metadata to output node.
357 sceneNode.mMetaData = aiMetadata::Alloc( static_cast<unsigned int>(metadataList.size()) );
358 size_t meta_idx( 0 );
359
360 for(const CAMFImporter_NodeElement_Metadata& metadata: metadataList)
361 {
362 sceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx++), metadata.Type, aiString(metadata.Value));
363 }
364 }// if(!metadataList.empty())
365}
366
367void AMFImporter::Postprocess_BuildNodeAndObject(const CAMFImporter_NodeElement_Object& pNodeElement, std::list<aiMesh*>& pMeshList, aiNode** pSceneNode)
368{
369CAMFImporter_NodeElement_Color* object_color = nullptr;
370
371 // create new aiNode and set name as <object> has.
372 *pSceneNode = new aiNode;
373 (*pSceneNode)->mName = pNodeElement.ID;
374 // read mesh and color
375 for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
376 {
377 std::vector<aiVector3D> vertex_arr;
378 std::vector<CAMFImporter_NodeElement_Color*> color_arr;
379
380 // color for object
381 if(ne_child->Type == CAMFImporter_NodeElement::ENET_Color) object_color = (CAMFImporter_NodeElement_Color*)ne_child;
382
383 if(ne_child->Type == CAMFImporter_NodeElement::ENET_Mesh)
384 {
385 // Create arrays from children of mesh: vertices.
386 PostprocessHelper_CreateMeshDataArray(*((CAMFImporter_NodeElement_Mesh*)ne_child), vertex_arr, color_arr);
387 // Use this arrays as a source when creating every aiMesh
388 Postprocess_BuildMeshSet(*((CAMFImporter_NodeElement_Mesh*)ne_child), vertex_arr, color_arr, object_color, pMeshList, **pSceneNode);
389 }
390 }// for(const CAMFImporter_NodeElement* ne_child: pNodeElement)
391}
392
393void AMFImporter::Postprocess_BuildMeshSet(const CAMFImporter_NodeElement_Mesh& pNodeElement, const std::vector<aiVector3D>& pVertexCoordinateArray,
394 const std::vector<CAMFImporter_NodeElement_Color*>& pVertexColorArray,
395 const CAMFImporter_NodeElement_Color* pObjectColor, std::list<aiMesh*>& pMeshList, aiNode& pSceneNode)
396{
397std::list<unsigned int> mesh_idx;
398
399 // all data stored in "volume", search for it.
400 for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
401 {
402 const CAMFImporter_NodeElement_Color* ne_volume_color = nullptr;
403 const SPP_Material* cur_mat = nullptr;
404
405 if(ne_child->Type == CAMFImporter_NodeElement::ENET_Volume)
406 {
407 /******************* Get faces *******************/
408 const CAMFImporter_NodeElement_Volume* ne_volume = reinterpret_cast<const CAMFImporter_NodeElement_Volume*>(ne_child);
409
410 std::list<SComplexFace> complex_faces_list;// List of the faces of the volume.
411 std::list<std::list<SComplexFace> > complex_faces_toplist;// List of the face list for every mesh.
412
413 // check if volume use material
414 if(!ne_volume->MaterialID.empty())
415 {
416 if(!Find_ConvertedMaterial(ne_volume->MaterialID, &cur_mat)) Throw_ID_NotFound(ne_volume->MaterialID);
417 }
418
419 // inside "volume" collect all data and place to arrays or create new objects
420 for(const CAMFImporter_NodeElement* ne_volume_child: ne_volume->Child)
421 {
422 // color for volume
423 if(ne_volume_child->Type == CAMFImporter_NodeElement::ENET_Color)
424 {
425 ne_volume_color = reinterpret_cast<const CAMFImporter_NodeElement_Color*>(ne_volume_child);
426 }
427 else if(ne_volume_child->Type == CAMFImporter_NodeElement::ENET_Triangle)// triangles, triangles colors
428 {
429 const CAMFImporter_NodeElement_Triangle& tri_al = *reinterpret_cast<const CAMFImporter_NodeElement_Triangle*>(ne_volume_child);
430
431 SComplexFace complex_face;
432
433 // initialize pointers
434 complex_face.Color = nullptr;
435 complex_face.TexMap = nullptr;
436 // get data from triangle children: color, texture coordinates.
437 if(tri_al.Child.size())
438 {
439 for(const CAMFImporter_NodeElement* ne_triangle_child: tri_al.Child)
440 {
441 if(ne_triangle_child->Type == CAMFImporter_NodeElement::ENET_Color)
442 complex_face.Color = reinterpret_cast<const CAMFImporter_NodeElement_Color*>(ne_triangle_child);
443 else if(ne_triangle_child->Type == CAMFImporter_NodeElement::ENET_TexMap)
444 complex_face.TexMap = reinterpret_cast<const CAMFImporter_NodeElement_TexMap*>(ne_triangle_child);
445 }
446 }// if(tri_al.Child.size())
447
448 // create new face and store it.
449 complex_face.Face.mNumIndices = 3;
450 complex_face.Face.mIndices = new unsigned int[3];
451 complex_face.Face.mIndices[0] = static_cast<unsigned int>(tri_al.V[0]);
452 complex_face.Face.mIndices[1] = static_cast<unsigned int>(tri_al.V[1]);
453 complex_face.Face.mIndices[2] = static_cast<unsigned int>(tri_al.V[2]);
454 complex_faces_list.push_back(complex_face);
455 }
456 }// for(const CAMFImporter_NodeElement* ne_volume_child: ne_volume->Child)
457
458 /**** Split faces list: one list per mesh ****/
459 PostprocessHelper_SplitFacesByTextureID(complex_faces_list, complex_faces_toplist);
460
461 /***** Create mesh for every faces list ******/
462 for(std::list<SComplexFace>& face_list_cur: complex_faces_toplist)
463 {
464 auto VertexIndex_GetMinimal = [](const std::list<SComplexFace>& pFaceList, const size_t* pBiggerThan) -> size_t
465 {
466 size_t rv;
467
468 if(pBiggerThan != nullptr)
469 {
470 bool found = false;
471
472 for(const SComplexFace& face: pFaceList)
473 {
474 for(size_t idx_vert = 0; idx_vert < face.Face.mNumIndices; idx_vert++)
475 {
476 if(face.Face.mIndices[idx_vert] > *pBiggerThan)
477 {
478 rv = face.Face.mIndices[idx_vert];
479 found = true;
480
481 break;
482 }
483 }
484
485 if(found) break;
486 }
487
488 if(!found) return *pBiggerThan;
489 }
490 else
491 {
492 rv = pFaceList.front().Face.mIndices[0];
493 }// if(pBiggerThan != nullptr) else
494
495 for(const SComplexFace& face: pFaceList)
496 {
497 for(size_t vi = 0; vi < face.Face.mNumIndices; vi++)
498 {
499 if(face.Face.mIndices[vi] < rv)
500 {
501 if(pBiggerThan != nullptr)
502 {
503 if(face.Face.mIndices[vi] > *pBiggerThan) rv = face.Face.mIndices[vi];
504 }
505 else
506 {
507 rv = face.Face.mIndices[vi];
508 }
509 }
510 }
511 }// for(const SComplexFace& face: pFaceList)
512
513 return rv;
514 };// auto VertexIndex_GetMinimal = [](const std::list<SComplexFace>& pFaceList, const size_t* pBiggerThan) -> size_t
515
516 auto VertexIndex_Replace = [](std::list<SComplexFace>& pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void
517 {
518 for(const SComplexFace& face: pFaceList)
519 {
520 for(size_t vi = 0; vi < face.Face.mNumIndices; vi++)
521 {
522 if(face.Face.mIndices[vi] == pIdx_From) face.Face.mIndices[vi] = static_cast<unsigned int>(pIdx_To);
523 }
524 }
525 };// auto VertexIndex_Replace = [](std::list<SComplexFace>& pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void
526
527 auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D
528 {
529 // Color priorities(In descending order):
530 // 1. triangle color;
531 // 2. vertex color;
532 // 3. volume color;
533 // 4. object color;
534 // 5. material;
535 // 6. default - invisible coat.
536 //
537 // Fill vertices colors in color priority list above that's points from 1 to 6.
538 if((pIdx < pVertexColorArray.size()) && (pVertexColorArray[pIdx] != nullptr))// check for vertex color
539 {
540 if(pVertexColorArray[pIdx]->Composed)
541 throw DeadlyImportError("IME: vertex color composed");
542 else
543 return pVertexColorArray[pIdx]->Color;
544 }
545 else if(ne_volume_color != nullptr)// check for volume color
546 {
547 if(ne_volume_color->Composed)
548 throw DeadlyImportError("IME: volume color composed");
549 else
550 return ne_volume_color->Color;
551 }
552 else if(pObjectColor != nullptr)// check for object color
553 {
554 if(pObjectColor->Composed)
555 throw DeadlyImportError("IME: object color composed");
556 else
557 return pObjectColor->Color;
558 }
559 else if(cur_mat != nullptr)// check for material
560 {
561 return cur_mat->GetColor(pVertexCoordinateArray.at(pIdx).x, pVertexCoordinateArray.at(pIdx).y, pVertexCoordinateArray.at(pIdx).z);
562 }
563 else// set default color.
564 {
565 return {0, 0, 0, 0};
566 }// if((vi < pVertexColorArray.size()) && (pVertexColorArray[vi] != nullptr)) else
567
568 };// auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D
569
570 aiMesh* tmesh = new aiMesh;
571
572 tmesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;// Only triangles is supported by AMF.
573 //
574 // set geometry and colors (vertices)
575 //
576 // copy faces/triangles
577 tmesh->mNumFaces = static_cast<unsigned int>(face_list_cur.size());
578 tmesh->mFaces = new aiFace[tmesh->mNumFaces];
579
580 // Create vertices list and optimize indices. Optimisation mean following.In AMF all volumes use one big list of vertices. And one volume
581 // can use only part of vertices list, for example: vertices list contain few thousands of vertices and volume use vertices 1, 3, 10.
582 // Do you need all this thousands of garbage? Of course no. So, optimisation step transformate sparse indices set to continuous.
583 size_t VertexCount_Max = tmesh->mNumFaces * 3;// 3 - triangles.
584 std::vector<aiVector3D> vert_arr, texcoord_arr;
585 std::vector<aiColor4D> col_arr;
586
587 vert_arr.reserve(VertexCount_Max * 2);// "* 2" - see below TODO.
588 col_arr.reserve(VertexCount_Max * 2);
589
590 {// fill arrays
591 size_t vert_idx_from, vert_idx_to;
592
593 // first iteration.
594 vert_idx_to = 0;
595 vert_idx_from = VertexIndex_GetMinimal(face_list_cur, nullptr);
596 vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from));
597 col_arr.push_back(Vertex_CalculateColor(vert_idx_from));
598 if(vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to);
599
600 // rest iterations
601 do
602 {
603 vert_idx_from = VertexIndex_GetMinimal(face_list_cur, &vert_idx_to);
604 if(vert_idx_from == vert_idx_to) break;// all indices are transferred,
605
606 vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from));
607 col_arr.push_back(Vertex_CalculateColor(vert_idx_from));
608 vert_idx_to++;
609 if(vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to);
610
611 } while(true);
612 }// fill arrays. END.
613
614 //
615 // check if triangle colors are used and create additional faces if needed.
616 //
617 for(const SComplexFace& face_cur: face_list_cur)
618 {
619 if(face_cur.Color != nullptr)
620 {
621 aiColor4D face_color;
622 size_t vert_idx_new = vert_arr.size();
623
624 if(face_cur.Color->Composed)
625 throw DeadlyImportError("IME: face color composed");
626 else
627 face_color = face_cur.Color->Color;
628
629 for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++)
630 {
631 vert_arr.push_back(vert_arr.at(face_cur.Face.mIndices[idx_ind]));
632 col_arr.push_back(face_color);
633 face_cur.Face.mIndices[idx_ind] = static_cast<unsigned int>(vert_idx_new++);
634 }
635 }// if(face_cur.Color != nullptr)
636 }// for(const SComplexFace& face_cur: face_list_cur)
637
638 //
639 // if texture is used then copy texture coordinates too.
640 //
641 if(face_list_cur.front().TexMap != nullptr)
642 {
643 size_t idx_vert_new = vert_arr.size();
644 ///TODO: clean unused vertices. "* 2": in certain cases - mesh full of triangle colors - vert_arr will contain duplicated vertices for
645 /// colored triangles and initial vertices (for colored vertices) which in real became unused. This part need more thinking about
646 /// optimisation.
647 bool* idx_vert_used;
648
649 idx_vert_used = new bool[VertexCount_Max * 2];
650 for(size_t i = 0, i_e = VertexCount_Max * 2; i < i_e; i++) idx_vert_used[i] = false;
651
652 // This ID's will be used when set materials ID in scene.
653 tmesh->mMaterialIndex = static_cast<unsigned int>(PostprocessHelper_GetTextureID_Or_Create(face_list_cur.front().TexMap->TextureID_R,
654 face_list_cur.front().TexMap->TextureID_G,
655 face_list_cur.front().TexMap->TextureID_B,
656 face_list_cur.front().TexMap->TextureID_A));
657 texcoord_arr.resize(VertexCount_Max * 2);
658 for(const SComplexFace& face_cur: face_list_cur)
659 {
660 for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++)
661 {
662 const size_t idx_vert = face_cur.Face.mIndices[idx_ind];
663
664 if(!idx_vert_used[idx_vert])
665 {
666 texcoord_arr.at(idx_vert) = face_cur.TexMap->TextureCoordinate[idx_ind];
667 idx_vert_used[idx_vert] = true;
668 }
669 else if(texcoord_arr.at(idx_vert) != face_cur.TexMap->TextureCoordinate[idx_ind])
670 {
671 // in that case one vertex is shared with many texture coordinates. We need to duplicate vertex with another texture
672 // coordinates.
673 vert_arr.push_back(vert_arr.at(idx_vert));
674 col_arr.push_back(col_arr.at(idx_vert));
675 texcoord_arr.at(idx_vert_new) = face_cur.TexMap->TextureCoordinate[idx_ind];
676 face_cur.Face.mIndices[idx_ind] = static_cast<unsigned int>(idx_vert_new++);
677 }
678 }// for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++)
679 }// for(const SComplexFace& face_cur: face_list_cur)
680
681 delete [] idx_vert_used;
682 // shrink array
683 texcoord_arr.resize(idx_vert_new);
684 }// if(face_list_cur.front().TexMap != nullptr)
685
686 //
687 // copy collected data to mesh
688 //
689 tmesh->mNumVertices = static_cast<unsigned int>(vert_arr.size());
690 tmesh->mVertices = new aiVector3D[tmesh->mNumVertices];
691 tmesh->mColors[0] = new aiColor4D[tmesh->mNumVertices];
692
693 memcpy(tmesh->mVertices, vert_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D));
694 memcpy(tmesh->mColors[0], col_arr.data(), tmesh->mNumVertices * sizeof(aiColor4D));
695 if(texcoord_arr.size() > 0)
696 {
697 tmesh->mTextureCoords[0] = new aiVector3D[tmesh->mNumVertices];
698 memcpy(tmesh->mTextureCoords[0], texcoord_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D));
699 tmesh->mNumUVComponents[0] = 2;// U and V stored in "x", "y" of aiVector3D.
700 }
701
702 size_t idx_face = 0;
703 for(const SComplexFace& face_cur: face_list_cur) tmesh->mFaces[idx_face++] = face_cur.Face;
704
705 // store new aiMesh
706 mesh_idx.push_back(static_cast<unsigned int>(pMeshList.size()));
707 pMeshList.push_back(tmesh);
708 }// for(const std::list<SComplexFace>& face_list_cur: complex_faces_toplist)
709 }// if(ne_child->Type == CAMFImporter_NodeElement::ENET_Volume)
710 }// for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
711
712 // if meshes was created then assign new indices with current aiNode
713 if(mesh_idx.size() > 0)
714 {
715 std::list<unsigned int>::const_iterator mit = mesh_idx.begin();
716
717 pSceneNode.mNumMeshes = static_cast<unsigned int>(mesh_idx.size());
718 pSceneNode.mMeshes = new unsigned int[pSceneNode.mNumMeshes];
719 for(size_t i = 0; i < pSceneNode.mNumMeshes; i++) pSceneNode.mMeshes[i] = *mit++;
720 }// if(mesh_idx.size() > 0)
721}
722
723void AMFImporter::Postprocess_BuildMaterial(const CAMFImporter_NodeElement_Material& pMaterial)
724{
725SPP_Material new_mat;
726
727 new_mat.ID = pMaterial.ID;
728 for(const CAMFImporter_NodeElement* mat_child: pMaterial.Child)
729 {
730 if(mat_child->Type == CAMFImporter_NodeElement::ENET_Color)
731 {
732 new_mat.Color = (CAMFImporter_NodeElement_Color*)mat_child;
733 }
734 else if(mat_child->Type == CAMFImporter_NodeElement::ENET_Metadata)
735 {
736 new_mat.Metadata.push_back((CAMFImporter_NodeElement_Metadata*)mat_child);
737 }
738 }// for(const CAMFImporter_NodeElement* mat_child; pMaterial.Child)
739
740 // place converted material to special list
741 mMaterial_Converted.push_back(new_mat);
742}
743
744void AMFImporter::Postprocess_BuildConstellation(CAMFImporter_NodeElement_Constellation& pConstellation, std::list<aiNode*>& pNodeList) const
745{
746aiNode* con_node;
747std::list<aiNode*> ch_node;
748
749 // We will build next hierarchy:
750 // aiNode as parent (<constellation>) for set of nodes as a children
751 // |- aiNode for transformation (<instance> -> <delta...>, <r...>) - aiNode for pointing to object ("objectid")
752 // ...
753 // \_ aiNode for transformation (<instance> -> <delta...>, <r...>) - aiNode for pointing to object ("objectid")
754 con_node = new aiNode;
755 con_node->mName = pConstellation.ID;
756 // Walk through children and search for instances of another objects, constellations.
757 for(const CAMFImporter_NodeElement* ne: pConstellation.Child)
758 {
759 aiMatrix4x4 tmat;
760 aiNode* t_node;
761 aiNode* found_node;
762
763 if(ne->Type == CAMFImporter_NodeElement::ENET_Metadata) continue;
764 if(ne->Type != CAMFImporter_NodeElement::ENET_Instance) throw DeadlyImportError("Only <instance> nodes can be in <constellation>.");
765
766 // create alias for conveniance
767 CAMFImporter_NodeElement_Instance& als = *((CAMFImporter_NodeElement_Instance*)ne);
768 // find referenced object
769 if(!Find_ConvertedNode(als.ObjectID, pNodeList, &found_node)) Throw_ID_NotFound(als.ObjectID);
770
771 // create node for apllying transformation
772 t_node = new aiNode;
773 t_node->mParent = con_node;
774 // apply transformation
775 aiMatrix4x4::Translation(als.Delta, tmat), t_node->mTransformation *= tmat;
776 aiMatrix4x4::RotationX(als.Rotation.x, tmat), t_node->mTransformation *= tmat;
777 aiMatrix4x4::RotationY(als.Rotation.y, tmat), t_node->mTransformation *= tmat;
778 aiMatrix4x4::RotationZ(als.Rotation.z, tmat), t_node->mTransformation *= tmat;
779 // create array for one child node
780 t_node->mNumChildren = 1;
781 t_node->mChildren = new aiNode*[t_node->mNumChildren];
782 SceneCombiner::Copy(&t_node->mChildren[0], found_node);
783 t_node->mChildren[0]->mParent = t_node;
784 ch_node.push_back(t_node);
785 }// for(const CAMFImporter_NodeElement* ne: pConstellation.Child)
786
787 // copy found aiNode's as children
788 if(ch_node.size() == 0) throw DeadlyImportError("<constellation> must have at least one <instance>.");
789
790 size_t ch_idx = 0;
791
792 con_node->mNumChildren = static_cast<unsigned int>(ch_node.size());
793 con_node->mChildren = new aiNode*[con_node->mNumChildren];
794 for(aiNode* node: ch_node) con_node->mChildren[ch_idx++] = node;
795
796 // and place "root" of <constellation> node to node list
797 pNodeList.push_back(con_node);
798}
799
800void AMFImporter::Postprocess_BuildScene(aiScene* pScene)
801{
802std::list<aiNode*> node_list;
803std::list<aiMesh*> mesh_list;
804std::list<CAMFImporter_NodeElement_Metadata*> meta_list;
805
806 //
807 // Because for AMF "material" is just complex colors mixing so aiMaterial will not be used.
808 // For building aiScene we are must to do few steps:
809 // at first creating root node for aiScene.
810 pScene->mRootNode = new aiNode;
811 pScene->mRootNode->mParent = nullptr;
812 pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED;
813 // search for root(<amf>) element
814 CAMFImporter_NodeElement* root_el = nullptr;
815
816 for(CAMFImporter_NodeElement* ne: mNodeElement_List)
817 {
818 if(ne->Type != CAMFImporter_NodeElement::ENET_Root) continue;
819
820 root_el = ne;
821
822 break;
823 }// for(const CAMFImporter_NodeElement* ne: mNodeElement_List)
824
825 // Check if root element are found.
826 if(root_el == nullptr) throw DeadlyImportError("Root(<amf>) element not found.");
827
828 // after that walk through children of root and collect data. Five types of nodes can be placed at top level - in <amf>: <object>, <material>, <texture>,
829 // <constellation> and <metadata>. But at first we must read <material> and <texture> because they will be used in <object>. <metadata> can be read
830 // at any moment.
831 //
832 // 1. <material>
833 // 2. <texture> will be converted later when processing triangles list. \sa Postprocess_BuildMeshSet
834 for(const CAMFImporter_NodeElement* root_child: root_el->Child)
835 {
836 if(root_child->Type == CAMFImporter_NodeElement::ENET_Material) Postprocess_BuildMaterial(*((CAMFImporter_NodeElement_Material*)root_child));
837 }
838
839 // After "appearance" nodes we must read <object> because it will be used in <constellation> -> <instance>.
840 //
841 // 3. <object>
842 for(const CAMFImporter_NodeElement* root_child: root_el->Child)
843 {
844 if(root_child->Type == CAMFImporter_NodeElement::ENET_Object)
845 {
846 aiNode* tnode = nullptr;
847
848 // for <object> mesh and node must be built: object ID assigned to aiNode name and will be used in future for <instance>
849 Postprocess_BuildNodeAndObject(*((CAMFImporter_NodeElement_Object*)root_child), mesh_list, &tnode);
850 if(tnode != nullptr) node_list.push_back(tnode);
851
852 }
853 }// for(const CAMFImporter_NodeElement* root_child: root_el->Child)
854
855 // And finally read rest of nodes.
856 //
857 for(const CAMFImporter_NodeElement* root_child: root_el->Child)
858 {
859 // 4. <constellation>
860 if(root_child->Type == CAMFImporter_NodeElement::ENET_Constellation)
861 {
862 // <object> and <constellation> at top of self abstraction use aiNode. So we can use only aiNode list for creating new aiNode's.
863 Postprocess_BuildConstellation(*((CAMFImporter_NodeElement_Constellation*)root_child), node_list);
864 }
865
866 // 5, <metadata>
867 if(root_child->Type == CAMFImporter_NodeElement::ENET_Metadata) meta_list.push_back((CAMFImporter_NodeElement_Metadata*)root_child);
868 }// for(const CAMFImporter_NodeElement* root_child: root_el->Child)
869
870 // at now we can add collected metadata to root node
871 Postprocess_AddMetadata(meta_list, *pScene->mRootNode);
872 //
873 // Check constellation children
874 //
875 // As said in specification:
876 // "When multiple objects and constellations are defined in a single file, only the top level objects and constellations are available for printing."
877 // What that means? For example: if some object is used in constellation then you must show only constellation but not original object.
878 // And at this step we are checking that relations.
879nl_clean_loop:
880
881 if(node_list.size() > 1)
882 {
883 // walk through all nodes
884 for(std::list<aiNode*>::iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++)
885 {
886 // and try to find them in another top nodes.
887 std::list<aiNode*>::const_iterator next_it = nl_it;
888
889 next_it++;
890 for(; next_it != node_list.end(); next_it++)
891 {
892 if((*next_it)->FindNode((*nl_it)->mName) != nullptr)
893 {
894 // if current top node(nl_it) found in another top node then erase it from node_list and restart search loop.
895 node_list.erase(nl_it);
896
897 goto nl_clean_loop;
898 }
899 }// for(; next_it != node_list.end(); next_it++)
900 }// for(std::list<aiNode*>::const_iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++)
901 }
902
903 //
904 // move created objects to aiScene
905 //
906 //
907 // Nodes
908 if(node_list.size() > 0)
909 {
910 std::list<aiNode*>::const_iterator nl_it = node_list.begin();
911
912 pScene->mRootNode->mNumChildren = static_cast<unsigned int>(node_list.size());
913 pScene->mRootNode->mChildren = new aiNode*[pScene->mRootNode->mNumChildren];
914 for(size_t i = 0; i < pScene->mRootNode->mNumChildren; i++)
915 {
916 // Objects and constellation that must be showed placed at top of hierarchy in <amf> node. So all aiNode's in node_list must have
917 // mRootNode only as parent.
918 (*nl_it)->mParent = pScene->mRootNode;
919 pScene->mRootNode->mChildren[i] = *nl_it++;
920 }
921 }// if(node_list.size() > 0)
922
923 //
924 // Meshes
925 if(mesh_list.size() > 0)
926 {
927 std::list<aiMesh*>::const_iterator ml_it = mesh_list.begin();
928
929 pScene->mNumMeshes = static_cast<unsigned int>(mesh_list.size());
930 pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
931 for(size_t i = 0; i < pScene->mNumMeshes; i++) pScene->mMeshes[i] = *ml_it++;
932 }// if(mesh_list.size() > 0)
933
934 //
935 // Textures
936 pScene->mNumTextures = static_cast<unsigned int>(mTexture_Converted.size());
937 if(pScene->mNumTextures > 0)
938 {
939 size_t idx;
940
941 idx = 0;
942 pScene->mTextures = new aiTexture*[pScene->mNumTextures];
943 for(const SPP_Texture& tex_convd: mTexture_Converted)
944 {
945 pScene->mTextures[idx] = new aiTexture;
946 pScene->mTextures[idx]->mWidth = static_cast<unsigned int>(tex_convd.Width);
947 pScene->mTextures[idx]->mHeight = static_cast<unsigned int>(tex_convd.Height);
948 pScene->mTextures[idx]->pcData = (aiTexel*)tex_convd.Data;
949 // texture format description.
950 strcpy(pScene->mTextures[idx]->achFormatHint, tex_convd.FormatHint);
951 idx++;
952 }// for(const SPP_Texture& tex_convd: mTexture_Converted)
953
954 // Create materials for embedded textures.
955 idx = 0;
956 pScene->mNumMaterials = static_cast<unsigned int>(mTexture_Converted.size());
957 pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials];
958 for(const SPP_Texture& tex_convd: mTexture_Converted)
959 {
960 const aiString texture_id(AI_EMBEDDED_TEXNAME_PREFIX + to_string(idx));
961 const int mode = aiTextureOp_Multiply;
962 const int repeat = tex_convd.Tiled ? 1 : 0;
963
964 pScene->mMaterials[idx] = new aiMaterial;
965 pScene->mMaterials[idx]->AddProperty(&texture_id, AI_MATKEY_TEXTURE_DIFFUSE(0));
966 pScene->mMaterials[idx]->AddProperty(&mode, 1, AI_MATKEY_TEXOP_DIFFUSE(0));
967 pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0));
968 pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0));
969 idx++;
970 }
971 }// if(pScene->mNumTextures > 0)
972}// END: after that walk through children of root and collect data
973
974}// namespace Assimp
975
976#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER
977