Coverage Report

Created: 2025-08-26 06:41

/src/assimp/code/AssetLib/AC/ACLoader.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 AC3D importer class */
43
44
#ifndef ASSIMP_BUILD_NO_AC_IMPORTER
45
46
// internal headers
47
#include "ACLoader.h"
48
#include "Common/Importer.h"
49
#include <assimp/BaseImporter.h>
50
#include <assimp/ParsingUtils.h>
51
#include <assimp/Subdivision.h>
52
#include <assimp/config.h>
53
#include <assimp/fast_atof.h>
54
#include <assimp/importerdesc.h>
55
#include <assimp/light.h>
56
#include <assimp/material.h>
57
#include <assimp/scene.h>
58
#include <assimp/DefaultLogger.hpp>
59
#include <assimp/IOSystem.hpp>
60
#include <assimp/Importer.hpp>
61
#include <memory>
62
63
namespace Assimp {
64
65
static constexpr aiImporterDesc desc = {
66
    "AC3D Importer",
67
    "",
68
    "",
69
    "",
70
    aiImporterFlags_SupportTextFlavour,
71
    0,
72
    0,
73
    0,
74
    0,
75
    "ac acc ac3d"
76
};
77
78
static constexpr auto ACDoubleSidedFlag = 0x20;
79
80
// ------------------------------------------------------------------------------------------------
81
// skip to the next token
82
0
inline const char *AcSkipToNextToken(const char *buffer, const char *end) {
83
0
    if (!SkipSpaces(&buffer, end)) {
84
0
        ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL");
85
0
    }
86
0
    return buffer;
87
0
}
88
89
// ------------------------------------------------------------------------------------------------
90
// read a string (may be enclosed in double quotation marks). buffer must point to "
91
0
inline const char *AcGetString(const char *buffer, const char *end, std::string &out) {
92
0
    if (*buffer == '\0') {
93
0
        throw DeadlyImportError("AC3D: Unexpected EOF in string");
94
0
    }
95
0
    ++buffer;
96
0
    const char *sz = buffer;
97
0
    while ('\"' != *buffer && buffer != end) {
98
0
        if (IsLineEnd(*buffer)) {
99
0
            ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL in string");
100
0
            out = "ERROR";
101
0
            break;
102
0
        }
103
0
        ++buffer;
104
0
    }
105
0
    if (IsLineEnd(*buffer)) {
106
0
        return buffer;
107
0
    }
108
0
    out = std::string(sz, (unsigned int)(buffer - sz));
109
0
    ++buffer;
110
111
0
    return buffer;
112
0
}
113
114
// ------------------------------------------------------------------------------------------------
115
// read 1 to n floats prefixed with an optional predefined identifier
116
template <class T>
117
0
inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) {
118
0
    buffer = AcSkipToNextToken(buffer, end);
119
0
    if (0 != name_length) {
120
0
        if (0 != strncmp(buffer, name, name_length) || !IsSpace(buffer[name_length])) {
121
0
            ASSIMP_LOG_ERROR("AC3D: Unexpected token. ", name, " was expected.");
122
0
            return buffer;
123
0
        }
124
0
        buffer += name_length + 1;
125
0
    }
126
0
    for (unsigned int _i = 0; _i < num; ++_i) {
127
0
        buffer = AcSkipToNextToken(buffer, end);
128
0
        buffer = fast_atoreal_move(buffer, ((float *)out)[_i]);
129
0
    }
130
131
0
    return buffer;
132
0
}
Unexecuted instantiation: char const* Assimp::TAcCheckedLoadFloatArray<aiVector2t<float> >(char const*, char const*, char const*, unsigned long, unsigned long, aiVector2t<float>*)
Unexecuted instantiation: char const* Assimp::TAcCheckedLoadFloatArray<aiMatrix3x3t<float> >(char const*, char const*, char const*, unsigned long, unsigned long, aiMatrix3x3t<float>*)
Unexecuted instantiation: char const* Assimp::TAcCheckedLoadFloatArray<aiVector3t<float> >(char const*, char const*, char const*, unsigned long, unsigned long, aiVector3t<float>*)
Unexecuted instantiation: char const* Assimp::TAcCheckedLoadFloatArray<float>(char const*, char const*, char const*, unsigned long, unsigned long, float*)
Unexecuted instantiation: char const* Assimp::TAcCheckedLoadFloatArray<aiColor3D>(char const*, char const*, char const*, unsigned long, unsigned long, aiColor3D*)
133
134
// ------------------------------------------------------------------------------------------------
135
// Reverses vertex indices in a face.
136
0
static void flipWindingOrder(aiFace &f) {
137
0
    std::reverse(f.mIndices, f.mIndices + f.mNumIndices);
138
0
}
139
140
// ------------------------------------------------------------------------------------------------
141
// Duplicates a face and inverts it. Also duplicates all vertices (so the new face gets its own
142
// set of normals and isn’t smoothed against the original).
143
static void buildBacksideOfFace(const aiFace &origFace, aiFace *&outFaces, aiVector3D *&outVertices, const aiVector3D *allVertices,
144
0
    aiVector3D *&outUV, const aiVector3D *allUV, unsigned &curIdx) {
145
0
    auto &newFace = *outFaces++;
146
0
    newFace = origFace;
147
0
    flipWindingOrder(newFace);
148
0
    for (unsigned f = 0; f < newFace.mNumIndices; ++f) {
149
0
        *outVertices++ = allVertices[newFace.mIndices[f]];
150
0
        if (outUV) {
151
0
            *outUV = allUV[newFace.mIndices[f]];
152
0
            outUV++;
153
0
        }
154
0
        newFace.mIndices[f] = curIdx++;
155
0
    }
156
0
}
157
158
// ------------------------------------------------------------------------------------------------
159
// Constructor to be privately used by Importer
160
AC3DImporter::AC3DImporter() :
161
220
        mBuffer(),
