Coverage Report

Created: 2026-02-05 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/Blender/BlenderLoader.cpp
Line
Count
Source
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2026, assimp team
6
7
All rights reserved.
8
9
Redistribution and use of this software in source and binary forms,
10
with or without modification, are permitted provided that the
11
following conditions are met:
12
13
* Redistributions of source code must retain the above
14
  copyright notice, this list of conditions and the
15
  following disclaimer.
16
17
* Redistributions in binary form must reproduce the above
18
  copyright notice, this list of conditions and the
19
  following disclaimer in the documentation and/or other
20
  materials provided with the distribution.
21
22
* Neither the name of the assimp team, nor the names of its
23
  contributors may be used to endorse or promote products
24
  derived from this software without specific prior
25
  written permission of the assimp team.
26
27
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39
----------------------------------------------------------------------
40
*/
41
42
/** @file  BlenderLoader.cpp
43
 *  @brief Implementation of the Blender3D importer class.
44
 */
45
46
//#define ASSIMP_BUILD_NO_COMPRESSED_BLEND
47
// Uncomment this to disable support for (gzip)compressed .BLEND files
48
49
#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
50
51
#include "BlenderBMesh.h"
52
#include "BlenderCustomData.h"
53
#include "BlenderIntermediate.h"
54
#include "BlenderModifier.h"
55
#include <assimp/StringUtils.h>
56
#include <assimp/importerdesc.h>
57
#include <assimp/scene.h>
58
59
#include <assimp/MemoryIOWrapper.h>
60
#include <assimp/StreamReader.h>
61
#include <assimp/StringComparison.h>
62
63
#include <cctype>
64
#include <memory>
65
#include <utility>
66
67
// zlib is needed for compressed blend files
68
#ifndef ASSIMP_BUILD_NO_COMPRESSED_BLEND
69
#include "Common/Compression.h"
70
#endif
71
72
namespace Assimp {
73
74
template <>
75
0
const char *LogFunctions<BlenderImporter>::Prefix() {
76
0
    return "BLEND: ";
77
0
}
78
79
} // namespace Assimp
80
81
using namespace Assimp;
82
using namespace Assimp::Blender;
83
using namespace Assimp::Formatter;
84
85
static constexpr aiImporterDesc blenderDesc = {
86
    "Blender 3D Importer (http://www.blender3d.org)",
87
    "",
88
    "",
89
    "No animation support yet",
90
    aiImporterFlags_SupportBinaryFlavour,
91
    0,
92
    0,
93
    2,
94
    50,
95
    "blend"
96
};
97
98
// ------------------------------------------------------------------------------------------------
99
// Constructor to be privately used by Importer
100
BlenderImporter::BlenderImporter() :
101
31.7k
        modifier_cache(new BlenderModifierShowcase()) {
102
    // empty
103
31.7k
}
104
105
// ------------------------------------------------------------------------------------------------
106
// Destructor, private as well
107
31.7k
BlenderImporter::~BlenderImporter() {
108
31.7k
    delete modifier_cache;
109
31.7k
}
110
111
static constexpr char Token[] = "BLENDER";
112
113
// ------------------------------------------------------------------------------------------------
114
// Returns whether the class can handle the format of the given file.
115
186
bool BlenderImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
116
186
    return ParseMagicToken(pFile, pIOHandler).error.empty();
