Coverage Report

Created: 2025-11-11 06:59

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