Coverage Report

Created: 2026-03-12 06:25

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
81.0k
inline const char *AcSkipToNextToken(const char *buffer, const char *end) {
83
81.0k
    if (!SkipSpaces(&buffer, end)) {
84
0
        ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL");
85
0
    }
86
81.0k
    return buffer;
87
81.0k
}
88
89
// ------------------------------------------------------------------------------------------------
90
// read a string (may be enclosed in double quotation marks). buffer must point to "
91
19
inline const char *AcGetString(const char *buffer, const char *end, std::string &out) {
92
19
    if (*buffer == '\0') {
93
0
        throw DeadlyImportError("AC3D: Unexpected EOF in string");
94
0
    }
95
19
    ++buffer;
96
19
    const char *sz = buffer;
97
248
    while ('\"' != *buffer && buffer != end) {
98
232
        if (IsLineEnd(*buffer)) {
99
3
            ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL in string");
100
3
            out = "ERROR";
101
3
            break;
102
3
        }
103
229
        ++buffer;
104
229
    }
105
19
    if (IsLineEnd(*buffer)) {
106
3
        return buffer;
107
3
    }
108
16
    out = std::string(sz, (unsigned int)(buffer - sz));
109
16
    ++buffer;
110
111
16
    return buffer;
112
19
}
113
114
// ------------------------------------------------------------------------------------------------
115
// read 1 to n floats prefixed with an optional predefined identifier
116
template <class T>
117
24.7k
inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) {
118
24.7k
    buffer = AcSkipToNextToken(buffer, end);
119
24.7k
    if (0 != name_length) {
120
30
        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
30
        buffer += name_length + 1;
125
30
    }
126
81.0k
    for (unsigned int _i = 0; _i < num; ++_i) {
127
56.3k
        buffer = AcSkipToNextToken(buffer, end);
128
56.3k
        buffer = fast_atoreal_move(buffer, ((float *)out)[_i]);
129
56.3k
    }
130
131
24.7k
    return buffer;
132
24.7k
}
char const* Assimp::TAcCheckedLoadFloatArray<aiVector2t<float> >(char const*, char const*, char const*, unsigned long, unsigned long, aiVector2t<float>*)
Line
Count
Source
117
17.9k
inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) {
118
17.9k
    buffer = AcSkipToNextToken(buffer, end);
119
17.9k
    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
53.8k
    for (unsigned int _i = 0; _i < num; ++_i) {
127
35.8k
        buffer = AcSkipToNextToken(buffer, end);
128
35.8k
        buffer = fast_atoreal_move(buffer, ((float *)out)[_i]);
129
35.8k
    }
130
131
17.9k
    return buffer;
132
17.9k
}
Unexecuted instantiation: char const* Assimp::TAcCheckedLoadFloatArray<aiMatrix3x3t<float> >(char const*, char const*, char const*, unsigned long, unsigned long, aiMatrix3x3t<float>*)
char const* Assimp::TAcCheckedLoadFloatArray<aiVector3t<float> >(char const*, char const*, char const*, unsigned long, unsigned long, aiVector3t<float>*)
Line
Count
Source
117
5
inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) {
118
5
    buffer = AcSkipToNextToken(buffer, end);
119
5
    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
20
    for (unsigned int _i = 0; _i < num; ++_i) {
127
15
        buffer = AcSkipToNextToken(buffer, end);
128
15
        buffer = fast_atoreal_move(buffer, ((float *)out)[_i]);
129
15
    }
130
131
5
    return buffer;
132
5
}
char const* Assimp::TAcCheckedLoadFloatArray<float>(char const*, char const*, char const*, unsigned long, unsigned long, float*)
Line
Count
Source
117
6.79k
inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) {
118
6.79k
    buffer = AcSkipToNextToken(buffer, end);
119
6.79k
    if (0 != name_length) {
120
10
        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
10
        buffer += name_length + 1;
125
10
    }
126
27.1k
    for (unsigned int _i = 0; _i < num; ++_i) {
127
20.3k
        buffer = AcSkipToNextToken(buffer, end);
128
20.3k
        buffer = fast_atoreal_move(buffer, ((float *)out)[_i]);
129
20.3k
    }
130
131
6.79k
    return buffer;
132
6.79k
}
char const* Assimp::TAcCheckedLoadFloatArray<aiColor3D>(char const*, char const*, char const*, unsigned long, unsigned long, aiColor3D*)
Line
Count
Source
117
20
inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) {
118
20
    buffer = AcSkipToNextToken(buffer, end);
119
20
    if (0 != name_length) {
120
20
        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
20
        buffer += name_length + 1;
125
20
    }
126
80
    for (unsigned int _i = 0; _i < num; ++_i) {
127
60
        buffer = AcSkipToNextToken(buffer, end);
128
60
        buffer = fast_atoreal_move(buffer, ((float *)out)[_i]);
129
60
    }
130
131
20
    return buffer;
132
20
}
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
40.0k
        mBuffer(),