117
186
}
118
119
// ------------------------------------------------------------------------------------------------
120
// Loader registry entry
121
31.9k
const aiImporterDesc *BlenderImporter::GetInfo() const {
122
31.9k
    return &blenderDesc;
123
31.9k
}
124
125
// ------------------------------------------------------------------------------------------------
126
// Setup configuration properties for the loader
127
26
void BlenderImporter::SetupProperties(const Importer * /*pImp*/) {
128
    // nothing to be done for the moment
129
26
}
130
131
// ------------------------------------------------------------------------------------------------
132
// Imports the given file into the given scene structure.
133
void BlenderImporter::InternReadFile(const std::string &pFile,
134
26
        aiScene *pScene, IOSystem *pIOHandler) {
135
26
    FileDatabase file;
136
26
    StreamOrError streamOrError = ParseMagicToken(pFile, pIOHandler);
137
26
    if (!streamOrError.error.empty()) {
138
0
        ThrowException(streamOrError.error);
139
0
    }
140
26
    std::shared_ptr<IOStream> stream = std::move(streamOrError.stream);
141
142
26
    char version[4] = { 0 };
143
26
    file.i64bit = (stream->Read(version, 1, 1), version[0] == '-');
144
26
    file.little = (stream->Read(version, 1, 1), version[0] == 'v');
145
146
26
    stream->Read(version, 3, 1);
147
26
    version[3] = '\0';
148
149
26
    LogInfo("Blender version is ", version[0], ".", version + 1,
150
26
            " (64bit: ", file.i64bit ? "true" : "false",
151
26
            ", little endian: ", file.little ? "true" : "false", ")");
152
153
26
    ParseBlendFile(file, std::move(stream));
154
155
26
    Scene scene;
156
26
    ExtractScene(scene, file);
157
158
26
    ConvertBlendFile(pScene, scene, file);
159
26
}
160
161
// ------------------------------------------------------------------------------------------------
162
26
void BlenderImporter::ParseBlendFile(FileDatabase &out, std::shared_ptr<IOStream> stream) {
163
26
    out.reader = std::make_shared<StreamReaderAny>(stream, out.little);
164
165
26
    DNAParser dna_reader(out);
166
26
    const DNA *dna = nullptr;
167
168
26
    out.entries.reserve(128);
169
26
    { // even small BLEND files tend to consist of many file blocks
170
26
        SectionParser parser(*out.reader, out.i64bit);
171
172
        // first parse the file in search for the DNA and insert all other sections into the database
173
17.1k
        while ((parser.Next(), 1)) {
174
17.1k
            const FileBlockHead &head = parser.GetCurrent();
175
176
17.1k
            if (head.id == "ENDB") {
177
20
                break; // only valid end of the file
178
17.1k
            } else if (head.id == "DNA1") {
179
20
                dna_reader.Parse();
180
20
                dna = &dna_reader.GetDNA();
181
20
                continue;
182
20
            }
183
184
17.0k
            out.entries.push_back(head);
185
17.0k
        }
186
26
    }
187
26
    if (!dna) {
188
0
        ThrowException("SDNA not found");
189
0
    }
190
191
26
    std::sort(out.entries.begin(), out.entries.end());
192
26
}
193
194
// ------------------------------------------------------------------------------------------------
195
20
void BlenderImporter::ExtractScene(Scene &out, const FileDatabase &file) {
196
20
    const FileBlockHead *block = nullptr;
197
20
    std::map<std::string, size_t>::const_iterator it = file.dna.indices.find("Scene");
198
20
    if (it == file.dna.indices.end()) {
199
0
        ThrowException("There is no `Scene` structure record");
200
0
    }
201
202
20
    const Structure &ss = file.dna.structures[(*it).second];
203
204
    // we need a scene somewhere to start with.
205
11.1k
    for (const FileBlockHead &bl : file.entries) {
206
207
        // Fix: using the DNA index is more reliable to locate scenes
208
        //if (bl.id == "SC") {
209
210
11.1k
        if (bl.dna_index == (*it).second) {
211
20
            block = &bl;
212
20
            break;
213
20
        }
214
11.1k
    }
215
216
20
    if (!block) {
217
0
        ThrowException("There is not a single `Scene` record to load");
218
0
    }
219
220
20
    file.reader->SetCurrentPos(block->start);
221
20
    ss.Convert(out, file);
222
223
20
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
224
20
    ASSIMP_LOG_INFO(
225
20
            "(Stats) Fields read: ", file.stats().fields_read,
226
20
            ", pointers resolved: ", file.stats().pointers_resolved,
227
20
            ", cache hits: ", file.stats().cache_hits,
228
20
            ", cached objects: ", file.stats().cached_objects);
229
20
#endif
230
20
}
231
232
// ------------------------------------------------------------------------------------------------
233
2
void BlenderImporter::ParseSubCollection(const Blender::Scene &in, aiNode *root, const std::shared_ptr<Collection>& collection, ConversionData &conv_data) {
234
235
2
    std::deque<Object *> root_objects;
236
    // Count number of objects
237
5
    for (std::shared_ptr<CollectionObject> cur = std::static_pointer_cast<CollectionObject>(collection->gobject.first); cur; cur = cur->next) {
238
3
        if (cur->ob) {
239
3
            root_objects.push_back(cur->ob);
240
3
        }
241
3
    }
242
2
    std::deque<Collection *> root_children;
243
    // Count number of child nodes
244
3
    for (std::shared_ptr<CollectionChild> cur = std::static_pointer_cast<CollectionChild>(collection->children.first); cur; cur = cur->next) {
245
1
        if (cur->collection) {
246
1
            root_children.push_back(cur->collection.get());
247
1
        }
248
1
    }
249
2
    root->mNumChildren = static_cast<unsigned int>(root_objects.size() + root_children.size());
250
2
    root->mChildren = new aiNode *[root->mNumChildren]();
251
252
5
    for (unsigned int i = 0; i < static_cast<unsigned int>(root_objects.size()); ++i) {
253
3
        root->mChildren[i] = ConvertNode(in, root_objects[i], conv_data, aiMatrix4x4());
254
3
        root->mChildren[i]->mParent = root;
255
3
    }
256
257
    // For each subcollection create a new node to represent it
258
2
    unsigned int iterator = static_cast<unsigned int>(root_objects.size());
259
3
    for (std::shared_ptr<CollectionChild> cur = std::static_pointer_cast<CollectionChild>(collection->children.first); cur; cur = cur->next) {
260
1
        if (cur->collection) {
261
1
            root->mChildren[iterator] = new aiNode(cur->collection->id.name + 2); // skip over the name prefix 'OB'
262
1
            root->mChildren[iterator]->mParent = root;
263
1
            ParseSubCollection(in, root->mChildren[iterator], cur->collection, conv_data);
264
1
        }
265
1
        iterator += 1;
266
1
    }
267
2
}
268
269
// ------------------------------------------------------------------------------------------------
270
19
void BlenderImporter::ConvertBlendFile(aiScene *out, const Scene &in, const FileDatabase &file) {
271
19
    ConversionData conv(file);
272
273
19
    aiNode *root = out->mRootNode = new aiNode("<BlenderRoot>");
274
    // Iterate over all objects directly under master_collection,
275
    // If in.master_collection == null, then we're parsing something older.
276
19
    if (in.master_collection) {
277
1
        ParseSubCollection(in, root, in.master_collection, conv);
278
18
    } else {
279
18
        std::deque<const Object *> no_parents;
280
95
        for (std::shared_ptr<Base> cur = std::static_pointer_cast<Base>(in.base.first); cur; cur = cur->next) {
281
77
            if (cur->object) {
282
77
                if (!cur->object->parent) {
283
68
                    no_parents.push_back(cur->object.get());
284
68
                } else {
285
9
                    conv.objects.insert(cur->object.get());
286
9
                }
287
77
            }
288
77
        }
289
83
        for (std::shared_ptr<Base> cur = in.basact; cur; cur = cur->next) {
290
65
            if (cur->object) {
291
65
                if (cur->object->parent) {
292
9
                    conv.objects.insert(cur->object.get());
293
9
                }
294
65
            }
295
65
        }
296
297
18
        if (no_parents.empty()) {
298
0
            ThrowException("Expected at least one object with no parent");
299
0
        }
300
301
18
        root->mNumChildren = static_cast<unsigned int>(no_parents.size());
302
18
        root->mChildren = new aiNode *[root->mNumChildren]();
303
86
        for (unsigned int i = 0; i < root->mNumChildren; ++i) {
304
68
            root->mChildren[i] = ConvertNode(in, no_parents[i], conv, aiMatrix4x4());
305
68
            root->mChildren[i]->mParent = root;
306
68
        }
307
18
    }
308
309
19
    BuildMaterials(conv);
310
311
19
    if (conv.meshes->size()) {
312
19
        out->mMeshes = new aiMesh *[out->mNumMeshes = static_cast<unsigned int>(conv.meshes->size())];
313
19
        std::copy(conv.meshes->begin(), conv.meshes->end(), out->mMeshes);
314
19
        conv.meshes.dismiss();
315
19
    }
316
317
19
    if (conv.lights->size()) {
318
19
        out->mLights = new aiLight *[out->mNumLights = static_cast<unsigned int>(conv.lights->size())];
319
19
        std::copy(conv.lights->begin(), conv.lights->end(), out->mLights);
320
19
        conv.lights.dismiss();
321
19
    }
322
323
19
    if (conv.cameras->size()) {
324
19
        out->mCameras = new aiCamera *[out->mNumCameras = static_cast<unsigned int>(conv.cameras->size())];
325
19
        std::copy(conv.cameras->begin(), conv.cameras->end(), out->mCameras);
326
19
        conv.cameras.dismiss();
327
19
    }
328
329
19
    if (conv.materials->size()) {
330
19
        out->mMaterials = new aiMaterial *[out->mNumMaterials = static_cast<unsigned int>(conv.materials->size())];
331
19
        std::copy(conv.materials->begin(), conv.materials->end(), out->mMaterials);
332
19
        conv.materials.dismiss();
333
19
    }
334
335
19
    if (conv.textures->size()) {
336
1
        out->mTextures = new aiTexture *[out->mNumTextures = static_cast<unsigned int>(conv.textures->size())];
337
1
        std::copy(conv.textures->begin(), conv.textures->end(), out->mTextures);
338
1
        conv.textures.dismiss();
339
1
    }
340
341
    // acknowledge that the scene might come out incomplete
342
    // by Assimp's definition of `complete`: blender scenes
343
    // can consist of thousands of cameras or lights with
344
    // not a single mesh between them.
345
19
    if (!out->mNumMeshes) {
346
0
        out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
347
0
    }
348
19
}
349
350
// ------------------------------------------------------------------------------------------------
351
1
void BlenderImporter::ResolveImage(aiMaterial *out, const Material *mat, const MTex *tex, const Image *img, ConversionData &conv_data) {
352
1
    (void)mat;
353
1
    (void)tex;
354
1
    (void)conv_data;
355
1
    aiString name;
356
357
    // check if the file contents are bundled with the BLEND file
358
1
    if (img->packedfile) {
359
1
        name.data[0] = '*';
360
1
        name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast<unsigned int>(AI_MAXLEN - 1), static_cast<int32_t>(conv_data.textures->size()));
361
362
1
        conv_data.textures->push_back(new aiTexture());
363
1
        aiTexture *curTex = conv_data.textures->back();
364
365
        // usually 'img->name' will be the original file name of the embedded textures,
366
        // so we can extract the file extension from it.
367
1
        const size_t nlen = strlen(img->name);
368
1
        const char *s = img->name + nlen, *e = s;
369
5
        while (s >= img->name && *s != '.') {
370
4
            --s;
371
4
        }
372
373
1
        curTex->achFormatHint[0] = s + 1 > e ? '\0' : (char)::tolower((unsigned char)s[1]);
374
1
        curTex->achFormatHint[1] = s + 2 > e ? '\0' : (char)::tolower((unsigned char)s[2]);
375
1
        curTex->achFormatHint[2] = s + 3 > e ? '\0' : (char)::tolower((unsigned char)s[3]);
376
1
        curTex->achFormatHint[3] = '\0';
377
378
        // tex->mHeight = 0;
379
1
        curTex->mWidth = img->packedfile->size;
380
1
        uint8_t *ch = new uint8_t[curTex->mWidth];
381
382
1
        conv_data.db.reader->SetCurrentPos(static_cast<size_t>(img->packedfile->data->val));
383
1
        conv_data.db.reader->CopyAndAdvance(ch, curTex->mWidth);
384
385
1
        curTex->pcData = reinterpret_cast<aiTexel *>(ch);
386
387
1
        LogInfo("Reading embedded texture, original file was ", img->name);
388
1
    } else {
389
0
        name = aiString(img->name);
390
0
    }
391
392
1
    aiTextureType texture_type = aiTextureType_UNKNOWN;
393
1
    MTex::MapType map_type = tex->mapto;
394
395
1
    if (map_type & MTex::MapType_COL)
396
1
        texture_type = aiTextureType_DIFFUSE;
