Coverage Report

Created: 2026-09-14 07:44

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
662
const char *LogFunctions<BlenderImporter>::Prefix() {
76
662
    return "BLEND: ";
77
662
}
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
146k
        modifier_cache(new BlenderModifierShowcase()) {
102
    // empty
103
146k
}
104
105
// ------------------------------------------------------------------------------------------------
106
// Destructor, private as well
107
146k
BlenderImporter::~BlenderImporter() {
108
146k
    delete modifier_cache;
109
146k
}
110
111
static constexpr char Token[] = "BLENDER";
112
113
// ------------------------------------------------------------------------------------------------
114
// Returns whether the class can handle the format of the given file.
115
7.59k
bool BlenderImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
116
7.59k
    return ParseMagicToken(pFile, pIOHandler).error.empty();
117
7.59k
}
118
119
// ------------------------------------------------------------------------------------------------
120
// Loader registry entry
121
148k
const aiImporterDesc *BlenderImporter::GetInfo() const {
122
148k
    return &blenderDesc;
123
148k
}
124
125
// ------------------------------------------------------------------------------------------------
126
// Setup configuration properties for the loader
127
2.54k
void BlenderImporter::SetupProperties(const Importer * /*pImp*/) {
128
    // nothing to be done for the moment
129
2.54k
}
130
131
// ------------------------------------------------------------------------------------------------
132
// Imports the given file into the given scene structure.
133
void BlenderImporter::InternReadFile(const std::string &pFile,
134
2.54k
        aiScene *pScene, IOSystem *pIOHandler) {
135
2.54k
    FileDatabase file;
136
2.54k
    StreamOrError streamOrError = ParseMagicToken(pFile, pIOHandler);
137
2.54k
    if (!streamOrError.error.empty()) {
138
0
        ThrowException(streamOrError.error);
139
0
    }
140
2.54k
    std::shared_ptr<IOStream> stream = std::move(streamOrError.stream);
141
142
2.54k
    char version[4] = { 0 };
143
2.54k
    file.i64bit = (stream->Read(version, 1, 1), version[0] == '-');
144
2.54k
    file.little = (stream->Read(version, 1, 1), version[0] == 'v');
145
146
2.54k
    stream->Read(version, 3, 1);
147
2.54k
    version[3] = '\0';
148
149
2.54k
    LogInfo("Blender version is ", version[0], ".", version + 1,
150
2.54k
            " (64bit: ", file.i64bit ? "true" : "false",
151
2.54k
            ", little endian: ", file.little ? "true" : "false", ")");
152
153
2.54k
    ParseBlendFile(file, std::move(stream));
154
155
2.54k
    Scene scene;
156
2.54k
    ExtractScene(scene, file);
157
158
2.54k
    ConvertBlendFile(pScene, scene, file);
159
2.54k
}
160
161
// ------------------------------------------------------------------------------------------------
162
2.41k
void BlenderImporter::ParseBlendFile(FileDatabase &out, std::shared_ptr<IOStream> stream) {
163
2.41k
    out.reader = std::make_shared<StreamReaderAny>(stream, out.little);
164
165
2.41k
    DNAParser dna_reader(out);
166
2.41k
    const DNA *dna = nullptr;
167
168
2.41k
    out.entries.reserve(128);
169
2.41k
    { // even small BLEND files tend to consist of many file blocks
170
2.41k
        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
1.89M
        while ((parser.Next(), 1)) {
174
1.89M
            const FileBlockHead &head = parser.GetCurrent();
175
176
1.89M
            if (head.id == "ENDB") {
177
2.05k
                break; // only valid end of the file
178
1.89M
            } else if (head.id == "DNA1") {
179
2.22k
                dna_reader.Parse();
180
2.22k
                dna = &dna_reader.GetDNA();
181
2.22k
                continue;
182
2.22k
            }
183
184
1.88M
            out.entries.push_back(head);
185
1.88M
        }
186
2.41k
    }
187
2.41k
    if (!dna) {
188
0
        ThrowException("SDNA not found");
189
0
    }
190
191
2.41k
    std::sort(out.entries.begin(), out.entries.end());
192
2.41k
}
193
194
// ------------------------------------------------------------------------------------------------
195
2.05k
void BlenderImporter::ExtractScene(Scene &out, const FileDatabase &file) {
196
2.05k
    const FileBlockHead *block = nullptr;
197
2.05k
    std::map<std::string, size_t>::const_iterator it = file.dna.indices.find("Scene");
198
2.05k
    if (it == file.dna.indices.end()) {
199
30
        ThrowException("There is no `Scene` structure record");
200
30
    }
201
202
2.05k
    const Structure &ss = file.dna.structures[(*it).second];
203
204
    // we need a scene somewhere to start with.
205
1.38M
    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
1.38M
        if (bl.dna_index == (*it).second) {
211
1.83k
            block = &bl;
212
1.83k
            break;
213
1.83k
        }
214
1.38M
    }
215
216
2.05k
    if (!block) {
217
190
        ThrowException("There is not a single `Scene` record to load");
218
190
    }
219
220
2.05k
    file.reader->SetCurrentPos(block->start);
221
2.05k
    ss.Convert(out, file);
222
223
2.05k
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
224
2.05k
    ASSIMP_LOG_INFO(
225
2.05k
            "(Stats) Fields read: ", file.stats().fields_read,
226
2.05k
            ", pointers resolved: ", file.stats().pointers_resolved,
227
2.05k
            ", cache hits: ", file.stats().cache_hits,
228
2.05k
            ", cached objects: ", file.stats().cached_objects);