162
        configSplitBFCull(),
163
        configEvalSubdivision(),
164
        mNumMeshes(),
165
        mLights(),
166
40.0k
        mLightsCounter(0),
167
40.0k
        mGroupsCounter(0),
168
40.0k
        mPolysCounter(0),
169
40.0k
        mWorldsCounter(0) {
170
    // nothing to be done here
171
40.0k
}
172
173
// ------------------------------------------------------------------------------------------------
174
// Returns whether the class can handle the format of the given file.
175
530
bool AC3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
176
530
    static constexpr uint32_t tokens[] = { AI_MAKE_MAGIC("AC3D") };
177
530
    return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
178
530
}
179
180
// ------------------------------------------------------------------------------------------------
181
// Loader meta information
182
40.0k
const aiImporterDesc *AC3DImporter::GetInfo() const {
183
40.0k
    return &desc;
184
40.0k
}
185
186
// ------------------------------------------------------------------------------------------------
187
// Get a pointer to the next line from the file
188
42.3k
bool AC3DImporter::GetNextLine() {
189
42.3k
    SkipLine(&mBuffer.data, mBuffer.end);
190
42.3k
    return SkipSpaces(&mBuffer.data, mBuffer.end);
191
42.3k
}
192
193
// ------------------------------------------------------------------------------------------------
194
// Parse an object section in an AC file
195
13
bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
196
13
    if (!TokenMatch(mBuffer.data, "OBJECT", 6)) {
197
0
        return false;
198
0
    }
199
200
13
    SkipSpaces(&mBuffer.data, mBuffer.end);
201
202
13
    ++mNumMeshes;
203
204
13
    objects.emplace_back();
205
13
    Object &obj = objects.back();
206
207
13
    aiLight *light = nullptr;
208
13
    if (!ASSIMP_strincmp(mBuffer.data, "light", 5)) {
209
        // This is a light source. Add it to the list
210
3
        mLights->push_back(light = new aiLight());
211
212
        // Return a point light with no attenuation
213
3
        light->mType = aiLightSource_POINT;
214
3
        light->mColorDiffuse = light->mColorSpecular = aiColor3D(1.f, 1.f, 1.f);
215
3
        light->mAttenuationConstant = 1.f;
216
217
        // Generate a default name for both the light source and the node
218
3
        light->mName.length = ::ai_snprintf(light->mName.data, AI_MAXLEN, "ACLight_%i", static_cast<unsigned int>(mLights->size()) - 1);
219
3
        obj.name = std::string(light->mName.data);
220
221
3
        ASSIMP_LOG_VERBOSE_DEBUG("AC3D: Light source encountered");
222
3
        obj.type = Object::Light;
223
10
    } else if (!ASSIMP_strincmp(mBuffer.data, "group", 5)) {
224
0
        obj.type = Object::Group;
225
10
    } else if (!ASSIMP_strincmp(mBuffer.data, "world", 5)) {
226
5
        obj.type = Object::World;
227
5
    } else {
228
5
        obj.type = Object::Poly;
229
5
    }
