Coverage Report

Created: 2026-06-15 06:20

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
// ------------------------------------------------------------------------------------------------
65
Material::Material(uint64_t id, const Element& element, const Document& doc, const std::string& name) :
66
4.39k
        Object(id,element,name) {
67
4.39k
    const Scope& sc = GetRequiredScope(element);
68
69
4.39k
    const Element* const ShadingModel = sc["ShadingModel"];
70
4.39k
    const Element* const MultiLayer = sc["MultiLayer"];
71
72
4.39k
    if(MultiLayer) {
73
3.84k
        multilayer = !!ParseTokenAsInt(GetRequiredToken(*MultiLayer,0));
74
3.84k
    }
75
76
4.39k
    if(ShadingModel) {
77
3.40k
        shading = ParseTokenAsString(GetRequiredToken(*ShadingModel,0));
78
3.40k
    } else {
79
987
        DOMWarning("shading mode not specified, assuming phong",&element);
80
987
        shading = "phong";
81
987
    }
82
83
    // lower-case shading because Blender (for example) writes "Phong"
84
179k
    for (size_t i = 0; i < shading.length(); ++i) {
85
174k
        shading[i] = static_cast<char>(tolower(static_cast<unsigned char>(shading[i])));
86
174k
    }
87
4.39k
    std::string templateName;
88
4.39k
    if(shading == "phong") {
89
3.38k
        templateName = "Material.FbxSurfacePhong";
90
3.38k
    } else if(shading == "lambert") {
91
678
        templateName = "Material.FbxSurfaceLambert";
92
678
    } else {
93
332
        DOMWarning("shading mode not recognized: " + shading,&element);
94
332
    }
95
96
4.39k
    props = GetPropertyTable(doc,templateName,element,sc);
97
98
    // resolve texture links
99
4.39k
    const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID());
100
17.6k
    for(const Connection* con : conns) {
101
        // texture link to properties, not objects
102
17.6k
        if ( 0 == con->PropertyName().length()) {
103
398
            continue;
104
398
        }
105
106
17.2k
        const Object* const ob = con->SourceObject();
107
17.2k
        if(nullptr == ob) {
108
809
            DOMWarning("failed to read source object for texture link, ignoring",&element);
109
809
            continue;
110
809
        }
111
112
16.4k
        const Texture* const tex = dynamic_cast<const Texture*>(ob);
113
16.4k
        if(nullptr == tex) {
114
5.04k
            const LayeredTexture* const layeredTexture = dynamic_cast<const LayeredTexture*>(ob);
115
5.04k
            if(!layeredTexture) {
116
236
                DOMWarning("source object for texture link is not a texture or layered texture, ignoring",&element);
117
236
                continue;
118
236
            }
119
4.80k
            const std::string& prop = con->PropertyName();
120
4.80k
            if (layeredTextures.find(prop) != layeredTextures.end()) {
121
2.43k
                DOMWarning("duplicate layered texture link: " + prop,&element);
122
2.43k
            }
123
124
4.80k
            layeredTextures[prop] = layeredTexture;
125
4.80k
            ((LayeredTexture*)layeredTexture)->fillTexture(doc);
126
11.3k
        } else {
127
11.3k
            const std::string& prop = con->PropertyName();
128
11.3k
            if (textures.find(prop) != textures.end()) {
129
5.84k
                DOMWarning("duplicate texture link: " + prop,&element);
130
5.84k
            }
131
132
11.3k
            textures[prop] = tex;
133
11.3k
        }
134
16.4k
    }
135
4.39k
}
136
137
// ------------------------------------------------------------------------------------------------
138
Texture::Texture(uint64_t id, const Element& element, const Document& doc, const std::string& name) :
139
4.20k
        Object(id,element,name),
140
4.20k
        uvTrans(0.0f, 0.0f),
141
4.20k
        uvScaling(1.0f,1.0f),