397
0
    else if (map_type & MTex::MapType_NORM) {
398
0
        if (tex->tex->imaflag & Tex::ImageFlags_NORMALMAP) {
399
0
            texture_type = aiTextureType_NORMALS;
400
0
        } else {
401
0
            texture_type = aiTextureType_HEIGHT;
402
0
        }
403
0
        out->AddProperty(&tex->norfac, 1, AI_MATKEY_BUMPSCALING);
404
0
    } else if (map_type & MTex::MapType_COLSPEC)
405
0
        texture_type = aiTextureType_SPECULAR;
406
0
    else if (map_type & MTex::MapType_COLMIR)
407
0
        texture_type = aiTextureType_REFLECTION;
408
    //else if (map_type & MTex::MapType_REF)
409
0
    else if (map_type & MTex::MapType_SPEC)
410
0
        texture_type = aiTextureType_SHININESS;
411
0
    else if (map_type & MTex::MapType_EMIT)
412
0
        texture_type = aiTextureType_EMISSIVE;
413
    //else if (map_type & MTex::MapType_ALPHA)
414
    //else if (map_type & MTex::MapType_HAR)
415
    //else if (map_type & MTex::MapType_RAYMIRR)
416
    //else if (map_type & MTex::MapType_TRANSLU)
417
0
    else if (map_type & MTex::MapType_AMB)
418
0
        texture_type = aiTextureType_AMBIENT;
419
0
    else if (map_type & MTex::MapType_DISPLACE)
420
0
        texture_type = aiTextureType_DISPLACEMENT;
421
    //else if (map_type & MTex::MapType_WARP)
422
423
1
    out->AddProperty(&name, AI_MATKEY_TEXTURE(texture_type,
424
1
                                    conv_data.next_texture[texture_type]++));