230
231
51
    while (GetNextLine()) {
232
51
        if (TokenMatch(mBuffer.data, "kids", 4)) {
233
13
            SkipSpaces(&mBuffer.data, mBuffer.end);
234
13
            unsigned int num = strtoul10(mBuffer.data, &mBuffer.data);
235
13
            GetNextLine();
236
13
            if (num) {
237
                // load the children of this object recursively
238
5
                obj.children.reserve(num);
239
13
                for (unsigned int i = 0; i < num; ++i) {
240
8
                    if (!LoadObjectSection(obj.children)) {
241
0
                        ASSIMP_LOG_WARN("AC3D: wrong number of kids");
242
0
                        break;
243
0
                    }
244
8
                }
245
5
            }
246
13
            return true;
247
38
        } else if (TokenMatch(mBuffer.data, "name", 4)) {
248
8
            SkipSpaces(&mBuffer.data, mBuffer.data);
249
8
            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
8
            if (light) {
254
3
                light->mName.Set(obj.name);
255
3
            }
256
30
        } else if (TokenMatch(mBuffer.data, "texture", 7)) {
257
6
            SkipSpaces(&mBuffer.data, mBuffer.end);
258
6
            std::string texture;
259
6
            mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, texture);
260
6
            obj.textures.push_back(texture);
261
24
        } else if (TokenMatch(mBuffer.data, "texrep", 6)) {
262
1
            SkipSpaces(&mBuffer.data, mBuffer.end);
263
1
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &obj.texRepeat);
264
1
            if (!obj.texRepeat.x || !obj.texRepeat.y)
265
0
                obj.texRepeat = aiVector2D(1.f, 1.f);
266
23
        } else if (TokenMatch(mBuffer.data, "texoff", 6)) {
267
1
            SkipSpaces(&mBuffer.data, mBuffer.end);
268
1
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &obj.texOffset);
269
22
        } 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
22
        } else if (TokenMatch(mBuffer.data, "loc", 3)) {
273
5
            SkipSpaces(&mBuffer.data, mBuffer.end);
274
5
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 3, &obj.translation);
275
17
        } else if (TokenMatch(mBuffer.data, "subdiv", 6)) {
276
2
            SkipSpaces(&mBuffer.data, mBuffer.end);
277
2
            obj.subDiv = strtoul10(mBuffer.data, &mBuffer.data);
278
15
        } else if (TokenMatch(mBuffer.data, "crease", 6)) {
279
3
            SkipSpaces(&mBuffer.data, mBuffer.end);
280
3
            obj.crease = fast_atof(mBuffer.data);
281
12
        } else if (TokenMatch(mBuffer.data, "numvert", 7)) {
282
5
            SkipSpaces(&mBuffer.data, mBuffer.end);
283
284
5
            unsigned int t = strtoul10(mBuffer.data, &mBuffer.data);
285
5
            if (t >= AI_MAX_ALLOC(aiVector3D)) {
286
0
                throw DeadlyImportError("AC3D: Too many vertices, would run out of memory");
287
0
            }
288
5
            obj.vertices.reserve(t);
289
6.79k
            for (unsigned int i = 0; i < t; ++i) {
290
6.79k
                if (!GetNextLine()) {
291
0
                    ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: not all vertices have been parsed yet");
292
0
                    break;
293
6.79k
                } else if (!IsNumeric(*mBuffer.data)) {
294
1
                    ASSIMP_LOG_ERROR("AC3D: Unexpected token: not all vertices have been parsed yet");
295
1
                    --mBuffer.data; // make sure the line is processed a second time
296
1
                    break;
297
1
                }
298
6.78k
                obj.vertices.emplace_back();
299
6.78k
                aiVector3D &v = obj.vertices.back();
300
6.78k
                mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 3, &v.x);
301
6.78k
            }