142
4.20k
        uvRotation(0.0f),
143
4.20k
        type(),
144
4.20k
        relativeFileName(),
145
4.20k
        fileName(),
146
4.20k
        alphaSource(),
147
4.20k
        props(),
148
4.20k
        media(nullptr) {
149
4.20k
    const Scope& sc = GetRequiredScope(element);
150
151
4.20k
    const Element* const Type = sc["Type"];
152
4.20k
    const Element* const FileName = sc["FileName"];
153
4.20k
    const Element* const RelativeFilename = sc["RelativeFilename"];
154
4.20k
    const Element* const ModelUVTranslation = sc["ModelUVTranslation"];
155
4.20k
    const Element* const ModelUVScaling = sc["ModelUVScaling"];
156
4.20k
    const Element* const Texture_Alpha_Source = sc["Texture_Alpha_Source"];
157
4.20k
    const Element* const Cropping = sc["Cropping"];
158
159
4.20k
    if(Type) {
160
3.23k
        type = ParseTokenAsString(GetRequiredToken(*Type,0));
161
3.23k
    }
162
163
4.20k
    if(FileName) {
164
2.79k
        fileName = ParseTokenAsString(GetRequiredToken(*FileName,0));
165
2.79k
    }
166
167
4.20k
    if(RelativeFilename) {
168
2.55k
        relativeFileName = ParseTokenAsString(GetRequiredToken(*RelativeFilename,0));
169
2.55k
    }
170
171
4.20k
    if(ModelUVTranslation) {
172
1.41k
        uvTrans = aiVector2D(ParseTokenAsFloat(GetRequiredToken(*ModelUVTranslation,0)),
173
1.41k
            ParseTokenAsFloat(GetRequiredToken(*ModelUVTranslation,1))
174
1.41k
        );
175
1.41k
    }
176
177
4.20k
    if(ModelUVScaling) {
178
1.46k
        uvScaling = aiVector2D(ParseTokenAsFloat(GetRequiredToken(*ModelUVScaling,0)),
179
1.46k
            ParseTokenAsFloat(GetRequiredToken(*ModelUVScaling,1))
180
1.46k
        );
181
1.46k
    }
182
183
4.20k
    if(Cropping) {
184
1.66k
        crop[0] = ParseTokenAsInt(GetRequiredToken(*Cropping,0));
185
1.66k
        crop[1] = ParseTokenAsInt(GetRequiredToken(*Cropping,1));
186
1.66k
        crop[2] = ParseTokenAsInt(GetRequiredToken(*Cropping,2));
187
1.66k
        crop[3] = ParseTokenAsInt(GetRequiredToken(*Cropping,3));
188
2.53k
    } else {
189
        // vc8 doesn't support the crop() syntax in initialization lists
190
        // (and vc9 WARNS about the new (i.e. compliant) behaviour).
191
2.53k
        crop[0] = crop[1] = crop[2] = crop[3] = 0;
192
2.53k
    }
193
194
4.20k
    if(Texture_Alpha_Source) {
195
1.08k
        alphaSource = ParseTokenAsString(GetRequiredToken(*Texture_Alpha_Source,0));
196
1.08k
    }
197
198
4.20k
    props = GetPropertyTable(doc,"Texture.FbxFileTexture",element,sc);
199
200
    // 3DS Max and FBX SDK use "Scaling" and "Translation" instead of "ModelUVScaling" and "ModelUVTranslation". Use these properties if available.
201
4.20k
    bool ok;
202
4.20k
    const aiVector3D& scaling = PropertyGet<aiVector3D>(*props, "Scaling", ok);
203
4.20k
    if (ok) {
204
1.44k
        uvScaling.x = scaling.x;
205
1.44k
        uvScaling.y = scaling.y;
206
1.44k
    }
207
208
4.20k
    const aiVector3D& trans = PropertyGet<aiVector3D>(*props, "Translation", ok);
209
4.20k
    if (ok) {
210
1.41k
        uvTrans.x = trans.x;
211
1.41k
        uvTrans.y = trans.y;
212
1.41k
    }
213
214
4.20k
    const aiVector3D &rotation = PropertyGet<aiVector3D>(*props, "Rotation", ok);
215
4.20k
    if (ok) {
216
1.40k
        uvRotation = rotation.z;
217
1.40k
    }
218
219
    // resolve video links
220
4.20k
    if(doc.Settings().readTextures) {
221
3.70k
        const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID());
222
9.08k
        for(const Connection* con : conns) {
223
9.08k
            const Object* const ob = con->SourceObject();
224
9.08k
            if (nullptr == ob) {
225
1.43k
                DOMWarning("failed to read source object for texture link, ignoring",&element);
226
1.43k
                continue;
227
1.43k
            }
228
229
7.65k
            const Video* const video = dynamic_cast<const Video*>(ob);
230
7.65k
            if(video) {
231
6.54k
                media = video;
232
6.54k
            }
233
7.65k
        }
234
3.70k
    }
