Coverage Report

Created: 2026-03-12 06:25

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