Coverage Report

Created: 2026-10-28 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/3MF/XmlSerializer.cpp
Line
Count
Source
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2026, assimp team
6
7
All rights reserved.
8
9
Redistribution and use of this software in source and binary forms,
10
with or without modification, are permitted provided that the
11
following conditions are met:
12
13
* Redistributions of source code must retain the above
14
  copyright notice, this list of conditions and the
15
  following disclaimer.
16
17
* Redistributions in binary form must reproduce the above
18
  copyright notice, this list of conditions and the
19
  following disclaimer in the documentation and/or other
20
  materials provided with the distribution.
21
22
* Neither the name of the assimp team, nor the names of its
23
  contributors may be used to endorse or promote products
24
  derived from this software without specific prior
25
  written permission of the assimp team.
26
27
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39
----------------------------------------------------------------------
40
*/
41
#include "XmlSerializer.h"
42
#include "D3MFOpcPackage.h"
43
#include "3MFXmlTags.h"
44
#include "3MFTypes.h"
45
#include <assimp/scene.h>
46
#include <assimp/DefaultLogger.hpp>
47
48
#include <set>
49
#include <utility>
50
51
namespace Assimp {
52
namespace D3MF {
53
54
static constexpr int IdNotSet = -1;
55
56
namespace {
57
58
static constexpr size_t ColRGBA_Len = 9;
59
static constexpr size_t ColRGB_Len = 7;
60
61
// format of the color string: #RRGGBBAA or #RRGGBB (3MF Core chapter 5.1.1)
62
1
bool validateColorString(const std::string color) {
63
1
    const size_t len = color.size();
64
1
    if (ColRGBA_Len != len && ColRGB_Len != len) {
65
0
        return false;
66
0
    }
67
68
1
    return true;
69
1
}
70
71
2.41M
aiFace ReadTriangle(XmlNode &node, int &texId0, int &texId1, int &texId2) {
72
2.41M
    aiFace face;
73
74
2.41M
    face.mNumIndices = 3;
75
2.41M
    face.mIndices = new unsigned int[face.mNumIndices];
76
2.41M
    face.mIndices[0] = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::v1).as_string()));
77
2.41M
    face.mIndices[1] = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::v2).as_string()));
78
2.41M
    face.mIndices[2] = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::v3).as_string()));
79
80
2.41M
    texId0 = texId1 = texId2 = IdNotSet;
81
2.41M
    XmlParser::getIntAttribute(node, XmlTag::p1, texId0);
82
2.41M
    XmlParser::getIntAttribute(node, XmlTag::p2, texId1);
83
2.41M
    XmlParser::getIntAttribute(node, XmlTag::p3, texId2);
84
85
2.41M
    return face;
