Coverage Report

Created: 2026-06-15 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/AC/ACLoader.cpp
Line
Count
Source
1
/*
2
---------------------------------------------------------------------------
3
Open Asset Import Library (assimp)
4
---------------------------------------------------------------------------
5
6
Copyright (c) 2006-2026, 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
75.0k
inline const char *AcSkipToNextToken(const char *buffer, const char *end) {
83
75.0k
    if (!SkipSpaces(&buffer, end)) {
84
0
        ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL");
85
0
    }
86
75.0k
    return buffer;
87
75.0k
}
88
89
// ------------------------------------------------------------------------------------------------
90
// read a string (may be enclosed in double quotation marks). buffer must point to "
91
4
inline const char *AcGetString(const char *buffer, const char *end, std::string &out) {
92
4
    if (*buffer == '\0') {
93
0
        throw DeadlyImportError("AC3D: Unexpected EOF in string");
94
0
    }
95
4
    ++buffer;
96
4
    const char *sz = buffer;
97
36
    while ('\"' != *buffer && buffer != end) {
98
32
        if (IsLineEnd(*buffer)) {
99
0
            ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL in string");
100
0
            out = "ERROR";
101
0
            break;
102
0
        }
103
32
        ++buffer;
104
32
    }
105
4
    if (IsLineEnd(*buffer)) {
106
0
        return buffer;
107
0
    }
108
4
    out = std::string(sz, (unsigned int)(buffer - sz));
109
4
    ++buffer;
110
111
4
    return buffer;
112
4
}
113
114
// ------------------------------------------------------------------------------------------------
115
// read 1 to n floats prefixed with an optional predefined identifier
116
template <class T>
117
22.8k
inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) {
118
22.8k
    buffer = AcSkipToNextToken(buffer, end);
119
22.8k
    if (0 != name_length) {
120
12
        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
12
        buffer += name_length + 1;
125
12
    }
126
75.0k
    for (unsigned int _i = 0; _i < num; ++_i) {
127
52.1k
        buffer = AcSkipToNextToken(buffer, end);
128
52.1k
        buffer = fast_atoreal_move(buffer, ((float *)out)[_i]);
129
52.1k
    }
130
131
22.8k
    return buffer;
132
22.8k
}
char const* Assimp::TAcCheckedLoadFloatArray<aiVector2t<float> >(char const*, char const*, char const*, unsigned long, unsigned long, aiVector2t<float>*)
Line
Count
Source
117
16.5k
inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) {
118
16.5k
    buffer = AcSkipToNextToken(buffer, end);
119
16.5k
    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
49.5k
    for (unsigned int _i = 0; _i < num; ++_i) {
127
33.0k
        buffer = AcSkipToNextToken(buffer, end);
128
33.0k
        buffer = fast_atoreal_move(buffer, ((float *)out)[_i]);
129
33.0k
    }
130
131
16.5k
    return buffer;
132
16.5k
}
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>*)
char const* Assimp::TAcCheckedLoadFloatArray<float>(char const*, char const*, char const*, unsigned long, unsigned long, float*)
Line
Count
Source
117
6.37k
inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) {
118
6.37k
    buffer = AcSkipToNextToken(buffer, end);
119
6.37k
    if (0 != name_length) {
120
4
        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
4
        buffer += name_length + 1;
125
4
    }
126
25.4k
    for (unsigned int _i = 0; _i < num; ++_i) {
127
19.1k
        buffer = AcSkipToNextToken(buffer, end);
128
19.1k
        buffer = fast_atoreal_move(buffer, ((float *)out)[_i]);
129
19.1k
    }
130
131
6.37k
    return buffer;
132
6.37k
}
char const* Assimp::TAcCheckedLoadFloatArray<aiColor3D>(char const*, char const*, char const*, unsigned long, unsigned long, aiColor3D*)
Line
Count
Source
117
8
inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) {
118
8
    buffer = AcSkipToNextToken(buffer, end);
119
8
    if (0 != name_length) {
120
8
        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
8
        buffer += name_length + 1;
125
8
    }
126
32
    for (unsigned int _i = 0; _i < num; ++_i) {
127
24
        buffer = AcSkipToNextToken(buffer, end);
128
24
        buffer = fast_atoreal_move(buffer, ((float *)out)[_i]);
129
24
    }
130
131
8
    return buffer;
132
8
}
133
134
// ------------------------------------------------------------------------------------------------
135
// Reverses vertex indices in a face.
136
4.63k
static void flipWindingOrder(aiFace &f) {
137
4.63k
    std::reverse(f.mIndices, f.mIndices + f.mNumIndices);
138
4.63k
}
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
4.63k
    aiVector3D *&outUV, const aiVector3D *allUV, unsigned &curIdx) {
145
4.63k
    auto &newFace = *outFaces++;
146
4.63k
    newFace = origFace;
147
4.63k
    flipWindingOrder(newFace);
148
18.5k
    for (unsigned f = 0; f < newFace.mNumIndices; ++f) {
149
13.8k
        *outVertices++ = allVertices[newFace.mIndices[f]];
150
13.8k
        if (outUV) {
151
0
            *outUV = allUV[newFace.mIndices[f]];
152
0
            outUV++;
153
0
        }
154
13.8k
        newFace.mIndices[f] = curIdx++;
155
13.8k
    }
156
4.63k
}
157
158
// ------------------------------------------------------------------------------------------------
159
// Constructor to be privately used by Importer
160
AC3DImporter::AC3DImporter() :
161
63.6k
        mBuffer(),
162
        configSplitBFCull(),
163
        configEvalSubdivision(),
164
        mNumMeshes(),
165
        mLights(),
166
63.6k
        mLightsCounter(0),
167
63.6k
        mGroupsCounter(0),
168
63.6k
        mPolysCounter(0),
169
63.6k
        mWorldsCounter(0) {
170
    // nothing to be done here
171
63.6k
}
172
173
// ------------------------------------------------------------------------------------------------
174
// Returns whether the class can handle the format of the given file.
175
68
bool AC3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
176
68
    static constexpr uint32_t tokens[] = { AI_MAKE_MAGIC("AC3D") };
177
68
    return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
178
68
}
179
180
// ------------------------------------------------------------------------------------------------
181
// Loader meta information
182
63.6k
const aiImporterDesc *AC3DImporter::GetInfo() const {
183
63.6k
    return &desc;
184
63.6k
}
185
186
// ------------------------------------------------------------------------------------------------
187
// Get a pointer to the next line from the file
188
39.2k
bool AC3DImporter::GetNextLine() {
189
39.2k
    SkipLine(&mBuffer.data, mBuffer.end);
190
39.2k
    return SkipSpaces(&mBuffer.data, mBuffer.end);
191
39.2k
}
192
193
// ------------------------------------------------------------------------------------------------
194
// Parse an object section in an AC file
195
4
bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
196
4
    if (!TokenMatch(mBuffer.data, "OBJECT", 6)) {
197
0
        return false;
198
0
    }
199
200
4
    SkipSpaces(&mBuffer.data, mBuffer.end);
201
202
4
    ++mNumMeshes;
203
204
4
    objects.emplace_back();
205
4
    Object &obj = objects.back();
206
207
4
    aiLight *light = nullptr;
208
4
    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
4
    } else if (!ASSIMP_strincmp(mBuffer.data, "group", 5)) {
224
0
        obj.type = Object::Group;
225
4
    } else if (!ASSIMP_strincmp(mBuffer.data, "world", 5)) {
226
2
        obj.type = Object::World;
227
2
    } else {
228
2
        obj.type = Object::Poly;
229
2
    }
230
231
13
    while (GetNextLine()) {
232
13
        if (TokenMatch(mBuffer.data, "kids", 4)) {
233
4
            SkipSpaces(&mBuffer.data, mBuffer.end);
234
4
            unsigned int num = strtoul10(mBuffer.data, &mBuffer.data);
235
4
            GetNextLine();
236
4
            if (num) {
237
                // load the children of this object recursively
238
2
                obj.children.reserve(num);
239
4
                for (unsigned int i = 0; i < num; ++i) {
240
2
                    if (!LoadObjectSection(obj.children)) {
241
0
                        ASSIMP_LOG_WARN("AC3D: wrong number of kids");
242
0
                        break;
243
0
                    }
244
2
                }
245
2
            }
246
4
            return true;
247
9
        } else if (TokenMatch(mBuffer.data, "name", 4)) {
248
2
            SkipSpaces(&mBuffer.data, mBuffer.data);
249
2
            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
2
            if (light) {
254
0
                light->mName.Set(obj.name);
255
0
            }
256
7
        } 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
7
        } 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
7
        } 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
7
        } 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
7
        } 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
7
        } else if (TokenMatch(mBuffer.data, "subdiv", 6)) {
276
0
            SkipSpaces(&mBuffer.data, mBuffer.end);
277
0
            obj.subDiv = strtoul10(mBuffer.data, &mBuffer.data);
278
7
        } else if (TokenMatch(mBuffer.data, "crease", 6)) {
279
1
            SkipSpaces(&mBuffer.data, mBuffer.end);
280
1
            obj.crease = fast_atof(mBuffer.data);
281
6
        } else if (TokenMatch(mBuffer.data, "numvert", 7)) {
282
2
            SkipSpaces(&mBuffer.data, mBuffer.end);
283
284
2
            unsigned int t = strtoul10(mBuffer.data, &mBuffer.data);
285
2
            if (t >= AI_MAX_ALLOC(aiVector3D)) {
286
0
                throw DeadlyImportError("AC3D: Too many vertices, would run out of memory");
287
0
            }
288
2
            obj.vertices.reserve(t);
289
6.36k
            for (unsigned int i = 0; i < t; ++i) {
290
6.36k
                if (!GetNextLine()) {
291
0
                    ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: not all vertices have been parsed yet");
292
0
                    break;
293
6.36k
                } 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
6.36k
                obj.vertices.emplace_back();
299
6.36k
                aiVector3D &v = obj.vertices.back();
300
6.36k
                mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 3, &v.x);
301
6.36k
            }
302
4
        } else if (TokenMatch(mBuffer.data, "numsurf", 7)) {
303
2
            SkipSpaces(&mBuffer.data, mBuffer.end);
304
305
2
            bool Q3DWorkAround = false;
306
307
2
            const unsigned int t = strtoul10(mBuffer.data, &mBuffer.data);
308
2
            obj.surfaces.reserve(t);
309
4.07k
            for (unsigned int i = 0; i < t; ++i) {
310
4.07k
                GetNextLine();
311
4.07k
                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
4.07k
                SkipSpaces(&mBuffer.data, mBuffer.end);
324
4.07k
                obj.surfaces.emplace_back();
325
4.07k
                Surface &surf = obj.surfaces.back();
326
4.07k
                surf.flags = strtoul_cppstyle(mBuffer.data);
327
328
12.2k
                while (true) {
329
12.2k
                    if (!GetNextLine()) {
330
0
                        throw DeadlyImportError("AC3D: Unexpected EOF: surface is incomplete");
331
0
                    }
332
12.2k
                    if (TokenMatch(mBuffer.data, "mat", 3)) {
333
4.07k
                        SkipSpaces(&mBuffer.data, mBuffer.end);
334
4.07k
                        surf.mat = strtoul10(mBuffer.data);
335
8.15k
                    } else if (TokenMatch(mBuffer.data, "refs", 4)) {
336
                        // --- see fix notes above
337
4.07k
                        if (Q3DWorkAround) {
338
0
                            if (!surf.entries.empty()) {
339
0
                                mBuffer.data -= 6;
340
0
                                break;
341
0
                            }
342
0
                        }
343
344
4.07k
                        SkipSpaces(&mBuffer.data, mBuffer.end);
345
4.07k
                        const unsigned int m = strtoul10(mBuffer.data);
346
4.07k
                        surf.entries.reserve(m);
347
348
4.07k
                        obj.numRefs += m;
349
350
20.5k
                        for (unsigned int k = 0; k < m; ++k) {
351
16.5k
                            if (!GetNextLine()) {
352
0
                                ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: surface references are incomplete");
353
0
                                break;
354
0
                            }
355
16.5k
                            surf.entries.emplace_back();
356
16.5k
                            Surface::SurfaceEntry &entry = surf.entries.back();
357
358
16.5k
                            entry.first = strtoul10(mBuffer.data, &mBuffer.data);
359
16.5k
                            SkipSpaces(&mBuffer.data, mBuffer.end);
360
16.5k
                            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &entry.second);
361
16.5k
                        }
362
4.07k
                    } else {
363
4.07k
                        --mBuffer.data; // make sure the line is processed a second time
364
4.07k
                        break;
365
4.07k
                    }
366
12.2k
                }
367
4.07k
            }
368
2
        }
369
13
    }
370
4
    ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: \'kids\' line was expected");
371
372
0
    return false;
373
4
}
374
375
// ------------------------------------------------------------------------------------------------
376
// Convert a material from AC3DImporter::Material to aiMaterial
377
void AC3DImporter::ConvertMaterial(const Object &object,
378
        const Material &matSrc,
379
2
        aiMaterial &matDest) {
380
2
    aiString s;
381
382
2
    if (matSrc.name.length()) {
383
2
        s.Set(matSrc.name);
384
2
        matDest.AddProperty(&s, AI_MATKEY_NAME);
385
2
    }
386
2
    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
2
    matDest.AddProperty<aiColor3D>(&matSrc.rgb, 1, AI_MATKEY_COLOR_DIFFUSE);
401
2
    matDest.AddProperty<aiColor3D>(&matSrc.amb, 1, AI_MATKEY_COLOR_AMBIENT);
402
2
    matDest.AddProperty<aiColor3D>(&matSrc.emis, 1, AI_MATKEY_COLOR_EMISSIVE);
403
2
    matDest.AddProperty<aiColor3D>(&matSrc.spec, 1, AI_MATKEY_COLOR_SPECULAR);
404
405
2
    int n = -1;
406
2
    if (matSrc.shin) {
407
0
        n = aiShadingMode_Phong;
408
0
        matDest.AddProperty<float>(&matSrc.shin, 1, AI_MATKEY_SHININESS);
409
2
    } else {
410
2
        n = aiShadingMode_Gouraud;
411
2
    }
412
2
    matDest.AddProperty<int>(&n, 1, AI_MATKEY_SHADING_MODEL);
413
414
2
    float f = 1.f - matSrc.trans;
415
2
    matDest.AddProperty<float>(&f, 1, AI_MATKEY_OPACITY);
416
2
}
417
418
// ------------------------------------------------------------------------------------------------
419
// Converts the loaded data to the internal verbose representation
420
aiNode *AC3DImporter::ConvertObjectSection(Object &object,
421
        std::vector<aiMesh *> &meshes,
422
        std::vector<aiMaterial *> &outMaterials,
423
        const std::vector<Material> &materials,
424
4
        aiNode *parent) {
425
4
    aiNode *node = new aiNode();
426
4
    node->mParent = parent;
427
4
    if (object.vertices.size()) {
428
2
        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
2
        } else {
462
            // need to generate one or more meshes for this object.
463
            // find out how many different materials we have
464
2
            typedef std::pair<unsigned int, unsigned int> IntPair;
465
2
            typedef std::vector<IntPair> MatTable;
466
2
            MatTable needMat(materials.size(), IntPair(0, 0));
467
468
2
            std::vector<Surface>::iterator it, end = object.surfaces.end();
469
2
            std::vector<Surface::SurfaceEntry>::iterator it2, end2;
470
471
4.07k
            for (it = object.surfaces.begin(); it != end; ++it) {
472
4.07k
                unsigned int idx = (*it).mat;
473
4.07k
                if (idx >= needMat.size()) {
474
0
                    ASSIMP_LOG_ERROR("AC3D: material index is out of range");
475
0
                    idx = 0;
476
0
                }
477
4.07k
                if ((*it).entries.empty()) {
478
0
                    ASSIMP_LOG_WARN("AC3D: surface has zero vertex references");
479
0
                }
480
4.07k
                const bool isDoubleSided = ACDoubleSidedFlag == (it->flags & ACDoubleSidedFlag);
481
4.07k
                const int doubleSidedFactor = isDoubleSided ? 2 : 1;
482
483
                // validate all vertex indices to make sure we won't crash here
484
4.07k
                for (it2 = (*it).entries.begin(),
485
4.07k
                    end2 = (*it).entries.end();
486
20.5k
                        it2 != end2; ++it2) {
487
16.5k
                    if ((*it2).first >= object.vertices.size()) {
488
0
                        ASSIMP_LOG_WARN("AC3D: Invalid vertex reference");
489
0
                        (*it2).first = 0;
490
0
                    }
491
16.5k
                }
492
493
4.07k
                if (!needMat[idx].first) {
494
2
                    ++node->mNumMeshes;
495
2
                }
496
497
4.07k
                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
345
                case Surface::TriangleStrip:
511
345
                    needMat[idx].first += static_cast<unsigned int>(it->entries.size() - 2) * doubleSidedFactor;
512
345
                    needMat[idx].second += static_cast<unsigned int>(it->entries.size() - 2) * 3 * doubleSidedFactor;
513
345
                    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
3.73k
                case Surface::Polygon:
523
                    // the number of faces increments by one, the number
524
                    // of vertices by surface.numref.
525
3.73k
                    needMat[idx].first += doubleSidedFactor;
526
3.73k
                    needMat[idx].second += static_cast<unsigned int>(it->entries.size()) * doubleSidedFactor;
527
4.07k
                };
528
4.07k
            }
529
2
            unsigned int *pip = node->mMeshes = new unsigned int[node->mNumMeshes];
530
2
            unsigned int mat = 0;
531
2
            const size_t oldm = meshes.size();
532
2
            for (MatTable::const_iterator cit = needMat.begin(), cend = needMat.end();
533
4
                    cit != cend; ++cit, ++mat) {
534
2
                if (!(*cit).first) {
535
0
                    continue;
536
0
                }
537
538
                // allocate a new aiMesh object
539
2
                *pip++ = (unsigned int)meshes.size();
540
2
                aiMesh *mesh = new aiMesh();
541
2
                meshes.push_back(mesh);
542
543
2
                mesh->mMaterialIndex = static_cast<unsigned int>(outMaterials.size());
544
2
                outMaterials.push_back(new aiMaterial());
545
2
                ConvertMaterial(object, materials[mat], *outMaterials.back());
546
547
                // allocate storage for vertices and normals
548
2
                mesh->mNumFaces = (*cit).first;
549
2
                if (mesh->mNumFaces == 0) {
550
0
                    throw DeadlyImportError("AC3D: No faces");
551
2
                } else if (mesh->mNumFaces > AI_MAX_ALLOC(aiFace)) {
552
0
                    throw DeadlyImportError("AC3D: Too many faces, would run out of memory");
553
0
                }
554
2
                aiFace *faces = mesh->mFaces = new aiFace[mesh->mNumFaces];
555
556
2
                mesh->mNumVertices = (*cit).second;
557
2
                if (mesh->mNumVertices == 0) {
558
0
                    throw DeadlyImportError("AC3D: No vertices");
559
2
                } else if (mesh->mNumVertices > AI_MAX_ALLOC(aiVector3D)) {
560
0
                    throw DeadlyImportError("AC3D: Too many vertices, would run out of memory");
561
0
                }
562
2
                aiVector3D *vertices = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
563
2
                unsigned int cur = 0;
564
565
                // allocate UV coordinates, but only if the texture name for the
566
                // surface is not empty
567
2
                aiVector3D *uv = nullptr;
568
2
                if (!object.textures.empty()) {
569
0
                    uv = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
570
0
                    mesh->mNumUVComponents[0] = 2;
571
0
                }
572
573
4.07k
                for (it = object.surfaces.begin(); it != end; ++it) {
574
4.07k
                    if (mat == (*it).mat) {
575
4.07k
                        const Surface &src = *it;
576
4.07k
                        const bool isDoubleSided = ACDoubleSidedFlag == (src.flags & ACDoubleSidedFlag);
577
578
                        // closed polygon
579
4.07k
                        uint8_t type = (*it).GetType();
580
4.07k
                        if (type == Surface::Polygon) {
581
3.73k
                            aiFace &face = *faces++;
582
3.73k
                            face.mNumIndices = (unsigned int)src.entries.size();
583
3.73k
                            if (0 != face.mNumIndices) {
584
3.73k
                                face.mIndices = new unsigned int[face.mNumIndices];
585
14.9k
                                for (unsigned int i = 0; i < face.mNumIndices; ++i, ++vertices) {
586
11.1k
                                    const Surface::SurfaceEntry &entry = src.entries[i];
587
11.1k
                                    face.mIndices[i] = cur++;
588
589
                                    // copy vertex positions
590
11.1k
                                    if (static_cast<unsigned>(vertices - mesh->mVertices) >= mesh->mNumVertices) {
591
0
                                        throw DeadlyImportError("AC3D: Invalid number of vertices");
592
0
                                    }
593
11.1k
                                    *vertices = object.vertices[entry.first] + object.translation;
594
595
                                    // copy texture coordinates
596
11.1k
                                    if (uv) {
597
0
                                        uv->x = entry.second.x;
598
0
                                        uv->y = entry.second.y;
599
0
                                        ++uv;
600
0
                                    }
601
11.1k
                                }
602
3.73k
                                if(isDoubleSided) // Need a backface?
603
0
                                    buildBacksideOfFace(faces[-1], faces, vertices, mesh->mVertices, uv, mesh->mTextureCoords[0], cur);
604
3.73k
                            }
605
3.73k
                        } else if (type == Surface::TriangleStrip) {
606
4.97k
                            for (unsigned int i = 0; i < (unsigned int)src.entries.size() - 2; ++i) {
607
4.63k
                                const Surface::SurfaceEntry &entry1 = src.entries[i];
608
4.63k
                                const Surface::SurfaceEntry &entry2 = src.entries[i + 1];
609
4.63k
                                const Surface::SurfaceEntry &entry3 = src.entries[i + 2];
610
4.63k
                                const unsigned int verticesNeeded = isDoubleSided ? 6 : 3;
611
4.63k
                                if (static_cast<unsigned>(vertices - mesh->mVertices) + verticesNeeded > mesh->mNumVertices) {
612
0
                                    throw DeadlyImportError("AC3D: Invalid number of vertices");
613
0
                                }
614
615
4.63k
                                aiFace &face = *faces++;
616
4.63k
                                face.mNumIndices = 3;
617
4.63k
                                face.mIndices = new unsigned int[face.mNumIndices];
618
4.63k
                                face.mIndices[0] = cur++;
619
4.63k
                                face.mIndices[1] = cur++;
620
4.63k
                                face.mIndices[2] = cur++;
621
4.63k
                                if (!(i & 1)) {
622
2.40k
                                    *vertices++ = object.vertices[entry1.first] + object.translation;
623
2.40k
                                    if (uv) {
624
0
                                        uv->x = entry1.second.x;
625
0
                                        uv->y = entry1.second.y;
626
0
                                        ++uv;
627
0
                                    }
628
2.40k
                                    *vertices++ = object.vertices[entry2.first] + object.translation;
629
2.40k
                                    if (uv) {
630
0
                                        uv->x = entry2.second.x;
631
0
                                        uv->y = entry2.second.y;
632
0
                                        ++uv;
633
0
                                    }
634
2.40k
                                } else {
635
2.22k
                                    *vertices++ = object.vertices[entry2.first] + object.translation;
636
2.22k
                                    if (uv) {
637
0
                                        uv->x = entry2.second.x;
638
0
                                        uv->y = entry2.second.y;
639
0
                                        ++uv;
640
0
                                    }
641
2.22k
                                    *vertices++ = object.vertices[entry1.first] + object.translation;
642
2.22k
                                    if (uv) {
643
0
                                        uv->x = entry1.second.x;
644
0
                                        uv->y = entry1.second.y;
645
0
                                        ++uv;
646
0
                                    }
647
2.22k
                                }
648
4.63k
                                if (static_cast<unsigned>(vertices - mesh->mVertices) >= mesh->mNumVertices) {
649
0
                                    throw DeadlyImportError("AC3D: Invalid number of vertices");
650
0
                                }
651
4.63k
                                *vertices++ = object.vertices[entry3.first] + object.translation;
652
4.63k
                                if (uv) {
653
0
                                    uv->x = entry3.second.x;
654
0
                                    uv->y = entry3.second.y;
655
0
                                    ++uv;
656
0
                                }
657
4.63k
                                if(isDoubleSided) // Need a backface?
658
4.63k
                                    buildBacksideOfFace(faces[-1], faces, vertices, mesh->mVertices, uv, mesh->mTextureCoords[0], cur);
659
4.63k
                            }
660
345
                        } else {
661
662
0
                            it2 = (*it).entries.begin();
663
664
                            // either a closed or an unclosed line
665
0
                            unsigned int tmp = (unsigned int)(*it).entries.size();
666
0
                            if (Surface::OpenLine == type) --tmp;
667
0
                            for (unsigned int m = 0; m < tmp; ++m) {
668
0
                                if (static_cast<unsigned>(vertices - mesh->mVertices) + 2 > mesh->mNumVertices) {
669
0
                                    throw DeadlyImportError("AC3D: Invalid number of vertices");
670
0
                                }
671
672
0
                                aiFace &face = *faces++;
673
674
0
                                face.mNumIndices = 2;
675
0
                                face.mIndices = new unsigned int[2];
676
0
                                face.mIndices[0] = cur++;
677
0
                                face.mIndices[1] = cur++;
678
679
                                // copy vertex positions
680
0
                                if (it2 == (*it).entries.end()) {
681
0
                                    throw DeadlyImportError("AC3D: Bad line");
682
0
                                }
683
0
                                ai_assert((*it2).first < object.vertices.size());
684
0
                                *vertices++ = object.vertices[(*it2).first];
685
686
                                // copy texture coordinates
687
0
                                if (uv) {
688
0
                                    uv->x = (*it2).second.x;
689
0
                                    uv->y = (*it2).second.y;
690
0
                                    ++uv;
691
0
                                }
692
693
0
                                if (Surface::ClosedLine == type && tmp - 1 == m) {
694
                                    // if this is a closed line repeat its beginning now
695
0
                                    it2 = (*it).entries.begin();
696
0
                                } else
697
0
                                    ++it2;
698
699
                                // second point
700
0
                                *vertices++ = object.vertices[(*it2).first];
701
702
0
                                if (uv) {
703
0
                                    uv->x = (*it2).second.x;
704
0
                                    uv->y = (*it2).second.y;
705
0
                                    ++uv;
706
0
                                }
707
0
                            }
708
0
                        }
709
4.07k
                    }
710
4.07k
                }
711
2
            }
712
713
            // Now apply catmull clark subdivision if necessary. We split meshes into
714
            // materials which is not done by AC3D during smoothing, so we need to
715
            // collect all meshes using the same material group.
716
2
            if (object.subDiv) {
717
0
                if (configEvalSubdivision) {
718
0
                    std::unique_ptr<Subdivider> div(Subdivider::Create(Subdivider::CATMULL_CLARKE));
719
0
                    ASSIMP_LOG_INFO("AC3D: Evaluating subdivision surface: ", object.name);
720
721
0
                    std::vector<aiMesh *> cpy(meshes.size() - oldm, nullptr);
722
0
                    div->Subdivide(&meshes[oldm], cpy.size(), &cpy.front(), object.subDiv, true);
723
0
                    std::copy(cpy.begin(), cpy.end(), meshes.begin() + oldm);
724
725
                    // previous meshes are deleted vy Subdivide().
726
0
                } else {
727
0
                    ASSIMP_LOG_INFO("AC3D: Letting the subdivision surface untouched due to my configuration: ", object.name);
728
0
                }
729
0
            }
730
2
        }
731
2
    }
732
733
4
    if (object.name.length())
734
2
        node->mName.Set(object.name);
735
2
    else {
736
        // generate a name depending on the type of the node
737
2
        switch (object.type) {
738
0
        case Object::Group:
739
0
            node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACGroup_%i", mGroupsCounter++);
740
0
            break;
741
0
        case Object::Poly:
742
0
            node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACPoly_%i", mPolysCounter++);
743
0
            break;
744
0
        case Object::Light:
745
0
            node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACLight_%i", mLightsCounter++);
746
0
            break;
747
748
            // there shouldn't be more than one world, but we don't care
749
2
        case Object::World:
750
2
            node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACWorld_%i", mWorldsCounter++);
751
2
            break;
752
2
        }
753
2
    }
754
755
    // setup the local transformation matrix of the object
756
    // compute the transformation offset to the parent node
757
4
    node->mTransformation = aiMatrix4x4(object.rotation);
758
759
4
    if (object.type == Object::Group || !object.numRefs) {
760
2
        node->mTransformation.a4 = object.translation.x;
761
2
        node->mTransformation.b4 = object.translation.y;
762
2
        node->mTransformation.c4 = object.translation.z;
763
2
    }
764
765
    // add children to the object
766
4
    if (object.children.size()) {
767
2
        node->mNumChildren = (unsigned int)object.children.size();
768
2
        node->mChildren = new aiNode *[node->mNumChildren];
769
4
        for (unsigned int i = 0; i < node->mNumChildren; ++i) {
770
2
            node->mChildren[i] = ConvertObjectSection(object.children[i], meshes, outMaterials, materials, node);
771
2
        }
772
2
    }
773
774
4
    return node;
775
4
}
776
777
// ------------------------------------------------------------------------------------------------
778
2
void AC3DImporter::SetupProperties(const Importer *pImp) {
779
2
    configSplitBFCull = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_SEPARATE_BFCULL, 1) ? true : false;
780
2
    configEvalSubdivision = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_EVAL_SUBDIVISION, 1) ? true : false;
781
2
}
782
783
// ------------------------------------------------------------------------------------------------
784
// Imports the given file into the given scene structure.
785
void AC3DImporter::InternReadFile(const std::string &pFile,
786
2
        aiScene *pScene, IOSystem *pIOHandler) {
787
2
    std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb"));
788
789
    // Check whether we can read from the file
790
2
    if (file == nullptr) {
791
0
        throw DeadlyImportError("Failed to open AC3D file ", pFile, ".");
792
0
    }
793
794
    // allocate storage and copy the contents of the file to a memory buffer
795
2
    std::vector<char> mBuffer2;
796
2
    TextFileToBuffer(file.get(), mBuffer2);
797
798
2
    mBuffer.data = &mBuffer2[0];
799
2
    mBuffer.end = &mBuffer2[0] + mBuffer2.size();
800
2
    mNumMeshes = 0;
801
802
2
    mLightsCounter = mPolysCounter = mWorldsCounter = mGroupsCounter = 0;
803
804
2
    if (::strncmp(mBuffer.data, "AC3D", 4)) {
805
0
        throw DeadlyImportError("AC3D: No valid AC3D file, magic sequence not found");
806
0
    }
807
808
    // print the file format version to the console
809
2
    unsigned int version = HexDigitToDecimal(mBuffer.data[4]);
810
2
    char msg[3];
811
2
    ASSIMP_itoa10(msg, 3, version);
812
2
    ASSIMP_LOG_INFO("AC3D file format version: ", msg);
813
814
2
    std::vector<Material> materials;
815
2
    materials.reserve(5);
816
817
2
    std::vector<Object> rootObjects;
818
2
    rootObjects.reserve(5);
819
820
2
    std::vector<aiLight *> lights;
821
2
    mLights = &lights;
822
823
6
    while (GetNextLine()) {
824
4
        if (TokenMatch(mBuffer.data, "MATERIAL", 8)) {
825
2
            materials.emplace_back();
826
2
            Material &mat = materials.back();
827
828
            // manually parse the material ... sscanf would use the buldin atof ...
829
            // Format: (name) rgb %f %f %f  amb %f %f %f  emis %f %f %f  spec %f %f %f  shi %d  trans %f
830
831
2
            mBuffer.data = AcSkipToNextToken(mBuffer.data, mBuffer.end);
832
2
            if ('\"' == *mBuffer.data) {
833
2
                mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, mat.name);
834
2
                mBuffer.data = AcSkipToNextToken(mBuffer.data, mBuffer.end);
835
2
            }
836
837
2
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "rgb", 3, 3, &mat.rgb);
838
2
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "amb", 3, 3, &mat.amb);
839
2
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "emis", 4, 3, &mat.emis);
840
2
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "spec", 4, 3, &mat.spec);
841
2
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "shi", 3, 1, &mat.shin);
842
2
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "trans", 5, 1, &mat.trans);
843
2
        } else {
844
2
            LoadObjectSection(rootObjects);
845
2
        }
846
4
    }
847
848
2
    if (rootObjects.empty() || mNumMeshes == 0u) {
849
0
        throw DeadlyImportError("AC3D: No meshes have been loaded");
850
0
    }
851
2
    if (materials.empty()) {
852
0
        ASSIMP_LOG_WARN("AC3D: No material has been found");
853
0
        materials.emplace_back();
854
0
    }
855
856
2
    mNumMeshes += (mNumMeshes >> 2u) + 1;
857
2
    std::vector<aiMesh *> meshes;
858
2
    meshes.reserve(mNumMeshes);
859
860
2
    std::vector<aiMaterial *> omaterials;
861
2
    materials.reserve(mNumMeshes);
862
863
    // generate a dummy root if there are multiple objects on the top layer
864
2
    Object *root = nullptr;
865
2
    if (1 == rootObjects.size())
866
2
        root = &rootObjects[0];
867
0
    else {
868
0
        root = new Object();
869
0
    }
870
871
    // now convert the imported stuff to our output data structure
872
2
    pScene->mRootNode = ConvertObjectSection(*root, meshes, omaterials, materials);
873
2
    if (1 != rootObjects.size()) {
874
0
        delete root;
875
0
    }
876
877
2
    if (::strncmp(pScene->mRootNode->mName.data, "Node", 4) == 0) {
878
0
        pScene->mRootNode->mName.Set("<AC3DWorld>");
879
0
    }
880
881
    // copy meshes
882
2
    if (meshes.empty()) {
883
0
        throw DeadlyImportError("An unknown error occurred during converting");
884
0
    }
885
2
    pScene->mNumMeshes = (unsigned int)meshes.size();
886
2
    pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
887
2
    ::memcpy(pScene->mMeshes, &meshes[0], pScene->mNumMeshes * sizeof(void *));
888
889
    // copy materials
890
2
    pScene->mNumMaterials = (unsigned int)omaterials.size();
891
2
    pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
892
2
    ::memcpy(pScene->mMaterials, &omaterials[0], pScene->mNumMaterials * sizeof(void *));
893
894
    // copy lights
895
2
    pScene->mNumLights = (unsigned int)lights.size();
896
2
    if (!lights.empty()) {
897
0
        pScene->mLights = new aiLight *[lights.size()];
898
0
        ::memcpy(pScene->mLights, &lights[0], lights.size() * sizeof(void *));
899
0
    }
900
2
}
901
902
} // namespace Assimp
903
904
#endif //!defined ASSIMP_BUILD_NO_AC_IMPORTER