235
4.20k
}
236
237
238
3.59k
Texture::~Texture() = default;
239
240
LayeredTexture::LayeredTexture(uint64_t id, const Element& element, const Document& /*doc*/, const std::string& name) :
241
344
        Object(id,element,name),
242
344
        blendMode(BlendMode_Modulate),
243
344
        alpha(1) {
244
344
    const Scope& sc = GetRequiredScope(element);
245
246
344
    const Element* const BlendModes = sc["BlendModes"];
247
344
    const Element* const Alphas = sc["Alphas"];
248
249
344
    if (nullptr != BlendModes) {
250
1
        blendMode = (BlendMode)ParseTokenAsInt(GetRequiredToken(*BlendModes,0));
251
1
    }
252
344
    if (nullptr != Alphas) {
253
0
        alpha = ParseTokenAsFloat(GetRequiredToken(*Alphas,0));
254
0
    }
255
344
}
256
257
342
LayeredTexture::~LayeredTexture() = default;
258
259
4.80k
void LayeredTexture::fillTexture(const Document& doc) {
260
4.80k
    const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID());
261
105k
    for(size_t i = 0; i < conns.size();++i) {
262
100k
        const Connection* con = conns.at(i);
263
264
100k
        const Object* const ob = con->SourceObject();
265
100k
        if (nullptr == ob) {
266
95.4k
            DOMWarning("failed to read source object for texture link, ignoring",&element);
267
95.4k
            continue;
268
95.4k
        }
269
270
4.96k
        const Texture* const tex = dynamic_cast<const Texture*>(ob);
271
272
4.96k
        textures.push_back(tex);
273
4.96k
    }
274
4.80k
}
275
276
// ------------------------------------------------------------------------------------------------
277
Video::Video(uint64_t id, const Element &element, const Document &doc, const std::string &name) :
278
1.29k
        Object(id, element, name),
279
1.29k
        contentLength(0),
