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