302
7
        } else if (TokenMatch(mBuffer.data, "numsurf", 7)) {
303
5
            SkipSpaces(&mBuffer.data, mBuffer.end);
304
305
5
            bool Q3DWorkAround = false;
306
307
5
            const unsigned int t = strtoul10(mBuffer.data, &mBuffer.data);
308
5
            obj.surfaces.reserve(t);
309
4.38k
            for (unsigned int i = 0; i < t; ++i) {
310
4.37k
                GetNextLine();
311
4.37k
                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.37k
                SkipSpaces(&mBuffer.data, mBuffer.end);
324
4.37k
                obj.surfaces.emplace_back();
325
4.37k
                Surface &surf = obj.surfaces.back();
326
4.37k
                surf.flags = strtoul_cppstyle(mBuffer.data);
327
328
13.1k
                while (true) {
329
13.1k
                    if (!GetNextLine()) {
330
0
                        throw DeadlyImportError("AC3D: Unexpected EOF: surface is incomplete");
331
0
                    }
332
13.1k
                    if (TokenMatch(mBuffer.data, "mat", 3)) {
333
4.37k
                        SkipSpaces(&mBuffer.data, mBuffer.end);
334
4.37k
                        surf.mat = strtoul10(mBuffer.data);
335
8.75k
                    } else if (TokenMatch(mBuffer.data, "refs", 4)) {
336
                        // --- see fix notes above
337
4.37k
                        if (Q3DWorkAround) {
338
0
                            if (!surf.entries.empty()) {
339
0
                                mBuffer.data -= 6;
340
0
                                break;
341
0
                            }
342
0
                        }
343
344
4.37k
                        SkipSpaces(&mBuffer.data, mBuffer.end);
345
4.37k
                        const unsigned int m = strtoul10(mBuffer.data);
346
4.37k
                        surf.entries.reserve(m);
347
348
4.37k
                        obj.numRefs += m;
349
350
22.3k
                        for (unsigned int k = 0; k < m; ++k) {
351
17.9k
                            if (!GetNextLine()) {
352
0
                                ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: surface references are incomplete");
353
0
                                break;
354
0
                            }
355
17.9k
                            surf.entries.emplace_back();
356
17.9k
                            Surface::SurfaceEntry &entry = surf.entries.back();
357
358
17.9k
                            entry.first = strtoul10(mBuffer.data, &mBuffer.data);
359
17.9k
                            SkipSpaces(&mBuffer.data, mBuffer.end);
360
17.9k
                            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &entry.second);
361
17.9k
                        }
362
4.37k
                    } else {
363
4.37k
                        --mBuffer.data; // make sure the line is processed a second time
364
4.37k
                        break;
365
4.37k
                    }
366
13.1k
                }
367
4.37k
            }
368
5
        }
369
51
    }
370
13
    ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: \'kids\' line was expected");
371
372
0
    return false;
373
13
}
374
375
// ------------------------------------------------------------------------------------------------
376
// Convert a material from AC3DImporter::Material to aiMaterial
377
void AC3DImporter::ConvertMaterial(const Object &object,
378
        const Material &matSrc,
379
5
        aiMaterial &matDest) {
380
5
    aiString s;
381
382
5
    if (matSrc.name.length()) {
383
5
        s.Set(matSrc.name);
384
5
        matDest.AddProperty(&s, AI_MATKEY_NAME);
385
5
    }
386
5
    if (!object.textures.empty()) {
387
3
        s.Set(object.textures[0]);
388
3
        matDest.AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0));
389
390
        // UV transformation
391
3
        if (1.f != object.texRepeat.x || 1.f != object.texRepeat.y ||
392
2
                object.texOffset.x || object.texOffset.y) {
393
1
            aiUVTransform transform;
394
1
            transform.mScaling = object.texRepeat;
395
1
            transform.mTranslation = object.texOffset;
396
1
            matDest.AddProperty(&transform, 1, AI_MATKEY_UVTRANSFORM_DIFFUSE(0));
397
1
        }
398
3
    }
399
400
5
    matDest.AddProperty<aiColor3D>(&matSrc.rgb, 1, AI_MATKEY_COLOR_DIFFUSE);
401
5
    matDest.AddProperty<aiColor3D>(&matSrc.amb, 1, AI_MATKEY_COLOR_AMBIENT);
402
5
    matDest.AddProperty<aiColor3D>(&matSrc.emis, 1, AI_MATKEY_COLOR_EMISSIVE);
403
5
    matDest.AddProperty<aiColor3D>(&matSrc.spec, 1, AI_MATKEY_COLOR_SPECULAR);
404
405
5
    int n = -1;
406
5
    if (matSrc.shin) {
407
3
        n = aiShadingMode_Phong;
408
3
        matDest.AddProperty<float>(&matSrc.shin, 1, AI_MATKEY_SHININESS);
409
3
    } else {
410
2
        n = aiShadingMode_Gouraud;
411
2
    }
412
5
    matDest.AddProperty<int>(&n, 1, AI_MATKEY_SHADING_MODEL);
413
414
5
    float f = 1.f - matSrc.trans;
415
5
    matDest.AddProperty<float>(&f, 1, AI_MATKEY_OPACITY);