86
2.41M
}
87
88
1.20M
aiVector3D ReadVertex(XmlNode &node) {
89
1.20M
    aiVector3D vertex;
90
1.20M
    vertex.x = ai_strtof(node.attribute(XmlTag::x).as_string(), nullptr);
91
1.20M
    vertex.y = ai_strtof(node.attribute(XmlTag::y).as_string(), nullptr);
92
1.20M
    vertex.z = ai_strtof(node.attribute(XmlTag::z).as_string(), nullptr);
93
94
1.20M
    return vertex;
95
1.20M
}
96
97
2.43M
bool getNodeAttribute(const XmlNode &node, const std::string &attribute, std::string &value) {
98
2.43M
    pugi::xml_attribute objectAttribute = node.attribute(attribute.c_str());
99
2.43M
    if (!objectAttribute.empty()) {
100
12.5k
        value = objectAttribute.as_string();
101
12.5k
        return true;
102
12.5k
    }
103
104
2.42M
    return false;
105
2.43M
}
106
107
2.42M
bool getNodeAttribute(const XmlNode &node, const std::string &attribute, int &value) {
108
2.42M
    std::string strValue;
109
2.42M
    const bool ret = getNodeAttribute(node, attribute, strValue);
110
2.42M
    if (ret) {
111
5.31k
        value = std::atoi(strValue.c_str());
112
5.31k
        return true;
113
5.31k
    }
114
115
2.41M
    return false;
116
2.42M
}
117
118
30
aiMatrix4x4 parseTransformMatrix(const std::string& matrixStr) {
119
    // split the string
120
30
    std::vector<float> numbers;
121
30
    std::string currentNumber;
122
700
    for (char c : matrixStr) {
123
700
        if (c == ' ') {
124
330
            if (!currentNumber.empty()) {
125
330
                numbers.push_back(ai_strtof(currentNumber.c_str(), nullptr));
126
330
                currentNumber.clear();
127
330
            }
128
370
        } else {
129
370
            currentNumber.push_back(c);
130
370
        }
131
700
    }
132
30
    if (!currentNumber.empty()) {
133
30
        numbers.push_back(ai_strtof(currentNumber.c_str(), nullptr));
134
30
    }
135
136
    // A 3MF transform is a row-major 4x3 affine matrix (3MF Core 3.3); the implicit
137
    // fourth column is (0,0,0,1). Anything shorter is malformed, so fall back to identity.
138
30
    constexpr size_t AffineMatrixValueCount = 12;
139
30
    if (numbers.size() < AffineMatrixValueCount) {
140
0
        return aiMatrix4x4();
141
0
    }
142
143
30
    aiMatrix4x4 transformMatrix;
144
30
    transformMatrix.a1 = numbers[0];
145
30
    transformMatrix.b1 = numbers[1];
146
30
    transformMatrix.c1 = numbers[2];
147
30
    transformMatrix.d1 = 0;
148
149
30
    transformMatrix.a2 = numbers[3];
150
30
    transformMatrix.b2 = numbers[4];
151
30
    transformMatrix.c2 = numbers[5];
152
30
    transformMatrix.d2 = 0;
153
154
30
    transformMatrix.a3 = numbers[6];
155
30
    transformMatrix.b3 = numbers[7];
156
30
    transformMatrix.c3 = numbers[8];
157
30
    transformMatrix.d3 = 0;
158
159
30
    transformMatrix.a4 = numbers[9];
160
30
    transformMatrix.b4 = numbers[10];
161
30
    transformMatrix.c4 = numbers[11];
162
30
    transformMatrix.d4 = 1;
163
164
30
    return transformMatrix;
165
30
}
166
167
1
bool parseColor(const std::string &color, aiColor4D &diffuse) {
168
1
    if (color.empty()) {
169
0
        return false;
170
0
    }
171
172
1
    if (!validateColorString(color)) {
173
0
        return false;
174
0
    }
175
176
1
    if ('#' != color[0]) {
177
0
        return false;
178
0
    }
179
180
1
    char r[3] = { color[1], color[2], '\0' };
181
1
    diffuse.r = static_cast<ai_real>(strtol(r, nullptr, 16)) / ai_real(255.0);
182
183
1
    char g[3] = { color[3], color[4], '\0' };
184
1
    diffuse.g = static_cast<ai_real>(strtol(g, nullptr, 16)) / ai_real(255.0);
185
186
1
    char b[3] = { color[5], color[6], '\0' };
187
1
    diffuse.b = static_cast<ai_real>(strtol(b, nullptr, 16)) / ai_real(255.0);
188
1
    const size_t len = color.size();
189
1
    if (ColRGB_Len == len) {
190
0
        return true;
191
0
    }
192
193
1
    char a[3] = { color[7], color[8], '\0' };
194
1
    diffuse.a = static_cast<ai_real>(strtol(a, nullptr, 16)) / ai_real(255.0);
195
196
1
    return true;
197
1
}
198
199
0
void assignDiffuseColor(XmlNode &node, aiMaterial *mat) {
200
0
    const char *color = node.attribute(XmlTag::basematerials_displaycolor).as_string();
201
0
    aiColor4D diffuse;
202
0
    if (parseColor(color, diffuse)) {
203
0
        mat->AddProperty<aiColor4D>(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
204
0
    }
205
0
}
206
207
} // namespace
208
209
XmlSerializer::XmlSerializer(XmlParser &xmlParser, D3MFOpcPackage *package) :
210
224
        mResourcesByFile(),
211
224
        mCurrentResources(nullptr),
212
224
        mMeshCount(0),
213
224
        mXmlParser(xmlParser),