280
1.29k
        content(nullptr) {
281
1.29k
    const Scope& sc = GetRequiredScope(element);
282
283
1.29k
    const Element* const Type = sc["Type"];
284
1.29k
    const Element* const FileName = sc.FindElementCaseInsensitive("FileName");  //some files retain the information as "Filename", others "FileName", who knows
285
1.29k
    const Element* const RelativeFilename = sc["RelativeFilename"];
286
1.29k
    const Element* const Content = sc["Content"];
287
288
1.29k
    if(Type) {
289
1.12k
        type = ParseTokenAsString(GetRequiredToken(*Type,0));
290
1.12k
    }
291
292
1.29k
    if(FileName) {
293
1.11k
        fileName = ParseTokenAsString(GetRequiredToken(*FileName,0));
294
1.11k
    }
295
296
1.29k
    if(RelativeFilename) {
297
1.08k
        relativeFileName = ParseTokenAsString(GetRequiredToken(*RelativeFilename,0));
298
1.08k
    }
299
300
1.29k
    if(Content && !Content->Tokens().empty()) {
301
        //this field is omitted when the embedded texture is already loaded, let's ignore if it's not found
302
220
        try {
303
220
            const Token& token = GetRequiredToken(*Content, 0);
304
220
            const char* data = token.begin();
305
220
            if (!token.IsBinary()) {
306
220
                if (*data != '"') {
307
8
                    DOMError("embedded content is not surrounded by quotation marks", &element);
308
212
                } else {
309
212
                    size_t targetLength = 0;
310
212
                    auto numTokens = Content->Tokens().size();
311
                    // First time compute size (it could be large like 64Gb and it is good to allocate it once)
312
3.38k
                    for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) {
313
3.17k
                        const Token& dataToken = GetRequiredToken(*Content, tokenIdx);
314
3.17k
                        size_t tokenLength = dataToken.end() - dataToken.begin() - 2; // ignore double quotes
315
3.17k
                        const char* base64data = dataToken.begin() + 1;
316
3.17k
                        const size_t outLength = Util::ComputeDecodedSizeBase64(base64data, tokenLength);
317
3.17k
                        if (outLength == 0) {
318
5
                            DOMError("Corrupted embedded content found", &element);
319
5
                        }
320
3.17k
                        targetLength += outLength;
321
3.17k
                    }
322
207
                    if (targetLength == 0) {
323
0
                        DOMError("Corrupted embedded content found", &element);
324
0
                    }
325
207
                    content = new uint8_t[targetLength];
326
207
                    contentLength = static_cast<uint64_t>(targetLength);
327
207
                    size_t dst_offset = 0;
328
2.68k
                    for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) {
329
2.48k
                        const Token& dataToken = GetRequiredToken(*Content, tokenIdx);
330
2.48k
                        size_t tokenLength = dataToken.end() - dataToken.begin() - 2; // ignore double quotes
331
2.48k
                        const char* base64data = dataToken.begin() + 1;
332
2.48k
                        dst_offset += Util::DecodeBase64(base64data, tokenLength, content + dst_offset, targetLength - dst_offset);
333
2.48k
                    }
334
207
                    if (targetLength != dst_offset) {
335
116
                        delete[] content;
336
116
                        contentLength = 0;
337
116
                        DOMError("Corrupted embedded content found", &element);
338
116
                    }
339
207
                }
340
220
            } else if (static_cast<size_t>(token.end() - data) < 5) {
341
0
                DOMError("binary data array is too short, need five (5) bytes for type signature and element count", &element);
342
0
            } else if (*data != 'R') {
343
0
                DOMWarning("video content is not raw binary data, ignoring", &element);
344
0
            } else {
345
                // read number of elements
346
0
                uint32_t len = 0;
347
0
                ::memcpy(&len, data + 1, sizeof(len));
348
0
                AI_SWAP4(len);
349
350
0
                contentLength = len;
351
352
0
                content = new uint8_t[len];
353
0
                ::memcpy(content, data + 5, len);
354
0
            }
355
220
        } catch (const runtime_error& runtimeError) {
356
            //we don't need the content data for contents that has already been loaded
357
129
            ASSIMP_LOG_VERBOSE_DEBUG("Caught exception in FBXMaterial (likely because content was already loaded): ",
358
129
                    runtimeError.what());
359
129
        }
360
220
    }
361
362
1.29k
    props = GetPropertyTable(doc,"Video.FbxVideo",element,sc);
363
1.29k
}
364
365
1.28k
Video::~Video() {
366
1.28k
    if (contentLength > 0) {
367
90
        delete[] content;
368
90
    }
369
1.28k
}
370
371
} //!FBX
372
} //!Assimp
373
374
#endif // ASSIMP_BUILD_NO_FBX_IMPORTER