229
2.05k
#endif
230
2.05k
}
231
232
// ------------------------------------------------------------------------------------------------
233
71
void BlenderImporter::ParseSubCollection(const Blender::Scene &in, aiNode *root, const std::shared_ptr<Collection>& collection, ConversionData &conv_data) {
234
235
71
    std::deque<Object *> root_objects;
236
    // Count number of objects
237
164
    for (std::shared_ptr<CollectionObject> cur = std::static_pointer_cast<CollectionObject>(collection->gobject.first); cur; cur = cur->next) {
238
93
        if (cur->ob) {
239
93
            root_objects.push_back(cur->ob);
240
93
        }
241
93
    }
242
71
    std::deque<Collection *> root_children;
243
    // Count number of child nodes
244
104
    for (std::shared_ptr<CollectionChild> cur = std::static_pointer_cast<CollectionChild>(collection->children.first); cur; cur = cur->next) {
245
33
        if (cur->collection) {
246
32
            root_children.push_back(cur->collection.get());
247
32
        }
248
33
    }
249
71
    root->mNumChildren = static_cast<unsigned int>(root_objects.size() + root_children.size());
250
71
    root->mChildren = new aiNode *[root->mNumChildren]();
251
252
160
    for (unsigned int i = 0; i < static_cast<unsigned int>(root_objects.size()); ++i) {
253
89
        root->mChildren[i] = ConvertNode(in, root_objects[i], conv_data, aiMatrix4x4());
254
89
        root->mChildren[i]->mParent = root;
255
89
    }
256
257
    // For each subcollection create a new node to represent it
258
71
    unsigned int iterator = static_cast<unsigned int>(root_objects.size());
259
104
    for (std::shared_ptr<CollectionChild> cur = std::static_pointer_cast<CollectionChild>(collection->children.first); cur; cur = cur->next) {
260
33
        if (cur->collection) {
261
32
            root->mChildren[iterator] = new aiNode(cur->collection->id.name + 2); // skip over the name prefix 'OB'
262
32
            root->mChildren[iterator]->mParent = root;
263
32
            ParseSubCollection(in, root->mChildren[iterator], cur->collection, conv_data);
264
32
        }
265
33
        iterator += 1;
266
33
    }
267
71
}
268
269
// ------------------------------------------------------------------------------------------------
270
1.01k
void BlenderImporter::ConvertBlendFile(aiScene *out, const Scene &in, const FileDatabase &file) {
271
1.01k
    ConversionData conv(file);
272
273
1.01k
    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
1.01k
    if (in.master_collection) {
277
39
        ParseSubCollection(in, root, in.master_collection, conv);
278
976
    } else {
279
976
        std::deque<const Object *> no_parents;
280
4.04k
        for (std::shared_ptr<Base> cur = std::static_pointer_cast<Base>(in.base.first); cur; cur = cur->next) {
281
3.07k
            if (cur->object) {
282
3.03k
                if (!cur->object->parent) {
283
2.68k
                    no_parents.push_back(cur->object.get());
284
2.68k
                } else {
285
351
                    conv.objects.insert(cur->object.get());
286
351
                }
287
3.03k
            }
288
3.07k
        }
289
3.72k
        for (std::shared_ptr<Base> cur = in.basact; cur; cur = cur->next) {
290
2.75k
            if (cur->object) {
291
2.71k
                if (cur->object->parent) {
292
351
                    conv.objects.insert(cur->object.get());
293
351
                }
294
2.71k
            }
295
2.75k
        }
296
297
976
        if (no_parents.empty()) {
298
130
            ThrowException("Expected at least one object with no parent");
299
130
        }
300
301
976
        root->mNumChildren = static_cast<unsigned int>(no_parents.size());
302
976
        root->mChildren = new aiNode *[root->mNumChildren]();
303
2.99k
        for (unsigned int i = 0; i < root->mNumChildren; ++i) {
304
2.01k
            root->mChildren[i] = ConvertNode(in, no_parents[i], conv, aiMatrix4x4());
305
2.01k
            root->mChildren[i]->mParent = root;
306
2.01k
        }
307
976
    }
308
309
1.01k
    BuildMaterials(conv);
310
311
1.01k
    if (conv.meshes->size()) {
312
511
        out->mMeshes = new aiMesh *[out->mNumMeshes = static_cast<unsigned int>(conv.meshes->size())];
313
511
        std::copy(conv.meshes->begin(), conv.meshes->end(), out->mMeshes);
314
511
        conv.meshes.dismiss();
315
511
    }
316
317
1.01k
    if (conv.lights->size()) {
318
521
        out->mLights = new aiLight *[out->mNumLights = static_cast<unsigned int>(conv.lights->size())];
319
521
        std::copy(conv.lights->begin(), conv.lights->end(), out->mLights);
320
521
        conv.lights.dismiss();
321
521
    }
322
323
1.01k
    if (conv.cameras->size()) {
324
524
        out->mCameras = new aiCamera *[out->mNumCameras = static_cast<unsigned int>(conv.cameras->size())];
325
524
        std::copy(conv.cameras->begin(), conv.cameras->end(), out->mCameras);
326
524
        conv.cameras.dismiss();
327
524
    }
328
329
1.01k
    if (conv.materials->size()) {
330
511
        out->mMaterials = new aiMaterial *[out->mNumMaterials = static_cast<unsigned int>(conv.materials->size())];
331
511
        std::copy(conv.materials->begin(), conv.materials->end(), out->mMaterials);
332
511
        conv.materials.dismiss();
333
511
    }
334
335
1.01k
    if (conv.textures->size()) {
336
3
        out->mTextures = new aiTexture *[out->mNumTextures = static_cast<unsigned int>(conv.textures->size())];
337
3
        std::copy(conv.textures->begin(), conv.textures->end(), out->mTextures);
338
3
        conv.textures.dismiss();
339
3
    }
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
1.01k
    if (!out->mNumMeshes) {
346
62
        out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
347
62
    }
348
1.01k
}
349
350
// ------------------------------------------------------------------------------------------------
351
153
void BlenderImporter::ResolveImage(aiMaterial *out, const Material *mat, const MTex *tex, const Image *img, ConversionData &conv_data) {
352
153
    (void)mat;
353
153
    (void)tex;
354
153
    (void)conv_data;
355
153
    aiString name;
356
357
    // check if the file contents are bundled with the BLEND file
358
153
    if (img->packedfile) {
359
3
        name.data[0] = '*';
360
3
        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
3
        conv_data.textures->push_back(new aiTexture());
363
3
        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
3
        const size_t nlen = strlen(img->name);
368
3
        const char *s = img->name + nlen, *e = s;
369
15
        while (s >= img->name && *s != '.') {
370
12
            --s;
371
12
        }
372
373
3
        curTex->achFormatHint[0] = s + 1 > e ? '\0' : (char)::tolower((unsigned char)s[1]);
374
3
        curTex->achFormatHint[1] = s + 2 > e ? '\0' : (char)::tolower((unsigned char)s[2]);
375
3
        curTex->achFormatHint[2] = s + 3 > e ? '\0' : (char)::tolower((unsigned char)s[3]);
376
3
        curTex->achFormatHint[3] = '\0';
377
378
        // tex->mHeight = 0;
379
3
        curTex->mWidth = img->packedfile->size;
380
3
        uint8_t *ch = new uint8_t[curTex->mWidth];
381
382
3
        conv_data.db.reader->SetCurrentPos(static_cast<size_t>(img->packedfile->data->val));
383
3
        conv_data.db.reader->CopyAndAdvance(ch, curTex->mWidth);
384
385
3
        curTex->pcData = reinterpret_cast<aiTexel *>(ch);
386
387
3
        LogInfo("Reading embedded texture, original file was ", img->name);
388
150
    } else {
389
150
        name = aiString(img->name);
390
150
    }
391
392
153
    aiTextureType texture_type = aiTextureType_UNKNOWN;
393
153
    MTex::MapType map_type = tex->mapto;
394
395
153
    if (map_type & MTex::MapType_COL)
396
144
        texture_type = aiTextureType_DIFFUSE;
397
9
    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
9
    } else if (map_type & MTex::MapType_COLSPEC)