162
        configSplitBFCull(),
163
        configEvalSubdivision(),
164
        mNumMeshes(),
165
        mLights(),
166
220
        mLightsCounter(0),
167
220
        mGroupsCounter(0),
168
220
        mPolysCounter(0),
169
220
        mWorldsCounter(0) {
170
    // nothing to be done here
171
220
}
172
173
// ------------------------------------------------------------------------------------------------
174
// Returns whether the class can handle the format of the given file.
175
115
bool AC3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
176
115
    static constexpr uint32_t tokens[] = { AI_MAKE_MAGIC("AC3D") };
177
115
    return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
178
115
}
179
180
// ------------------------------------------------------------------------------------------------
181
// Loader meta information
182
210
const aiImporterDesc *AC3DImporter::GetInfo() const {
183
210
    return &desc;
184
210
}
185
186
// ------------------------------------------------------------------------------------------------
187
// Get a pointer to the next line from the file
188
0
bool AC3DImporter::GetNextLine() {
189
0
    SkipLine(&mBuffer.data, mBuffer.end);
190
0
    return SkipSpaces(&mBuffer.data, mBuffer.end);
191
0
}
192
193
// ------------------------------------------------------------------------------------------------
194
// Parse an object section in an AC file
195
0
bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
196
0
    if (!TokenMatch(mBuffer.data, "OBJECT", 6)) {
197
0
        return false;
198
0
    }
199
200
0
    SkipSpaces(&mBuffer.data, mBuffer.end);
201
202
0
    ++mNumMeshes;
203
204
0
    objects.emplace_back();
205
0
    Object &obj = objects.back();
206
207
0
    aiLight *light = nullptr;
208
0
    if (!ASSIMP_strincmp(mBuffer.data, "light", 5)) {
209
        // This is a light source. Add it to the list
210
0
        mLights->push_back(light = new aiLight());
211
212
        // Return a point light with no attenuation
213
0
        light->mType = aiLightSource_POINT;
214
0
        light->mColorDiffuse = light->mColorSpecular = aiColor3D(1.f, 1.f, 1.f);
215
0
        light->mAttenuationConstant = 1.f;
216
217
        // Generate a default name for both the light source and the node
218
0
        light->mName.length = ::ai_snprintf(light->mName.data, AI_MAXLEN, "ACLight_%i", static_cast<unsigned int>(mLights->size()) - 1);
219
0
        obj.name = std::string(light->mName.data);
220
221
0
        ASSIMP_LOG_VERBOSE_DEBUG("AC3D: Light source encountered");
222
0
        obj.type = Object::Light;
223
0
    } else if (!ASSIMP_strincmp(mBuffer.data, "group", 5)) {
224
0
        obj.type = Object::Group;
225
0
    } else if (!ASSIMP_strincmp(mBuffer.data, "world", 5)) {
226
0
        obj.type = Object::World;
227
0
    } else {
228
0
        obj.type = Object::Poly;
229
0
    }
230
231
0
    while (GetNextLine()) {
232
0
        if (TokenMatch(mBuffer.data, "kids", 4)) {
233
0
            SkipSpaces(&mBuffer.data, mBuffer.end);
234
0
            unsigned int num = strtoul10(mBuffer.data, &mBuffer.data);
235
0
            GetNextLine();
236
0
            if (num) {
237
                // load the children of this object recursively
238
0
                obj.children.reserve(num);
239
0
                for (unsigned int i = 0; i < num; ++i) {
240
0
                    if (!LoadObjectSection(obj.children)) {
241
0
                        ASSIMP_LOG_WARN("AC3D: wrong number of kids");
242
0
                        break;
243
0
                    }
244
0
                }
245
0
            }
246
0
            return true;
247
0
        } else if (TokenMatch(mBuffer.data, "name", 4)) {
248
0
            SkipSpaces(&mBuffer.data, mBuffer.data);
249
0
            mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, obj.name);
250
251
            // If this is a light source, we'll also need to store
252
            // the name of the node in it.
253
0
            if (light) {
254
0
                light->mName.Set(obj.name);
255
0
            }
256
0
        } else if (TokenMatch(mBuffer.data, "texture", 7)) {
257
0
            SkipSpaces(&mBuffer.data, mBuffer.end);
258
0
            std::string texture;
259
0
            mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, texture);
260
0
            obj.textures.push_back(texture);
