Coverage Report

Created: 2025-06-22 07:30

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