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