405
0
        texture_type = aiTextureType_SPECULAR;
406
9
    else if (map_type & MTex::MapType_COLMIR)
407
0
        texture_type = aiTextureType_REFLECTION;
408
    //else if (map_type & MTex::MapType_REF)
409
9
    else if (map_type & MTex::MapType_SPEC)
410
0
        texture_type = aiTextureType_SHININESS;
411
9
    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
9
    else if (map_type & MTex::MapType_AMB)
418
8
        texture_type = aiTextureType_AMBIENT;
419
1
    else if (map_type & MTex::MapType_DISPLACE)
420
0
        texture_type = aiTextureType_DISPLACEMENT;
421
    //else if (map_type & MTex::MapType_WARP)
422
423
153
    out->AddProperty(&name, AI_MATKEY_TEXTURE(texture_type,
424
153
                                    conv_data.next_texture[texture_type]++));
425
153
}
426
427
// ------------------------------------------------------------------------------------------------
428
74
void BlenderImporter::AddSentinelTexture(aiMaterial *out, const Material *mat, const MTex *tex, ConversionData &conv_data) {
429
74
    (void)mat;
430
74
    (void)tex;
431
74
    (void)conv_data;
432
433
74
    aiString name;
434
74
    name.length = ai_snprintf(name.data, AI_MAXLEN, "Procedural,num=%i,type=%s", conv_data.sentinel_cnt++,
435
74
            GetTextureTypeDisplayString(tex->tex->type));
436
74
    out->AddProperty(&name, AI_MATKEY_TEXTURE_DIFFUSE(
437
74
                                    conv_data.next_texture[aiTextureType_DIFFUSE]++));
438
74
}
439
440
// ------------------------------------------------------------------------------------------------
441
415
void BlenderImporter::ResolveTexture(aiMaterial *out, const Material *mat, const MTex *tex, ConversionData &conv_data) {
442
415
    const Tex *rtex = tex->tex.get();
443
415
    if (!rtex || !rtex->type) {
444
187
        return;
445
187
    }
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
228
    const char *dispnam = "";
450
228
    switch (rtex->type) {
451
        // these are listed in blender's UI
452
3
    case Tex::Type_CLOUDS:
453
3
    case Tex::Type_WOOD:
454
3
    case Tex::Type_MARBLE:
455
3
    case Tex::Type_MAGIC:
456
3
    case Tex::Type_BLEND:
457
3
    case Tex::Type_STUCCI:
458
4
    case Tex::Type_NOISE:
459
4
    case Tex::Type_PLUGIN:
460
4
    case Tex::Type_MUSGRAVE:
461
74
    case Tex::Type_VORONOI:
462
74
    case Tex::Type_DISTNOISE:
463
74
    case Tex::Type_ENVMAP:
464
465
        // these do no appear in the UI, why?
466
74
    case Tex::Type_POINTDENSITY:
467
74
    case Tex::Type_VOXELDATA:
468
469
74
        LogWarn("Encountered a texture with an unsupported type: ", dispnam);
470
74
        AddSentinelTexture(out, mat, tex, conv_data);
471
74
        break;
472
473
154
    case Tex::Type_IMAGE:
474
154
        if (!rtex->ima) {
475
1
            LogError("A texture claims to be an Image, but no image reference is given");
476
1
            break;
477
1
        }
478
153
        ResolveImage(out, mat, tex, rtex->ima.get(), conv_data);
479
153
        break;
480
481
0
    default:
482
0
        ai_assert(false);
483
228
    };
484
228
}
485
486
// ------------------------------------------------------------------------------------------------
487
573
void BlenderImporter::BuildDefaultMaterial(Blender::ConversionData &conv_data) {
488
    // add a default material if necessary
489
573
    unsigned int index = static_cast<unsigned int>(-1);
490
902
    for (aiMesh *mesh : conv_data.meshes.get()) {
491
902
        if (mesh->mMaterialIndex == static_cast<unsigned int>(-1)) {
492
493
288
            if (index == static_cast<unsigned int>(-1)) {
494
                // Setup a default material.
495
158
                std::shared_ptr<Material> p(new Material());
496
158
                const size_t len = ::strlen(AI_DEFAULT_MATERIAL_NAME);
497
158
                ai_assert(len < sizeof(p->id.name) - 2);
498
158
                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
158
                p->r = p->g = p->b = 0.6f;
505
158
                p->specr = p->specg = p->specb = 0.6f;
506
158
                p->ambr = p->ambg = p->ambb = 0.0f;
507
158
                p->mirr = p->mirg = p->mirb = 0.0f;
508
158
                p->emit = 0.f;
509
158
                p->alpha = 0.f;
510
158
                p->har = 0;
511
512
158
                index = static_cast<unsigned int>(conv_data.materials_raw.size());
513
158
                conv_data.materials_raw.push_back(p);
514
158
                LogInfo("Adding default material");
515
158
            }
516
288
            mesh->mMaterialIndex = index;
517
288
        }
518
902
    }
519
573
}
520
521
586
void BlenderImporter::AddBlendParams(aiMaterial *result, const Material *source) {
522
586
    aiColor3D diffuseColor(source->r, source->g, source->b);
523
586
    result->AddProperty(&diffuseColor, 1, "$mat.blend.diffuse.color", 0, 0);
524
525
586
    float diffuseIntensity = source->ref;
526
586
    result->AddProperty(&diffuseIntensity, 1, "$mat.blend.diffuse.intensity", 0, 0);
527
528
586
    int diffuseShader = source->diff_shader;
529
586
    result->AddProperty(&diffuseShader, 1, "$mat.blend.diffuse.shader", 0, 0);
530
531
586
    int diffuseRamp = 0;
532
586
    result->AddProperty(&diffuseRamp, 1, "$mat.blend.diffuse.ramp", 0, 0);
533
534
586
    aiColor3D specularColor(source->specr, source->specg, source->specb);
535
586
    result->AddProperty(&specularColor, 1, "$mat.blend.specular.color", 0, 0);
536
537
586
    float specularIntensity = source->spec;
538
586
    result->AddProperty(&specularIntensity, 1, "$mat.blend.specular.intensity", 0, 0);
539
540
586
    int specularShader = source->spec_shader;
541
586
    result->AddProperty(&specularShader, 1, "$mat.blend.specular.shader", 0, 0);
542
543
586
    int specularRamp = 0;
544
586
    result->AddProperty(&specularRamp, 1, "$mat.blend.specular.ramp", 0, 0);
545
546
586
    int specularHardness = source->har;
547
586
    result->AddProperty(&specularHardness, 1, "$mat.blend.specular.hardness", 0, 0);
548
549
586
    int transparencyUse = source->mode & MA_TRANSPARENCY ? 1 : 0;
550
586
    result->AddProperty(&transparencyUse, 1, "$mat.blend.transparency.use", 0, 0);
551
552
586
    int transparencyMethod = source->mode & MA_RAYTRANSP ? 2 : (source->mode & MA_ZTRANSP ? 1 : 0);
553
586
    result->AddProperty(&transparencyMethod, 1, "$mat.blend.transparency.method", 0, 0);
554
555
586
    float transparencyAlpha = source->alpha;
556
586
    result->AddProperty(&transparencyAlpha, 1, "$mat.blend.transparency.alpha", 0, 0);
557
558
586
    float transparencySpecular = source->spectra;
559
586
    result->AddProperty(&transparencySpecular, 1, "$mat.blend.transparency.specular", 0, 0);
560
561
586
    float transparencyFresnel = source->fresnel_tra;
562
586
    result->AddProperty(&transparencyFresnel, 1, "$mat.blend.transparency.fresnel", 0, 0);
563
564
586
    float transparencyBlend = source->fresnel_tra_i;
565
586
    result->AddProperty(&transparencyBlend, 1, "$mat.blend.transparency.blend", 0, 0);
566
567
586
    float transparencyIor = source->ang;
568
586
    result->AddProperty(&transparencyIor, 1, "$mat.blend.transparency.ior", 0, 0);
569
570
586
    float transparencyFilter = source->filter;
571
586
    result->AddProperty(&transparencyFilter, 1, "$mat.blend.transparency.filter", 0, 0);
572
573
586
    float transparencyFalloff = source->tx_falloff;
574
586
    result->AddProperty(&transparencyFalloff, 1, "$mat.blend.transparency.falloff", 0, 0);
575
576
586
    float transparencyLimit = source->tx_limit;
577
586
    result->AddProperty(&transparencyLimit, 1, "$mat.blend.transparency.limit", 0, 0);
578
579
586
    int transparencyDepth = source->ray_depth_tra;
580
586
    result->AddProperty(&transparencyDepth, 1, "$mat.blend.transparency.depth", 0, 0);
581
582
586
    float transparencyGlossAmount = source->gloss_tra;
583
586
    result->AddProperty(&transparencyGlossAmount, 1, "$mat.blend.transparency.glossAmount", 0, 0);
584
585
586
    float transparencyGlossThreshold = source->adapt_thresh_tra;
586
586
    result->AddProperty(&transparencyGlossThreshold, 1, "$mat.blend.transparency.glossThreshold", 0, 0);
587
588
586
    int transparencyGlossSamples = source->samp_gloss_tra;
589
586
    result->AddProperty(&transparencyGlossSamples, 1, "$mat.blend.transparency.glossSamples", 0, 0);
590
591
586
    int mirrorUse = source->mode & MA_RAYMIRROR ? 1 : 0;
592
586
    result->AddProperty(&mirrorUse, 1, "$mat.blend.mirror.use", 0, 0);
593
594
586
    float mirrorReflectivity = source->ray_mirror;
595
586
    result->AddProperty(&mirrorReflectivity, 1, "$mat.blend.mirror.reflectivity", 0, 0);
596
597
586
    aiColor3D mirrorColor(source->mirr, source->mirg, source->mirb);
598
586
    result->AddProperty(&mirrorColor, 1, "$mat.blend.mirror.color", 0, 0);
599
600
586
    float mirrorFresnel = source->fresnel_mir;
601
586
    result->AddProperty(&mirrorFresnel, 1, "$mat.blend.mirror.fresnel", 0, 0);
602
603
586
    float mirrorBlend = source->fresnel_mir_i;
604
586
    result->AddProperty(&mirrorBlend, 1, "$mat.blend.mirror.blend", 0, 0);
605
606
586
    int mirrorDepth = source->ray_depth;
607
586
    result->AddProperty(&mirrorDepth, 1, "$mat.blend.mirror.depth", 0, 0);
608
609
586
    float mirrorMaxDist = source->dist_mir;
610
586
    result->AddProperty(&mirrorMaxDist, 1, "$mat.blend.mirror.maxDist", 0, 0);
611
612
586
    int mirrorFadeTo = source->fadeto_mir;
613
586
    result->AddProperty(&mirrorFadeTo, 1, "$mat.blend.mirror.fadeTo", 0, 0);
614
615
586
    float mirrorGlossAmount = source->gloss_mir;
616
586
    result->AddProperty(&mirrorGlossAmount, 1, "$mat.blend.mirror.glossAmount", 0, 0);
617
618
586
    float mirrorGlossThreshold = source->adapt_thresh_mir;
619
586
    result->AddProperty(&mirrorGlossThreshold, 1, "$mat.blend.mirror.glossThreshold", 0, 0);
620
621
586
    int mirrorGlossSamples = source->samp_gloss_mir;
622
586
    result->AddProperty(&mirrorGlossSamples, 1, "$mat.blend.mirror.glossSamples", 0, 0);
623
624
586
    float mirrorGlossAnisotropic = source->aniso_gloss_mir;
625
586
    result->AddProperty(&mirrorGlossAnisotropic, 1, "$mat.blend.mirror.glossAnisotropic", 0, 0);
626
586
}
627
628
573
void BlenderImporter::BuildMaterials(ConversionData &conv_data) {
629
573
    conv_data.materials->reserve(conv_data.materials_raw.size());
630
631
573
    BuildDefaultMaterial(conv_data);
632
633
586
    for (const std::shared_ptr<Material> &mat : conv_data.materials_raw) {
634
635
        // reset per material global counters
636
11.7k
        for (size_t i = 0; i < sizeof(conv_data.next_texture) / sizeof(conv_data.next_texture[0]); ++i) {
637
11.1k
            conv_data.next_texture[i] = 0;
638
11.1k
        }
639
640
586
        aiMaterial *mout = new aiMaterial();
641
586
        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
586
        aiString name = aiString(mat->id.name + 2); // skip over the name prefix 'MA'
646
586
        mout->AddProperty(&name, AI_MATKEY_NAME);
647
648
        // basic material colors
649
586
        aiColor3D col(mat->r, mat->g, mat->b);
650
586
        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
586
            mout->AddProperty(&col, 1, AI_MATKEY_COLOR_DIFFUSE);
655
656
586
            if (mat->emit) {
657
55
                aiColor3D emit_col(mat->emit * mat->r, mat->emit * mat->g, mat->emit * mat->b);
658
55
                mout->AddProperty(&emit_col, 1, AI_MATKEY_COLOR_EMISSIVE);
659
55
            }
660
586
        }
661
662
586
        col = aiColor3D(mat->specr, mat->specg, mat->specb);
663
586
        mout->AddProperty(&col, 1, AI_MATKEY_COLOR_SPECULAR);
664
665
        // is hardness/shininess set?
666
586
        if (mat->har) {
667
395
            const float har = mat->har;
668
395
            mout->AddProperty(&har, 1, AI_MATKEY_SHININESS);
669
395
        }
670
671
586
        col = aiColor3D(mat->ambr, mat->ambg, mat->ambb);
672
586
        mout->AddProperty(&col, 1, AI_MATKEY_COLOR_AMBIENT);
673
674
        // is mirror enabled?
675
586
        if (mat->mode & MA_RAYMIRROR) {
676
4
            const float ray_mirror = mat->ray_mirror;
677
4
            mout->AddProperty(&ray_mirror, 1, AI_MATKEY_REFLECTIVITY);
678
4
        }
679
680
586
        col = aiColor3D(mat->mirr, mat->mirg, mat->mirb);
681
586
        mout->AddProperty(&col, 1, AI_MATKEY_COLOR_REFLECTIVE);
682
683
11.1k
        for (size_t i = 0; i < sizeof(mat->mtex) / sizeof(mat->mtex[0]); ++i) {
684
10.5k
            if (!mat->mtex[i]) {
685
10.1k
                continue;
686
10.1k
            }
687
688
415
            ResolveTexture(mout, mat.get(), mat->mtex[i].get(), conv_data);
689
415
        }
690
691
586
        AddBlendParams(mout, mat.get());
692
586
    }
