1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2019, assimp team
7 
8 
9 
10 All rights reserved.
11 
12 Redistribution and use of this software in source and binary forms,
13 with or without modification, are permitted provided that the following
14 conditions are met:
15 
16 * Redistributions of source code must retain the above
17   copyright notice, this list of conditions and the
18   following disclaimer.
19 
20 * Redistributions in binary form must reproduce the above
21   copyright notice, this list of conditions and the
22   following disclaimer in the documentation and/or other
23   materials provided with the distribution.
24 
25 * Neither the name of the assimp team, nor the names of its
26   contributors may be used to endorse or promote products
27   derived from this software without specific prior
28   written permission of the assimp team.
29 
30 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 ---------------------------------------------------------------------------
42 */
43 
44 
45 #ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER
46 
47 #include <stdlib.h>
48 #include "ObjFileMtlImporter.h"
49 #include "ObjTools.h"
50 #include "ObjFileData.h"
51 #include <assimp/fast_atof.h>
52 #include <assimp/ParsingUtils.h>
53 #include <assimp/material.h>
54 #include <assimp/DefaultLogger.hpp>
55 
56 namespace Assimp    {
57 
58 // Material specific token (case insensitive compare)
59 static const std::string DiffuseTexture       = "map_Kd";
60 static const std::string AmbientTexture       = "map_Ka";
61 static const std::string SpecularTexture      = "map_Ks";
62 static const std::string OpacityTexture       = "map_d";
63 static const std::string EmissiveTexture1     = "map_emissive";
64 static const std::string EmissiveTexture2     = "map_Ke";
65 static const std::string BumpTexture1         = "map_bump";
66 static const std::string BumpTexture2         = "bump";
67 static const std::string NormalTexture        = "map_Kn";
68 static const std::string ReflectionTexture    = "refl";
69 static const std::string DisplacementTexture1 = "map_disp";
70 static const std::string DisplacementTexture2 = "disp";
71 static const std::string SpecularityTexture   = "map_ns";
72 
73 // texture option specific token
74 static const std::string BlendUOption       = "-blendu";
75 static const std::string BlendVOption       = "-blendv";
76 static const std::string BoostOption        = "-boost";
77 static const std::string ModifyMapOption    = "-mm";
78 static const std::string OffsetOption       = "-o";
79 static const std::string ScaleOption        = "-s";
80 static const std::string TurbulenceOption   = "-t";
81 static const std::string ResolutionOption   = "-texres";
82 static const std::string ClampOption        = "-clamp";
83 static const std::string BumpOption         = "-bm";
84 static const std::string ChannelOption      = "-imfchan";
85 static const std::string TypeOption         = "-type";
86 
87 // -------------------------------------------------------------------
88 //  Constructor
ObjFileMtlImporter(std::vector<char> & buffer,const std::string &,ObjFile::Model * pModel)89 ObjFileMtlImporter::ObjFileMtlImporter( std::vector<char> &buffer,
90                                        const std::string &,
91                                        ObjFile::Model *pModel ) :
92     m_DataIt( buffer.begin() ),
93     m_DataItEnd( buffer.end() ),
94     m_pModel( pModel ),
95     m_uiLine( 0 )
96 {
97     ai_assert( NULL != m_pModel );
98     if ( NULL == m_pModel->m_pDefaultMaterial )
99     {
100         m_pModel->m_pDefaultMaterial = new ObjFile::Material;
101         m_pModel->m_pDefaultMaterial->MaterialName.Set( "default" );
102     }
103     load();
104 }
105 
106 // -------------------------------------------------------------------
107 //  Destructor
~ObjFileMtlImporter()108 ObjFileMtlImporter::~ObjFileMtlImporter()
109 {
110     // empty
111 }
112 
113 // -------------------------------------------------------------------
114 //  Private copy constructor
ObjFileMtlImporter(const ObjFileMtlImporter &)115 ObjFileMtlImporter::ObjFileMtlImporter(const ObjFileMtlImporter & )
116 {
117     // empty
118 }
119 
120 // -------------------------------------------------------------------
121 //  Private copy constructor
operator =(const ObjFileMtlImporter &)122 ObjFileMtlImporter &ObjFileMtlImporter::operator = ( const ObjFileMtlImporter & )
123 {
124     return *this;
125 }
126 
127 // -------------------------------------------------------------------
128 //  Loads the material description
load()129 void ObjFileMtlImporter::load()
130 {
131     if ( m_DataIt == m_DataItEnd )
132         return;
133 
134     while ( m_DataIt != m_DataItEnd )
135     {
136         switch (*m_DataIt)
137         {
138         case 'k':
139         case 'K':
140             {
141                 ++m_DataIt;
142                 if (*m_DataIt == 'a') // Ambient color
143                 {
144                     ++m_DataIt;
145                     getColorRGBA( &m_pModel->m_pCurrentMaterial->ambient );
146                 }
147                 else if (*m_DataIt == 'd')  // Diffuse color
148                 {
149                     ++m_DataIt;
150                     getColorRGBA( &m_pModel->m_pCurrentMaterial->diffuse );
151                 }
152                 else if (*m_DataIt == 's')
153                 {
154                     ++m_DataIt;
155                     getColorRGBA( &m_pModel->m_pCurrentMaterial->specular );
156                 }
157                 else if (*m_DataIt == 'e')
158                 {
159                     ++m_DataIt;
160                     getColorRGBA( &m_pModel->m_pCurrentMaterial->emissive );
161                 }
162                 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
163             }
164             break;
165         case 'T':
166             {
167                 ++m_DataIt;
168                 if (*m_DataIt == 'f') // Material transmission
169                 {
170                     ++m_DataIt;
171                     getColorRGBA( &m_pModel->m_pCurrentMaterial->transparent);
172                 }
173                 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
174             }
175             break;
176         case 'd':
177             {
178                 if( *(m_DataIt+1) == 'i' && *( m_DataIt + 2 ) == 's' && *( m_DataIt + 3 ) == 'p' ) {
179                     // A displacement map
180                     getTexture();
181                 } else {
182                     // Alpha value
183                     ++m_DataIt;
184                     getFloatValue( m_pModel->m_pCurrentMaterial->alpha );
185                     m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
186                 }
187             }
188             break;
189 
190         case 'N':
191         case 'n':
192             {
193                 ++m_DataIt;
194                 switch(*m_DataIt)
195                 {
196                 case 's':   // Specular exponent
197                     ++m_DataIt;
198                     getFloatValue(m_pModel->m_pCurrentMaterial->shineness);
199                     break;
200                 case 'i':   // Index Of refraction
201                     ++m_DataIt;
202                     getFloatValue(m_pModel->m_pCurrentMaterial->ior);
203                     break;
204                 case 'e':   // New material
205                     createMaterial();
206                     break;
207                 }
208                 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
209             }
210             break;
211 
212         case 'm':   // Texture
213         case 'b':   // quick'n'dirty - for 'bump' sections
214         case 'r':   // quick'n'dirty - for 'refl' sections
215             {
216                 getTexture();
217                 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
218             }
219             break;
220 
221         case 'i':   // Illumination model
222             {
223                 m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
224                 getIlluminationModel( m_pModel->m_pCurrentMaterial->illumination_model );
225                 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
226             }
227             break;
228 
229         default:
230             {
231                 m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
232             }
233             break;
234         }
235     }
236 }
237 
238 // -------------------------------------------------------------------
239 //  Loads a color definition
getColorRGBA(aiColor3D * pColor)240 void ObjFileMtlImporter::getColorRGBA( aiColor3D *pColor )
241 {
242     ai_assert( NULL != pColor );
243 
244     ai_real r( 0.0 ), g( 0.0 ), b( 0.0 );
245     m_DataIt = getFloat<DataArrayIt>( m_DataIt, m_DataItEnd, r );
246     pColor->r = r;
247 
248     // we have to check if color is default 0 with only one token
249     if( !IsLineEnd( *m_DataIt ) ) {
250         m_DataIt = getFloat<DataArrayIt>( m_DataIt, m_DataItEnd, g );
251         m_DataIt = getFloat<DataArrayIt>( m_DataIt, m_DataItEnd, b );
252     }
253     pColor->g = g;
254     pColor->b = b;
255 }
256 
257 // -------------------------------------------------------------------
258 //  Loads the kind of illumination model.
getIlluminationModel(int & illum_model)259 void ObjFileMtlImporter::getIlluminationModel( int &illum_model )
260 {
261     m_DataIt = CopyNextWord<DataArrayIt>( m_DataIt, m_DataItEnd, m_buffer, BUFFERSIZE );
262     illum_model = atoi(m_buffer);
263 }
264 
265 // -------------------------------------------------------------------
266 //  Loads a single float value.
getFloatValue(ai_real & value)267 void ObjFileMtlImporter::getFloatValue( ai_real &value )
268 {
269     m_DataIt = CopyNextWord<DataArrayIt>( m_DataIt, m_DataItEnd, m_buffer, BUFFERSIZE );
270     value = (ai_real) fast_atof(m_buffer);
271 }
272 
273 // -------------------------------------------------------------------
274 //  Creates a material from loaded data.
createMaterial()275 void ObjFileMtlImporter::createMaterial()
276 {
277     std::string line( "" );
278     while( !IsLineEnd( *m_DataIt ) ) {
279         line += *m_DataIt;
280         ++m_DataIt;
281     }
282 
283     std::vector<std::string> token;
284     const unsigned int numToken = tokenize<std::string>( line, token, " \t" );
285     std::string name( "" );
286     if ( numToken == 1 ) {
287         name = AI_DEFAULT_MATERIAL_NAME;
288     } else {
289         // skip newmtl and all following white spaces
290         std::size_t first_ws_pos = line.find_first_of(" \t");
291         std::size_t first_non_ws_pos = line.find_first_not_of(" \t", first_ws_pos);
292         if (first_non_ws_pos != std::string::npos) {
293             name = line.substr(first_non_ws_pos);
294         }
295     }
296 
297     name = trim_whitespaces(name);
298 
299     std::map<std::string, ObjFile::Material*>::iterator it = m_pModel->m_MaterialMap.find( name );
300     if ( m_pModel->m_MaterialMap.end() == it) {
301         // New Material created
302         m_pModel->m_pCurrentMaterial = new ObjFile::Material();
303         m_pModel->m_pCurrentMaterial->MaterialName.Set( name );
304         m_pModel->m_MaterialLib.push_back( name );
305         m_pModel->m_MaterialMap[ name ] = m_pModel->m_pCurrentMaterial;
306 
307         if (m_pModel->m_pCurrentMesh) {
308             m_pModel->m_pCurrentMesh->m_uiMaterialIndex = static_cast<unsigned int>(m_pModel->m_MaterialLib.size() - 1);
309         }
310     } else {
311         // Use older material
312         m_pModel->m_pCurrentMaterial = (*it).second;
313     }
314 }
315 
316 // -------------------------------------------------------------------
317 //  Gets a texture name from data.
getTexture()318 void ObjFileMtlImporter::getTexture() {
319     aiString *out( NULL );
320     int clampIndex = -1;
321 
322     const char *pPtr( &(*m_DataIt) );
323     if ( !ASSIMP_strincmp( pPtr, DiffuseTexture.c_str(), static_cast<unsigned int>(DiffuseTexture.size()) ) ) {
324         // Diffuse texture
325         out = & m_pModel->m_pCurrentMaterial->texture;
326         clampIndex = ObjFile::Material::TextureDiffuseType;
327     } else if ( !ASSIMP_strincmp( pPtr,AmbientTexture.c_str(), static_cast<unsigned int>(AmbientTexture.size()) ) ) {
328         // Ambient texture
329         out = & m_pModel->m_pCurrentMaterial->textureAmbient;
330         clampIndex = ObjFile::Material::TextureAmbientType;
331     } else if ( !ASSIMP_strincmp( pPtr, SpecularTexture.c_str(), static_cast<unsigned int>(SpecularTexture.size()) ) ) {
332         // Specular texture
333         out = & m_pModel->m_pCurrentMaterial->textureSpecular;
334         clampIndex = ObjFile::Material::TextureSpecularType;
335     } else if ( !ASSIMP_strincmp( pPtr, DisplacementTexture1.c_str(), static_cast<unsigned int>(DisplacementTexture1.size()) ) ||
336                 !ASSIMP_strincmp( pPtr, DisplacementTexture2.c_str(), static_cast<unsigned int>(DisplacementTexture2.size()) ) ) {
337         // Displacement texture
338         out = &m_pModel->m_pCurrentMaterial->textureDisp;
339         clampIndex = ObjFile::Material::TextureDispType;
340     } else if ( !ASSIMP_strincmp( pPtr, OpacityTexture.c_str(), static_cast<unsigned int>(OpacityTexture.size()) ) ) {
341         // Opacity texture
342         out = & m_pModel->m_pCurrentMaterial->textureOpacity;
343         clampIndex = ObjFile::Material::TextureOpacityType;
344     } else if ( !ASSIMP_strincmp( pPtr, EmissiveTexture1.c_str(), static_cast<unsigned int>(EmissiveTexture1.size()) ) ||
345                 !ASSIMP_strincmp( pPtr, EmissiveTexture2.c_str(), static_cast<unsigned int>(EmissiveTexture2.size()) ) ) {
346         // Emissive texture
347         out = & m_pModel->m_pCurrentMaterial->textureEmissive;
348         clampIndex = ObjFile::Material::TextureEmissiveType;
349     } else if ( !ASSIMP_strincmp( pPtr, BumpTexture1.c_str(), static_cast<unsigned int>(BumpTexture1.size()) ) ||
350                 !ASSIMP_strincmp( pPtr, BumpTexture2.c_str(), static_cast<unsigned int>(BumpTexture2.size()) ) ) {
351         // Bump texture
352         out = & m_pModel->m_pCurrentMaterial->textureBump;
353         clampIndex = ObjFile::Material::TextureBumpType;
354     } else if ( !ASSIMP_strincmp( pPtr,NormalTexture.c_str(), static_cast<unsigned int>(NormalTexture.size()) ) ) {
355         // Normal map
356         out = & m_pModel->m_pCurrentMaterial->textureNormal;
357         clampIndex = ObjFile::Material::TextureNormalType;
358     } else if( !ASSIMP_strincmp( pPtr, ReflectionTexture.c_str(), static_cast<unsigned int>(ReflectionTexture.size()) ) ) {
359         // Reflection texture(s)
360         //Do nothing here
361         return;
362     } else if ( !ASSIMP_strincmp( pPtr, SpecularityTexture.c_str(), static_cast<unsigned int>(SpecularityTexture.size()) ) ) {
363         // Specularity scaling (glossiness)
364         out = & m_pModel->m_pCurrentMaterial->textureSpecularity;
365         clampIndex = ObjFile::Material::TextureSpecularityType;
366     } else {
367         ASSIMP_LOG_ERROR("OBJ/MTL: Encountered unknown texture type");
368         return;
369     }
370 
371     bool clamp = false;
372     getTextureOption(clamp, clampIndex, out);
373     m_pModel->m_pCurrentMaterial->clamp[clampIndex] = clamp;
374 
375     std::string texture;
376     m_DataIt = getName<DataArrayIt>( m_DataIt, m_DataItEnd, texture );
377     if ( NULL!=out ) {
378         out->Set( texture );
379     }
380 }
381 
382 /* /////////////////////////////////////////////////////////////////////////////
383  * Texture Option
384  * /////////////////////////////////////////////////////////////////////////////
385  * According to http://en.wikipedia.org/wiki/Wavefront_.obj_file#Texture_options
386  * Texture map statement can contains various texture option, for example:
387  *
388  *  map_Ka -o 1 1 1 some.png
389  *  map_Kd -clamp on some.png
390  *
391  * So we need to parse and skip these options, and leave the last part which is
392  * the url of image, otherwise we will get a wrong url like "-clamp on some.png".
393  *
394  * Because aiMaterial supports clamp option, so we also want to return it
395  * /////////////////////////////////////////////////////////////////////////////
396  */
getTextureOption(bool & clamp,int & clampIndex,aiString * & out)397 void ObjFileMtlImporter::getTextureOption(bool &clamp, int &clampIndex, aiString *&out) {
398     m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
399 
400     // If there is any more texture option
401     while (!isEndOfBuffer(m_DataIt, m_DataItEnd) && *m_DataIt == '-')
402     {
403         const char *pPtr( &(*m_DataIt) );
404         //skip option key and value
405         int skipToken = 1;
406 
407         if (!ASSIMP_strincmp(pPtr, ClampOption.c_str(), static_cast<unsigned int>(ClampOption.size())))
408         {
409             DataArrayIt it = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
410             char value[3];
411             CopyNextWord(it, m_DataItEnd, value, sizeof(value) / sizeof(*value));
412             if (!ASSIMP_strincmp(value, "on", 2))
413             {
414                 clamp = true;
415             }
416 
417             skipToken = 2;
418         }
419         else if( !ASSIMP_strincmp( pPtr, TypeOption.c_str(), static_cast<unsigned int>(TypeOption.size()) ) )
420         {
421             DataArrayIt it = getNextToken<DataArrayIt>( m_DataIt, m_DataItEnd );
422             char value[ 12 ];
423             CopyNextWord( it, m_DataItEnd, value, sizeof( value ) / sizeof( *value ) );
424             if( !ASSIMP_strincmp( value, "cube_top", 8 ) )
425             {
426                 clampIndex = ObjFile::Material::TextureReflectionCubeTopType;
427                 out = &m_pModel->m_pCurrentMaterial->textureReflection[0];
428             }
429             else if( !ASSIMP_strincmp( value, "cube_bottom", 11 ) )
430             {
431                 clampIndex = ObjFile::Material::TextureReflectionCubeBottomType;
432                 out = &m_pModel->m_pCurrentMaterial->textureReflection[1];
433             }
434             else if( !ASSIMP_strincmp( value, "cube_front", 10 ) )
435             {
436                 clampIndex = ObjFile::Material::TextureReflectionCubeFrontType;
437                 out = &m_pModel->m_pCurrentMaterial->textureReflection[2];
438             }
439             else if( !ASSIMP_strincmp( value, "cube_back", 9 ) )
440             {
441                 clampIndex = ObjFile::Material::TextureReflectionCubeBackType;
442                 out = &m_pModel->m_pCurrentMaterial->textureReflection[3];
443             }
444             else if( !ASSIMP_strincmp( value, "cube_left", 9 ) )
445             {
446                 clampIndex = ObjFile::Material::TextureReflectionCubeLeftType;
447                 out = &m_pModel->m_pCurrentMaterial->textureReflection[4];
448             }
449             else if( !ASSIMP_strincmp( value, "cube_right", 10 ) )
450             {
451                 clampIndex = ObjFile::Material::TextureReflectionCubeRightType;
452                 out = &m_pModel->m_pCurrentMaterial->textureReflection[5];
453             }
454             else if( !ASSIMP_strincmp( value, "sphere", 6 ) )
455             {
456                 clampIndex = ObjFile::Material::TextureReflectionSphereType;
457                 out = &m_pModel->m_pCurrentMaterial->textureReflection[0];
458             }
459 
460             skipToken = 2;
461         }
462         else if (!ASSIMP_strincmp(pPtr, BlendUOption.c_str(), static_cast<unsigned int>(BlendUOption.size()))
463                 || !ASSIMP_strincmp(pPtr, BlendVOption.c_str(), static_cast<unsigned int>(BlendVOption.size()))
464                 || !ASSIMP_strincmp(pPtr, BoostOption.c_str(), static_cast<unsigned int>(BoostOption.size()))
465                 || !ASSIMP_strincmp(pPtr, ResolutionOption.c_str(), static_cast<unsigned int>(ResolutionOption.size()))
466                 || !ASSIMP_strincmp(pPtr, BumpOption.c_str(), static_cast<unsigned int>(BumpOption.size()))
467                 || !ASSIMP_strincmp(pPtr, ChannelOption.c_str(), static_cast<unsigned int>(ChannelOption.size())))
468         {
469             skipToken = 2;
470         }
471         else if (!ASSIMP_strincmp(pPtr, ModifyMapOption.c_str(), static_cast<unsigned int>(ModifyMapOption.size())))
472         {
473             skipToken = 3;
474         }
475         else if (  !ASSIMP_strincmp(pPtr, OffsetOption.c_str(), static_cast<unsigned int>(OffsetOption.size()))
476                 || !ASSIMP_strincmp(pPtr, ScaleOption.c_str(), static_cast<unsigned int>(ScaleOption.size()))
477                 || !ASSIMP_strincmp(pPtr, TurbulenceOption.c_str(), static_cast<unsigned int>(TurbulenceOption.size()))
478                 )
479         {
480             skipToken = 4;
481         }
482 
483         for (int i = 0; i < skipToken; ++i)
484         {
485             m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
486         }
487     }
488 }
489 
490 // -------------------------------------------------------------------
491 
492 } // Namespace Assimp
493 
494 #endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER
495