416
5
}
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
13
        aiNode *parent) {
425
13
    aiNode *node = new aiNode();
426
13
    node->mParent = parent;
427
13
    if (object.vertices.size()) {
428
5
        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
5
        } else {
462
            // need to generate one or more meshes for this object.
463
            // find out how many different materials we have
464
5
            typedef std::pair<unsigned int, unsigned int> IntPair;
465
5
            typedef std::vector<IntPair> MatTable;
466
5
            MatTable needMat(materials.size(), IntPair(0, 0));
467
468
5
            std::vector<Surface>::iterator it, end = object.surfaces.end();
469
5
            std::vector<Surface::SurfaceEntry>::iterator it2, end2;
470
471
4.38k
            for (it = object.surfaces.begin(); it != end; ++it) {
472
4.37k
                unsigned int idx = (*it).mat;
473
4.37k
                if (idx >= needMat.size()) {
474
0
                    ASSIMP_LOG_ERROR("AC3D: material index is out of range");
475
0
                    idx = 0;
476
0
                }
477
4.37k
                if ((*it).entries.empty()) {
478
0
                    ASSIMP_LOG_WARN("AC3D: surface has zero vertex references");
479
0
                }
480
4.37k
                const bool isDoubleSided = ACDoubleSidedFlag == (it->flags & ACDoubleSidedFlag);
481
4.37k
                const int doubleSidedFactor = isDoubleSided ? 2 : 1;
482
483
                // validate all vertex indices to make sure we won't crash here
484
4.37k
                for (it2 = (*it).entries.begin(),
485
4.37k
                    end2 = (*it).entries.end();
486
22.3k
                        it2 != end2; ++it2) {
487
17.9k
                    if ((*it2).first >= object.vertices.size()) {
488
35
                        ASSIMP_LOG_WARN("AC3D: Invalid vertex reference");
489
35
                        (*it2).first = 0;
490
35
                    }
491
17.9k
                }
492
493
4.37k
                if (!needMat[idx].first) {
494
5
                    ++node->mNumMeshes;
495
5
                }
496
497
4.37k
                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
358
                case Surface::TriangleStrip:
511
358
                    needMat[idx].first += static_cast<unsigned int>(it->entries.size() - 2) * doubleSidedFactor;
512
358
                    needMat[idx].second += static_cast<unsigned int>(it->entries.size() - 2) * 3 * doubleSidedFactor;
513
358
                    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
4.02k
                case Surface::Polygon:
523
                    // the number of faces increments by one, the number
524
                    // of vertices by surface.numref.
525
4.02k
                    needMat[idx].first += doubleSidedFactor;
526
4.02k
                    needMat[idx].second += static_cast<unsigned int>(it->entries.size()) * doubleSidedFactor;
527
4.37k
                };
528
4.37k
            }
529
5
            unsigned int *pip = node->mMeshes = new unsigned int[node->mNumMeshes];
530
5
            unsigned int mat = 0;
531
5
            const size_t oldm = meshes.size();
532
5
            for (MatTable::const_iterator cit = needMat.begin(), cend = needMat.end();
533
10
                    cit != cend; ++cit, ++mat) {
534
5
                if (!(*cit).first) {
535
0
                    continue;
536
0
                }
537
538
                // allocate a new aiMesh object
539
5
                *pip++ = (unsigned int)meshes.size();
540
5
                aiMesh *mesh = new aiMesh();
541
5
                meshes.push_back(mesh);
542
543
5
                mesh->mMaterialIndex = static_cast<unsigned int>(outMaterials.size());
544
5
                outMaterials.push_back(new aiMaterial());
545
5
                ConvertMaterial(object, materials[mat], *outMaterials.back());
546
547
                // allocate storage for vertices and normals
548
5
                mesh->mNumFaces = (*cit).first;
549
5
                if (mesh->mNumFaces == 0) {
550
0
                    throw DeadlyImportError("AC3D: No faces");
551
5
                } else if (mesh->mNumFaces > AI_MAX_ALLOC(aiFace)) {
552
0
                    throw DeadlyImportError("AC3D: Too many faces, would run out of memory");
553
0
                }
554
5
                aiFace *faces = mesh->mFaces = new aiFace[mesh->mNumFaces];
555
556
5
                mesh->mNumVertices = (*cit).second;
557
5
                if (mesh->mNumVertices == 0) {
558
0
                    throw DeadlyImportError("AC3D: No vertices");
559
5
                } else if (mesh->mNumVertices > AI_MAX_ALLOC(aiVector3D)) {
560
0
                    throw DeadlyImportError("AC3D: Too many vertices, would run out of memory");
561
0
                }
562
5
                aiVector3D *vertices = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
563
5
                unsigned int cur = 0;
564
565
                // allocate UV coordinates, but only if the texture name for the
566
                // surface is not empty
567
5
                aiVector3D *uv = nullptr;
568
5
                if (!object.textures.empty()) {
569
3
                    uv = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
570
3
                    mesh->mNumUVComponents[0] = 2;
571
3
                }
572
573
4.38k
                for (it = object.surfaces.begin(); it != end; ++it) {
574
4.37k
                    if (mat == (*it).mat) {
575
4.37k
                        const Surface &src = *it;
576
4.37k
                        const bool isDoubleSided = ACDoubleSidedFlag == (src.flags & ACDoubleSidedFlag);
577
578
                        // closed polygon
579
4.37k
                        uint8_t type = (*it).GetType();
580
4.37k
                        if (type == Surface::Polygon) {
581
4.02k
                            aiFace &face = *faces++;
582
4.02k
                            face.mNumIndices = (unsigned int)src.entries.size();
583
4.02k
                            if (0 != face.mNumIndices) {
584
4.02k
                                face.mIndices = new unsigned int[face.mNumIndices];
585
16.3k
                                for (unsigned int i = 0; i < face.mNumIndices; ++i, ++vertices) {
586
12.3k
                                    const Surface::SurfaceEntry &entry = src.entries[i];
587
12.3k
                                    face.mIndices[i] = cur++;
588
589
                                    // copy vertex positions
590
12.3k
                                    if (static_cast<unsigned>(vertices - mesh->mVertices) >= mesh->mNumVertices) {
591
0
                                        throw DeadlyImportError("AC3D: Invalid number of vertices");
592
0
                                    }
593
12.3k
                                    *vertices = object.vertices[entry.first] + object.translation;
594
595
                                    // copy texture coordinates
596
12.3k
                                    if (uv) {
597
1.10k
                                        uv->x = entry.second.x;
598
1.10k
                                        uv->y = entry.second.y;
599
1.10k
                                        ++uv;
600
1.10k
                                    }
601
12.3k
                                }
602
4.02k
                                if(isDoubleSided) // Need a backface?
603
0
                                    buildBacksideOfFace(faces[-1], faces, vertices, mesh->mVertices, uv, mesh->mTextureCoords[0], cur);
604
4.02k
                            }
605
4.02k
                        } else if (type == Surface::TriangleStrip) {
606
5.27k
                            for (unsigned int i = 0; i < (unsigned int)src.entries.size() - 2; ++i) {
607
4.91k
                                const Surface::SurfaceEntry &entry1 = src.entries[i];
608
4.91k
                                const Surface::SurfaceEntry &entry2 = src.entries[i + 1];
609
4.91k
                                const Surface::SurfaceEntry &entry3 = src.entries[i + 2];
610
4.91k
                                const unsigned int verticesNeeded = isDoubleSided ? 6 : 3;
611
4.91k
                                if (static_cast<unsigned>(vertices - mesh->mVertices) + verticesNeeded > mesh->mNumVertices) {
612
0
                                    throw DeadlyImportError("AC3D: Invalid number of vertices");
613
0
                                }
614
615
4.91k
                                aiFace &face = *faces++;
616
4.91k
                                face.mNumIndices = 3;
617
4.91k
                                face.mIndices = new unsigned int[face.mNumIndices];
618
4.91k
                                face.mIndices[0] = cur++;
619
4.91k
                                face.mIndices[1] = cur++;
620
4.91k
                                face.mIndices[2] = cur++;
621
4.91k
                                if (!(i & 1)) {
622
2.55k
                                    *vertices++ = object.vertices[entry1.first] + object.translation;
623
2.55k
                                    if (uv) {
624
144
                                        uv->x = entry1.second.x;
625
144
                                        uv->y = entry1.second.y;
626
144
                                        ++uv;
627
144
                                    }
628
2.55k
                                    *vertices++ = object.vertices[entry2.first] + object.translation;
629
2.55k
                                    if (uv) {
630
144
                                        uv->x = entry2.second.x;
631
144
                                        uv->y = entry2.second.y;
632
144
                                        ++uv;
633
144
                                    }
634
2.55k
                                } else {
635
2.36k
                                    *vertices++ = object.vertices[entry2.first] + object.translation;
636
2.36k
                                    if (uv) {
637
142
                                        uv->x = entry2.second.x;
638
142
                                        uv->y = entry2.second.y;
639
142
                                        ++uv;
640
142
                                    }
641
2.36k
                                    *vertices++ = object.vertices[entry1.first] + object.translation;
642
2.36k
                                    if (uv) {
643
142
                                        uv->x = entry1.second.x;
644
142
                                        uv->y = entry1.second.y;
645
142
                                        ++uv;
646
142
                                    }
647
2.36k
                                }
648
4.91k
                                if (static_cast<unsigned>(vertices - mesh->mVertices) >= mesh->mNumVertices) {
649
0
                                    throw DeadlyImportError("AC3D: Invalid number of vertices");
650
0
                                }
651
4.91k
                                *vertices++ = object.vertices[entry3.first] + object.translation;
652
4.91k
                                if (uv) {
653
286
                                    uv->x = entry3.second.x;
654
286
                                    uv->y = entry3.second.y;
655
286
                                    ++uv;
656
286
                                }
657
4.91k
                                if(isDoubleSided) // Need a backface?
658
4.63k
                                    buildBacksideOfFace(faces[-1], faces, vertices, mesh->mVertices, uv, mesh->mTextureCoords[0], cur);
659
4.91k
                            }
660
358
                        } 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.37k
                    }
710
4.37k
                }
711
5
            }
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
5
            if (object.subDiv) {
717
2
                if (configEvalSubdivision) {
718
2
                    std::unique_ptr<Subdivider> div(Subdivider::Create(Subdivider::CATMULL_CLARKE));
719
2
                    ASSIMP_LOG_INFO("AC3D: Evaluating subdivision surface: ", object.name);
720
721
2
                    std::vector<aiMesh *> cpy(meshes.size() - oldm, nullptr);
722
2
                    div->Subdivide(&meshes[oldm], cpy.size(), &cpy.front(), object.subDiv, true);
723
2
                    std::copy(cpy.begin(), cpy.end(), meshes.begin() + oldm);
724
725
                    // previous meshes are deleted vy Subdivide().
726
2
                } else {
727
0
                    ASSIMP_LOG_INFO("AC3D: Letting the subdivision surface untouched due to my configuration: ", object.name);
728
0
                }
729
2
            }
730
5
        }
