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