693
573
}
694
695
// ------------------------------------------------------------------------------------------------
696
2.09k
void BlenderImporter::CheckActualType(const ElemBase *dt, const char *check) {
697
2.09k
    ai_assert(dt);
698
2.09k
    if (strcmp(dt->dna_type, check)) {
699
64
        ThrowException("Expected object at ", std::hex, dt, " to be of type `", check,
700
64
                "`, but it claims to be a `", dt->dna_type, "`instead");
701
64
    }
702
2.09k
}
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
975
        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
975
    typedef std::pair<const int, size_t> MyPair;
722
975
    if ((!mesh->totface && !mesh->totloop) || !mesh->totvert) {
723
6
        return;
724
6
    }
725
726
    // some sanity checks
727
969
    if (static_cast<size_t>(mesh->totface) > mesh->mface.size()) {
728
6
        ThrowException("Number of faces is larger than the corresponding array");
729
6
    }
730
731
969
    if (static_cast<size_t>(mesh->totvert) > mesh->mvert.size()) {
732
5
        ThrowException("Number of vertices is larger than the corresponding array");
733
5
    }
734
735
969
    if (static_cast<size_t>(mesh->totloop) > mesh->mloop.size()) {
736
2
        ThrowException("Number of vertices is larger than the corresponding array");
737
2
    }