731
5
    }
732
733
13
    if (object.name.length())
734
8
        node->mName.Set(object.name);
735
5
    else {
736
        // generate a name depending on the type of the node
737
5
        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
5
        case Object::World:
750
5
            node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACWorld_%i", mWorldsCounter++);
751
5
            break;
752
5
        }
753
5
    }
754
755
    // setup the local transformation matrix of the object
756
    // compute the transformation offset to the parent node
757
13
    node->mTransformation = aiMatrix4x4(object.rotation);
758
759
13
    if (object.type == Object::Group || !object.numRefs) {
760
8
        node->mTransformation.a4 = object.translation.x;
761
8
        node->mTransformation.b4 = object.translation.y;
762
8
        node->mTransformation.c4 = object.translation.z;
763
8
    }
764
765
    // add children to the object
766
13
    if (object.children.size()) {
767
5
        node->mNumChildren = (unsigned int)object.children.size();
768
5
        node->mChildren = new aiNode *[node->mNumChildren];
769
13
        for (unsigned int i = 0; i < node->mNumChildren; ++i) {
770
8
            node->mChildren[i] = ConvertObjectSection(object.children[i], meshes, outMaterials, materials, node);
771
8
        }
772
5
    }
773
774
13
    return node;
775
13
}
776
777
// ------------------------------------------------------------------------------------------------
778
5
void AC3DImporter::SetupProperties(const Importer *pImp) {
779
5
    configSplitBFCull = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_SEPARATE_BFCULL, 1) ? true : false;