261
0
        } else if (TokenMatch(mBuffer.data, "texrep", 6)) {
262
0
            SkipSpaces(&mBuffer.data, mBuffer.end);
263
0
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &obj.texRepeat);
264
0
            if (!obj.texRepeat.x || !obj.texRepeat.y)
265
0
                obj.texRepeat = aiVector2D(1.f, 1.f);
266
0
        } else if (TokenMatch(mBuffer.data, "texoff", 6)) {
267
0
            SkipSpaces(&mBuffer.data, mBuffer.end);
268
0
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &obj.texOffset);
269
0
        } else if (TokenMatch(mBuffer.data, "rot", 3)) {
270
0
            SkipSpaces(&mBuffer.data, mBuffer.end);
271
0
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 9, &obj.rotation);
272
0
        } else if (TokenMatch(mBuffer.data, "loc", 3)) {
273
0
            SkipSpaces(&mBuffer.data, mBuffer.end);
274
0
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 3, &obj.translation);
275
0
        } else if (TokenMatch(mBuffer.data, "subdiv", 6)) {
276
0
            SkipSpaces(&mBuffer.data, mBuffer.end);
277
0
            obj.subDiv = strtoul10(mBuffer.data, &mBuffer.data);
278
0
        } else if (TokenMatch(mBuffer.data, "crease", 6)) {
279
0
            SkipSpaces(&mBuffer.data, mBuffer.end);
280
0
            obj.crease = fast_atof(mBuffer.data);
281
0
        } else if (TokenMatch(mBuffer.data, "numvert", 7)) {
282
0
            SkipSpaces(&mBuffer.data, mBuffer.end);
283
284
0
            unsigned int t = strtoul10(mBuffer.data, &mBuffer.data);
285
0
            if (t >= AI_MAX_ALLOC(aiVector3D)) {
286
0
                throw DeadlyImportError("AC3D: Too many vertices, would run out of memory");
287
0
            }
288
0
            obj.vertices.reserve(t);
289
0
            for (unsigned int i = 0; i < t; ++i) {
290
0
                if (!GetNextLine()) {
291
0
                    ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: not all vertices have been parsed yet");
292
0
                    break;
293
0
                } else if (!IsNumeric(*mBuffer.data)) {
294
0
                    ASSIMP_LOG_ERROR("AC3D: Unexpected token: not all vertices have been parsed yet");
295
0
                    --mBuffer.data; // make sure the line is processed a second time
296
0
                    break;
297
0
                }
298
0
                obj.vertices.emplace_back();
299
0
                aiVector3D &v = obj.vertices.back();
300
0
                mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 3, &v.x);
301
0
            }
302
0
        } else if (TokenMatch(mBuffer.data, "numsurf", 7)) {
303
0
            SkipSpaces(&mBuffer.data, mBuffer.end);
304
305
0
            bool Q3DWorkAround = false;
306
307
0
            const unsigned int t = strtoul10(mBuffer.data, &mBuffer.data);
308
0
            obj.surfaces.reserve(t);
309
0
            for (unsigned int i = 0; i < t; ++i) {
310
0
                GetNextLine();
311
0
                if (!TokenMatch(mBuffer.data, "SURF", 4)) {
312
                    // FIX: this can occur for some files - Quick 3D for
313
                    // example writes no surf chunks
314
0
                    if (!Q3DWorkAround) {
315
0
                        ASSIMP_LOG_WARN("AC3D: SURF token was expected");
316
0
                        ASSIMP_LOG_VERBOSE_DEBUG("Continuing with Quick3D Workaround enabled");
317
0
                    }
318
0
                    --mBuffer.data; // make sure the line is processed a second time
319
                    // break; --- see fix notes above
320
321
0
                    Q3DWorkAround = true;
322
0
                }
323
0
                SkipSpaces(&mBuffer.data, mBuffer.end);
324
0
                obj.surfaces.emplace_back();
325
0
                Surface &surf = obj.surfaces.back();
326
0
                surf.flags = strtoul_cppstyle(mBuffer.data);
327
328
0
                while (true) {
329
0
                    if (!GetNextLine()) {
330
0
                        throw DeadlyImportError("AC3D: Unexpected EOF: surface is incomplete");
331
0
                    }
332
0
                    if (TokenMatch(mBuffer.data, "mat", 3)) {
333
0
                        SkipSpaces(&mBuffer.data, mBuffer.end);
334
0
                        surf.mat = strtoul10(mBuffer.data);
335
0
                    } else if (TokenMatch(mBuffer.data, "refs", 4)) {
336
                        // --- see fix notes above
337
0
                        if (Q3DWorkAround) {
338
0
                            if (!surf.entries.empty()) {
339
0
                                mBuffer.data -= 6;
340
0
                                break;
341
0
                            }
342
0
                        }
343
344
0
                        SkipSpaces(&mBuffer.data, mBuffer.end);
345
0
                        const unsigned int m = strtoul10(mBuffer.data);
346
0
                        surf.entries.reserve(m);
347
348
0
                        obj.numRefs += m;
349
350
0
                        for (unsigned int k = 0; k < m; ++k) {
351
0
                            if (!GetNextLine()) {
352
0
                                ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: surface references are incomplete");
353
0
                                break;
354
0
                            }
355
0
                            surf.entries.emplace_back();
356
0
                            Surface::SurfaceEntry &entry = surf.entries.back();
357
358
0
                            entry.first = strtoul10(mBuffer.data, &mBuffer.data);
359
0
                            SkipSpaces(&mBuffer.data, mBuffer.end);
360
0
                            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &entry.second);
361
0
                        }
362
0
                    } else {
363
0
                        --mBuffer.data; // make sure the line is processed a second time
364
0
                        break;
365
0
                    }
366
0
                }
367
0
            }
