Coverage Report

Created: 2025-08-03 06:54

/src/assimp/code/AssetLib/XGL/XGLLoader.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 Implementation of the XGL/ZGL importer class */
43
44
#ifndef ASSIMP_BUILD_NO_XGL_IMPORTER
45
46
#include "XGLLoader.h"
47
#include "Common/Compression.h"
48
49
#include <assimp/ParsingUtils.h>
50
#include <assimp/fast_atof.h>
51
#include <assimp/MemoryIOWrapper.h>
52
#include <assimp/StreamReader.h>
53
#include <assimp/importerdesc.h>
54
#include <assimp/mesh.h>
55
#include <assimp/scene.h>
56
57
#include <memory>
58
#include <utility>
59
60
namespace Assimp {
61
62
static constexpr uint32_t ErrorId = ~0u;
63
64
template <>
65
0
const char *LogFunctions<XGLImporter>::Prefix() {
66
0
  return "XGL: ";
67
0
}
68
69
static constexpr aiImporterDesc desc = {
70
  "XGL Importer", "", "",  "",
71
    aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportCompressedFlavour,
72
  0,  0,  0,  0,  "xgl zgl"};
73
74
// ------------------------------------------------------------------------------------------------
75
414
XGLImporter::XGLImporter() : mXmlParser(nullptr), m_scene(nullptr) {
76
    // empty
77
414
}
78
79
// ------------------------------------------------------------------------------------------------
80
414
XGLImporter::~XGLImporter() {
81
414
    clear();
82
414
}
83
84
// ------------------------------------------------------------------------------------------------
85
29
bool XGLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
86
29
  static const char *tokens[] = { "<world>", "<World>", "<WORLD>" };
87
29
  return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
88
29
}
89
90
// ------------------------------------------------------------------------------------------------
91
424
const aiImporterDesc *XGLImporter::GetInfo() const {
92
424
  return &desc;
93
424
}
94
95
// ------------------------------------------------------------------------------------------------
96
2
void XGLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
97
2
    clear();
98
2
#ifndef ASSIMP_BUILD_NO_COMPRESSED_XGL
99
2
  std::vector<char> uncompressed;
100
2
#endif
101
102
2
  m_scene = pScene;
103
2
  std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
104
105
  // check whether we can read from the file
106
2
    if (stream == nullptr) {
107
0
        throw DeadlyImportError("Failed to open XGL/ZGL file " + pFile);
108
0
    }
109
110
    // see if its compressed, if so uncompress it
111
2
  if (GetExtension(pFile) == "zgl") {
112
#ifdef ASSIMP_BUILD_NO_COMPRESSED_XGL
113
    ThrowException("Cannot read ZGL file since Assimp was built without compression support");
114
#else
115
0
    std::unique_ptr<StreamReaderLE> raw_reader(new StreamReaderLE(stream));
116
117
0
        Compression compression;
118
0
        size_t total = 0l;
119
0
        if (compression.open(Compression::Format::Binary, Compression::FlushMode::NoFlush, -Compression::MaxWBits)) {
120
            // skip two extra bytes, zgl files do carry a crc16 upfront (I think)
121
0
            raw_reader->IncPtr(2);
122
0
            total = compression.decompress((unsigned char *)raw_reader->GetPtr(), raw_reader->GetRemainingSize(), uncompressed);
123
0
            compression.close();
124
0
        }
125
    // replace the input stream with a memory stream
126
0
        stream = std::make_shared<MemoryIOStream>(reinterpret_cast<uint8_t *>(uncompressed.data()), total);
127
0
#endif
128
0
  }
129
130
  // parse the XML file
131
2
    mXmlParser = new XmlParser;
132
2
    if (!mXmlParser->parse(stream.get())) {
133
2
        throw DeadlyImportError("XML parse error while loading XGL file ", pFile);
134
2
  }
135
136
0
  TempScope scope;
137
0
    XmlNode *worldNode = mXmlParser->findNode("WORLD");
138
0
    if (nullptr != worldNode) {
139
0
    ReadWorld(*worldNode, scope);
140
0
  }