738
739
    // collect per-submesh numbers
740
969
    std::map<int, size_t> per_mat;
741
969
    std::map<int, size_t> per_mat_verts;
742
706k
    for (int i = 0; i < mesh->totface; ++i) {
743
744
705k
        const MFace &mf = mesh->mface[i];
745
705k
        per_mat[mf.mat_nr]++;
746
705k
        per_mat_verts[mf.mat_nr] += mf.v4 ? 4 : 3;
747
705k
    }
748
749
5.81k
    for (int i = 0; i < mesh->totpoly; ++i) {
750
4.85k
        const MPoly &mp = mesh->mpoly[i];
751
4.85k
        per_mat[mp.mat_nr]++;
752
4.85k
        per_mat_verts[mp.mat_nr] += mp.totloop;
753
4.85k
    }
754
755
    // ... and allocate the corresponding meshes
756
969
    const size_t old = temp->size();
757
969
    temp->reserve(temp->size() + per_mat.size());
758
759
969
    std::map<size_t, size_t> mat_num_to_mesh_idx;
760
3.97k
    for (MyPair &it : per_mat) {
761
762
3.97k
        mat_num_to_mesh_idx[it.first] = temp->size();
763
3.97k
        temp->push_back(new aiMesh());
764
765
3.97k
        aiMesh *out = temp->back();
766
3.97k
        out->mVertices = new aiVector3D[per_mat_verts[it.first]];
767
3.97k
        out->mNormals = new aiVector3D[per_mat_verts[it.first]];
768
769
        //out->mNumFaces = 0
770
        //out->mNumVertices = 0
771
3.97k
        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
3.97k
        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
3.97k
        if (mesh->mat) {
782
783
845
            if (static_cast<size_t>(it.first) >= mesh->mat.size()) {
784
56
                ThrowException("Material index is out of range");
785
56
            }
786
787
845
            std::shared_ptr<Material> mat = mesh->mat[it.first];
788
845
            const std::deque<std::shared_ptr<Material>>::iterator has = std::find(
789
845
                    conv_data.materials_raw.begin(),
790
845
                    conv_data.materials_raw.end(), mat);
791
792
845
            if (has != conv_data.materials_raw.end()) {
793
165
                out->mMaterialIndex = static_cast<unsigned int>(std::distance(conv_data.materials_raw.begin(), has));
794
680
            } else {
795
680
                out->mMaterialIndex = static_cast<unsigned int>(conv_data.materials_raw.size());
796
680
                conv_data.materials_raw.push_back(mat);
797
680
            }
798
845
        } else
799
3.13k
            out->mMaterialIndex = static_cast<unsigned int>(-1);
800
3.97k
    }