425
1
}
426
427
// ------------------------------------------------------------------------------------------------
428
0
void BlenderImporter::AddSentinelTexture(aiMaterial *out, const Material *mat, const MTex *tex, ConversionData &conv_data) {
429
0
    (void)mat;
430
0
    (void)tex;
431
0
    (void)conv_data;
432
433
0
    aiString name;
434
0
    name.length = ai_snprintf(name.data, AI_MAXLEN, "Procedural,num=%i,type=%s", conv_data.sentinel_cnt++,
435
0
            GetTextureTypeDisplayString(tex->tex->type));
436
0
    out->AddProperty(&name, AI_MATKEY_TEXTURE_DIFFUSE(
437
0
                                    conv_data.next_texture[aiTextureType_DIFFUSE]++));
438
0
}
439
440
// ------------------------------------------------------------------------------------------------
441
18
void BlenderImporter::ResolveTexture(aiMaterial *out, const Material *mat, const MTex *tex, ConversionData &conv_data) {
442
18
    const Tex *rtex = tex->tex.get();
443
18
    if (!rtex || !rtex->type) {
444
17
        return;
445
17
    }
446
447
    // We can't support most of the texture types because they're mostly procedural.
448
    // These are substituted by a dummy texture.
449
1
    const char *dispnam = "";
450
1
    switch (rtex->type) {
451
        // these are listed in blender's UI
452
0
    case Tex::Type_CLOUDS:
453
0
    case Tex::Type_WOOD:
454
0
    case Tex::Type_MARBLE:
455
0
    case Tex::Type_MAGIC:
456
0
    case Tex::Type_BLEND:
457
0
    case Tex::Type_STUCCI:
458
0
    case Tex::Type_NOISE:
459
0
    case Tex::Type_PLUGIN:
460
0
    case Tex::Type_MUSGRAVE:
461
0
    case Tex::Type_VORONOI:
462
0
    case Tex::Type_DISTNOISE:
463
0
    case Tex::Type_ENVMAP:
464
465
        // these do no appear in the UI, why?
466
0
    case Tex::Type_POINTDENSITY:
467
0
    case Tex::Type_VOXELDATA:
468
469
0
        LogWarn("Encountered a texture with an unsupported type: ", dispnam);
470
0
        AddSentinelTexture(out, mat, tex, conv_data);
471
0
        break;
472
473
1
    case Tex::Type_IMAGE:
474
1
        if (!rtex->ima) {
475
0
            LogError("A texture claims to be an Image, but no image reference is given");
476
0
            break;
477
0
        }
478
1
        ResolveImage(out, mat, tex, rtex->ima.get(), conv_data);
479
1
        break;
480
481
0
    default:
482
0
        ai_assert(false);
483
1
    };
484
1
}
485
486
// ------------------------------------------------------------------------------------------------
487
19
void BlenderImporter::BuildDefaultMaterial(Blender::ConversionData &conv_data) {
488
    // add a default material if necessary
489
19
    unsigned int index = static_cast<unsigned int>(-1);
490
34
    for (aiMesh *mesh : conv_data.meshes.get()) {
491
34
        if (mesh->mMaterialIndex == static_cast<unsigned int>(-1)) {
492
493
4
            if (index == static_cast<unsigned int>(-1)) {
494
                // Setup a default material.
495
4
                std::shared_ptr<Material> p(new Material());
496
4
                const size_t len = ::strlen(AI_DEFAULT_MATERIAL_NAME);
497
4
                ai_assert(len < sizeof(p->id.name) - 2);
498
4
                memcpy(p->id.name + 2, AI_DEFAULT_MATERIAL_NAME, len);
499
500
                // Note: MSVC11 does not zero-initialize Material here, although it should.
501
                // Thus all relevant fields should be explicitly initialized. We cannot add
502
                // a default constructor to Material since the DNA codegen does not support
503
                // parsing it.
504
4
                p->r = p->g = p->b = 0.6f;
505
4
                p->specr = p->specg = p->specb = 0.6f;
506
4
                p->ambr = p->ambg = p->ambb = 0.0f;
507
4
                p->mirr = p->mirg = p->mirb = 0.0f;
508
4
                p->emit = 0.f;
509
4
                p->alpha = 0.f;
510
4
                p->har = 0;
511
512
4
                index = static_cast<unsigned int>(conv_data.materials_raw.size());
513
4
                conv_data.materials_raw.push_back(p);
514
4
                LogInfo("Adding default material");
515
4
            }
516
4
            mesh->mMaterialIndex = index;
517
4
        }
518
34
    }
519
19
}
520
521
24
void BlenderImporter::AddBlendParams(aiMaterial *result, const Material *source) {
522
24
    aiColor3D diffuseColor(source->r, source->g, source->b);
523
24
    result->AddProperty(&diffuseColor, 1, "$mat.blend.diffuse.color", 0, 0);
524
525
24
    float diffuseIntensity = source->ref;
526
24
    result->AddProperty(&diffuseIntensity, 1, "$mat.blend.diffuse.intensity", 0, 0);
527
528
24
    int diffuseShader = source->diff_shader;
529
24
    result->AddProperty(&diffuseShader, 1, "$mat.blend.diffuse.shader", 0, 0);
530
531
24
    int diffuseRamp = 0;
532
24
    result->AddProperty(&diffuseRamp, 1, "$mat.blend.diffuse.ramp", 0, 0);
533
534
24
    aiColor3D specularColor(source->specr, source->specg, source->specb);
535
24
    result->AddProperty(&specularColor, 1, "$mat.blend.specular.color", 0, 0);
536
537
24
    float specularIntensity = source->spec;
538
24
    result->AddProperty(&specularIntensity, 1, "$mat.blend.specular.intensity", 0, 0);
539
540
24
    int specularShader = source->spec_shader;
541
24
    result->AddProperty(&specularShader, 1, "$mat.blend.specular.shader", 0, 0);
542
543
24
    int specularRamp = 0;
544
24
    result->AddProperty(&specularRamp, 1, "$mat.blend.specular.ramp", 0, 0);
545
546
24
    int specularHardness = source->har;
547
24
    result->AddProperty(&specularHardness, 1, "$mat.blend.specular.hardness", 0, 0);
548
549
24
    int transparencyUse = source->mode & MA_TRANSPARENCY ? 1 : 0;
550
24
    result->AddProperty(&transparencyUse, 1, "$mat.blend.transparency.use", 0, 0);
551
552
24
    int transparencyMethod = source->mode & MA_RAYTRANSP ? 2 : (source->mode & MA_ZTRANSP ? 1 : 0);
553
24
    result->AddProperty(&transparencyMethod, 1, "$mat.blend.transparency.method", 0, 0);
554
555
24
    float transparencyAlpha = source->alpha;
556
24
    result->AddProperty(&transparencyAlpha, 1, "$mat.blend.transparency.alpha", 0, 0);
557
558
24
    float transparencySpecular = source->spectra;
559
24
    result->AddProperty(&transparencySpecular, 1, "$mat.blend.transparency.specular", 0, 0);
560
561
24
    float transparencyFresnel = source->fresnel_tra;
562
24
    result->AddProperty(&transparencyFresnel, 1, "$mat.blend.transparency.fresnel", 0, 0);
563
564
24
    float transparencyBlend = source->fresnel_tra_i;
565
24
    result->AddProperty(&transparencyBlend, 1, "$mat.blend.transparency.blend", 0, 0);
566
567
24
    float transparencyIor = source->ang;
568
24
    result->AddProperty(&transparencyIor, 1, "$mat.blend.transparency.ior", 0, 0);
569
570
24
    float transparencyFilter = source->filter;
571
24
    result->AddProperty(&transparencyFilter, 1, "$mat.blend.transparency.filter", 0, 0);
572
573
24
    float transparencyFalloff = source->tx_falloff;
574
24
    result->AddProperty(&transparencyFalloff, 1, "$mat.blend.transparency.falloff", 0, 0);
575
576
24
    float transparencyLimit = source->tx_limit;
577
24
    result->AddProperty(&transparencyLimit, 1, "$mat.blend.transparency.limit", 0, 0);
578
579
24
    int transparencyDepth = source->ray_depth_tra;
580
24
    result->AddProperty(&transparencyDepth, 1, "$mat.blend.transparency.depth", 0, 0);
581
582
24
    float transparencyGlossAmount = source->gloss_tra;
583
24
    result->AddProperty(&transparencyGlossAmount, 1, "$mat.blend.transparency.glossAmount", 0, 0);
584
585
24
    float transparencyGlossThreshold = source->adapt_thresh_tra;
586
24
    result->AddProperty(&transparencyGlossThreshold, 1, "$mat.blend.transparency.glossThreshold", 0, 0);
587
588
24
    int transparencyGlossSamples = source->samp_gloss_tra;
589
24
    result->AddProperty(&transparencyGlossSamples, 1, "$mat.blend.transparency.glossSamples", 0, 0);
590
591
24
    int mirrorUse = source->mode & MA_RAYMIRROR ? 1 : 0;
592
24
    result->AddProperty(&mirrorUse, 1, "$mat.blend.mirror.use", 0, 0);
593
594
24
    float mirrorReflectivity = source->ray_mirror;
595
24
    result->AddProperty(&mirrorReflectivity, 1, "$mat.blend.mirror.reflectivity", 0, 0);
596
597
24
    aiColor3D mirrorColor(source->mirr, source->mirg, source->mirb);
598
24
    result->AddProperty(&mirrorColor, 1, "$mat.blend.mirror.color", 0, 0);
599
600
24
    float mirrorFresnel = source->fresnel_mir;
601
24
    result->AddProperty(&mirrorFresnel, 1, "$mat.blend.mirror.fresnel", 0, 0);
602
603
24
    float mirrorBlend = source->fresnel_mir_i;
604
24
    result->AddProperty(&mirrorBlend, 1, "$mat.blend.mirror.blend", 0, 0);
605
606
24
    int mirrorDepth = source->ray_depth;
607
24
    result->AddProperty(&mirrorDepth, 1, "$mat.blend.mirror.depth", 0, 0);
608
609
24
    float mirrorMaxDist = source->dist_mir;
610
24
    result->AddProperty(&mirrorMaxDist, 1, "$mat.blend.mirror.maxDist", 0, 0);
611
612
24
    int mirrorFadeTo = source->fadeto_mir;
613
24
    result->AddProperty(&mirrorFadeTo, 1, "$mat.blend.mirror.fadeTo", 0, 0);
614
615
24
    float mirrorGlossAmount = source->gloss_mir;
616
24
    result->AddProperty(&mirrorGlossAmount, 1, "$mat.blend.mirror.glossAmount", 0, 0);
617
618
24
    float mirrorGlossThreshold = source->adapt_thresh_mir;
619
24
    result->AddProperty(&mirrorGlossThreshold, 1, "$mat.blend.mirror.glossThreshold", 0, 0);
620
621
24
    int mirrorGlossSamples = source->samp_gloss_mir;
622
24
    result->AddProperty(&mirrorGlossSamples, 1, "$mat.blend.mirror.glossSamples", 0, 0);
623
624
24
    float mirrorGlossAnisotropic = source->aniso_gloss_mir;
625
24
    result->AddProperty(&mirrorGlossAnisotropic, 1, "$mat.blend.mirror.glossAnisotropic", 0, 0);
626
24
}
627
628
19
void BlenderImporter::BuildMaterials(ConversionData &conv_data) {
629
19
    conv_data.materials->reserve(conv_data.materials_raw.size());
630
631
19
    BuildDefaultMaterial(conv_data);
632
633
24
    for (const std::shared_ptr<Material> &mat : conv_data.materials_raw) {
634
635
        // reset per material global counters
636
480
        for (size_t i = 0; i < sizeof(conv_data.next_texture) / sizeof(conv_data.next_texture[0]); ++i) {
637
456
            conv_data.next_texture[i] = 0;
638
456
        }
639
640
24
        aiMaterial *mout = new aiMaterial();
641
24
        conv_data.materials->push_back(mout);
642
        // For any new material field handled here, the default material above must be updated with an appropriate default value.
643
644
        // set material name
645
24
        aiString name = aiString(mat->id.name + 2); // skip over the name prefix 'MA'
646
24
        mout->AddProperty(&name, AI_MATKEY_NAME);
647
648
        // basic material colors
649
24
        aiColor3D col(mat->r, mat->g, mat->b);
650
24
        if (mat->r || mat->g || mat->b) {
651
652
            // Usually, zero diffuse color means no diffuse color at all in the equation.
653
            // So we omit this member to express this intent.
654
24
            mout->AddProperty(&col, 1, AI_MATKEY_COLOR_DIFFUSE);
655
656
24
            if (mat->emit) {
657
0
                aiColor3D emit_col(mat->emit * mat->r, mat->emit * mat->g, mat->emit * mat->b);
658
0
                mout->AddProperty(&emit_col, 1, AI_MATKEY_COLOR_EMISSIVE);
659
0
            }
660
24
        }
661
662
24
        col = aiColor3D(mat->specr, mat->specg, mat->specb);
663
24
        mout->AddProperty(&col, 1, AI_MATKEY_COLOR_SPECULAR);
664
665
        // is hardness/shininess set?
666
24
        if (mat->har) {
667
19
            const float har = mat->har;
668
19
            mout->AddProperty(&har, 1, AI_MATKEY_SHININESS);
669
19
        }
670
671
24
        col = aiColor3D(mat->ambr, mat->ambg, mat->ambb);
672
24
        mout->AddProperty(&col, 1, AI_MATKEY_COLOR_AMBIENT);
673
674
        // is mirror enabled?
675
24
        if (mat->mode & MA_RAYMIRROR) {
676
1
            const float ray_mirror = mat->ray_mirror;
677
1
            mout->AddProperty(&ray_mirror, 1, AI_MATKEY_REFLECTIVITY);
678
1
        }
679
680
24
        col = aiColor3D(mat->mirr, mat->mirg, mat->mirb);
681
24
        mout->AddProperty(&col, 1, AI_MATKEY_COLOR_REFLECTIVE);
682
683
456
        for (size_t i = 0; i < sizeof(mat->mtex) / sizeof(mat->mtex[0]); ++i) {
684
432
            if (!mat->mtex[i]) {
685
414
                continue;
686
414
            }
687
688
18
            ResolveTexture(mout, mat.get(), mat->mtex[i].get(), conv_data);
689
18
        }
690
691
24
        AddBlendParams(mout, mat.get());
692
24
    }
693
19
}
694
695
// ------------------------------------------------------------------------------------------------
696
77
void BlenderImporter::CheckActualType(const ElemBase *dt, const char *check) {
697
77
    ai_assert(dt);
698
77
    if (strcmp(dt->dna_type, check)) {
699
0
        ThrowException("Expected object at ", std::hex, dt, " to be of type `", check,
700
0
                "`, but it claims to be a `", dt->dna_type, "`instead");
701
0
    }
702
77
}
703
704
// ------------------------------------------------------------------------------------------------
705
0
void BlenderImporter::NotSupportedObjectType(const Object *obj, const char *type) {
706
0
    LogWarn("Object `", obj->id.name, "` - type is unsupported: `", type, "`, skipping");
707
0
}
708
709
// ------------------------------------------------------------------------------------------------
710
void BlenderImporter::ConvertMesh(const Scene & /*in*/, const Object * /*obj*/, const Mesh *mesh,
711
32
        ConversionData &conv_data, TempArray<std::vector, aiMesh> &temp) {
712
    // TODO: Resolve various problems with BMesh triangulation before re-enabling.
713
    //       See issues #400, #373, #318  #315 and #132.
714
#if defined(TODO_FIX_BMESH_CONVERSION)
715
    BlenderBMeshConverter BMeshConverter(mesh);
716
    if (BMeshConverter.ContainsBMesh()) {
717
        mesh = BMeshConverter.TriangulateBMesh();
718
    }
719
#endif
720
721
32
    typedef std::pair<const int, size_t> MyPair;
722
32
    if ((!mesh->totface && !mesh->totloop) || !mesh->totvert) {
723
0
        return;
724
0
    }
725
726
    // some sanity checks
727
32
    if (static_cast<size_t>(mesh->totface) > mesh->mface.size()) {
728
0
        ThrowException("Number of faces is larger than the corresponding array");
729
0
    }
730
731
32
    if (static_cast<size_t>(mesh->totvert) > mesh->mvert.size()) {
732
0
        ThrowException("Number of vertices is larger than the corresponding array");
733
0
    }
734
735
32
    if (static_cast<size_t>(mesh->totloop) > mesh->mloop.size()) {
736
0
        ThrowException("Number of vertices is larger than the corresponding array");
737
0
    }
738
739
    // collect per-submesh numbers
740
32
    std::map<int, size_t> per_mat;
741
32
    std::map<int, size_t> per_mat_verts;
742
5.07k
    for (int i = 0; i < mesh->totface; ++i) {
743
744
5.04k
        const MFace &mf = mesh->mface[i];
745
5.04k
        per_mat[mf.mat_nr]++;
746
5.04k
        per_mat_verts[mf.mat_nr] += mf.v4 ? 4 : 3;
747
5.04k
    }
748
749
670
    for (int i = 0; i < mesh->totpoly; ++i) {
750
638
        const MPoly &mp = mesh->mpoly[i];
751
638
        per_mat[mp.mat_nr]++;
752
638
        per_mat_verts[mp.mat_nr] += mp.totloop;
753
638
    }
754
755
    // ... and allocate the corresponding meshes
756
32
    const size_t old = temp->size();
757
32
    temp->reserve(temp->size() + per_mat.size());
758
759
32
    std::map<size_t, size_t> mat_num_to_mesh_idx;
760
32
    for (MyPair &it : per_mat) {
761
762
32
        mat_num_to_mesh_idx[it.first] = temp->size();
763
32
        temp->push_back(new aiMesh());
764
765
32
        aiMesh *out = temp->back();
766
32
        out->mVertices = new aiVector3D[per_mat_verts[it.first]];
767
32
        out->mNormals = new aiVector3D[per_mat_verts[it.first]];
768
769
        //out->mNumFaces = 0
770
        //out->mNumVertices = 0
771
32
        out->mFaces = new aiFace[it.second]();
772
773
        // all sub-meshes created from this mesh are named equally. this allows
774
        // curious users to recover the original adjacency.
775
32
        out->mName = aiString(mesh->id.name + 2);
776
        // skip over the name prefix 'ME'
777
778
        // resolve the material reference and add this material to the set of
779
        // output materials. The (temporary) material index is the index
780
        // of the material entry within the list of resolved materials.
781
32
        if (mesh->mat) {
782
783
28
            if (static_cast<size_t>(it.first) >= mesh->mat.size()) {
784
0
                ThrowException("Material index is out of range");
785
0
            }
786
787
28
            std::shared_ptr<Material> mat = mesh->mat[it.first];
788
28
            const std::deque<std::shared_ptr<Material>>::iterator has = std::find(
789
28
                    conv_data.materials_raw.begin(),
790
28
                    conv_data.materials_raw.end(), mat);
791
792
28
            if (has != conv_data.materials_raw.end()) {
793
8
                out->mMaterialIndex = static_cast<unsigned int>(std::distance(conv_data.materials_raw.begin(), has));
794
20
            } else {
795
20
                out->mMaterialIndex = static_cast<unsigned int>(conv_data.materials_raw.size());
796
20
                conv_data.materials_raw.push_back(mat);
797
20
            }
798
28
        } else
799
4
            out->mMaterialIndex = static_cast<unsigned int>(-1);
800
32
    }
801
802
5.07k
    for (int i = 0; i < mesh->totface; ++i) {
803
804
5.04k
        const MFace &mf = mesh->mface[i];
805
806
5.04k
        aiMesh *const out = temp[mat_num_to_mesh_idx[mf.mat_nr]];
807
5.04k
        aiFace &f = out->mFaces[out->mNumFaces++];
808
809
5.04k
        f.mIndices = new unsigned int[f.mNumIndices = mf.v4 ? 4 : 3];
810
5.04k
        aiVector3D *vo = out->mVertices + out->mNumVertices;
811
5.04k
        aiVector3D *vn = out->mNormals + out->mNumVertices;
812
813
        // XXX we can't fold this easily, because we are restricted
814
        // to the member names from the BLEND file (v1,v2,v3,v4)
815
        // which are assigned by the genblenddna.py script and
816
        // cannot be changed without breaking the entire
817
        // import process.
818
819
5.04k
        if (mf.v1 >= mesh->totvert) {
820
0
            ThrowException("Vertex index v1 out of range");
821
0
        }
822
5.04k
        const MVert *v = &mesh->mvert[mf.v1];
823
5.04k
        vo->x = v->co[0];
824
5.04k
        vo->y = v->co[1];
825
5.04k
        vo->z = v->co[2];
826
5.04k
        vn->x = v->no[0];
827
5.04k
        vn->y = v->no[1];
828
5.04k
        vn->z = v->no[2];
829
5.04k
        f.mIndices[0] = out->mNumVertices++;
830
5.04k
        ++vo;
831
5.04k
        ++vn;
832
833
        //  if (f.mNumIndices >= 2) {
834
5.04k
        if (mf.v2 >= mesh->totvert) {
835
0
            ThrowException("Vertex index v2 out of range");
836
0
        }
837
5.04k
        v = &mesh->mvert[mf.v2];
838
5.04k
        vo->x = v->co[0];
839
5.04k
        vo->y = v->co[1];
840
5.04k
        vo->z = v->co[2];
841
5.04k
        vn->x = v->no[0];
842
5.04k
        vn->y = v->no[1];
843
5.04k
        vn->z = v->no[2];
844
5.04k
        f.mIndices[1] = out->mNumVertices++;
845
5.04k
        ++vo;
846
5.04k
        ++vn;
847
848
5.04k
        if (mf.v3 >= mesh->totvert) {
849
0
            ThrowException("Vertex index v3 out of range");
850
0
        }
851
        //  if (f.mNumIndices >= 3) {
852
5.04k
        v = &mesh->mvert[mf.v3];
853
5.04k
        vo->x = v->co[0];
854
5.04k
        vo->y = v->co[1];
855
5.04k
        vo->z = v->co[2];
856
5.04k
        vn->x = v->no[0];
857
5.04k
        vn->y = v->no[1];
858
5.04k
        vn->z = v->no[2];
859
5.04k
        f.mIndices[2] = out->mNumVertices++;
860
5.04k
        ++vo;
861
5.04k
        ++vn;
862
863
5.04k
        if (mf.v4 >= mesh->totvert) {
864
0
            ThrowException("Vertex index v4 out of range");
865
0
        }
866
        //  if (f.mNumIndices >= 4) {
867
5.04k
        if (mf.v4) {
868
4.88k
            v = &mesh->mvert[mf.v4];
869
4.88k
            vo->x = v->co[0];
870
4.88k
            vo->y = v->co[1];
871
4.88k
            vo->z = v->co[2];
872
4.88k
            vn->x = v->no[0];
873
4.88k
            vn->y = v->no[1];
874
4.88k
            vn->z = v->no[2];
875
4.88k
            f.mIndices[3] = out->mNumVertices++;
876
4.88k
            ++vo;
877
4.88k
            ++vn;
878
879
4.88k
            out->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
880
4.88k
        } else
881
161
            out->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
882
883
        //  }
884
        //  }
885
        //  }
886
5.04k
    }
887
888
670
    for (int i = 0; i < mesh->totpoly; ++i) {
889
890
638
        const MPoly &mf = mesh->mpoly[i];
891
892
638
        aiMesh *const out = temp[mat_num_to_mesh_idx[mf.mat_nr]];
893
638
        aiFace &f = out->mFaces[out->mNumFaces++];
894
895
638
        f.mIndices = new unsigned int[f.mNumIndices = mf.totloop];
896
638
        aiVector3D *vo = out->mVertices + out->mNumVertices;
897
638
        aiVector3D *vn = out->mNormals + out->mNumVertices;
898
899
        // XXX we can't fold this easily, because we are restricted
900
        // to the member names from the BLEND file (v1,v2,v3,v4)
901
        // which are assigned by the genblenddna.py script and
902
        // cannot be changed without breaking the entire
903
        // import process.
904
3.15k
        for (int j = 0; j < mf.totloop; ++j) {
905
2.52k
            const MLoop &loop = mesh->mloop[mf.loopstart + j];
906
907
2.52k
            if (loop.v >= mesh->totvert) {
908
0
                ThrowException("Vertex index out of range");
909
0
            }
910
911
2.52k
            const MVert &v = mesh->mvert[loop.v];
912
913
2.52k
            vo->x = v.co[0];
914
2.52k
            vo->y = v.co[1];
915
2.52k
            vo->z = v.co[2];
916
2.52k
            vn->x = v.no[0];
917
2.52k
            vn->y = v.no[1];
918
2.52k
            vn->z = v.no[2];
919
2.52k
            f.mIndices[j] = out->mNumVertices++;
920
921
2.52k
            ++vo;
922
2.52k
            ++vn;
923
2.52k
        }
924
638
        if (mf.totloop == 3) {
925
32
            out->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
926
606
        } else {
927
606
            out->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
928
606
        }
929
638
    }
930
931
    // TODO should we create the TextureUVMapping map in Convert<Material> to prevent redundant processing?
932
933
    // create texture <-> uvname mapping for all materials
934
    // key is texture number, value is data *
935
32
    typedef std::map<uint32_t, const MLoopUV *> TextureUVMapping;
936
    // key is material number, value is the TextureUVMapping for the material
937
32
    typedef std::map<uint32_t, TextureUVMapping> MaterialTextureUVMappings;
938
32
    MaterialTextureUVMappings matTexUvMappings;
939
32
    const uint32_t maxMat = static_cast<uint32_t>(mesh->mat.size());
940
60
    for (uint32_t m = 0; m < maxMat; ++m) {
941
        // get material by index
942
28
        const std::shared_ptr<Material> pMat = mesh->mat[m];
943
28
        TextureUVMapping texuv;
944
28
        const uint32_t maxTex = sizeof(pMat->mtex) / sizeof(pMat->mtex[0]);
945
532
        for (uint32_t t = 0; t < maxTex; ++t) {
946
504
            if (pMat->mtex[t] && pMat->mtex[t]->uvname[0]) {
947
                // get the CustomData layer for given uvname and correct type
948
0
                const ElemBase *pLoop = getCustomDataLayerData(mesh->ldata, CD_MLOOPUV, pMat->mtex[t]->uvname);
949
0
                if (pLoop) {
950
0
                    texuv.insert(std::make_pair(t, dynamic_cast<const MLoopUV *>(pLoop)));
951
0
                }
952
0
            }
953
504
        }
954
28
        if (texuv.size()) {
955
0
            matTexUvMappings.insert(std::make_pair(m, texuv));
956
0
        }
957
28
    }
958
959
    // collect texture coordinates, they're stored in a separate per-face buffer
960
32
    if (mesh->mtface || mesh->mloopuv) {
961
3
        if (mesh->totface > static_cast<int>(mesh->mtface.size())) {
962
0
            ThrowException("Number of UV faces is larger than the corresponding UV face array (#1)");
963
0
        }
964
6
        for (std::vector<aiMesh *>::iterator it = temp->begin() + old; it != temp->end(); ++it) {
965
3
            ai_assert(0 != (*it)->mNumVertices);
966
3
            ai_assert(0 != (*it)->mNumFaces);
967
3
            const auto itMatTexUvMapping = matTexUvMappings.find((*it)->mMaterialIndex);
968
3
            if (itMatTexUvMapping == matTexUvMappings.end()) {
969
                // default behaviour like before
970
3
                (*it)->mTextureCoords[0] = new aiVector3D[(*it)->mNumVertices];
971
3
            } else {
972
                // create texture coords for every mapped tex
973
0
                for (uint32_t i = 0; i < itMatTexUvMapping->second.size(); ++i) {
974
0
                    (*it)->mTextureCoords[i] = new aiVector3D[(*it)->mNumVertices];
975
0
                }
976
0
            }
977
3
            (*it)->mNumFaces = (*it)->mNumVertices = 0;
978
3
        }
979
980
10
        for (int i = 0; i < mesh->totface; ++i) {
981
7
            const MTFace *v = &mesh->mtface[i];
982
983
7
            aiMesh *const out = temp[mat_num_to_mesh_idx[mesh->mface[i].mat_nr]];
984
7
            const aiFace &f = out->mFaces[out->mNumFaces++];
985
986
7
            aiVector3D *vo = &out->mTextureCoords[0][out->mNumVertices];
987
35
            for (unsigned int j = 0; j < f.mNumIndices; ++j, ++vo, ++out->mNumVertices) {
988
28
                vo->x = v->uv[j][0];
989
28
                vo->y = v->uv[j][1];
990
28
            }
991
7
        }
992
993
9
        for (int i = 0; i < mesh->totpoly; ++i) {
994
6
            const MPoly &v = mesh->mpoly[i];
995
6
            aiMesh *const out = temp[mat_num_to_mesh_idx[v.mat_nr]];
996
6
            const aiFace &f = out->mFaces[out->mNumFaces++];
997
998
6
            const auto itMatTexUvMapping = matTexUvMappings.find(v.mat_nr);
999
6
            if (itMatTexUvMapping == matTexUvMappings.end()) {
1000
                // old behavior
1001
6
                aiVector3D *vo = &out->mTextureCoords[0][out->mNumVertices];
1002
30
                for (unsigned int j = 0; j < f.mNumIndices; ++j, ++vo, ++out->mNumVertices) {
1003
24
                    const MLoopUV &uv = mesh->mloopuv[v.loopstart + j];
1004
24
                    vo->x = uv.uv[0];
1005
24
                    vo->y = uv.uv[1];
1006
24
                }
1007
6
            } else {
1008
                // create textureCoords for every mapped tex
1009
0
                for (uint32_t m = 0; m < itMatTexUvMapping->second.size(); ++m) {
1010
0
                    const MLoopUV *tm = itMatTexUvMapping->second[m];
1011
0
                    aiVector3D *vo = &out->mTextureCoords[m][out->mNumVertices];
1012
0
                    uint32_t j = 0;
1013
0
                    for (; j < f.mNumIndices; ++j, ++vo) {
1014
0
                        const MLoopUV &uv = tm[v.loopstart + j];
1015
0
                        vo->x = uv.uv[0];
1016
0
                        vo->y = uv.uv[1];
1017
0
                    }
1018
                    // only update written mNumVertices in last loop
1019
                    // TODO why must the numVertices be incremented here?
1020
0
                    if (m == itMatTexUvMapping->second.size() - 1) {
1021
0
                        out->mNumVertices += j;
1022
0
                    }
1023
0
                }
1024
0
            }
1025
6
        }
1026
3
    }
1027
1028
    // collect texture coordinates, old-style (marked as deprecated in current blender sources)
1029
32
    if (mesh->tface) {
1030
0
        if (mesh->totface > static_cast<int>(mesh->tface.size())) {
1031
0
            ThrowException("Number of faces is larger than the corresponding UV face array (#2)");
1032
0
        }
1033
0
        for (std::vector<aiMesh *>::iterator it = temp->begin() + old; it != temp->end(); ++it) {
1034
0
            ai_assert(0 != (*it)->mNumVertices);
1035
0
            ai_assert(0 != (*it)->mNumFaces);
1036
1037
0
            (*it)->mTextureCoords[0] = new aiVector3D[(*it)->mNumVertices];
1038
0
            (*it)->mNumFaces = (*it)->mNumVertices = 0;
1039
0
        }
1040
1041
0
        for (int i = 0; i < mesh->totface; ++i) {
1042
0
            const TFace *v = &mesh->tface[i];
1043
1044
0
            aiMesh *const out = temp[mat_num_to_mesh_idx[mesh->mface[i].mat_nr]];
1045
0
            const aiFace &f = out->mFaces[out->mNumFaces++];
1046
1047
0
            aiVector3D *vo = &out->mTextureCoords[0][out->mNumVertices];
1048
0
            for (unsigned int j = 0; j < f.mNumIndices; ++j, ++vo, ++out->mNumVertices) {
1049
0
                vo->x = v->uv[j][0];
1050
0
                vo->y = v->uv[j][1];
1051
0
            }
1052
0
        }
1053
0
    }
1054
1055
    // collect vertex colors, stored separately as well
1056
32
    if (mesh->mcol || mesh->mloopcol) {
1057
1
        if (mesh->totface > static_cast<int>((mesh->mcol.size() / 4))) {
1058
0
            ThrowException("Number of faces is larger than the corresponding color face array");
1059
0
        }
1060
2
        for (std::vector<aiMesh *>::iterator it = temp->begin() + old; it != temp->end(); ++it) {
1061
1
            ai_assert(0 != (*it)->mNumVertices);
1062
1
            ai_assert(0 != (*it)->mNumFaces);
1063
1064
1
            (*it)->mColors[0] = new aiColor4D[(*it)->mNumVertices];
1065
1
            (*it)->mNumFaces = (*it)->mNumVertices = 0;
1066
1
        }
1067
1068
7
        for (int i = 0; i < mesh->totface; ++i) {
1069
1070
6
            aiMesh *const out = temp[mat_num_to_mesh_idx[mesh->mface[i].mat_nr]];
1071
6
            const aiFace &f = out->mFaces[out->mNumFaces++];
1072
1073
6
            aiColor4D *vo = &out->mColors[0][out->mNumVertices];
1074
30
            for (unsigned int n = 0; n < f.mNumIndices; ++n, ++vo, ++out->mNumVertices) {
1075
24
                const MCol *col = &mesh->mcol[(i << 2) + n];
1076
1077
24
                vo->r = col->r;
1078
24
                vo->g = col->g;
1079
24
                vo->b = col->b;
1080
24
                vo->a = col->a;
1081
24
            }
1082
6
            for (unsigned int n = f.mNumIndices; n < 4; ++n)
1083
0
                ;
1084
6
        }
1085
1086
1
        for (int i = 0; i < mesh->totpoly; ++i) {
1087
0
            const MPoly &v = mesh->mpoly[i];
1088
0
            aiMesh *const out = temp[mat_num_to_mesh_idx[v.mat_nr]];
1089
0
            const aiFace &f = out->mFaces[out->mNumFaces++];
1090
1091
0
            aiColor4D *vo = &out->mColors[0][out->mNumVertices];
1092
0
            const ai_real scaleZeroToOne = 1.f / 255.f;
1093
0
            for (unsigned int j = 0; j < f.mNumIndices; ++j, ++vo, ++out->mNumVertices) {
1094
0
                const MLoopCol &col = mesh->mloopcol[v.loopstart + j];
1095
0
                vo->r = ai_real(col.r) * scaleZeroToOne;
1096
0
                vo->g = ai_real(col.g) * scaleZeroToOne;
1097
0
                vo->b = ai_real(col.b) * scaleZeroToOne;
1098
0
                vo->a = ai_real(col.a) * scaleZeroToOne;
1099
0
            }
1100
0
        }
1101
1
    }
1102
1103
32
    return;
1104
32
}
1105
1106
// ------------------------------------------------------------------------------------------------
1107
20
aiCamera *BlenderImporter::ConvertCamera(const Scene & /*in*/, const Object *obj, const Camera *cam, ConversionData & /*conv_data*/) {
1108
20
    std::unique_ptr<aiCamera> out(new aiCamera());
1109
20
    out->mName = obj->id.name + 2;
1110
20
    out->mPosition = aiVector3D(0.f, 0.f, 0.f);
1111
20
    out->mUp = aiVector3D(0.f, 1.f, 0.f);
1112
20
    out->mLookAt = aiVector3D(0.f, 0.f, -1.f);
1113
20
    if (cam->sensor_x && cam->lens) {
1114
8
        out->mHorizontalFOV = 2.f * std::atan2(cam->sensor_x, 2.f * cam->lens);
1115
8
    }
1116
20
    out->mClipPlaneNear = cam->clipsta;
1117
20
    out->mClipPlaneFar = cam->clipend;
1118
1119
20
    return out.release();
1120
20
}
1121
1122
// ------------------------------------------------------------------------------------------------
1123
25
aiLight *BlenderImporter::ConvertLight(const Scene & /*in*/, const Object *obj, const Lamp *lamp, ConversionData & /*conv_data*/) {
1124
25
    std::unique_ptr<aiLight> out(new aiLight());
1125
25
    out->mName = obj->id.name + 2;
1126
1127
25
    switch (lamp->type) {
1128
23
    case Lamp::Type_Local:
1129
23
        out->mType = aiLightSource_POINT;
1130
23
        break;
1131
0
    case Lamp::Type_Spot:
1132
0
        out->mType = aiLightSource_SPOT;
1133
1134
        // blender orients directional lights as facing toward -z
1135
0
        out->mDirection = aiVector3D(0.f, 0.f, -1.f);
1136
0
        out->mUp = aiVector3D(0.f, 1.f, 0.f);
1137
1138
0
        out->mAngleInnerCone = lamp->spotsize * (1.0f - lamp->spotblend);
1139
0
        out->mAngleOuterCone = lamp->spotsize;
1140
0
        break;
1141
0
    case Lamp::Type_Sun:
1142
0
        out->mType = aiLightSource_DIRECTIONAL;
1143
1144
        // blender orients directional lights as facing toward -z
1145
0
        out->mDirection = aiVector3D(0.f, 0.f, -1.f);
1146
0
        out->mUp = aiVector3D(0.f, 1.f, 0.f);
1147
0
        break;
1148
1149
2
    case Lamp::Type_Area:
1150
2
        out->mType = aiLightSource_AREA;
1151
1152
2
        if (lamp->area_shape == 0) {
1153
1
            out->mSize = aiVector2D(lamp->area_size, lamp->area_size);
1154
1
        } else {
1155
1
            out->mSize = aiVector2D(lamp->area_size, lamp->area_sizey);
1156
1
        }
1157
1158
        // blender orients directional lights as facing toward -z
1159
2
        out->mDirection = aiVector3D(0.f, 0.f, -1.f);
1160
2
        out->mUp = aiVector3D(0.f, 1.f, 0.f);
1161
2
        break;
1162
1163
0
    default:
1164
0
        break;
1165
25
    }
1166
1167
25
    out->mColorAmbient = aiColor3D(lamp->r, lamp->g, lamp->b) * lamp->energy;
1168
25
    out->mColorSpecular = aiColor3D(lamp->r, lamp->g, lamp->b) * lamp->energy;
1169
25
    out->mColorDiffuse = aiColor3D(lamp->r, lamp->g, lamp->b) * lamp->energy;
1170
1171
    // If default values are supplied, compute the coefficients from light's max distance
1172
    // Read this: https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/
1173
    //
1174
25
    if (lamp->constant_coefficient == 1.0f && lamp->linear_coefficient == 0.0f && lamp->quadratic_coefficient == 0.0f && lamp->dist > 0.0f) {
1175
0
        out->mAttenuationConstant = 1.0f;
1176
0
        out->mAttenuationLinear = 2.0f / lamp->dist;
1177
0
        out->mAttenuationQuadratic = 1.0f / (lamp->dist * lamp->dist);
1178
25
    } else {
1179
25
        out->mAttenuationConstant = lamp->constant_coefficient;
1180
25
        out->mAttenuationLinear = lamp->linear_coefficient;
1181
25
        out->mAttenuationQuadratic = lamp->quadratic_coefficient;
1182
25
    }
1183
1184
25
    return out.release();
1185
25
}
1186
1187
// ------------------------------------------------------------------------------------------------
1188
77
aiNode *BlenderImporter::ConvertNode(const Scene &in, const Object *obj, ConversionData &conv_data, const aiMatrix4x4 &parentTransform) {
1189
77
    std::deque<const Object *> children;
1190
120
    for (ObjectSet::iterator it = conv_data.objects.begin(); it != conv_data.objects.end();) {
1191
43
        const Object *object = *it;
1192
43
        if (object->parent == obj) {
1193
6
            children.push_back(object);
1194
1195
6
            conv_data.objects.erase(it++);
1196
6
            continue;
1197
6
        }
1198
37
        ++it;
1199
37
    }
1200
1201
77
    std::unique_ptr<aiNode> node(new aiNode(obj->id.name + 2)); // skip over the name prefix 'OB'
1202
77
    if (obj->data) {
1203
77
        switch (obj->type) {
1204
0
        case Object ::Type_EMPTY:
1205
0
            break; // do nothing
1206
1207
            // supported object types
1208
32
        case Object ::Type_MESH: {
1209
32
            const size_t old = conv_data.meshes->size();
1210
1211
32
            CheckActualType(obj->data.get(), "Mesh");
1212
32
            ConvertMesh(in, obj, static_cast<const Mesh *>(obj->data.get()), conv_data, conv_data.meshes);
1213
1214
32
            if (conv_data.meshes->size() > old) {
1215
32
                node->mMeshes = new unsigned int[node->mNumMeshes = static_cast<unsigned int>(conv_data.meshes->size() - old)];
1216
64
                for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
1217
32
                    node->mMeshes[i] = static_cast<unsigned int>(i + old);
1218
32
                }
1219
32
            }
1220
32
        } break;
1221
25
        case Object ::Type_LAMP: {
1222
25
            CheckActualType(obj->data.get(), "Lamp");
1223
25
            aiLight *mesh = ConvertLight(in, obj, static_cast<const Lamp *>(obj->data.get()), conv_data);
1224
1225
25
            if (mesh) {
1226
25
                conv_data.lights->push_back(mesh);
1227
25
            }
1228
25
        } break;
1229
20
        case Object ::Type_CAMERA: {
1230
20
            CheckActualType(obj->data.get(), "Camera");
1231
20
            aiCamera *mesh = ConvertCamera(in, obj, static_cast<const Camera *>(obj->data.get()), conv_data);
1232
1233
20
            if (mesh) {
1234
20
                conv_data.cameras->push_back(mesh);
1235
20
            }
1236
20
        } break;
1237
1238
            // unsupported object types / log, but do not break
1239
0
        case Object ::Type_CURVE:
1240
0
            NotSupportedObjectType(obj, "Curve");
1241
0
            break;
1242
0
        case Object ::Type_SURF:
1243
0
            NotSupportedObjectType(obj, "Surface");
1244
0
            break;
1245
0
        case Object ::Type_FONT:
1246
0
            NotSupportedObjectType(obj, "Font");
1247
0
            break;
1248
0
        case Object ::Type_MBALL:
1249
0
            NotSupportedObjectType(obj, "MetaBall");
1250
0
            break;
1251
0
        case Object ::Type_WAVE:
1252
0
            NotSupportedObjectType(obj, "Wave");
1253
0
            break;
1254
0
        case Object ::Type_LATTICE:
1255
0
            NotSupportedObjectType(obj, "Lattice");
1256
0
            break;
1257
1258
            // invalid or unknown type
1259
0
        default:
1260
0
            break;
1261
77
        }
1262
77
    }
