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