141
142
0
  MeshArray &meshes = scope.meshes_linear;
143
0
  std::vector<aiMaterial *> &materials = scope.materials_linear;
144
0
  if (meshes.empty() || materials.empty()) {
145
0
    ThrowException("failed to extract data from XGL file, no meshes loaded");
146
0
  }
147
148
  // copy meshes
149
0
  m_scene->mNumMeshes = static_cast<unsigned int>(meshes.size());
150
0
  m_scene->mMeshes = new aiMesh *[m_scene->mNumMeshes]();
151
0
  std::copy(meshes.begin(), meshes.end(), m_scene->mMeshes);
152
153
  // copy materials
154
0
  m_scene->mNumMaterials = static_cast<unsigned int>(materials.size());
155
0
  m_scene->mMaterials = new aiMaterial *[m_scene->mNumMaterials]();
156
0
  std::copy(materials.begin(), materials.end(), m_scene->mMaterials);
157
158
0
  if (scope.light) {
159
0
    m_scene->mNumLights = 1;
160
0
    m_scene->mLights = new aiLight *[1];
161
0
    m_scene->mLights[0] = scope.light;
162
163
0
    scope.light->mName = m_scene->mRootNode->mName;
164
0
  }
165
166
0
  scope.dismiss();
167
0
}
168
169
// ------------------------------------------------------------------------------------------------
170
416
void XGLImporter::clear() {
171
416
    delete mXmlParser;
172
416
    mXmlParser = nullptr;
173
416
}
174
175
176
// ------------------------------------------------------------------------------------------------
177
0
void XGLImporter::ReadWorld(XmlNode &node, TempScope &scope) {
178
0
    for (XmlNode &currentNode : node.children()) {
179
0
        const std::string &s = ai_stdStrToLower(currentNode.name());
180
181
    // XXX right now we'd skip <lighting> if it comes after
182
    // <object> or <mesh>
183
0
    if (s == "lighting") {
184
0
            ReadLighting(currentNode, scope);
185
0
    } else if (s == "object" || s == "mesh" || s == "mat") {
186
0
      break;
187
0
    }
188
0
  }
189
190
0
  aiNode *const nd = ReadObject(node, scope);
191
0
  if (nd == nullptr) {
192
0
    ThrowException("failure reading <world>");
193
0
  }
194
195
0
  if (nd->mName.length == 0) {
196
0
    nd->mName.Set("WORLD");
197
0
  }
198
199
0
  m_scene->mRootNode = nd;
200
0
}
201
202
// ------------------------------------------------------------------------------------------------
203
0
void XGLImporter::ReadLighting(XmlNode &node, TempScope &scope) {
204
0
    const std::string &s = ai_stdStrToLower(node.name());
205
0
  if (s == "directionallight") {
206
0
    scope.light = ReadDirectionalLight(node);
207
0
  } else if (s == "ambient") {
208
0
    LogWarn("ignoring <ambient> tag");
209
0
  } else if (s == "spheremap") {
210
0
    LogWarn("ignoring <spheremap> tag");
211
0
  }
212
0
}
213
214
// ------------------------------------------------------------------------------------------------
215
0
aiLight *XGLImporter::ReadDirectionalLight(XmlNode &node) {
216
0
  std::unique_ptr<aiLight> l(new aiLight());
217
0
  l->mType = aiLightSource_DIRECTIONAL;
218
0
  find_node_by_name_predicate predicate("directionallight");
219
0
  XmlNode child = node.find_child(std::move(predicate));
220
0
  if (child.empty()) {
221
0
    return nullptr;
222
0
  }
223
224
0
  const std::string &s = ai_stdStrToLower(child.name());
225
0
  if (s == "direction") {
226
0
    l->mDirection = ReadVec3(child);
227
0
  } else if (s == "diffuse") {
228
0
    l->mColorDiffuse = ReadCol3(child);
229
0
  } else if (s == "specular") {
230
0
    l->mColorSpecular = ReadCol3(child);
231
0
  }
232
233
0
  return l.release();
234
0
}
235
236
// ------------------------------------------------------------------------------------------------
237
0
aiNode *XGLImporter::ReadObject(XmlNode &node, TempScope &scope) {
238
0
  aiNode *nd = new aiNode;
239
0
  std::vector<aiNode *> children;
240
0
  std::vector<unsigned int> meshes;
241
242
0
  try {
243
0
    for (XmlNode &child : node.children()) {
244
0
      const std::string &s = ai_stdStrToLower(child.name());
245
0
      if (s == "mesh") {
246
0
        const size_t prev = scope.meshes_linear.size();
247
0
        if (ReadMesh(child, scope)) {
248
0
          const size_t newc = scope.meshes_linear.size();
249
0
          for (size_t i = 0; i < newc - prev; ++i) {
250
0
            meshes.push_back(static_cast<unsigned int>(i + prev));
251
0
          }
252
0
        }
253
0
      } else if (s == "mat") {
254
0
        const uint32_t matId = ReadMaterial(child, scope);
255
0
                if (matId == ErrorId) {
256
0
                    ThrowException("Invalid material id detected.");
257
0
                }
258
0
      } else if (s == "object") {
259
0
        children.push_back(ReadObject(child, scope));
260
0
      } else if (s == "objectref") {
261
        // XXX
262
0
      } else if (s == "meshref") {
263
0
        const unsigned int id = static_cast<unsigned int>(ReadIndexFromText(child));
264
265
0
        std::multimap<unsigned int, aiMesh *>::iterator it = scope.meshes.find(id), end = scope.meshes.end();
266
0
        if (it == end) {
267
0
          ThrowException("<meshref> index out of range");
268
0
        }
269
270
0
        for (; it != end && (*it).first == id; ++it) {
271
          // ok, this is n^2 and should get optimized one day
272
0
          aiMesh *const m = it->second;
273
0
          unsigned int i = 0, mcount = static_cast<unsigned int>(scope.meshes_linear.size());
274
0
          for (; i < mcount; ++i) {
275
0
            if (scope.meshes_linear[i] == m) {
276
0
              meshes.push_back(i);
277
0
              break;
278
0
            }
279
0
          }
280
281
0
          ai_assert(i < mcount);
282
0
        }
283
0
      } else if (s == "transform") {
284
0
        nd->mTransformation = ReadTrafo(child);
285
0
      }
286
0
    }
287
0
  } catch (...) {
288
0
    for (aiNode *ch : children) {
289
0
      delete ch;
290
0
    }
291
0
    throw;
292
0
  }
293
294
  // FIX: since we used std::multimap<> to keep meshes by id, mesh order now depends on the behaviour
295
  // of the multimap implementation with respect to the ordering of entries with same values.
296
  // C++11 gives the guarantee that it uses insertion order, before it is implementation-specific.
297
  // Sort by material id to always guarantee a deterministic result.
298
0
  std::sort(meshes.begin(), meshes.end(), SortMeshByMaterialId(scope));
299
300
  // link meshes to node
301
0
  nd->mNumMeshes = static_cast<unsigned int>(meshes.size());
302
0
  if (0 != nd->mNumMeshes) {
303
0
    nd->mMeshes = new unsigned int[nd->mNumMeshes]();
304
0
    for (unsigned int i = 0; i < nd->mNumMeshes; ++i) {
305
0
      nd->mMeshes[i] = meshes[i];
306
0
    }
307
0
  }
308
309
  // link children to parent
310
0
  nd->mNumChildren = static_cast<unsigned int>(children.size());
311
0
  if (nd->mNumChildren) {
312
0
    nd->mChildren = new aiNode *[nd->mNumChildren]();
313
0
    for (unsigned int i = 0; i < nd->mNumChildren; ++i) {
314
0
      nd->mChildren[i] = children[i];
315
0
      children[i]->mParent = nd;
316
0
    }
317
0
  }
318
319
0
  return nd;
320
0
}
321
322
// ------------------------------------------------------------------------------------------------
323
0
aiMatrix4x4 XGLImporter::ReadTrafo(XmlNode &node) {
324
0
  aiVector3D forward, up, right, position;
325
0
  float scale = 1.0f;
326
327
0
  aiMatrix4x4 m;
328
0
  XmlNode child = node.child("TRANSFORM");
329
0
  if (child.empty()) {
330
0
    return m;
331
0
  }
332
333
0
  for (XmlNode &sub_child : child.children()) {
334
0
        const std::string &s = ai_stdStrToLower(sub_child.name());
335
0
    if (s == "forward") {
336
0
      forward = ReadVec3(sub_child);
337
0
    } else if (s == "up") {
338
0
      up = ReadVec3(sub_child);
339
0
    } else if (s == "position") {
340
0
      position = ReadVec3(sub_child);
341
0
    }
342
0
    if (s == "scale") {
343
0
      scale = ReadFloat(sub_child);
344
0
      if (scale < 0.f) {
345
        // this is wrong, but we can leave the value and pass it to the caller
346
0
        LogError("found negative scaling in <transform>, ignoring");
347
0
      }
348
0
    }
349
0
  }
350
351
0
    if (forward.SquareLength() < 1e-4 || up.SquareLength() < 1e-4) {
352
0
      LogError("A direction vector in <transform> is zero, ignoring trafo");
353
0
      return m;
354
0
    }
355
356
0
    forward.Normalize();
357
0
    up.Normalize();
358
359
0
    right = forward ^ up;
360
0
    if (std::fabs(up * forward) > 1e-4) {
361
      // this is definitely wrong - a degenerate coordinate space ruins everything
362
      // so substitute identity transform.
363
0
      LogError("<forward> and <up> vectors in <transform> are skewing, ignoring trafo");
364
0
      return m;
365
0
    }
366
367
0
    right *= scale;
368
0
    up *= scale;
369
0
    forward *= scale;
370
371
0
    m.a1 = right.x;
372
0
    m.b1 = right.y;
373
0
    m.c1 = right.z;
374
375
0
    m.a2 = up.x;
376
0
    m.b2 = up.y;
377
0
    m.c2 = up.z;
378
379
0
    m.a3 = forward.x;
380
0
    m.b3 = forward.y;
381
0
    m.c3 = forward.z;
382
383
0
    m.a4 = position.x;
384
0
    m.b4 = position.y;
385
0
    m.c4 = position.z;
386
387
0
  return m;
388
0
}
389
390
// ------------------------------------------------------------------------------------------------
391
0
aiMesh *XGLImporter::ToOutputMesh(const TempMaterialMesh &m) {
392
0
  std::unique_ptr<aiMesh> mesh(new aiMesh());
393
394
0
  mesh->mNumVertices = static_cast<unsigned int>(m.positions.size());
395
0
  mesh->mVertices = new aiVector3D[mesh->mNumVertices];
396
0
  std::copy(m.positions.begin(), m.positions.end(), mesh->mVertices);
397
398
0
  if (!m.normals.empty()) {
399
0
    mesh->mNormals = new aiVector3D[mesh->mNumVertices];
400
0
    std::copy(m.normals.begin(), m.normals.end(), mesh->mNormals);
401
0
  }
402
403
0
  if (!m.uvs.empty()) {
404
0
    mesh->mNumUVComponents[0] = 2;
405
0
    mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
406
407
0
    for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
408
0
      mesh->mTextureCoords[0][i] = aiVector3D(m.uvs[i].x, m.uvs[i].y, 0.f);
409
0
    }
410
0
  }