368
0
        }
369
0
    }
370
0
    ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: \'kids\' line was expected");
371
372
0
    return false;
373
0
}
374
375
// ------------------------------------------------------------------------------------------------
376
// Convert a material from AC3DImporter::Material to aiMaterial
377
void AC3DImporter::ConvertMaterial(const Object &object,
378
        const Material &matSrc,
379
0
        aiMaterial &matDest) {
380
0
    aiString s;
381
382
0
    if (matSrc.name.length()) {
383
0
        s.Set(matSrc.name);
384
0
        matDest.AddProperty(&s, AI_MATKEY_NAME);
385
0
    }
386
0
    if (!object.textures.empty()) {
387
0
        s.Set(object.textures[0]);
388
0
        matDest.AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0));
389
390
        // UV transformation
391
0
        if (1.f != object.texRepeat.x || 1.f != object.texRepeat.y ||
392
0
                object.texOffset.x || object.texOffset.y) {
393
0
            aiUVTransform transform;
394
0
            transform.mScaling = object.texRepeat;
395
0
            transform.mTranslation = object.texOffset;
396
0
            matDest.AddProperty(&transform, 1, AI_MATKEY_UVTRANSFORM_DIFFUSE(0));
397
0
        }
398
0
    }
399
400
0
    matDest.AddProperty<aiColor3D>(&matSrc.rgb, 1, AI_MATKEY_COLOR_DIFFUSE);
401
0
    matDest.AddProperty<aiColor3D>(&matSrc.amb, 1, AI_MATKEY_COLOR_AMBIENT);
402
0
    matDest.AddProperty<aiColor3D>(&matSrc.emis, 1, AI_MATKEY_COLOR_EMISSIVE);
403
0
    matDest.AddProperty<aiColor3D>(&matSrc.spec, 1, AI_MATKEY_COLOR_SPECULAR);
404
405
0
    int n = -1;
406
0
    if (matSrc.shin) {
407
0
        n = aiShadingMode_Phong;
408
0
        matDest.AddProperty<float>(&matSrc.shin, 1, AI_MATKEY_SHININESS);
409
0
    } else {
410
0
        n = aiShadingMode_Gouraud;
411
0
    }
412
0
    matDest.AddProperty<int>(&n, 1, AI_MATKEY_SHADING_MODEL);
413
414
0
    float f = 1.f - matSrc.trans;
415
0
    matDest.AddProperty<float>(&f, 1, AI_MATKEY_OPACITY);