801
802
534k
    for (int i = 0; i < mesh->totface; ++i) {
803
804
533k
        const MFace &mf = mesh->mface[i];
805
806
533k
        aiMesh *const out = temp[mat_num_to_mesh_idx[mf.mat_nr]];
807
533k
        aiFace &f = out->mFaces[out->mNumFaces++];
808
809
533k
        f.mIndices = new unsigned int[f.mNumIndices = mf.v4 ? 4 : 3];
810
533k
        aiVector3D *vo = out->mVertices + out->mNumVertices;
811
533k
        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
533k
        if (mf.v1 >= mesh->totvert) {
820
43
            ThrowException("Vertex index v1 out of range");
821
43
        }
822
533k
        const MVert *v = &mesh->mvert[mf.v1];
823
533k
        vo->x = v->co[0];
824
533k
        vo->y = v->co[1];
825
533k
        vo->z = v->co[2];
826
533k
        vn->x = v->no[0];
827
533k
        vn->y = v->no[1];
828
533k
        vn->z = v->no[2];
829
533k
        f.mIndices[0] = out->mNumVertices++;
830
533k
        ++vo;
831
533k
        ++vn;
832
833
        //  if (f.mNumIndices >= 2) {
834
533k
        if (mf.v2 >= mesh->totvert) {
835
31
            ThrowException("Vertex index v2 out of range");
836
31
        }
837
533k
        v = &mesh->mvert[mf.v2];
838
533k
        vo->x = v->co[0];
839
533k
        vo->y = v->co[1];
840
533k
        vo->z = v->co[2];
841
533k
        vn->x = v->no[0];
842
533k
        vn->y = v->no[1];
843
533k
        vn->z = v->no[2];
844
533k
        f.mIndices[1] = out->mNumVertices++;
845
533k
        ++vo;
846
533k
        ++vn;
847
848
533k
        if (mf.v3 >= mesh->totvert) {
849
37
            ThrowException("Vertex index v3 out of range");
850
37
        }
851
        //  if (f.mNumIndices >= 3) {
852
533k
        v = &mesh->mvert[mf.v3];
853
533k
        vo->x = v->co[0];
854
533k
        vo->y = v->co[1];
855
533k
        vo->z = v->co[2];
856
533k
        vn->x = v->no[0];
857
533k
        vn->y = v->no[1];
858
533k
        vn->z = v->no[2];
859
533k
        f.mIndices[2] = out->mNumVertices++;
860
533k
        ++vo;
861
533k
        ++vn;
862
863
533k
        if (mf.v4 >= mesh->totvert) {
864
51
            ThrowException("Vertex index v4 out of range");
865
51
        }
866
        //  if (f.mNumIndices >= 4) {
867
533k
        if (mf.v4) {
868
511k
            v = &mesh->mvert[mf.v4];
869
511k
            vo->x = v->co[0];
870
511k
            vo->y = v->co[1];
871
511k
            vo->z = v->co[2];
872
511k
            vn->x = v->no[0];
873
511k
            vn->y = v->no[1];
874
511k
            vn->z = v->no[2];
875
511k
            f.mIndices[3] = out->mNumVertices++;
876
511k
            ++vo;
877
511k
            ++vn;
878
879
511k
            out->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
880
511k
        } else
881
22.2k
            out->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
882
883
        //  }
884
        //  }
885
        //  }
886
533k
    }
887
888
4.42k
    for (int i = 0; i < mesh->totpoly; ++i) {
889
890
3.45k
        const MPoly &mf = mesh->mpoly[i];
891
892
3.45k
        aiMesh *const out = temp[mat_num_to_mesh_idx[mf.mat_nr]];
893
3.45k
        aiFace &f = out->mFaces[out->mNumFaces++];
894
895
3.45k
        f.mIndices = new unsigned int[f.mNumIndices = mf.totloop];
896
3.45k
        aiVector3D *vo = out->mVertices + out->mNumVertices;
897
3.45k
        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
16.7k
        for (int j = 0; j < mf.totloop; ++j) {
905
13.2k
            const MLoop &loop = mesh->mloop[mf.loopstart + j];
906
907
13.2k
            if (loop.v >= mesh->totvert) {
908
15
                ThrowException("Vertex index out of range");
909
15
            }
910
911
13.2k
            const MVert &v = mesh->mvert[loop.v];
912
913
13.2k
            vo->x = v.co[0];
914
13.2k
            vo->y = v.co[1];
915
13.2k
            vo->z = v.co[2];
916
13.2k
            vn->x = v.no[0];
917
13.2k
            vn->y = v.no[1];
918
13.2k
            vn->z = v.no[2];
919
13.2k
            f.mIndices[j] = out->mNumVertices++;
920
921
13.2k
            ++vo;
922
13.2k
            ++vn;
923
13.2k
        }
924
3.45k
        if (mf.totloop == 3) {
925
494
            out->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
926
2.96k
        } else {
927
2.96k
            out->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
928
2.96k
        }
929
3.45k
    }
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
969
    typedef std::map<uint32_t, const MLoopUV *> TextureUVMapping;
936
    // key is material number, value is the TextureUVMapping for the material
937
969
    typedef std::map<uint32_t, TextureUVMapping> MaterialTextureUVMappings;
938
969
    MaterialTextureUVMappings matTexUvMappings;
939
969
    const uint32_t maxMat = static_cast<uint32_t>(mesh->mat.size());
940
1.57k
    for (uint32_t m = 0; m < maxMat; ++m) {
941
        // get material by index
942
607
        const std::shared_ptr<Material> pMat = mesh->mat[m];
943
607
        TextureUVMapping texuv;
944
607
        const uint32_t maxTex = sizeof(pMat->mtex) / sizeof(pMat->mtex[0]);
945
11.5k
        for (uint32_t t = 0; t < maxTex; ++t) {
946
10.9k
            if (pMat->mtex[t] && pMat->mtex[t]->uvname[0]) {
947
                // get the CustomData layer for given uvname and correct type
948
28
                const ElemBase *pLoop = getCustomDataLayerData(mesh->ldata, CD_MLOOPUV, pMat->mtex[t]->uvname);
949
28
                if (pLoop) {
950
21
                    texuv.insert(std::make_pair(t, dynamic_cast<const MLoopUV *>(pLoop)));
951
21
                }
952
28
            }
953
10.9k
        }
954
607
        if (texuv.size()) {
955
11
            matTexUvMappings.insert(std::make_pair(m, texuv));
956
11
        }
957
607
    }
