/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 |