416
0
}
417
418
// ------------------------------------------------------------------------------------------------
419
// Converts the loaded data to the internal verbose representation
420
aiNode *AC3DImporter::ConvertObjectSection(Object &object,
421
        MeshArray &meshes,
422
        std::vector<aiMaterial *> &outMaterials,
423
        const std::vector<Material> &materials,
424
0
        aiNode *parent) {
425
0
    aiNode *node = new aiNode();
426
0
    node->mParent = parent;
427
0
    if (object.vertices.size()) {
428
0
        if (!object.surfaces.size() || !object.numRefs) {
429
            /* " An object with 7 vertices (no surfaces, no materials defined).
430
                 This is a good way of getting point data into AC3D.
431
                 The Vertex->create convex-surface/object can be used on these
432
                 vertices to 'wrap' a 3d shape around them "
433
                 (http://www.opencity.info/html/ac3dfileformat.html)
434
435
                 therefore: if no surfaces are defined return point data only
436
             */
437
438
0
            ASSIMP_LOG_INFO("AC3D: No surfaces defined in object definition, "
439
0
                            "a point list is returned");
440
441
0
            meshes.push_back(new aiMesh());
442
0
            aiMesh *mesh = meshes.back();
443
444
0
            mesh->mNumFaces = mesh->mNumVertices = (unsigned int)object.vertices.size();
445
0
            aiFace *faces = mesh->mFaces = new aiFace[mesh->mNumFaces];
446
0
            aiVector3D *verts = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
447
448
0
            for (unsigned int i = 0; i < mesh->mNumVertices; ++i, ++faces, ++verts) {
449
0
                *verts = object.vertices[i];
450
0
                faces->mNumIndices = 1;
451
0
                faces->mIndices = new unsigned int[1];
452
0
                faces->mIndices[0] = i;
453
0
            }
454
455
            // use the primary material in this case. this should be the
456
            // default material if all objects of the file contain points
457
            // and no faces.
458
0
            mesh->mMaterialIndex = 0;
459
0
            outMaterials.push_back(new aiMaterial());
460
0
            ConvertMaterial(object, materials[0], *outMaterials.back());
461
0
        } else {
462
            // need to generate one or more meshes for this object.
463
            // find out how many different materials we have
464
0
            typedef std::pair<unsigned int, unsigned int> IntPair;
465
0
            typedef std::vector<IntPair> MatTable;
466
0
            MatTable needMat(materials.size(), IntPair(0, 0));
467
468
0
            std::vector<Surface>::iterator it, end = object.surfaces.end();
469
0
            std::vector<Surface::SurfaceEntry>::iterator it2, end2;
470
471
0
            for (it = object.surfaces.begin(); it != end; ++it) {
472
0
                unsigned int idx = (*it).mat;
473
0
                if (idx >= needMat.size()) {
474
0
                    ASSIMP_LOG_ERROR("AC3D: material index is out of range");
475
0
                    idx = 0;
476
0
                }
477
0
                if ((*it).entries.empty()) {
478
0
                    ASSIMP_LOG_WARN("AC3D: surface has zero vertex references");
479
0
                }
480
0
                const bool isDoubleSided = ACDoubleSidedFlag == (it->flags & ACDoubleSidedFlag);
481
0
                const int doubleSidedFactor = isDoubleSided ? 2 : 1;
482
483
                // validate all vertex indices to make sure we won't crash here
484
0
                for (it2 = (*it).entries.begin(),
485
0
                    end2 = (*it).entries.end();
486
0
                        it2 != end2; ++it2) {
487
0
                    if ((*it2).first >= object.vertices.size()) {
488
0
                        ASSIMP_LOG_WARN("AC3D: Invalid vertex reference");
489
0
                        (*it2).first = 0;
490
0
                    }
491
0
                }
492
493
0
                if (!needMat[idx].first) {
494
0
                    ++node->mNumMeshes;
495
0
                }
496
497
0
                switch ((*it).GetType()) {
498
0
                case Surface::ClosedLine: // closed line
499
0
                    needMat[idx].first += static_cast<unsigned int>((*it).entries.size());
500
0
                    needMat[idx].second += static_cast<unsigned int>((*it).entries.size() << 1u);
501
0
                    break;
502
503
                    // unclosed line
504
0
                case Surface::OpenLine:
505
0
                    needMat[idx].first += static_cast<unsigned int>((*it).entries.size() - 1);
506
0
                    needMat[idx].second += static_cast<unsigned int>(((*it).entries.size() - 1) << 1u);
507
0
                    break;
508
509
                    // triangle strip
510
0
                case Surface::TriangleStrip:
511
0
                    needMat[idx].first += static_cast<unsigned int>(it->entries.size() - 2) * doubleSidedFactor;
512
0
                    needMat[idx].second += static_cast<unsigned int>(it->entries.size() - 2) * 3 * doubleSidedFactor;
513
0
                    break;
514
515
0
                default:
516
                    // Coerce unknowns to a polygon and warn
517
0
                    ASSIMP_LOG_WARN("AC3D: The type flag of a surface is unknown: ", (*it).flags);
518
0
                    (*it).flags &= ~(Surface::Mask);
519
                    // fallthrough
520
521
                    // polygon
522
0
                case Surface::Polygon:
523
                    // the number of faces increments by one, the number
524
                    // of vertices by surface.numref.
525
0
                    needMat[idx].first += doubleSidedFactor;
526
0
                    needMat[idx].second += static_cast<unsigned int>(it->entries.size()) * doubleSidedFactor;
527
0
                };
528
0
            }
529
0
            unsigned int *pip = node->mMeshes = new unsigned int[node->mNumMeshes];
530
0
            unsigned int mat = 0;
531
0
            const size_t oldm = meshes.size();
532
0
            for (MatTable::const_iterator cit = needMat.begin(), cend = needMat.end();
533
0
                    cit != cend; ++cit, ++mat) {
534
0
                if (!(*cit).first) {
535
0
                    continue;
536
0
                }
537
538
                // allocate a new aiMesh object
539
0
                *pip++ = (unsigned int)meshes.size();
540
0
                aiMesh *mesh = new aiMesh();
541
0
                meshes.push_back(mesh);
542
543
0
                mesh->mMaterialIndex = static_cast<unsigned int>(outMaterials.size());
544
0
                outMaterials.push_back(new aiMaterial());
545
0
                ConvertMaterial(object, materials[mat], *outMaterials.back());
546
547
                // allocate storage for vertices and normals
548
0
                mesh->mNumFaces = (*cit).first;
549
0
                if (mesh->mNumFaces == 0) {
550
0
                    throw DeadlyImportError("AC3D: No faces");
551
0
                } else if (mesh->mNumFaces > AI_MAX_ALLOC(aiFace)) {
552
0
                    throw DeadlyImportError("AC3D: Too many faces, would run out of memory");
553
0
                }
554
0
                aiFace *faces = mesh->mFaces = new aiFace[mesh->mNumFaces];
555
556
0
                mesh->mNumVertices = (*cit).second;
557
0
                if (mesh->mNumVertices == 0) {
558
0
                    throw DeadlyImportError("AC3D: No vertices");
559
0
                } else if (mesh->mNumVertices > AI_MAX_ALLOC(aiVector3D)) {
560
0
                    throw DeadlyImportError("AC3D: Too many vertices, would run out of memory");
561
0
                }
562
0
                aiVector3D *vertices = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
563
0
                unsigned int cur = 0;
564
565
                // allocate UV coordinates, but only if the texture name for the
566
                // surface is not empty
567
0
                aiVector3D *uv = nullptr;
568
0
                if (!object.textures.empty()) {
569
0
                    uv = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
570
0
                    mesh->mNumUVComponents[0] = 2;
571
0
                }
572
573
0
                for (it = object.surfaces.begin(); it != end; ++it) {
574
0
                    if (mat == (*it).mat) {
575
0
                        const Surface &src = *it;
576
0
                        const bool isDoubleSided = ACDoubleSidedFlag == (src.flags & ACDoubleSidedFlag);
577
578
                        // closed polygon
579
0
                        uint8_t type = (*it).GetType();
580
0
                        if (type == Surface::Polygon) {
581
0
                            aiFace &face = *faces++;
582
0
                            face.mNumIndices = (unsigned int)src.entries.size();
583
0
                            if (0 != face.mNumIndices) {
584
0
                                face.mIndices = new unsigned int[face.mNumIndices];
585
0
                                for (unsigned int i = 0; i < face.mNumIndices; ++i, ++vertices) {
586
0
                                    const Surface::SurfaceEntry &entry = src.entries[i];
587
0
                                    face.mIndices[i] = cur++;
588
589
                                    // copy vertex positions
590
0
                                    if (static_cast<unsigned>(vertices - mesh->mVertices) >= mesh->mNumVertices) {
591
0
                                        throw DeadlyImportError("AC3D: Invalid number of vertices");
592
0
                                    }
593
0
                                    *vertices = object.vertices[entry.first] + object.translation;
594
595
                                    // copy texture coordinates
596
0
                                    if (uv) {
597
0
                                        uv->x = entry.second.x;
598
0
                                        uv->y = entry.second.y;
599
0
                                        ++uv;
600
0
                                    }
601
0
                                }
602
0
                                if(isDoubleSided) // Need a backface?
603
0
                                    buildBacksideOfFace(faces[-1], faces, vertices, mesh->mVertices, uv, mesh->mTextureCoords[0], cur);
604
0
                            }
605
0
                        } else if (type == Surface::TriangleStrip) {
606
0
                            for (unsigned int i = 0; i < (unsigned int)src.entries.size() - 2; ++i) {
607
0
                                const Surface::SurfaceEntry &entry1 = src.entries[i];
608
0
                                const Surface::SurfaceEntry &entry2 = src.entries[i + 1];
609
0
                                const Surface::SurfaceEntry &entry3 = src.entries[i + 2];
610
611
0
                                aiFace &face = *faces++;
612
0
                                face.mNumIndices = 3;
613
0
                                face.mIndices = new unsigned int[face.mNumIndices];
614
0
                                face.mIndices[0] = cur++;
615
0
                                face.mIndices[1] = cur++;
616
0
                                face.mIndices[2] = cur++;
617
0
                                if (!(i & 1)) {
618
0
                                    *vertices++ = object.vertices[entry1.first] + object.translation;
619
0
                                    if (uv) {
620
0
                                        uv->x = entry1.second.x;
621
0
                                        uv->y = entry1.second.y;
622
0
                                        ++uv;
623
0
                                    }
624
0
                                    *vertices++ = object.vertices[entry2.first] + object.translation;
625
0
                                    if (uv) {
626
0
                                        uv->x = entry2.second.x;
627
0
                                        uv->y = entry2.second.y;
628
0
                                        ++uv;
629
0
                                    }
630
0
                                } else {
631
0
                                    *vertices++ = object.vertices[entry2.first] + object.translation;
632
0
                                    if (uv) {
633
0
                                        uv->x = entry2.second.x;
634
0
                                        uv->y = entry2.second.y;
635
0
                                        ++uv;
636
0
                                    }
637
0
                                    *vertices++ = object.vertices[entry1.first] + object.translation;
638
0
                                    if (uv) {
639
0
                                        uv->x = entry1.second.x;
640
0
                                        uv->y = entry1.second.y;
641
0
                                        ++uv;
642
0
                                    }
643
0
                                }
644
0
                                if (static_cast<unsigned>(vertices - mesh->mVertices) >= mesh->mNumVertices) {
645
0
                                    throw DeadlyImportError("AC3D: Invalid number of vertices");
646
0
                                }
647
0
                                *vertices++ = object.vertices[entry3.first] + object.translation;
648
0
                                if (uv) {
649
0
                                    uv->x = entry3.second.x;
650
0
                                    uv->y = entry3.second.y;
651
0
                                    ++uv;
652
0
                                }
653
0
                                if(isDoubleSided) // Need a backface?
654
0
                                    buildBacksideOfFace(faces[-1], faces, vertices, mesh->mVertices, uv, mesh->mTextureCoords[0], cur);
655
0
                            }
656
0
                        } else {
657
658
0
                            it2 = (*it).entries.begin();
659
660
                            // either a closed or an unclosed line
661
0
                            unsigned int tmp = (unsigned int)(*it).entries.size();
662
0
                            if (Surface::OpenLine == type) --tmp;
663
0
                            for (unsigned int m = 0; m < tmp; ++m) {
664
0
                                aiFace &face = *faces++;
665
666
0
                                face.mNumIndices = 2;
667
0
                                face.mIndices = new unsigned int[2];
668
0
                                face.mIndices[0] = cur++;
669
0
                                face.mIndices[1] = cur++;
670
671
                                // copy vertex positions
672
0
                                if (it2 == (*it).entries.end()) {
673
0
                                    throw DeadlyImportError("AC3D: Bad line");
674
0
                                }
675
0
                                ai_assert((*it2).first < object.vertices.size());
676
0
                                *vertices++ = object.vertices[(*it2).first];
677
678
                                // copy texture coordinates
679
0
                                if (uv) {
680
0
                                    uv->x = (*it2).second.x;
681
0
                                    uv->y = (*it2).second.y;
682
0
                                    ++uv;
683
0
                                }
684
685
0
                                if (Surface::ClosedLine == type && tmp - 1 == m) {
686
                                    // if this is a closed line repeat its beginning now
687
0
                                    it2 = (*it).entries.begin();
688
0
                                } else
689
0
                                    ++it2;
690
691
                                // second point
692
0
                                *vertices++ = object.vertices[(*it2).first];
693
694
0
                                if (uv) {
695
0
                                    uv->x = (*it2).second.x;
696
0
                                    uv->y = (*it2).second.y;
697
0
                                    ++uv;
698
0
                                }
699
0
                            }
700
0
                        }
701
0
                    }
702
0
                }
703
0
            }
704
705
            // Now apply catmull clark subdivision if necessary. We split meshes into
706
            // materials which is not done by AC3D during smoothing, so we need to
707
            // collect all meshes using the same material group.
708
0
            if (object.subDiv) {
709
0
                if (configEvalSubdivision) {
710
0
                    std::unique_ptr<Subdivider> div(Subdivider::Create(Subdivider::CATMULL_CLARKE));
711
0
                    ASSIMP_LOG_INFO("AC3D: Evaluating subdivision surface: ", object.name);
712
713
0
                    MeshArray cpy(meshes.size() - oldm, nullptr);
714
0
                    div->Subdivide(&meshes[oldm], cpy.size(), &cpy.front(), object.subDiv, true);
715
0
                    std::copy(cpy.begin(), cpy.end(), meshes.begin() + oldm);
716
717
                    // previous meshes are deleted vy Subdivide().
718
0
                } else {
719
0
                    ASSIMP_LOG_INFO("AC3D: Letting the subdivision surface untouched due to my configuration: ", object.name);
720
0
                }
721
0
            }
722
0
        }
723
0
    }
724
725
0
    if (object.name.length())
726
0
        node->mName.Set(object.name);
727
0
    else {
728
        // generate a name depending on the type of the node
729
0
        switch (object.type) {
730
0
        case Object::Group:
731
0
            node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACGroup_%i", mGroupsCounter++);
732
0
            break;
733
0
        case Object::Poly:
734
0
            node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACPoly_%i", mPolysCounter++);
735
0
            break;
736
0
        case Object::Light:
737
0
            node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACLight_%i", mLightsCounter++);
738
0
            break;
739
740
            // there shouldn't be more than one world, but we don't care
741
0
        case Object::World:
742
0
            node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACWorld_%i", mWorldsCounter++);
743
0
            break;
744
0
        }
745
0
    }
746
747
    // setup the local transformation matrix of the object
748
    // compute the transformation offset to the parent node
749
0
    node->mTransformation = aiMatrix4x4(object.rotation);
750
751
0
    if (object.type == Object::Group || !object.numRefs) {
752
0
        node->mTransformation.a4 = object.translation.x;
753
0
        node->mTransformation.b4 = object.translation.y;
754
0
        node->mTransformation.c4 = object.translation.z;
755
0
    }
756
757
    // add children to the object
758
0
    if (object.children.size()) {
759
0
        node->mNumChildren = (unsigned int)object.children.size();
760
0
        node->mChildren = new aiNode *[node->mNumChildren];
761
0
        for (unsigned int i = 0; i < node->mNumChildren; ++i) {
762
0
            node->mChildren[i] = ConvertObjectSection(object.children[i], meshes, outMaterials, materials, node);
763
0
        }
764
0
    }
765
766
0
    return node;
767
0
}
768
769
// ------------------------------------------------------------------------------------------------
770
0
void AC3DImporter::SetupProperties(const Importer *pImp) {
771
0
    configSplitBFCull = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_SEPARATE_BFCULL, 1) ? true : false;