780
5
    configEvalSubdivision = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_EVAL_SUBDIVISION, 1) ? true : false;
781
5
}
782
783
// ------------------------------------------------------------------------------------------------
784
// Imports the given file into the given scene structure.
785
void AC3DImporter::InternReadFile(const std::string &pFile,
786
5
        aiScene *pScene, IOSystem *pIOHandler) {
787
5
    std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb"));
788
789
    // Check whether we can read from the file
790
5
    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
5
    std::vector<char> mBuffer2;
796
5
    TextFileToBuffer(file.get(), mBuffer2);
797
798
5
    mBuffer.data = &mBuffer2[0];
799
5
    mBuffer.end = &mBuffer2[0] + mBuffer2.size();
800
5
    mNumMeshes = 0;
801
802
5
    mLightsCounter = mPolysCounter = mWorldsCounter = mGroupsCounter = 0;
803
804
5
    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
5
    unsigned int version = HexDigitToDecimal(mBuffer.data[4]);
810
5
    char msg[3];
811
5
    ASSIMP_itoa10(msg, 3, version);
812
5
    ASSIMP_LOG_INFO("AC3D file format version: ", msg);
813
814
5
    std::vector<Material> materials;
815
5
    materials.reserve(5);
816
817
5
    std::vector<Object> rootObjects;
818
5
    rootObjects.reserve(5);
819
820
5
    std::vector<aiLight *> lights;
821
5
    mLights = &lights;
822
823
15
    while (GetNextLine()) {
824
10
        if (TokenMatch(mBuffer.data, "MATERIAL", 8)) {
825
5
            materials.emplace_back();
826
5
            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
5
            mBuffer.data = AcSkipToNextToken(mBuffer.data, mBuffer.end);
832
5
            if ('\"' == *mBuffer.data) {
833
5
                mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, mat.name);
834
5
                mBuffer.data = AcSkipToNextToken(mBuffer.data, mBuffer.end);
835
5
            }
836
837
5
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "rgb", 3, 3, &mat.rgb);
838
5
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "amb", 3, 3, &mat.amb);
839
5
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "emis", 4, 3, &mat.emis);
840
5
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "spec", 4, 3, &mat.spec);
841
5
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "shi", 3, 1, &mat.shin);
842
5
            mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "trans", 5, 1, &mat.trans);
