1 /*
2 Open Asset Import Library (assimp)
3 ----------------------------------------------------------------------
4
5 Copyright (c) 2006-2019, assimp team
6
7
8 All rights reserved.
9
10 Redistribution and use of this software in source and binary forms,
11 with or without modification, are permitted provided that the
12 following conditions are met:
13
14 * Redistributions of source code must retain the above
15 copyright notice, this list of conditions and the
16 following disclaimer.
17
18 * Redistributions in binary form must reproduce the above
19 copyright notice, this list of conditions and the
20 following disclaimer in the documentation and/or other
21 materials provided with the distribution.
22
23 * Neither the name of the assimp team, nor the names of its
24 contributors may be used to endorse or promote products
25 derived from this software without specific prior
26 written permission of the assimp team.
27
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
40 ----------------------------------------------------------------------
41 */
42
43 /** @file FBXMaterial.cpp
44 * @brief Assimp::FBX::Material and Assimp::FBX::Texture implementation
45 */
46
47 #ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
48
49 #include "FBXParser.h"
50 #include "FBXDocument.h"
51 #include "FBXImporter.h"
52 #include "FBXImportSettings.h"
53 #include "FBXDocumentUtil.h"
54 #include "FBXProperties.h"
55 #include <assimp/ByteSwapper.h>
56
57 #include <algorithm> // std::transform
58 #include "FBXUtil.h"
59
60 namespace Assimp {
61 namespace FBX {
62
63 using namespace Util;
64
65 // ------------------------------------------------------------------------------------------------
Material(uint64_t id,const Element & element,const Document & doc,const std::string & name)66 Material::Material(uint64_t id, const Element& element, const Document& doc, const std::string& name)
67 : Object(id,element,name)
68 {
69 const Scope& sc = GetRequiredScope(element);
70
71 const Element* const ShadingModel = sc["ShadingModel"];
72 const Element* const MultiLayer = sc["MultiLayer"];
73
74 if(MultiLayer) {
75 multilayer = !!ParseTokenAsInt(GetRequiredToken(*MultiLayer,0));
76 }
77
78 if(ShadingModel) {
79 shading = ParseTokenAsString(GetRequiredToken(*ShadingModel,0));
80 }
81 else {
82 DOMWarning("shading mode not specified, assuming phong",&element);
83 shading = "phong";
84 }
85
86 std::string templateName;
87
88 // lower-case shading because Blender (for example) writes "Phong"
89 std::transform(shading.begin(), shading.end(), shading.begin(), ::tolower);
90 if(shading == "phong") {
91 templateName = "Material.FbxSurfacePhong";
92 }
93 else if(shading == "lambert") {
94 templateName = "Material.FbxSurfaceLambert";
95 }
96 else {
97 DOMWarning("shading mode not recognized: " + shading,&element);
98 }
99
100 props = GetPropertyTable(doc,templateName,element,sc);
101
102 // resolve texture links
103 const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID());
104 for(const Connection* con : conns) {
105
106 // texture link to properties, not objects
107 if (!con->PropertyName().length()) {
108 continue;
109 }
110
111 const Object* const ob = con->SourceObject();
112 if(!ob) {
113 DOMWarning("failed to read source object for texture link, ignoring",&element);
114 continue;
115 }
116
117 const Texture* const tex = dynamic_cast<const Texture*>(ob);
118 if(!tex) {
119 const LayeredTexture* const layeredTexture = dynamic_cast<const LayeredTexture*>(ob);
120 if(!layeredTexture) {
121 DOMWarning("source object for texture link is not a texture or layered texture, ignoring",&element);
122 continue;
123 }
124 const std::string& prop = con->PropertyName();
125 if (layeredTextures.find(prop) != layeredTextures.end()) {
126 DOMWarning("duplicate layered texture link: " + prop,&element);
127 }
128
129 layeredTextures[prop] = layeredTexture;
130 ((LayeredTexture*)layeredTexture)->fillTexture(doc);
131 }
132 else
133 {
134 const std::string& prop = con->PropertyName();
135 if (textures.find(prop) != textures.end()) {
136 DOMWarning("duplicate texture link: " + prop,&element);
137 }
138
139 textures[prop] = tex;
140 }
141
142 }
143 }
144
145
146 // ------------------------------------------------------------------------------------------------
~Material()147 Material::~Material()
148 {
149 }
150
151
152 // ------------------------------------------------------------------------------------------------
Texture(uint64_t id,const Element & element,const Document & doc,const std::string & name)153 Texture::Texture(uint64_t id, const Element& element, const Document& doc, const std::string& name)
154 : Object(id,element,name)
155 , uvScaling(1.0f,1.0f)
156 , media(0)
157 {
158 const Scope& sc = GetRequiredScope(element);
159
160 const Element* const Type = sc["Type"];
161 const Element* const FileName = sc["FileName"];
162 const Element* const RelativeFilename = sc["RelativeFilename"];
163 const Element* const ModelUVTranslation = sc["ModelUVTranslation"];
164 const Element* const ModelUVScaling = sc["ModelUVScaling"];
165 const Element* const Texture_Alpha_Source = sc["Texture_Alpha_Source"];
166 const Element* const Cropping = sc["Cropping"];
167
168 if(Type) {
169 type = ParseTokenAsString(GetRequiredToken(*Type,0));
170 }
171
172 if(FileName) {
173 fileName = ParseTokenAsString(GetRequiredToken(*FileName,0));
174 }
175
176 if(RelativeFilename) {
177 relativeFileName = ParseTokenAsString(GetRequiredToken(*RelativeFilename,0));
178 }
179
180 if(ModelUVTranslation) {
181 uvTrans = aiVector2D(ParseTokenAsFloat(GetRequiredToken(*ModelUVTranslation,0)),
182 ParseTokenAsFloat(GetRequiredToken(*ModelUVTranslation,1))
183 );
184 }
185
186 if(ModelUVScaling) {
187 uvScaling = aiVector2D(ParseTokenAsFloat(GetRequiredToken(*ModelUVScaling,0)),
188 ParseTokenAsFloat(GetRequiredToken(*ModelUVScaling,1))
189 );
190 }
191
192 if(Cropping) {
193 crop[0] = ParseTokenAsInt(GetRequiredToken(*Cropping,0));
194 crop[1] = ParseTokenAsInt(GetRequiredToken(*Cropping,1));
195 crop[2] = ParseTokenAsInt(GetRequiredToken(*Cropping,2));
196 crop[3] = ParseTokenAsInt(GetRequiredToken(*Cropping,3));
197 }
198 else {
199 // vc8 doesn't support the crop() syntax in initialization lists
200 // (and vc9 WARNS about the new (i.e. compliant) behaviour).
201 crop[0] = crop[1] = crop[2] = crop[3] = 0;
202 }
203
204 if(Texture_Alpha_Source) {
205 alphaSource = ParseTokenAsString(GetRequiredToken(*Texture_Alpha_Source,0));
206 }
207
208 props = GetPropertyTable(doc,"Texture.FbxFileTexture",element,sc);
209
210 // 3DS Max and FBX SDK use "Scaling" and "Translation" instead of "ModelUVScaling" and "ModelUVTranslation". Use these properties if available.
211 bool ok;
212 const aiVector3D& scaling = PropertyGet<aiVector3D>(*props, "Scaling", ok);
213 if (ok) {
214 uvScaling.x = scaling.x;
215 uvScaling.y = scaling.y;
216 }
217
218 const aiVector3D& trans = PropertyGet<aiVector3D>(*props, "Translation", ok);
219 if (ok) {
220 uvTrans.x = trans.x;
221 uvTrans.y = trans.y;
222 }
223
224 // resolve video links
225 if(doc.Settings().readTextures) {
226 const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID());
227 for(const Connection* con : conns) {
228 const Object* const ob = con->SourceObject();
229 if(!ob) {
230 DOMWarning("failed to read source object for texture link, ignoring",&element);
231 continue;
232 }
233
234 const Video* const video = dynamic_cast<const Video*>(ob);
235 if(video) {
236 media = video;
237 }
238 }
239 }
240 }
241
242
~Texture()243 Texture::~Texture()
244 {
245
246 }
247
LayeredTexture(uint64_t id,const Element & element,const Document &,const std::string & name)248 LayeredTexture::LayeredTexture(uint64_t id, const Element& element, const Document& /*doc*/, const std::string& name)
249 : Object(id,element,name)
250 ,blendMode(BlendMode_Modulate)
251 ,alpha(1)
252 {
253 const Scope& sc = GetRequiredScope(element);
254
255 const Element* const BlendModes = sc["BlendModes"];
256 const Element* const Alphas = sc["Alphas"];
257
258
259 if(BlendModes!=0)
260 {
261 blendMode = (BlendMode)ParseTokenAsInt(GetRequiredToken(*BlendModes,0));
262 }
263 if(Alphas!=0)
264 {
265 alpha = ParseTokenAsFloat(GetRequiredToken(*Alphas,0));
266 }
267 }
268
~LayeredTexture()269 LayeredTexture::~LayeredTexture()
270 {
271
272 }
273
fillTexture(const Document & doc)274 void LayeredTexture::fillTexture(const Document& doc)
275 {
276 const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID());
277 for(size_t i = 0; i < conns.size();++i)
278 {
279 const Connection* con = conns.at(i);
280
281 const Object* const ob = con->SourceObject();
282 if(!ob) {
283 DOMWarning("failed to read source object for texture link, ignoring",&element);
284 continue;
285 }
286
287 const Texture* const tex = dynamic_cast<const Texture*>(ob);
288
289 textures.push_back(tex);
290 }
291 }
292
293
294 // ------------------------------------------------------------------------------------------------
Video(uint64_t id,const Element & element,const Document & doc,const std::string & name)295 Video::Video(uint64_t id, const Element& element, const Document& doc, const std::string& name)
296 : Object(id,element,name)
297 , contentLength(0)
298 , content(0)
299 {
300 const Scope& sc = GetRequiredScope(element);
301
302 const Element* const Type = sc["Type"];
303 const Element* const FileName = sc.FindElementCaseInsensitive("FileName"); //some files retain the information as "Filename", others "FileName", who knows
304 const Element* const RelativeFilename = sc["RelativeFilename"];
305 const Element* const Content = sc["Content"];
306
307 if(Type) {
308 type = ParseTokenAsString(GetRequiredToken(*Type,0));
309 }
310
311 if(FileName) {
312 fileName = ParseTokenAsString(GetRequiredToken(*FileName,0));
313 }
314
315 if(RelativeFilename) {
316 relativeFileName = ParseTokenAsString(GetRequiredToken(*RelativeFilename,0));
317 }
318
319 if(Content && !Content->Tokens().empty()) {
320 //this field is omitted when the embedded texture is already loaded, let's ignore if it's not found
321 try {
322 const Token& token = GetRequiredToken(*Content, 0);
323 const char* data = token.begin();
324 if (!token.IsBinary()) {
325 if (*data != '"') {
326 DOMError("embedded content is not surrounded by quotation marks", &element);
327 }
328 else {
329 size_t targetLength = 0;
330 auto numTokens = Content->Tokens().size();
331 // First time compute size (it could be large like 64Gb and it is good to allocate it once)
332 for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx)
333 {
334 const Token& dataToken = GetRequiredToken(*Content, tokenIdx);
335 size_t tokenLength = dataToken.end() - dataToken.begin() - 2; // ignore double quotes
336 const char* base64data = dataToken.begin() + 1;
337 const size_t outLength = Util::ComputeDecodedSizeBase64(base64data, tokenLength);
338 if (outLength == 0)
339 {
340 DOMError("Corrupted embedded content found", &element);
341 }
342 targetLength += outLength;
343 }
344 if (targetLength == 0)
345 {
346 DOMError("Corrupted embedded content found", &element);
347 }
348 content = new uint8_t[targetLength];
349 contentLength = static_cast<uint64_t>(targetLength);
350 size_t dst_offset = 0;
351 for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx)
352 {
353 const Token& dataToken = GetRequiredToken(*Content, tokenIdx);
354 size_t tokenLength = dataToken.end() - dataToken.begin() - 2; // ignore double quotes
355 const char* base64data = dataToken.begin() + 1;
356 dst_offset += Util::DecodeBase64(base64data, tokenLength, content + dst_offset, targetLength - dst_offset);
357 }
358 if (targetLength != dst_offset)
359 {
360 delete[] content;
361 contentLength = 0;
362 DOMError("Corrupted embedded content found", &element);
363 }
364 }
365 }
366 else if (static_cast<size_t>(token.end() - data) < 5) {
367 DOMError("binary data array is too short, need five (5) bytes for type signature and element count", &element);
368 }
369 else if (*data != 'R') {
370 DOMWarning("video content is not raw binary data, ignoring", &element);
371 }
372 else {
373 // read number of elements
374 uint32_t len = 0;
375 ::memcpy(&len, data + 1, sizeof(len));
376 AI_SWAP4(len);
377
378 contentLength = len;
379
380 content = new uint8_t[len];
381 ::memcpy(content, data + 5, len);
382 }
383 } catch (const runtime_error& runtimeError)
384 {
385 //we don't need the content data for contents that has already been loaded
386 ASSIMP_LOG_DEBUG_F("Caught exception in FBXMaterial (likely because content was already loaded): ",
387 runtimeError.what());
388 }
389 }
390
391 props = GetPropertyTable(doc,"Video.FbxVideo",element,sc);
392 }
393
394
~Video()395 Video::~Video()
396 {
397 if(content) {
398 delete[] content;
399 }
400 }
401
402 } //!FBX
403 } //!Assimp
404
405 #endif
406