772
0
    configEvalSubdivision = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_EVAL_SUBDIVISION, 1) ? true : false;
773
0
}
774
775
// ------------------------------------------------------------------------------------------------
776
// Imports the given file into the given scene structure.
777
void AC3DImporter::InternReadFile(const std::string &pFile,
778
0
        aiScene *pScene, IOSystem *pIOHandler) {
779
0
    std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb"));
780
781
    // Check whether we can read from the file
782
0
    if (file == nullptr) {
783
0
        throw DeadlyImportError("Failed to open AC3D file ", pFile, ".");
784
0
    }
785
786
    // allocate storage and copy the contents of the file to a memory buffer
787
0
    std::vector<char> mBuffer2;
788
0
    TextFileToBuffer(file.get(), mBuffer2);
789
790
0
    mBuffer.data = &mBuffer2[0];
791
0
    mBuffer.end = &mBuffer2[0] + mBuffer2.size();
792
0
    mNumMeshes = 0;
793
794
0
    mLightsCounter = mPolysCounter = mWorldsCounter = mGroupsCounter = 0;
795
796
0
    if (::strncmp(mBuffer.data, "AC3D", 4)) {
797
0
        throw DeadlyImportError("AC3D: No valid AC3D file, magic sequence not found");
798
0
    }
799
800
    // print the file format version to the console
801
0
    unsigned int version = HexDigitToDecimal(mBuffer.data[4]);
802
0
    char msg[3];
803
0
    ASSIMP_itoa10(msg, 3, version);
804
0
    ASSIMP_LOG_INFO("AC3D file format version: ", msg);
805
806
0
    std::vector<Material> materials;
807
0
    materials.reserve(5);
808
809
0
    std::vector<Object> rootObjects;
810
0
    rootObjects.reserve(5);
811
812
0
    std::vector<aiLight *> lights;
813
0
    mLights = &lights;
814
815
0
    while (GetNextLine()) {
816
0
        if (TokenMatch(mBuffer.data, "MATERIAL", 8)) {
817
0
            materials.emplace_back();
818
0
            Material &mat = materials.back();
819
820
            // manually parse the material ... sscanf would use the buldin atof ...
821
            // Format: (name) rgb %f %f %f  amb %f %f %f  emis %f %f %f  spec %f %f %f  shi %d  trans %f
822
823
0
            mBuffer.data = AcSkipToNextToken(mBuffer.data, mBuffer.end);
824
0
            if ('\"' == *mBuffer.data) {
825
0
                mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, mat.name);
826
0
                mBuffer.data = AcSkipToNextToken(mBuffer.data, mBuffer.end);
827
0
            }
828
829
0
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "rgb", 3, 3, &mat.rgb);
830
0
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "amb", 3, 3, &mat.amb);
831
0
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "emis", 4, 3, &mat.emis);
832
0
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "spec", 4, 3, &mat.spec);
833
0
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "shi", 3, 1, &mat.shin);
834
0
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "trans", 5, 1, &mat.trans);
835
0
        } else {
836
0
            LoadObjectSection(rootObjects);
837
0
        }