1263
1264
385
    for (unsigned int x = 0; x < 4; ++x) {
1265
1.54k
        for (unsigned int y = 0; y < 4; ++y) {
1266
1.23k
            node->mTransformation[y][x] = obj->obmat[x][y];
1267
1.23k
        }
1268
308
    }
1269
1270
77
    aiMatrix4x4 m = parentTransform;
1271
77
    m = m.Inverse();
1272
1273
77
    node->mTransformation = m * node->mTransformation;
1274
1275
77
    if (children.size()) {
1276
4
        node->mNumChildren = static_cast<unsigned int>(children.size());
1277
4
        aiNode **nd = node->mChildren = new aiNode *[node->mNumChildren]();
1278
6
        for (const Object *nobj : children) {
1279
6
            *nd = ConvertNode(in, nobj, conv_data, node->mTransformation * parentTransform);
1280
6
            (*nd++)->mParent = node.get();
1281
6
        }
1282
4
    }
1283
1284
    // apply modifiers
1285
77
    modifier_cache->ApplyModifiers(*node, conv_data, in, *obj);
1286
1287
77
    return node.release();
1288
77
}
1289
1290
212
BlenderImporter::StreamOrError BlenderImporter::ParseMagicToken(const std::string &pFile, IOSystem *pIOHandler) const {
1291
212
    std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
1292
212
    if (stream == nullptr) {
1293
0
        return {{}, {}, "Could not open file for reading"};
1294
0
    }
1295
1296
212
    char magic[8] = { 0 };
1297
212
    stream->Read(magic, 7, 1);
1298
212
    if (strcmp(magic, Token) == 0) {
1299
44
        return {stream, {}, {}};
1300
44
    }
1301
1302
    // Check for presence of the gzip header. If yes, assume it is a
1303
    // compressed blend file and try uncompressing it, else fail. This is to
1304
    // avoid uncompressing random files which our loader might end up with.
1305
#ifdef ASSIMP_BUILD_NO_COMPRESSED_BLEND
1306
    return {{}, {}, "BLENDER magic bytes are missing, is this file compressed (Assimp was built without decompression support)?"};
1307
#else
1308
168
    if (magic[0] != 0x1f || static_cast<uint8_t>(magic[1]) != 0x8b) {
1309
134
        return {{}, {}, "BLENDER magic bytes are missing, couldn't find GZIP header either"};
1310
134
    }
1311
1312
34
    LogDebug("Found no BLENDER magic word but a GZIP header, might be a compressed file");
1313
34
    if (magic[2] != 8) {
1314
0
        return {{}, {}, "Unsupported GZIP compression method"};
1315
0
    }
1316
1317
    // http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
1318
34
    stream->Seek(0L, aiOrigin_SET);
1319
34
    std::shared_ptr<StreamReaderLE> reader = std::shared_ptr<StreamReaderLE>(new StreamReaderLE(stream));
1320
1321
34
    size_t total = 0;
1322
34
    Compression compression;
1323
34
    auto uncompressed = std::make_shared<std::vector<char>>();
1324
34
    if (compression.open(Compression::Format::Binary, Compression::FlushMode::NoFlush, 16 + Compression::MaxWBits)) {
1325
34
        total = compression.decompress((unsigned char *)reader->GetPtr(), reader->GetRemainingSize(), *uncompressed);
1326
34
        compression.close();
1327
34
    }
1328
1329
    // replace the input stream with a memory stream
1330
34
    stream = std::make_shared<MemoryIOStream>(reinterpret_cast<uint8_t *>(uncompressed->data()), total);
1331
1332
    // .. and retry
1333
34
    stream->Read(magic, 7, 1);
1334
34
    if (strcmp(magic, Token) == 0) {
1335
8
        return {stream, uncompressed, {}};
1336
8
    }
1337
26
    return {{}, {}, "Found no BLENDER magic word in decompressed GZIP file"};
1338
34
#endif
1339
34
}
1340
1341
#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER