Coverage Report

Created: 2026-10-28 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/FBX/FBXMaterial.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  FBXMaterial.cpp
43
 *  @brief Assimp::FBX::Material and Assimp::FBX::Texture implementation
44
 */
45
46
#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
47
48
#include "FBXParser.h"
49
#include "FBXDocument.h"
50
#include "FBXImporter.h"
51
#include "FBXImportSettings.h"
52
#include "FBXDocumentUtil.h"
53
#include "FBXProperties.h"
54
#include <assimp/ByteSwapper.h>
55
#include <assimp/ParsingUtils.h>
56
57
#include "FBXUtil.h"
58
59
namespace Assimp {
60
namespace FBX {
61
62
using namespace Util;
63
64
13.6k
static float ParseTextureUVComponent(const Token &token) {
65
    // Metashape can store these coordinates as binary int32 rather than float/double.
66
13.6k
    if (token.IsBinary() && token.begin() != token.end() && *token.begin() == 'I') {
67
42
        return static_cast<float>(ParseTokenAsInt(token));
68
42
    }
69
13.5k
    return ParseTokenAsFloat(token);
70
13.6k
}
71
72
// ------------------------------------------------------------------------------------------------
73
Material::Material(uint64_t id, const Element& element, const Document& doc, const std::string& name) :
74
5.58k
        Object(id,element,name) {
75
5.58k
    const Scope& sc = GetRequiredScope(element);
76
77
5.58k
    const Element* const ShadingModel = sc["ShadingModel"];
78
5.58k
    const Element* const MultiLayer = sc["MultiLayer"];
79
80
5.58k
    if(MultiLayer) {
81
4.50k
        multilayer = !!ParseTokenAsInt(GetRequiredToken(*MultiLayer,0));
82
4.50k
    }
83
84
5.58k
    if(ShadingModel) {
85
4.06k
        shading = ParseTokenAsString(GetRequiredToken(*ShadingModel,0));
86
4.06k
    } else {
87
1.51k
        DOMWarning("shading mode not specified, assuming phong",&element);
88
1.51k
        shading = "phong";
89
1.51k
    }
90
91
    // lower-case shading because Blender (for example) writes "Phong"
92
51.7k
    for (size_t i = 0; i < shading.length(); ++i) {
93
46.2k
        shading[i] = static_cast<char>(tolower(static_cast<unsigned char>(shading[i])));
94
46.2k
    }
95
5.58k
    std::string templateName;
96
5.58k
    if(shading == "phong") {
97
4.47k
        templateName = "Material.FbxSurfacePhong";
98
4.47k
    } else if(shading == "lambert") {
99
559
        templateName = "Material.FbxSurfaceLambert";
100
559
    } else {
101
552
        DOMWarning("shading mode not recognized: " + shading,&element);
102
552
    }
103
104
5.58k
    props = GetPropertyTable(doc,templateName,element,sc);
105
106
    // resolve texture links
107
5.58k
    const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID());
108
26.4k
    for(const Connection* con : conns) {
109
        // texture link to properties, not objects
110
26.4k
        if ( 0 == con->PropertyName().length()) {
111
544
            continue;
112
544
        }
113
114
25.8k
        const Object* const ob = con->SourceObject();
115
25.8k
        if(nullptr == ob) {
116
3.12k
            DOMWarning("failed to read source object for texture link, ignoring",&element);
117
3.12k
            continue;
118
3.12k
        }
119
120
22.7k
        const Texture* const tex = dynamic_cast<const Texture*>(ob);
121
22.7k
        if(nullptr == tex) {
122
5.58k
            const LayeredTexture* const layeredTexture = dynamic_cast<const LayeredTexture*>(ob);
123
5.58k
            if(!layeredTexture) {
124
268
                DOMWarning("source object for texture link is not a texture or layered texture, ignoring",&element);
125
268
                continue;
126
268
            }
127
5.31k
            const std::string& prop = con->PropertyName();
128
5.31k
            if (layeredTextures.find(prop) != layeredTextures.end()) {
129
2.89k
                DOMWarning("duplicate layered texture link: " + prop,&element);
130
2.89k
            }
131
132
5.31k
            layeredTextures[prop] = layeredTexture;
133
5.31k
            ((LayeredTexture*)layeredTexture)->fillTexture(doc);
134
17.1k
        } else {
135
17.1k
            const std::string& prop = con->PropertyName();
136
17.1k
            if (textures.find(prop) != textures.end()) {
137
7.44k
                DOMWarning("duplicate texture link: " + prop,&element);
138
7.44k
            }
139
140
17.1k
            textures[prop] = tex;
141
17.1k
        }
142
22.7k
    }
143
5.58k
}
144
145
// ------------------------------------------------------------------------------------------------
146
Texture::Texture(uint64_t id, const Element& element, const Document& doc, const std::string& name) :
147
6.67k
        Object(id,element,name),
148
6.67k
        uvTrans(0.0f, 0.0f),
149
6.67k
        uvScaling(1.0f,1.0f),
150
6.67k
        uvRotation(0.0f),
151
6.67k
        type(),
152
6.67k
        relativeFileName(),
153
6.67k
        fileName(),
154
6.67k
        alphaSource(),
155
6.67k
        props(),
156
6.67k
        media(nullptr) {
157
6.67k
    const Scope& sc = GetRequiredScope(element);
158
159
6.67k
    const Element* const Type = sc["Type"];
160
6.67k
    const Element* const FileName = sc["FileName"];
161
6.67k
    const Element* const RelativeFilename = sc["RelativeFilename"];
162
6.67k
    const Element* const ModelUVTranslation = sc["ModelUVTranslation"];
163
6.67k
    const Element* const ModelUVScaling = sc["ModelUVScaling"];
164
6.67k
    const Element* const Texture_Alpha_Source = sc["Texture_Alpha_Source"];
165
6.67k
    const Element* const Cropping = sc["Cropping"];
166
167
6.67k
    if(Type) {
168
5.48k
        type = ParseTokenAsString(GetRequiredToken(*Type,0));
169
5.48k
    }
170
171
6.67k
    if(FileName) {
172
4.94k
        fileName = ParseTokenAsString(GetRequiredToken(*FileName,0));
173
4.94k
    }
174
175
6.67k
    if(RelativeFilename) {
176
4.68k
        relativeFileName = ParseTokenAsString(GetRequiredToken(*RelativeFilename,0));
177
4.68k
    }
178
179
6.67k
    if(ModelUVTranslation) {
180
3.48k
        uvTrans = aiVector2D(ParseTextureUVComponent(GetRequiredToken(*ModelUVTranslation, 0)),
181
3.48k
                ParseTextureUVComponent(GetRequiredToken(*ModelUVTranslation, 1)));
182
3.48k
    }
183
184
6.67k
    if(ModelUVScaling) {
185
3.39k
        uvScaling = aiVector2D(ParseTextureUVComponent(GetRequiredToken(*ModelUVScaling, 0)),
186
3.39k
                ParseTextureUVComponent(GetRequiredToken(*ModelUVScaling, 1)));
187
3.39k
    }
188
189
6.67k
    if(Cropping) {
190
3.79k
        crop[0] = ParseTokenAsInt(GetRequiredToken(*Cropping,0));
191
3.79k
        crop[1] = ParseTokenAsInt(GetRequiredToken(*Cropping,1));
192
3.79k
        crop[2] = ParseTokenAsInt(GetRequiredToken(*Cropping,2));
193
3.79k
        crop[3] = ParseTokenAsInt(GetRequiredToken(*Cropping,3));
194
3.79k
    } else {
195
        // vc8 doesn't support the crop() syntax in initialization lists
196
        // (and vc9 WARNS about the new (i.e. compliant) behaviour).
197
2.88k
        crop[0] = crop[1] = crop[2] = crop[3] = 0;
198
2.88k
    }
199
200
6.67k
    if(Texture_Alpha_Source) {
201
3.02k
        alphaSource = ParseTokenAsString(GetRequiredToken(*Texture_Alpha_Source,0));
202
3.02k
    }
203
204
6.67k
    props = GetPropertyTable(doc,"Texture.FbxFileTexture",element,sc);
205
206
    // 3DS Max and FBX SDK use "Scaling" and "Translation" instead of "ModelUVScaling" and "ModelUVTranslation". Use these properties if available.
207
6.67k
    bool ok;
208
6.67k
    const aiVector3D& scaling = PropertyGet<aiVector3D>(*props, "Scaling", ok);
209
6.67k
    if (ok) {
210
2.51k
        uvScaling.x = scaling.x;
211
2.51k
        uvScaling.y = scaling.y;
212
2.51k
    }
213
214
6.67k
    const aiVector3D& trans = PropertyGet<aiVector3D>(*props, "Translation", ok);
215
6.67k
    if (ok) {
216
2.53k
        uvTrans.x = trans.x;
217
2.53k
        uvTrans.y = trans.y;
218
2.53k
    }
219
220
6.67k
    const aiVector3D &rotation = PropertyGet<aiVector3D>(*props, "Rotation", ok);
221
6.67k
    if (ok) {
222
2.36k
        uvRotation = rotation.z;
223
2.36k
    }
224
225
    // resolve video links
226
6.67k
    if(doc.Settings().readTextures) {
227
5.63k
        const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID());
228
10.4k
        for(const Connection* con : conns) {
229
10.4k
            const Object* const ob = con->SourceObject();
230
10.4k
            if (nullptr == ob) {
231
1.49k
                DOMWarning("failed to read source object for texture link, ignoring",&element);
232
1.49k
                continue;
233
1.49k
            }
234
235
8.99k
            const Video* const video = dynamic_cast<const Video*>(ob);
236
8.99k
            if(video) {
237
7.67k
                media = video;
238
7.67k
            }
239
8.99k
        }
240
5.63k
    }
241
6.67k
}
242
243
244
5.63k
Texture::~Texture() = default;
245
246
LayeredTexture::LayeredTexture(uint64_t id, const Element& element, const Document& /*doc*/, const std::string& name) :
247
482
        Object(id,element,name),
248
482
        blendMode(BlendMode_Modulate),
249
482
        alpha(1) {
250
482
    const Scope& sc = GetRequiredScope(element);
251
252
482
    const Element* const BlendModes = sc["BlendModes"];
253
482
    const Element* const Alphas = sc["Alphas"];
254
255
482
    if (nullptr != BlendModes) {
256
2
        blendMode = (BlendMode)ParseTokenAsInt(GetRequiredToken(*BlendModes,0));
257
2
    }
258
482
    if (nullptr != Alphas) {
259
24
        alpha = ParseTokenAsFloat(GetRequiredToken(*Alphas,0));
260
24
    }
261
482
}
262
263
472
LayeredTexture::~LayeredTexture() = default;
264
265
5.31k
void LayeredTexture::fillTexture(const Document& doc) {
266
5.31k
    const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID());
267
240k
    for(size_t i = 0; i < conns.size();++i) {
268
235k
        const Connection* con = conns.at(i);
269
270
235k
        const Object* const ob = con->SourceObject();
271
235k
        if (nullptr == ob) {
272
21.3k
            DOMWarning("failed to read source object for texture link, ignoring",&element);
273
21.3k
            continue;
274
21.3k
        }
275
276
214k
        const Texture* const tex = dynamic_cast<const Texture*>(ob);
277
278
214k
        textures.push_back(tex);
279
214k
    }
280
5.31k
}
281
282
// ------------------------------------------------------------------------------------------------
283
Video::Video(uint64_t id, const Element &element, const Document &doc, const std::string &name) :
284
2.46k
        Object(id, element, name),
285
2.46k
        contentLength(0),
286
2.46k
        content(nullptr) {
287
2.46k
    const Scope& sc = GetRequiredScope(element);
288
289
2.46k
    const Element* const Type = sc["Type"];
290
2.46k
    const Element* const FileName = sc.FindElementCaseInsensitive("FileName");  //some files retain the information as "Filename", others "FileName", who knows
291
2.46k
    const Element* const RelativeFilename = sc["RelativeFilename"];
292
2.46k
    const Element* const Content = sc["Content"];
293
294
2.46k
    if(Type) {
295
2.24k
        type = ParseTokenAsString(GetRequiredToken(*Type,0));
296
2.24k
    }
297
298
2.46k
    if(FileName) {
299
2.22k
        fileName = ParseTokenAsString(GetRequiredToken(*FileName,0));
300
2.22k
    }
301
302
2.46k
    if(RelativeFilename) {
303
2.22k
        relativeFileName = ParseTokenAsString(GetRequiredToken(*RelativeFilename,0));
304
2.22k
    }
305
306
2.46k
    if(Content && !Content->Tokens().empty()) {
307
        //this field is omitted when the embedded texture is already loaded, let's ignore if it's not found
308
515
        try {
309
515
            const Token& token = GetRequiredToken(*Content, 0);
310
515
            const char* data = token.begin();
311
515
            if (!token.IsBinary()) {
312
495
                if (*data != '"') {
313
7
                    DOMError("embedded content is not surrounded by quotation marks", &element);
314
488
                } else {
315
488
                    size_t targetLength = 0;
316
488
                    auto numTokens = Content->Tokens().size();
317
                    // First time compute size (it could be large like 64Gb and it is good to allocate it once)
318
4.83k
                    for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) {
319
4.35k
                        const Token& dataToken = GetRequiredToken(*Content, tokenIdx);
320
4.35k
                        size_t tokenLength = dataToken.end() - dataToken.begin() - 2; // ignore double quotes
321
4.35k
                        const char* base64data = dataToken.begin() + 1;
322
4.35k
                        const size_t outLength = Util::ComputeDecodedSizeBase64(base64data, tokenLength);
323
4.35k
                        if (outLength == 0) {
324
5
                            DOMError("Corrupted embedded content found", &element);
325
5
                        }
326
4.35k
                        targetLength += outLength;
327
4.35k
                    }
328
483
                    if (targetLength == 0) {
329
1
                        DOMError("Corrupted embedded content found", &element);
330
1
                    }
331
482
                    content = new uint8_t[targetLength];
332
482
                    contentLength = static_cast<uint64_t>(targetLength);
333
482
                    size_t dst_offset = 0;
334
4.51k
                    for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) {
335
4.03k
                        const Token& dataToken = GetRequiredToken(*Content, tokenIdx);
336
4.03k
                        size_t tokenLength = dataToken.end() - dataToken.begin() - 2; // ignore double quotes
337
4.03k
                        const char* base64data = dataToken.begin() + 1;
338
4.03k
                        dst_offset += Util::DecodeBase64(base64data, tokenLength, content + dst_offset, targetLength - dst_offset);
339
4.03k
                    }
340
482
                    if (targetLength != dst_offset) {
341
202
                        delete[] content;
342
202
                        contentLength = 0;
343
202
                        DOMError("Corrupted embedded content found", &element);
344
202
                    }
345
482
                }
346
495
            } else if (static_cast<size_t>(token.end() - data) < 5) {
347
0
                DOMError("binary data array is too short, need five (5) bytes for type signature and element count", &element);
348
20
            } else if (*data != 'R') {
349
0
                DOMWarning("video content is not raw binary data, ignoring", &element);
350
20
            } else {
351
                // read number of elements
352
20
                uint32_t len = 0;
353
20
                ::memcpy(&len, data + 1, sizeof(len));
354
20
                AI_SWAP4(len);
355
356
20
                contentLength = len;
357
358
20
                content = new uint8_t[len];
359
20
                ::memcpy(content, data + 5, len);
360
20
            }
361
515
        } catch (const runtime_error& runtimeError) {
362
            //we don't need the content data for contents that has already been loaded
363
215
            ASSIMP_LOG_VERBOSE_DEBUG("Caught exception in FBXMaterial (likely because content was already loaded): ",
364
215
                    runtimeError.what());
365
215
        }
366
515
    }
367
368
2.46k
    props = GetPropertyTable(doc,"Video.FbxVideo",element,sc);
369
2.46k
}
370
371
2.45k
Video::~Video() {
372
2.45k
    if (contentLength > 0) {
373
299
        delete[] content;
374
299
    }
375
2.45k
}
376
377
} //!FBX
378
} //!Assimp
379
380
#endif // ASSIMP_BUILD_NO_FBX_IMPORTER