214
224
        mPackage(package) {
215
    // empty
216
224
}
217
218
224
XmlSerializer::~XmlSerializer() {
219
1.88k
    for (auto &file : mResourcesByFile) {
220
1.88k
        for (auto &it : file.second) {
221
1.67k
            delete it.second;
222
1.67k
        }
223
1.88k
    }
224
224
}
225
226
1.88k
void XmlSerializer::ReadModel(XmlNode &modelNode, const std::string &fileKey) {
227
1.88k
    mCurrentResources = &mResourcesByFile[fileKey];
228
229
1.88k
    XmlNode resNode = modelNode.child(XmlTag::resources);
230
1.88k
    for (auto &currentNode : resNode.children()) {
231
1.67k
        const std::string currentNodeName = currentNode.name();
232
1.67k
        if (currentNodeName == XmlTag::texture_2d) {
233
0
            ReadEmbeddecTexture(currentNode);
234
1.67k
        } else if (currentNodeName == XmlTag::texture_group) {
235
0
            ReadTextureGroup(currentNode);
236
1.67k
        } else if (currentNodeName == XmlTag::object) {
237
1.67k
            ReadObject(currentNode);
238
1.67k
        } else if (currentNodeName == XmlTag::basematerials) {
239
0
            ReadBaseMaterials(currentNode);
240
1
        } else if (currentNodeName == XmlTag::meta) {
241
0
            ReadMetadata(currentNode);
242
1
        } else if (currentNodeName == XmlTag::colorgroup) {
243
1
            ReadColorGroup(currentNode);
244
1
        }
245
1.67k
    }
246
1.88k
}
247
248
3.62k
void XmlSerializer::LoadModelFile(const std::string &path) {
249
3.62k
    if (path.empty() || nullptr == mPackage) {
250
0
        return;
251
0
    }
252
    // Already loaded (e.g. the root file, or referenced from multiple places).
253
3.62k
    if (mResourcesByFile.find(path) != mResourcesByFile.end()) {
254
0
        return;
255
0
    }
256
257
3.62k
    IOStream *stream = mPackage->OpenPart(path);
258
3.62k
    if (nullptr == stream) {
259
1.95k
        ASSIMP_LOG_WARN("3MF: cannot open referenced model part: ", path);
260
1.95k
        return;
261
1.95k
    }
262
263
1.66k
    XmlParser parser;
264
1.66k
    if (parser.parse(stream)) {
265
1.66k
        XmlNode modelNode = parser.getRootNode().child(XmlTag::model);
266
1.66k
        if (!modelNode.empty()) {
267
            // ReadObject copies all data into Object/aiMesh, so the parser may be
268
            // discarded once the resources of this part have been read.
269
1.66k
            ReadModel(modelNode, path);
270
1.66k
        }
271
1.66k
    }
272
1.66k
    mPackage->CloseStream(stream);
273
1.66k
}
274
275
224
void XmlSerializer::ImportXml(aiScene *scene) {
276
224
    if (nullptr == scene) {
277
0
        return;
278
0
    }
279
280
224
    scene->mRootNode = new aiNode(XmlTag::RootTag);
281
224
    XmlNode node = mXmlParser.getRootNode().child(XmlTag::model);
282
224
    if (node.empty()) {
283
0
        return;
284
0
    }
285
286
224
    const std::string rootKey = (nullptr != mPackage) ? mPackage->RootPath() : std::string();
287
224
    ReadModel(node, rootKey);
288
289
224
    XmlNode buildNode = node.child(XmlTag::build);
290
291
    // Production extension: build items and root-object components may reference objects
292
    // in separate model parts via a path attribute. Load every referenced part now, before
293
    // materials are copied into the scene, so material indices defined there stay valid.
294
224
    std::set<std::string> referencedFiles;
295
224
    if (!buildNode.empty()) {
296
3.61k
        for (auto &currentNode : buildNode.children()) {
297
3.61k
            if (std::string(currentNode.name()) == XmlTag::item) {
298
3.61k
                std::string path;
299
3.61k
                if (getNodeAttribute(currentNode, D3MF::XmlTag::p_path, path)) {
300
3.60k
                    referencedFiles.insert(D3MFOpcPackage::NormalizePath(path));
301
3.60k
                }
302
3.61k
            }
303
3.61k
        }
304
224
    }
305
224
    auto rootIt = mResourcesByFile.find(rootKey);
306
224
    if (rootIt != mResourcesByFile.end()) {
307
224
        for (auto &it : rootIt->second) {
308
13
            if (it.second->getType() == ResourceType::RT_Object) {
309
12
                Object *obj = static_cast<Object *>(it.second);
310
20
                for (const Component &c : obj->mComponents) {
311
20
                    if (!c.mPath.empty()) {
312
20
                        referencedFiles.insert(c.mPath);
313
20
                    }
314
20
                }
315
12
            }
316
13
        }
317
224
    }
318
3.62k
    for (const std::string &file : referencedFiles) {
319
3.62k
        LoadModelFile(file);
320
3.62k
    }
321
322
224
    StoreMaterialsInScene(scene);
323
324
224
    if (!buildNode.empty()) {
325
3.61k
        for (auto &currentNode : buildNode.children()) {
326
3.61k
            const std::string currentNodeName = currentNode.name();
327
3.61k
            if (currentNodeName == XmlTag::item) {
328
3.61k
                int objectId = IdNotSet;
329
3.61k
                std::string transformationMatrixStr;
330
3.61k
                std::string itemPath;
331
3.61k
                aiMatrix4x4 transformationMatrix;
332
3.61k
                getNodeAttribute(currentNode, D3MF::XmlTag::objectid, objectId);
333
3.61k
                bool hasTransform = getNodeAttribute(currentNode, D3MF::XmlTag::transform, transformationMatrixStr);
334
3.61k
                bool hasPath = getNodeAttribute(currentNode, D3MF::XmlTag::p_path, itemPath);
335
336
3.61k
                const std::string fileKey = hasPath ? D3MFOpcPackage::NormalizePath(itemPath) : rootKey;
337
3.61k
                auto fileIt = mResourcesByFile.find(fileKey);
338
3.61k
                if (fileIt == mResourcesByFile.end()) {
339
1.94k
                    continue;
340
1.94k
                }
341
1.67k
                auto it = fileIt->second.find(objectId);
342
1.67k
                if (it != fileIt->second.end() && it->second->getType() == ResourceType::RT_Object) {
343
1.67k
                    Object *obj = static_cast<Object *>(it->second);
344
1.67k
                    if (hasTransform) {
345
10
                        transformationMatrix = parseTransformMatrix(transformationMatrixStr);
346
10
                    }
347
348
1.67k
                    addObjectToNode(scene->mRootNode, obj, transformationMatrix, fileKey);
349
1.67k
                }
350
1.67k
            }
351
3.61k
        }
352
224
    }
353
354
    // import the metadata
355
224
    if (!mMetaData.empty()) {
356
0
        const size_t numMeta = mMetaData.size();
357
0
        scene->mMetaData = aiMetadata::Alloc(static_cast<unsigned int>(numMeta));
358
0
        for (size_t i = 0; i < numMeta; ++i) {
359
0
            aiString val(mMetaData[i].value);
360
0
            scene->mMetaData->Set(static_cast<unsigned int>(i), mMetaData[i].name, val);
361
0
        }
362
0
    }
363
364
    // import the meshes from every model part, materials are already stored
365
224
    scene->mNumMeshes = static_cast<unsigned int>(mMeshCount);
366
224
    if (scene->mNumMeshes != 0) {
367
209
        scene->mMeshes = new aiMesh *[scene->mNumMeshes]();
368
1.87k
        for (auto &file : mResourcesByFile) {
369
1.87k
            for (auto &it : file.second) {
370
1.67k
                if (it.second->getType() == ResourceType::RT_Object) {
371
1.67k
                    Object *obj = static_cast<Object *>(it.second);
372
1.67k
                    ai_assert(nullptr != obj);
373
3.33k
                    for (unsigned int i = 0; i < obj->mMeshes.size(); ++i) {
374
1.66k
                        scene->mMeshes[obj->mMeshIndex[i]] = obj->mMeshes[i];
375
1.66k
                    }
376
1.67k
                }
377
1.67k
            }
378
1.87k
        }
379
209
    }
380
224
}
381
382
1.67k
void XmlSerializer::addObjectToNode(aiNode *parent, Object *obj, const aiMatrix4x4 &nodeTransform, const std::string &fileKey) {
383
1.67k
    ai_assert(nullptr != obj);
384
385
1.67k
    aiNode *sceneNode = new aiNode(obj->mName);
386
1.67k
    sceneNode->mNumMeshes = static_cast<unsigned int>(obj->mMeshes.size());
387
1.67k
    sceneNode->mMeshes = new unsigned int[sceneNode->mNumMeshes];
388
1.67k
    std::copy(obj->mMeshIndex.begin(), obj->mMeshIndex.end(), sceneNode->mMeshes);
389
390
1.67k
    sceneNode->mTransformation = nodeTransform;
391
1.67k
    if (nullptr != parent) {
392
1.67k
        parent->addChildren(1, &sceneNode);
393
1.67k
    }
394
395
1.67k
    for (const Assimp::D3MF::Component &c : obj->mComponents) {
396
        // A component without a path references an object in the same model part;
397
        // with a path (production extension, already normalized) it references another part.
398
20
        const std::string &childKey = c.mPath.empty() ? fileKey : c.mPath;
399
20
        auto fileIt = mResourcesByFile.find(childKey);
400
20
        if (fileIt == mResourcesByFile.end()) {
401
14
            continue;
402
14
        }
403
6
        auto it = fileIt->second.find(c.mObjectId);
404
6
        if (it != fileIt->second.end() && it->second->getType() == ResourceType::RT_Object) {
405
6
            addObjectToNode(sceneNode, static_cast<Object *>(it->second), c.mTransformation, childKey);
406
6
        }
407
6
    }
408
1.67k
}
409
410
1.67k
void XmlSerializer::ReadObject(XmlNode &node) {
411
1.67k
    int id = IdNotSet, pid = IdNotSet, pindex = IdNotSet;
412
1.67k
    bool hasId = getNodeAttribute(node, XmlTag::id, id);
413
1.67k
    if (!hasId) {
414
0
        return;
415
0
    }
416
417
1.67k
    bool hasPid = getNodeAttribute(node, XmlTag::pid, pid);
418
1.67k
    bool hasPindex = getNodeAttribute(node, XmlTag::pindex, pindex);
419
420
1.67k
    Object *obj = new Object(id);
421
1.67k
    for (XmlNode &currentNode : node.children()) {
422
1.67k
        const std::string currentName = currentNode.name();
423
1.67k
        if (currentName == D3MF::XmlTag::mesh) {
424
1.66k
            auto mesh = ReadMesh(currentNode);
425
1.66k
            mesh->mName.Set(ai_to_string(id));
426
427
1.66k
            if (hasPid) {
428
0
                auto it = mCurrentResources->find(pid);
429
0
                if (hasPindex && it != mCurrentResources->end()) {
430
0
                    if (it->second->getType() == ResourceType::RT_BaseMaterials) {
431
0
                        BaseMaterials *materials = static_cast<BaseMaterials *>(it->second);
432
0
                        if (pindex >= 0 && static_cast<size_t>(pindex) < materials->mMaterialIndex.size()) {
433
0
                            mesh->mMaterialIndex = materials->mMaterialIndex[pindex];
434
0
                        }
435
0
                    } else if (it->second->getType() == ResourceType::RT_Texture2DGroup) {
436
0
                        Texture2DGroup *group = static_cast<Texture2DGroup *>(it->second);
437
0
                        const bool pindexValid = pindex >= 0 && static_cast<size_t>(pindex) < group->mTex2dCoords.size();
438
0
                        if (mesh->mTextureCoords[0] == nullptr) {
439
0
                            mesh->mNumUVComponents[0] = 2;
440
0
                            for (unsigned int i = 1; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
441
0
                                mesh->mNumUVComponents[i] = 0;
442
0
                            }
443
444
0
                            const std::string name = ai_to_string(group->mTexId);
445
0
                            for (size_t i = 0; i < mMaterials.size(); ++i) {
446
0
                                if (name == mMaterials[i]->GetName().C_Str()) {
447
0
                                    mesh->mMaterialIndex = static_cast<unsigned int>(i);
448
0
                                }
449
0
                            }
450
451
0
                            mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
452
0
                            if (pindexValid) {
453
0
                                for (unsigned int vertex_idx = 0; vertex_idx < mesh->mNumVertices; vertex_idx++) {
454
0
                                    mesh->mTextureCoords[0][vertex_idx] =
455
0
                                            aiVector3D(group->mTex2dCoords[pindex].x, group->mTex2dCoords[pindex].y, 0.0f);
456
0
                                }
457
0
                            }
458
0
                        } else if (pindexValid) {
459
0
                            for (unsigned int vertex_idx = 0; vertex_idx < mesh->mNumVertices; vertex_idx++) {
460
0
                                if (mesh->mTextureCoords[0][vertex_idx].z < 0) {
461
                                    // use default
462
0
                                    mesh->mTextureCoords[0][vertex_idx] =
463
0
                                            aiVector3D(group->mTex2dCoords[pindex].x, group->mTex2dCoords[pindex].y, 0.0f);
464
0
                                }
465
0
                            }
466
0
                        }
467
0
                    }else if (it->second->getType() == ResourceType::RT_ColorGroup) {
468
0
                        if (mesh->mColors[0] == nullptr) {
469
0
                            mesh->mColors[0] = new aiColor4D[mesh->mNumVertices];
470
471
0
                            ColorGroup *group = static_cast<ColorGroup *>(it->second);
472
0
                            if (pindex >= 0 && static_cast<size_t>(pindex) < group->mColors.size()) {
473
0
                                for (unsigned int vertex_idx = 0; vertex_idx < mesh->mNumVertices; vertex_idx++) {
474
0
                                    mesh->mColors[0][vertex_idx] = group->mColors[pindex];
475
0
                                }
476
0
                            }
477
0
                        }
478
0
                    }
479
0
                }
480
0
            }
481
482
1.66k
            obj->mMeshes.push_back(mesh);
483
1.66k
            obj->mMeshIndex.push_back(mMeshCount);
484
1.66k
            mMeshCount++;
485
1.66k
        } else if (currentName == D3MF::XmlTag::components) {
486
20
            for (XmlNode &currentSubNode : currentNode.children()) {
487
20
                const std::string subNodeName = currentSubNode.name();
488
20
                if (subNodeName == D3MF::XmlTag::component) {
489
20
                    int objectId = IdNotSet;
490
20
                    std::string componentTransformStr;
491
20
                    aiMatrix4x4 componentTransform;
492
20
                    if (getNodeAttribute(currentSubNode, D3MF::XmlTag::transform, componentTransformStr)) {
493
20
                        componentTransform = parseTransformMatrix(componentTransformStr);
494
20
                    }
495
496
                    // Production extension: a component may reference an object in another model
497
                    // part. Normalize the path once here so every later lookup can compare directly.
498
20
                    std::string componentPath;
499
20
                    if (getNodeAttribute(currentSubNode, D3MF::XmlTag::p_path, componentPath)) {
500
20
                        componentPath = D3MFOpcPackage::NormalizePath(componentPath);
501
20
                    }
502
503
20
                    if (getNodeAttribute(currentSubNode, D3MF::XmlTag::objectid, objectId)) {
504
20
                        obj->mComponents.push_back({ objectId, componentTransform, componentPath });
505
20
                    }
506
20
                }
507
20
            }
508
10
        }
509
1.67k
    }
510
511
1.67k
    mCurrentResources->insert(std::make_pair(id, obj));
512
1.67k
}
513
514
1.66k
aiMesh *XmlSerializer::ReadMesh(XmlNode &node) {
515
1.66k
    if (node.empty()) {
516
0
        return nullptr;
517
0
    }
518
519
1.66k
    aiMesh *mesh = new aiMesh();
520
3.33k
    for (XmlNode &currentNode : node.children()) {
521
3.33k
        const std::string currentName = currentNode.name();
522
3.33k
        if (currentName == XmlTag::vertices) {
523
1.66k
            ImportVertices(currentNode, mesh);
524
1.66k
        } else if (currentName == XmlTag::triangles) {
525
1.66k
            ImportTriangles(currentNode, mesh);
526
1.66k
        }
527
3.33k
    }
528
529
1.66k
    return mesh;
530
1.66k
}
531
532
0
void XmlSerializer::ReadMetadata(XmlNode &node) {
533
0
    pugi::xml_attribute attribute = node.attribute(D3MF::XmlTag::meta_name);
534
0
    const std::string name = attribute.as_string();
535
0
    const std::string value = node.value();
536
0
    if (name.empty()) {
537
0
        return;
538
0
    }
539
540
0
    MetaEntry entry;
541
0
    entry.name = name;
542
0
    entry.value = value;
543
0
    mMetaData.push_back(entry);
544
0
}
545
546
1.66k
void XmlSerializer::ImportVertices(XmlNode &node, aiMesh *mesh) {
547
1.66k
    ai_assert(nullptr != mesh);
548
549
1.66k
    std::vector<aiVector3D> vertices;
550
1.20M
    for (XmlNode &currentNode : node.children()) {
551
1.20M
        const std::string currentName = currentNode.name();
552
1.20M
        if (currentName == XmlTag::vertex) {
553
1.20M
            vertices.push_back(ReadVertex(currentNode));
554
1.20M
        }
555
1.20M
    }
556
557
1.66k
    mesh->mNumVertices = static_cast<unsigned int>(vertices.size());
558
1.66k
    mesh->mVertices = new aiVector3D[mesh->mNumVertices];
559
1.66k
    std::copy(vertices.begin(), vertices.end(), mesh->mVertices);
560
1.66k
}
561
562
1.66k
void XmlSerializer::ImportTriangles(XmlNode &node, aiMesh *mesh) {
563
1.66k
    std::vector<aiFace> faces;
564
2.41M
    for (XmlNode &currentNode : node.children()) {
565
2.41M
        const std::string currentName = currentNode.name();
566
2.41M
        if (currentName == XmlTag::triangle) {
567
2.41M
            int pid = IdNotSet;
568
2.41M
            bool hasPid = getNodeAttribute(currentNode, D3MF::XmlTag::pid, pid);
569
570
2.41M
            int pindex[3];
571
2.41M
            aiFace face = ReadTriangle(currentNode, pindex[0], pindex[1], pindex[2]);
572
2.41M
            if (hasPid && (pindex[0] != IdNotSet || pindex[1] != IdNotSet || pindex[2] != IdNotSet)) {
573
1
                auto it = mCurrentResources->find(pid);
574
1
                if (it != mCurrentResources->end()) {
575
1
                    if (it->second->getType() == ResourceType::RT_BaseMaterials) {
576
0
                        BaseMaterials *baseMaterials = static_cast<BaseMaterials *>(it->second);
577
578
0
                        auto update_material = [&](int idx) {
579
0
                            if (pindex[idx] != IdNotSet && static_cast<size_t>(pindex[idx]) < baseMaterials->mMaterialIndex.size()) {
580
0
                                mesh->mMaterialIndex = baseMaterials->mMaterialIndex[pindex[idx]];
581
0
                            }
582
0
                        };
583
584
0
                        update_material(0);
585
0
                        update_material(1);
586
0
                        update_material(2);
587
588
1
                    } else if (it->second->getType() == ResourceType::RT_Texture2DGroup) {
589
                        // Load texture coordinates into mesh, when any
590
0
                        Texture2DGroup *group = static_cast<Texture2DGroup *>(it->second); // fix bug
591
0
                        if (mesh->mTextureCoords[0] == nullptr) {
592
0
                            mesh->mNumUVComponents[0] = 2;
593
0
                            for (unsigned int i = 1; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
594
0
                                mesh->mNumUVComponents[i] = 0;
595
0
                            }
596
597
0
                            const std::string name = ai_to_string(group->mTexId);
598
0
                            for (size_t i = 0; i < mMaterials.size(); ++i) {
599
0
                                if (name == mMaterials[i]->GetName().C_Str()) {
600
0
                                    mesh->mMaterialIndex = static_cast<unsigned int>(i);
601
0
                                }
602
0
                            }
603
0
                            mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
604
0
                            for (unsigned int vertex_index = 0; vertex_index < mesh->mNumVertices; vertex_index++) {
605
0
                                mesh->mTextureCoords[0][vertex_index].z = IdNotSet;//mark not set
606
0
                            }
607
0
                        }
608
609
0
                        auto update_texture = [&](int idx) {
610
0
                            if (pindex[idx] != IdNotSet && static_cast<size_t>(pindex[idx]) < group->mTex2dCoords.size()) {
611
0
                                size_t vertex_index = face.mIndices[idx];
612
0
                                if (vertex_index < mesh->mNumVertices) {
613
0
                                    mesh->mTextureCoords[0][vertex_index] =
614
0
                                            aiVector3D(group->mTex2dCoords[pindex[idx]].x, group->mTex2dCoords[pindex[idx]].y, 0.0f);
615
0
                                }
616
0
                            }
617
0
                        };
618
619
0
                        update_texture(0);
620
0
                        update_texture(1);
621
0
                        update_texture(2);
622
623
1
                    } else if (it->second->getType() == ResourceType::RT_ColorGroup) {
624
                        // Load vertex color into mesh, when any
625
1
                        ColorGroup *group = static_cast<ColorGroup *>(it->second);
626
1
                        if (mesh->mColors[0] == nullptr) {
627
1
                            mesh->mColors[0] = new aiColor4D[mesh->mNumVertices];
628
1
                        }
629
630
3
                        auto update_color = [&](int idx) {
631
3
                            if (pindex[idx] != IdNotSet && static_cast<size_t>(pindex[idx]) < group->mColors.size()) {
632
0
                                size_t vertex_index = face.mIndices[idx];
633
0
                                if (vertex_index < mesh->mNumVertices) {
634
0
                                    mesh->mColors[0][vertex_index] = group->mColors[pindex[idx]];
635
0
                                }
636
0
                            }
637
3
                        };
638
639
1
                        update_color(0);
640
1
                        update_color(1);
641
1
                        update_color(2);
642
1
                    }
643
1
                }
644
1
            }
645
646
2.41M
            faces.push_back(face);
647
2.41M
        }
648
2.41M
    }
649
650
1.66k
    mesh->mNumFaces = static_cast<unsigned int>(faces.size());
651
1.66k
    mesh->mFaces = new aiFace[mesh->mNumFaces];
652
1.66k
    mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
653
654
1.66k
    std::copy(faces.begin(), faces.end(), mesh->mFaces);
655
1.66k
}
656
657
0
void XmlSerializer::ReadBaseMaterials(XmlNode &node) {
658
0
    int id = IdNotSet;
659
0
    if (getNodeAttribute(node, D3MF::XmlTag::id, id)) {
660
0
        BaseMaterials *baseMaterials = new BaseMaterials(id);
661
662
0
        for (XmlNode &currentNode : node.children()) {
663
0
            const std::string currentName = currentNode.name();
664
0
            if (currentName == XmlTag::basematerials_base) {
665
0
                baseMaterials->mMaterialIndex.push_back(static_cast<unsigned int>(mMaterials.size()));
666
0
                mMaterials.push_back(readMaterialDef(currentNode, id));
667
0
            }
668
0
        }
669
670
0
        mCurrentResources->insert(std::make_pair(id, baseMaterials));
671
0
    }
672
0
}
673
674
0
void XmlSerializer::ReadEmbeddecTexture(XmlNode &node) {
675
0
    if (node.empty()) {
676
0
        return;
677
0
    }
678
679
0
    std::string value;
680
0
    EmbeddedTexture *tex2D = nullptr;
681
0
    if (XmlParser::getStdStrAttribute(node, XmlTag::id, value)) {
682
0
        tex2D = new EmbeddedTexture(atoi(value.c_str()));
683
0
    }
684
0
    if (nullptr == tex2D) {
685
0
        return;
686
0
    }
687
688
0
    if (XmlParser::getStdStrAttribute(node, XmlTag::path, value)) {
689
0
        tex2D->mPath = value;
690
0
    }
691
0
    if (XmlParser::getStdStrAttribute(node, XmlTag::texture_content_type, value)) {
692
0
        tex2D->mContentType = value;
693
0
    }
694
0
    if (XmlParser::getStdStrAttribute(node, XmlTag::texture_tilestyleu, value)) {
695
0
        tex2D->mTilestyleU = value;
696
0
    }
697
0
    if (XmlParser::getStdStrAttribute(node, XmlTag::texture_tilestylev, value)) {
698
0
        tex2D->mTilestyleV = value;
699
0
    }
700
0
    mEmbeddedTextures.emplace_back(tex2D);
701
0
    StoreEmbeddedTexture(tex2D);
702
0
}
703
704
0
void XmlSerializer::StoreEmbeddedTexture(EmbeddedTexture *tex) {
705
0
    aiMaterial *mat = new aiMaterial;
706
0
    aiString s;
707
0
    s.Set(ai_to_string(tex->mId).c_str());
708
0
    mat->AddProperty(&s, AI_MATKEY_NAME);
709
0
    const std::string name = "*" + tex->mPath;
710
0
    s.Set(name);
711
0
    mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0));