838
0
    }
839
840
0
    if (rootObjects.empty() || mNumMeshes == 0u) {
841
0
        throw DeadlyImportError("AC3D: No meshes have been loaded");
842
0
    }
843
0
    if (materials.empty()) {
844
0
        ASSIMP_LOG_WARN("AC3D: No material has been found");
845
0
        materials.emplace_back();
846
0
    }
847
848
0
    mNumMeshes += (mNumMeshes >> 2u) + 1;
849
0
    MeshArray meshes;
850
0
    meshes.reserve(mNumMeshes);
851
852
0
    std::vector<aiMaterial *> omaterials;
853
0
    materials.reserve(mNumMeshes);
854
855
    // generate a dummy root if there are multiple objects on the top layer
856
0
    Object *root = nullptr;
857
0
    if (1 == rootObjects.size())
858
0
        root = &rootObjects[0];
859
0
    else {
860
0
        root = new Object();
861
0
    }
862
863
    // now convert the imported stuff to our output data structure
864
0
    pScene->mRootNode = ConvertObjectSection(*root, meshes, omaterials, materials);
865
0
    if (1 != rootObjects.size()) {
866
0
        delete root;
867
0
    }
868
869
0
    if (::strncmp(pScene->mRootNode->mName.data, "Node", 4) == 0) {
870
0
        pScene->mRootNode->mName.Set("<AC3DWorld>");
871
0
    }
872
873
    // copy meshes
874
0
    if (meshes.empty()) {
875
0
        throw DeadlyImportError("An unknown error occurred during converting");
876
0
    }
877
0
    pScene->mNumMeshes = (unsigned int)meshes.size();
878
0
    pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
879
0
    ::memcpy(pScene->mMeshes, &meshes[0], pScene->mNumMeshes * sizeof(void *));
880
881
    // copy materials
882
0
    pScene->mNumMaterials = (unsigned int)omaterials.size();
883
0
    pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
884
0
    ::memcpy(pScene->mMaterials, &omaterials[0], pScene->mNumMaterials * sizeof(void *));
885
886
    // copy lights
887
0
    pScene->mNumLights = (unsigned int)lights.size();
888
0
    if (!lights.empty()) {
889
0
        pScene->mLights = new aiLight *[lights.size()];
890
0
        ::memcpy(pScene->mLights, &lights[0], lights.size() * sizeof(void *));
891
0
    }
892
0
}
893
894
} // namespace Assimp
895
896
#endif //!defined ASSIMP_BUILD_NO_AC_IMPORTER