958
959
    // collect texture coordinates, they're stored in a separate per-face buffer
960
969
    if (mesh->mtface || mesh->mloopuv) {
961
65
        if (mesh->totface > static_cast<int>(mesh->mtface.size())) {
962
2
            ThrowException("Number of UV faces is larger than the corresponding UV face array (#1)");
963
2
        }
964
128
        for (std::vector<aiMesh *>::iterator it = temp->begin() + old; it != temp->end(); ++it) {
965
63
            ai_assert(0 != (*it)->mNumVertices);
966
63
            ai_assert(0 != (*it)->mNumFaces);
967
63
            const auto itMatTexUvMapping = matTexUvMappings.find((*it)->mMaterialIndex);
968
63
            if (itMatTexUvMapping == matTexUvMappings.end()) {
969
                // default behaviour like before
970
52
                (*it)->mTextureCoords[0] = new aiVector3D[(*it)->mNumVertices];
971
52
            } else {
972
                // create texture coords for every mapped tex
973
32
                for (uint32_t i = 0; i < itMatTexUvMapping->second.size(); ++i) {
974
21
                    (*it)->mTextureCoords[i] = new aiVector3D[(*it)->mNumVertices];
975
21
                }
976
11
            }
977
63
            (*it)->mNumFaces = (*it)->mNumVertices = 0;
978
63
        }
979
980
97
        for (int i = 0; i < mesh->totface; ++i) {
981
32
            const MTFace *v = &mesh->mtface[i];
982
983
32
            aiMesh *const out = temp[mat_num_to_mesh_idx[mesh->mface[i].mat_nr]];
984
32
            const aiFace &f = out->mFaces[out->mNumFaces++];
985
986
32
            aiVector3D *vo = &out->mTextureCoords[0][out->mNumVertices];
987
160
            for (unsigned int j = 0; j < f.mNumIndices; ++j, ++vo, ++out->mNumVertices) {
988
128
                vo->x = v->uv[j][0];
989
128
                vo->y = v->uv[j][1];
990
128
            }
991
32
        }
992
993
643
        for (int i = 0; i < mesh->totpoly; ++i) {
994
578
            const MPoly &v = mesh->mpoly[i];
995
578
            aiMesh *const out = temp[mat_num_to_mesh_idx[v.mat_nr]];
996
578
            const aiFace &f = out->mFaces[out->mNumFaces++];
997
998
578
            const auto itMatTexUvMapping = matTexUvMappings.find(v.mat_nr);
999
578
            if (itMatTexUvMapping == matTexUvMappings.end()) {
1000
                // old behavior
1001
567
                aiVector3D *vo = &out->mTextureCoords[0][out->mNumVertices];
1002
2.43k
                for (unsigned int j = 0; j < f.mNumIndices; ++j, ++vo, ++out->mNumVertices) {
1003
1.87k
                    const MLoopUV &uv = mesh->mloopuv[v.loopstart + j];
1004
1.87k
                    vo->x = uv.uv[0];
1005
1.87k
                    vo->y = uv.uv[1];
1006
1.87k
                }
1007
567
            } else {
1008
                // create textureCoords for every mapped tex
1009
32
                for (uint32_t m = 0; m < itMatTexUvMapping->second.size(); ++m) {
1010
21
                    const MLoopUV *tm = itMatTexUvMapping->second[m];
1011
21
                    aiVector3D *vo = &out->mTextureCoords[m][out->mNumVertices];
1012
21
                    uint32_t j = 0;
1013
105
                    for (; j < f.mNumIndices; ++j, ++vo) {
1014
84
                        const MLoopUV &uv = tm[v.loopstart + j];
1015
84
                        vo->x = uv.uv[0];
1016
84
                        vo->y = uv.uv[1];
1017
84
                    }
1018
                    // only update written mNumVertices in last loop
1019
                    // TODO why must the numVertices be incremented here?
1020
21
                    if (m == itMatTexUvMapping->second.size() - 1) {
1021
11
                        out->mNumVertices += j;
1022
11
                    }
1023
21
                }
1024
11
            }
1025
578
        }
1026
65
    }
1027
1028
    // collect texture coordinates, old-style (marked as deprecated in current blender sources)
1029
969
    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
969
    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
969
    return;
1104
975
}
1105
1106
// ------------------------------------------------------------------------------------------------
1107
525
aiCamera *BlenderImporter::ConvertCamera(const Scene & /*in*/, const Object *obj, const Camera *cam, ConversionData & /*conv_data*/) {
1108
525
    std::unique_ptr<aiCamera> out(new aiCamera());
1109
525
    out->mName = obj->id.name + 2;
1110
525
    out->mPosition = aiVector3D(0.f, 0.f, 0.f);
1111
525
    out->mUp = aiVector3D(0.f, 1.f, 0.f);
1112
525
    out->mLookAt = aiVector3D(0.f, 0.f, -1.f);
1113
525
    if (cam->sensor_x && cam->lens) {
1114
120
        out->mHorizontalFOV = 2.f * std::atan2(cam->sensor_x, 2.f * cam->lens);
1115
120
    }
1116
525
    out->mClipPlaneNear = cam->clipsta;
1117
525
    out->mClipPlaneFar = cam->clipend;
1118
1119
525
    return out.release();
1120
525
}
1121
1122
// ------------------------------------------------------------------------------------------------
1123
528
aiLight *BlenderImporter::ConvertLight(const Scene & /*in*/, const Object *obj, const Lamp *lamp, ConversionData & /*conv_data*/) {
1124
528
    std::unique_ptr<aiLight> out(new aiLight());
1125
528
    out->mName = obj->id.name + 2;
1126
1127
528
    switch (lamp->type) {
1128
526
    case Lamp::Type_Local:
1129
526
        out->mType = aiLightSource_POINT;
1130
526
        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
528
    }
1166
1167
528
    out->mColorAmbient = aiColor3D(lamp->r, lamp->g, lamp->b) * lamp->energy;
1168
528
    out->mColorSpecular = aiColor3D(lamp->r, lamp->g, lamp->b) * lamp->energy;
1169
528
    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
528
    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
528
    } else {
1179
528
        out->mAttenuationConstant = lamp->constant_coefficient;
1180
528
        out->mAttenuationLinear = lamp->linear_coefficient;
1181
528
        out->mAttenuationQuadratic = lamp->quadratic_coefficient;
1182
528
    }
1183
1184
528
    return out.release();