712
713
0
    aiColor3D col;
714
0
    mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_DIFFUSE);
715
0
    mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_AMBIENT);
716
0
    mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_EMISSIVE);
717
0
    mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_SPECULAR);
718
0
    mMaterials.emplace_back(mat);
719
0
}
720
721
0
void XmlSerializer::ReadTextureCoords2D(XmlNode &node, Texture2DGroup *tex2DGroup) {
722
0
    if (node.empty() || nullptr == tex2DGroup) {
723
0
        return;
724
0
    }
725
726
0
    int id = IdNotSet;
727
0
    if (XmlParser::getIntAttribute(node, "texid", id)) {
728
0
        tex2DGroup->mTexId = id;
729
0
    }
730
731
0
    double value = 0.0;
732
0
    for (XmlNode currentNode : node.children()) {
733
0
        const std::string currentName = currentNode.name();
734
0
        aiVector2D texCoord;
735
0
        if (currentName == XmlTag::texture_2d_coord) {
736
0
            XmlParser::getDoubleAttribute(currentNode, XmlTag::texture_cuurd_u, value);
737
0
            texCoord.x = (ai_real)value;
738
0
            XmlParser::getDoubleAttribute(currentNode, XmlTag::texture_cuurd_v, value);
739
0
            texCoord.y = (ai_real)value;
740
0
            tex2DGroup->mTex2dCoords.push_back(texCoord);
741
0
        }
742
0
    }
743
0
}
744
745
0
void XmlSerializer::ReadTextureGroup(XmlNode &node) {
746
0
    if (node.empty()) {
747
0
        return;
748
0
    }
749
750
0
    int id = IdNotSet;
751
0
    if (!XmlParser::getIntAttribute(node, XmlTag::id, id)) {
752
0
        return;
753
0
    }
754
755
0
    Texture2DGroup *group = new Texture2DGroup(id);
756
0
    ReadTextureCoords2D(node, group);
757
0
    mCurrentResources->insert(std::make_pair(id, group));
758
0
}
759
760
0
aiMaterial *XmlSerializer::readMaterialDef(XmlNode &node, unsigned int basematerialsId) {
761
0
    aiMaterial *material = new aiMaterial();
762
0
    material->mNumProperties = 0;
763
0
    std::string name;
764
0
    bool hasName = getNodeAttribute(node, D3MF::XmlTag::basematerials_name, name);
765
766
0
    std::string stdMaterialName;
767
0
    const std::string strId(ai_to_string(basematerialsId));
768
0
    stdMaterialName += "id";
769
0
    stdMaterialName += strId;
770
0
    stdMaterialName += "_";
771
0
    if (hasName) {
772
0
        stdMaterialName += name;
773
0
    } else {
774
0
        stdMaterialName += "basemat_";
775
0
        stdMaterialName += ai_to_string(mMaterials.size());
776
0
    }
777
778
0
    aiString assimpMaterialName(stdMaterialName);
779
0
    material->AddProperty(&assimpMaterialName, AI_MATKEY_NAME);
780
781
0
    assignDiffuseColor(node, material);
782
783
0
    return material;
784
0
}
785
786
1
void XmlSerializer::ReadColor(XmlNode &node, ColorGroup *colorGroup) {
787
1
    if (node.empty() || nullptr == colorGroup) {
788
0
        return;
789
0
    }
790
791
1
    for (XmlNode currentNode : node.children()) {
792
1
        const std::string currentName = currentNode.name();
793
1
        if (currentName == XmlTag::color_item) {
794
1
            const char *color = currentNode.attribute(XmlTag::color_value).as_string();
795
1
            aiColor4D color_value;
796
1
            if (parseColor(color, color_value)) {
797
1
                colorGroup->mColors.push_back(color_value);
798
1
            }
799
1
        }
800
1
    }
801
1
}
802
803
1
void XmlSerializer::ReadColorGroup(XmlNode &node) {
804
1
    if (node.empty()) {
805
0
        return;
806
0
    }
807
808
1
    int id = IdNotSet;
809
1
    if (!XmlParser::getIntAttribute(node, XmlTag::id, id)) {
810
0
        return;
811
0
    }
812
813
1
    ColorGroup *group = new ColorGroup(id);
814
1
    ReadColor(node, group);
815
1
    mCurrentResources->insert(std::make_pair(id, group));
816
1
}
817
818
224
void XmlSerializer::StoreMaterialsInScene(aiScene *scene) {
819
224
    if (nullptr == scene) {
820
0
        return;
821
0
    }
822
823
224
    scene->mNumMaterials = static_cast<unsigned int>(mMaterials.size());
824
224
    if (scene->mNumMaterials == 0) {
825
224
        return;
826
224
    }
827
828
0
    scene->mMaterials = new aiMaterial *[scene->mNumMaterials];
829
0
    for (size_t i = 0; i < mMaterials.size(); ++i) {
830
0
        scene->mMaterials[i] = mMaterials[i];
831
0
    }
832
0
}
833
834
} // namespace D3MF
835
} // namespace Assimp