Coverage Report

Created: 2025-12-05 06:25

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