843
5
        } else {
844
5
            LoadObjectSection(rootObjects);
845
5
        }
846
10
    }
847
848
5
    if (rootObjects.empty() || mNumMeshes == 0u) {
849
0
        throw DeadlyImportError("AC3D: No meshes have been loaded");
850
0
    }
851
5
    if (materials.empty()) {
852
0
        ASSIMP_LOG_WARN("AC3D: No material has been found");
853
0
        materials.emplace_back();
854
0
    }
855
856
5
    mNumMeshes += (mNumMeshes >> 2u) + 1;
857
5
    std::vector<aiMesh *> meshes;
858
5
    meshes.reserve(mNumMeshes);
859
860
5
    std::vector<aiMaterial *> omaterials;
861
5
    materials.reserve(mNumMeshes);
862
863
    // generate a dummy root if there are multiple objects on the top layer
864
5
    Object *root = nullptr;
865
5
    if (1 == rootObjects.size())
866
5
        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
5
    pScene->mRootNode = ConvertObjectSection(*root, meshes, omaterials, materials);
873
5
    if (1 != rootObjects.size()) {
874
0
        delete root;
875
0
    }
876
877
5
    if (::strncmp(pScene->mRootNode->mName.data, "Node", 4) == 0) {
878
0
        pScene->mRootNode->mName.Set("<AC3DWorld>");
879
0
    }
880
881
    // copy meshes
882
5
    if (meshes.empty()) {
883
0
        throw DeadlyImportError("An unknown error occurred during converting");
884
0
    }
885
5
    pScene->mNumMeshes = (unsigned int)meshes.size();
886
5
    pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
887
5
    ::memcpy(pScene->mMeshes, &meshes[0], pScene->mNumMeshes * sizeof(void *));
888
889
    // copy materials
890
5
    pScene->mNumMaterials = (unsigned int)omaterials.size();
891
5
    pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
892
5
    ::memcpy(pScene->mMaterials, &omaterials[0], pScene->mNumMaterials * sizeof(void *));
893
894
    // copy lights
895
5
    pScene->mNumLights = (unsigned int)lights.size();
896
5
    if (!lights.empty()) {
897
3
        pScene->mLights = new aiLight *[lights.size()];
898
3
        ::memcpy(pScene->mLights, &lights[0], lights.size() * sizeof(void *));
899
3
    }
900
5
}
901
902
} // namespace Assimp
903
904
#endif //!defined ASSIMP_BUILD_NO_AC_IMPORTER