411
412
0
  mesh->mNumFaces = static_cast<unsigned int>(m.vcounts.size());
413
0
  mesh->mFaces = new aiFace[m.vcounts.size()];
414
415
0
  unsigned int idx = 0;
416
0
  for (unsigned int i = 0; i < mesh->mNumFaces; ++i) {
417
0
    aiFace &f = mesh->mFaces[i];
418
0
    f.mNumIndices = m.vcounts[i];
419
0
    f.mIndices = new unsigned int[f.mNumIndices];
420
0
    for (unsigned int c = 0; c < f.mNumIndices; ++c) {
421
0
      f.mIndices[c] = idx++;
422
0
    }
423
0
  }
424
425
0
  ai_assert(idx == mesh->mNumVertices);
426
427
0
  mesh->mPrimitiveTypes = m.pflags;
428
0
  mesh->mMaterialIndex = m.matid;
429
430
0
    return mesh.release();
431
0
}
432
433
434
// ------------------------------------------------------------------------------------------------
435
0
inline static unsigned int generateMeshId(unsigned int meshId, bool nor, bool uv) {
436
0
    unsigned int currentMeshId = meshId | ((nor ? 1 : 0) << 31) | ((uv ? 1 : 0) << 30);
437
0
    return currentMeshId;
438
0
}
439
440
// ------------------------------------------------------------------------------------------------
441
0
bool XGLImporter::ReadMesh(XmlNode &node, TempScope &scope) {
442
0
  TempMesh t;
443
0
    uint32_t matId = 99999;
444
0
    bool mesh_created = false;
445
0
  std::map<unsigned int, TempMaterialMesh> bymat;
446
0
    const unsigned int mesh_id = ReadIDAttr(node);
447
0
  for (XmlNode &child : node.children()) {
448
0
        const std::string &s = ai_stdStrToLower(child.name());
449
450
0
    if (s == "mat") {
451
0
      matId = ReadMaterial(child, scope);
452
0
    } else if (s == "p") {
453
0
      pugi::xml_attribute attr = child.attribute("ID");
454
0
      if (attr.empty()) {
455
0
        LogWarn("no ID attribute on <p>, ignoring");
456
0
      } else {
457
0
        int id = attr.as_int();
458
0
        t.points[id] = ReadVec3(child);
459
0
      }
460
0
    } else if (s == "n") {
461
0
      pugi::xml_attribute attr = child.attribute("ID");
462
0
      if (attr.empty()) {
463
0
        LogWarn("no ID attribute on <n>, ignoring");
464
0
      } else {
465
0
        int id = attr.as_int();
466
0
        t.normals[id] = ReadVec3(child);
467
0
      }
468
0
    } else if (s == "tc") {
469
0
      pugi::xml_attribute attr = child.attribute("ID");
470
0
      if (attr.empty()) {
471
0
        LogWarn("no ID attribute on <tc>, ignoring");
472
0
      } else {
473
0
        int id = attr.as_int();
474
0
        t.uvs[id] = ReadVec2(child);
475
0
      }
476
0
    } else if (s == "f" || s == "l" || s == "p") {
477
0
      const unsigned int vcount = s == "f" ? 3 : (s == "l" ? 2 : 1);
478
479
0
      unsigned int meshId = ErrorId;
480
0
            TempFace tempFace[3] = {};
481
0
      bool has[3] = { false };
482
0
            meshId = ReadVertices(child, t, tempFace, has, meshId, scope);
483
0
            if (meshId == ErrorId) {
484
0
        ThrowException("missing material index");
485
0
      }
486
487
0
      bool nor = false, uv = false;
488
0
      for (unsigned int i = 0; i < vcount; ++i) {
489
0
        if (!has[i]) {
490
0
          ThrowException("missing face vertex data");
491
0
        }
492
493
0
        nor = nor || tempFace[i].has_normal;
494
0
        uv = uv || tempFace[i].has_uv;
495
0
      }
496
497
0
      if (meshId >= (1 << 30)) {
498
0
        LogWarn("material indices exhausted, this may cause errors in the output");
499
0
      }
500
0
            const unsigned int currentMeshId = generateMeshId(meshId, nor, uv);
501
502
            // Generate the temp mesh
503
0
      TempMaterialMesh &mesh = bymat[currentMeshId];
504
0
            mesh.matid = meshId;
505
0
            mesh_created = true;
506
507
0
      for (unsigned int i = 0; i < vcount; ++i) {
508
0
        mesh.positions.push_back(tempFace[i].pos);
509
0
        if (nor) {
510
0
          mesh.normals.push_back(tempFace[i].normal);
511
0
        }
512
0
        if (uv) {
513
0
          mesh.uvs.push_back(tempFace[i].uv);
514
0
        }
515
516
0
        mesh.pflags |= 1 << (vcount - 1);
517
0
      }
518
519
0
      mesh.vcounts.push_back(vcount);
520
0
    }
521
0
  }
522
523
0
    if (!mesh_created) {
524
0
        TempMaterialMesh &mesh = bymat[mesh_id];
525
0
        mesh.matid = matId;
526
0
    }
527
528
  // finally extract output meshes and add them to the scope
529
0
    AppendOutputMeshes(bymat, scope, mesh_id);
530
531
  // no id == not a reference, insert this mesh right *here*
532
0
    return mesh_id == ErrorId;
533
0
}
534
535
// ----------------------------------------------------------------------------------------------
536
void XGLImporter::AppendOutputMeshes(std::map<unsigned int, TempMaterialMesh> bymat, TempScope &scope,
537
0
        const unsigned int mesh_id) {
538
0
    using pairt = std::pair<const unsigned int, TempMaterialMesh>;
539
0
    for (const pairt &p : bymat) {
540
0
        aiMesh *const m = ToOutputMesh(p.second);
541
0
        scope.meshes_linear.push_back(m);
542
543
        // if this is a definition, keep it on the stack
544
0
        if (mesh_id != ErrorId) {
545
0
            scope.meshes.insert(std::pair<unsigned int, aiMesh *>(mesh_id, m));
546
0
        }
547
0
    }
548
0
}
549
550
// ----------------------------------------------------------------------------------------------
551
0
unsigned int XGLImporter::ReadVertices(XmlNode &child, TempMesh t, TempFace *tf, bool *has, unsigned int mid, TempScope &scope) {
552
0
    for (XmlNode &sub_child : child.children()) {
553
0
        const std::string &scn = ai_stdStrToLower(sub_child.name());
554
0
        if (scn == "fv1" || scn == "lv1" || scn == "pv1") {
555
0
            ReadFaceVertex(sub_child, t, tf[0]);
556
0
            has[0] = true;
557
0
        } else if (scn == "fv2" || scn == "lv2") {
558
0
            ReadFaceVertex(sub_child, t, tf[1]);
559
0
            has[1] = true;
560
0
        } else if (scn == "fv3") {
561
0
            ReadFaceVertex(sub_child, t, tf[2]);
562
0
            has[2] = true;
563
0
        } else if (scn == "mat") {
564
0
            if (mid != ErrorId) {
565
0
                LogWarn("only one material tag allowed per <f>");
566
0
            }
567
0
            mid = ResolveMaterialRef(sub_child, scope);
568
0
        } else if (scn == "matref") {
569
0
            if (mid != ErrorId) {
570
0
                LogWarn("only one material tag allowed per <f>");
571
0
            }
572
0
            mid = ResolveMaterialRef(sub_child, scope);
573
0
        }
574
0
    }
575
0
    return mid;
576
0
}
577
578
// ----------------------------------------------------------------------------------------------
579
0
unsigned int XGLImporter::ResolveMaterialRef(XmlNode &node, TempScope &scope) {
580
0
  const std::string &s = node.name();
581
0
  if (s == "mat") {
582
0
    ReadMaterial(node, scope);
583
0
    return static_cast<unsigned int>(scope.materials_linear.size() - 1);
584
0
  }
585
586
0
  const int id = ReadIndexFromText(node);
587
588
0
  auto it = scope.materials.find(id), end = scope.materials.end();
589
0
  if (it == end) {
590
0
    ThrowException("<matref> index out of range");
591
0
  }
592
593
  // ok, this is n^2 and should get optimized one day
594
0
  aiMaterial *const m = it->second;
595
596
0
  unsigned int i = 0, mcount = static_cast<unsigned int>(scope.materials_linear.size());
597
0
  for (; i < mcount; ++i) {
598
0
    if (scope.materials_linear[i] == m) {
599
0
      return i;
600
0
    }
601
0
  }
602
603
0
  ai_assert(false);
604
605
0
  return 0;
606
0
}
607
608
// ------------------------------------------------------------------------------------------------
609
0
unsigned int XGLImporter::ReadMaterial(XmlNode &node, TempScope &scope) {
610
0
    const unsigned int mat_id = ReadIDAttr(node);
611
612
0
  auto *mat = new aiMaterial;
613
0
  for (XmlNode &child : node.children()) {
614
0
        const std::string &s = ai_stdStrToLower(child.name());
615
0
    if (s == "amb") {
616
0
      const aiColor3D c = ReadCol3(child);
617
0
      mat->AddProperty(&c, 1, AI_MATKEY_COLOR_AMBIENT);
618
0
    } else if (s == "diff") {
619
0
      const aiColor3D c = ReadCol3(child);
620
0
      mat->AddProperty(&c, 1, AI_MATKEY_COLOR_DIFFUSE);
621
0
    } else if (s == "spec") {
622
0
      const aiColor3D c = ReadCol3(child);
623
0
      mat->AddProperty(&c, 1, AI_MATKEY_COLOR_SPECULAR);
624
0
    } else if (s == "emiss") {
625
0
      const aiColor3D c = ReadCol3(child);
626
0
      mat->AddProperty(&c, 1, AI_MATKEY_COLOR_EMISSIVE);
627
0
    } else if (s == "alpha") {
628
0
      const float f = ReadFloat(child);
629
0
      mat->AddProperty(&f, 1, AI_MATKEY_OPACITY);
630
0
    } else if (s == "shine") {
631
0
      const float f = ReadFloat(child);
632
0
      mat->AddProperty(&f, 1, AI_MATKEY_SHININESS);
633
0
    }
634
0
  }
635
636
0
  scope.materials[mat_id] = mat;
637
0
  scope.materials_linear.push_back(mat);
638
639
0
    return mat_id;
640
0
}
641
642
// ----------------------------------------------------------------------------------------------
643
0
void XGLImporter::ReadFaceVertex(XmlNode &node, const TempMesh &t, TempFace &out) {
644
0
  bool havep = false;
645
0
  for (XmlNode &child : node.children()) {
646
0
        const std::string &s = ai_stdStrToLower(child.name());
647
0
    if (s == "pref") {
648
0
      const unsigned int id = ReadIndexFromText(child);
649
0
      std::map<unsigned int, aiVector3D>::const_iterator it = t.points.find(id);
650
0
      if (it == t.points.end()) {
651
0
        ThrowException("point index out of range");
652
0
      }
653
654
0
      out.pos = (*it).second;
655
0
      havep = true;
656
0
    } else if (s == "nref") {
657
0
      const unsigned int id = ReadIndexFromText(child);
658
0
      std::map<unsigned int, aiVector3D>::const_iterator it = t.normals.find(id);
659
0
      if (it == t.normals.end()) {
660
0
        ThrowException("normal index out of range");
661
0
      }
662
663
0
      out.normal = (*it).second;
664
0
      out.has_normal = true;
665
0
    } else if (s == "tcref") {
666
0
      const unsigned int id = ReadIndexFromText(child);
667
0
      std::map<unsigned int, aiVector2D>::const_iterator it = t.uvs.find(id);
668
0
      if (it == t.uvs.end()) {
669
0
        ThrowException("uv index out of range");
670
0
      }
671
672
0
      out.uv = (*it).second;
673
0
      out.has_uv = true;
674
0
    } else if (s == "p") {
675
0
      out.pos = ReadVec3(child);
676
0
    } else if (s == "n") {
677
0
      out.normal = ReadVec3(child);
678
0
    } else if (s == "tc") {
679
0
      out.uv = ReadVec2(child);
680
0
    }
681
0
  }
682
683
0
  if (!havep) {
684
0
    ThrowException("missing <pref> in <fvN> element");
685
0
  }
686
0
}
687
688
// ------------------------------------------------------------------------------------------------
689
0
unsigned int XGLImporter::ReadIDAttr(XmlNode &node) {
690
0
  for (pugi::xml_attribute attr : node.attributes()) {
691
0
    if (!ASSIMP_stricmp(attr.name(), "id")) {
692
0
      return attr.as_int();
693
0
    }
694
0
  }
695
696
0
  return ErrorId;
697
0
}
698
699
// ------------------------------------------------------------------------------------------------
700
0
float XGLImporter::ReadFloat(XmlNode &node) {
701
0
    std::string v;
702
0
    XmlParser::getValueAsString(node, v);
703
0
    const char *s = v.c_str();
704
0
    const char *end = v.c_str() + v.size();
705
0
  if (!SkipSpaces(&s, end)) {
706
0
    LogError("unexpected EOL, failed to parse index element");
707
0
    return 0.f;
708
0
  }
709
0
    float t{ 0.0f };
710
0
  const char *se = fast_atoreal_move(s, t);
711
0
  if (se == s) {
712
0
    LogError("failed to read float text");
713
0
    return 0.f;
714
0
  }
715
716
0
  return t;
717
0
}
718
719
// ------------------------------------------------------------------------------------------------
720
0
unsigned int XGLImporter::ReadIndexFromText(XmlNode &node) {
721
0
    std::string v;
722
0
    XmlParser::getValueAsString(node, v);
723
0
    const char *s = v.c_str();
724
0
    const char *end = v.c_str() + v.size();
725
0
    if (!SkipSpaces(&s, end)) {
726
0
    LogError("unexpected EOL, failed to parse index element");
727
0
        return ErrorId;
728
0
  }
729
0
  const char *se = nullptr;
730
0
  const unsigned int t = strtoul10(s, &se);
731
732
0
  if (se == s) {
733
0
    LogError("failed to read index");
734
0
        return ErrorId;
735
0
  }
736
737
0
  return t;
738
0
}
739
740
// ------------------------------------------------------------------------------------------------
741
0
aiVector2D XGLImporter::ReadVec2(XmlNode &node) {
742
0
  aiVector2D vec;
743
0
    std::string val;
744
0
    XmlParser::getValueAsString(node, val);
745
0
    const char *s = val.c_str();
746
0
    const char *end = val.c_str() + val.size();
747
0
    ai_real v[2] = {};
748
0
  for (int i = 0; i < 2; ++i) {
749
0
    if (!SkipSpaces(&s, end)) {
750
0
      LogError("unexpected EOL, failed to parse vec2");
751
0
      return vec;
752
0
    }
753
754
0
    v[i] = fast_atof(&s);
755
756
0
    SkipSpaces(&s, end);
757
0
    if (i != 1 && *s != ',') {
758
0
      LogError("expected comma, failed to parse vec2");
759
0
      return vec;
760
0
    }
761
0
    ++s;
762
0
  }
763
0
  vec.x = v[0];
764
0
  vec.y = v[1];
765
766
0
  return vec;
767
0
}
768
769
// ------------------------------------------------------------------------------------------------
770
0
aiVector3D XGLImporter::ReadVec3(XmlNode &node) {
771
0
  aiVector3D vec;
772
0
    std::string v;
773
0
    XmlParser::getValueAsString(node, v);
774
0
  const char *s = v.c_str();
775
0
    const char *end = v.c_str() + v.size();
776
0
  for (int i = 0; i < 3; ++i) {
777
0
    if (!SkipSpaces(&s, end)) {
778
0
      LogError("unexpected EOL, failed to parse vec3");
779
0
      return vec;
780
0
    }
781
0
    vec[i] = fast_atof(&s);
782
783
0
    SkipSpaces(&s, end);
784
0
    if (i != 2 && *s != ',') {
785
0
      LogError("expected comma, failed to parse vec3");
786
0
      return vec;
787
0
    }
788
0
    ++s;
789
0
  }
790
791
0
  return vec;
792
0
}
793
794
// ------------------------------------------------------------------------------------------------
795
0
aiColor3D XGLImporter::ReadCol3(XmlNode &node) {
796
0
  const aiVector3D &v = ReadVec3(node);
797
0
  if (v.x < 0.f || v.x > 1.0f || v.y < 0.f || v.y > 1.0f || v.z < 0.f || v.z > 1.0f) {
798
0
    LogWarn("color values out of range, ignoring");
799
0
  }
800
0
  return aiColor3D(v.x, v.y, v.z);
801
0
}
802
803
} // namespace Assimp
804
805
#endif // ASSIMP_BUILD_NO_XGL_IMPORTER