1185
528
}
1186
1187
// ------------------------------------------------------------------------------------------------
1188
2.26k
aiNode *BlenderImporter::ConvertNode(const Scene &in, const Object *obj, ConversionData &conv_data, const aiMatrix4x4 &parentTransform) {
1189
2.26k
    std::deque<const Object *> children;
1190
3.56k
    for (ObjectSet::iterator it = conv_data.objects.begin(); it != conv_data.objects.end();) {
1191
1.29k
        const Object *object = *it;
1192
1.29k
        if (object->parent == obj) {
1193
184
            children.push_back(object);
1194
1195
184
            conv_data.objects.erase(it++);
1196
184
            continue;
1197
184
        }
1198
1.11k
        ++it;
1199
1.11k
    }
1200
1201
2.26k
    std::unique_ptr<aiNode> node(new aiNode(obj->id.name + 2)); // skip over the name prefix 'OB'
1202
2.26k
    if (obj->data) {
1203
2.13k
        switch (obj->type) {
1204
33
        case Object ::Type_EMPTY:
1205
33
            break; // do nothing
1206
1207
            // supported object types
1208
1.03k
        case Object ::Type_MESH: {
1209
1.03k
            const size_t old = conv_data.meshes->size();
1210
1211
1.03k
            CheckActualType(obj->data.get(), "Mesh");
1212
1.03k
            ConvertMesh(in, obj, static_cast<const Mesh *>(obj->data.get()), conv_data, conv_data.meshes);
1213
1214
1.03k
            if (conv_data.meshes->size() > old) {
1215
721
                node->mMeshes = new unsigned int[node->mNumMeshes = static_cast<unsigned int>(conv_data.meshes->size() - old)];
1216
1.62k
                for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
1217
902
                    node->mMeshes[i] = static_cast<unsigned int>(i + old);
1218
902
                }
1219
721
            }
1220
1.03k
        } break;
1221
533
        case Object ::Type_LAMP: {
1222
533
            CheckActualType(obj->data.get(), "Lamp");
1223
533
            aiLight *mesh = ConvertLight(in, obj, static_cast<const Lamp *>(obj->data.get()), conv_data);
1224
1225
533
            if (mesh) {
1226
528
                conv_data.lights->push_back(mesh);
1227
528
            }
1228
533
        } break;
1229
526
        case Object ::Type_CAMERA: {
1230
526
            CheckActualType(obj->data.get(), "Camera");
1231
526
            aiCamera *mesh = ConvertCamera(in, obj, static_cast<const Camera *>(obj->data.get()), conv_data);
1232
1233
526
            if (mesh) {
1234
525
                conv_data.cameras->push_back(mesh);
1235
525
            }
1236
526
        } 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
6
        default:
1260
6
            break;
1261
2.13k
        }
1262
2.13k
    }
1263
1264
9.76k
    for (unsigned int x = 0; x < 4; ++x) {
1265
39.0k
        for (unsigned int y = 0; y < 4; ++y) {
1266
31.2k
            node->mTransformation[y][x] = obj->obmat[x][y];
1267
31.2k
        }
1268
7.81k
    }
1269
1270
1.95k
    aiMatrix4x4 m = parentTransform;
1271
1.95k
    m = m.Inverse();
1272
1273
1.95k
    node->mTransformation = m * node->mTransformation;
1274
1275
1.95k
    if (children.size()) {
1276
114
        node->mNumChildren = static_cast<unsigned int>(children.size());
1277
114
        aiNode **nd = node->mChildren = new aiNode *[node->mNumChildren]();
1278
162
        for (const Object *nobj : children) {
1279
162
            *nd = ConvertNode(in, nobj, conv_data, node->mTransformation * parentTransform);
1280
162
            (*nd++)->mParent = node.get();
1281
162
        }
1282
114
    }
1283
1284
    // apply modifiers
1285
1.95k
    modifier_cache->ApplyModifiers(*node, conv_data, in, *obj);
1286
1287
1.95k
    return node.release();
1288
2.26k
}
1289
1290
10.1k
BlenderImporter::StreamOrError BlenderImporter::ParseMagicToken(const std::string &pFile, IOSystem *pIOHandler) const {
1291
10.1k
    std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
1292
10.1k
    if (stream == nullptr) {
1293
0
        return {{}, {}, "Could not open file for reading"};
1294
0
    }
1295
1296
10.1k
    char magic[8] = { 0 };
1297
10.1k
    stream->Read(magic, 7, 1);
1298
10.1k
    if (strcmp(magic, Token) == 0) {
1299
2.61k
        return {stream, {}, {}};
1300
2.61k
    }
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
7.52k
    if (magic[0] != 0x1f || static_cast<uint8_t>(magic[1]) != 0x8b) {
1309
6.48k
        return {{}, {}, "BLENDER magic bytes are missing, couldn't find GZIP header either"};
1310
6.48k
    }
1311
1312
1.03k
    LogDebug("Found no BLENDER magic word but a GZIP header, might be a compressed file");
1313
1.03k
    if (magic[2] != 8) {
1314
5
        return {{}, {}, "Unsupported GZIP compression method"};
1315
5
    }
1316
1317
    // http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
1318
1.03k
    stream->Seek(0L, aiOrigin_SET);
1319
1.03k
    std::shared_ptr<StreamReaderLE> reader = std::shared_ptr<StreamReaderLE>(new StreamReaderLE(stream));
1320
1321
1.03k
    size_t total = 0;
1322
1.03k
    Compression compression;
1323
1.03k
    auto uncompressed = std::make_shared<std::vector<char>>();
1324
1.03k
    if (compression.open(Compression::Format::Binary, Compression::FlushMode::NoFlush, 16 + Compression::MaxWBits)) {
1325
1.03k
        total = compression.decompress((unsigned char *)reader->GetPtr(), reader->GetRemainingSize(), *uncompressed);
1326
1.03k
        compression.close();
1327
1.03k
    }
1328
1329
    // replace the input stream with a memory stream
1330
1.03k
    stream = std::make_shared<MemoryIOStream>(reinterpret_cast<uint8_t *>(uncompressed->data()), total);
1331
1332
    // .. and retry
1333
1.03k
    stream->Read(magic, 7, 1);
1334
1.03k
    if (strcmp(magic, Token) == 0) {
1335
4
        return {stream, uncompressed, {}};
1336
4
    }
1337
1.03k
    return {{}, {}, "Found no BLENDER magic word in decompressed GZIP file"};
1338
1.03k
#endif
1339
1.03k
}
1340
1341
#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER