1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2016, assimp team
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 following
12 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 /** @file Implementation of the STL importer class */
43 
44 
45 #ifndef ASSIMP_BUILD_NO_NFF_IMPORTER
46 
47 // internal headers
48 #include "NFFLoader.h"
49 #include "ParsingUtils.h"
50 #include "StandardShapes.h"
51 #include "qnan.h"
52 #include "fast_atof.h"
53 #include "RemoveComments.h"
54 #include <assimp/IOSystem.hpp>
55 #include <assimp/DefaultLogger.hpp>
56 #include <assimp/scene.h>
57 #include <memory>
58 
59 
60 using namespace Assimp;
61 
62 static const aiImporterDesc desc = {
63     "Neutral File Format Importer",
64     "",
65     "",
66     "",
67     aiImporterFlags_SupportBinaryFlavour,
68     0,
69     0,
70     0,
71     0,
72     "enff nff"
73 };
74 
75 // ------------------------------------------------------------------------------------------------
76 // Constructor to be privately used by Importer
NFFImporter()77 NFFImporter::NFFImporter()
78 {}
79 
80 // ------------------------------------------------------------------------------------------------
81 // Destructor, private as well
~NFFImporter()82 NFFImporter::~NFFImporter()
83 {}
84 
85 // ------------------------------------------------------------------------------------------------
86 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem *,bool) const87 bool NFFImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool /*checkSig*/) const
88 {
89     return SimpleExtensionCheck(pFile,"nff","enff");
90 }
91 
92 // ------------------------------------------------------------------------------------------------
93 // Get the list of all supported file extensions
GetInfo() const94 const aiImporterDesc* NFFImporter::GetInfo () const
95 {
96     return &desc;
97 }
98 
99 // ------------------------------------------------------------------------------------------------
100 #define AI_NFF_PARSE_FLOAT(f) \
101     SkipSpaces(&sz); \
102     if (!::IsLineEnd(*sz))sz = fast_atoreal_move<float>(sz, (float&)f);
103 
104 // ------------------------------------------------------------------------------------------------
105 #define AI_NFF_PARSE_TRIPLE(v) \
106     AI_NFF_PARSE_FLOAT(v[0]) \
107     AI_NFF_PARSE_FLOAT(v[1]) \
108     AI_NFF_PARSE_FLOAT(v[2])
109 
110 // ------------------------------------------------------------------------------------------------
111 #define AI_NFF_PARSE_SHAPE_INFORMATION() \
112     aiVector3D center, radius(1.0f,get_qnan(),get_qnan()); \
113     AI_NFF_PARSE_TRIPLE(center); \
114     AI_NFF_PARSE_TRIPLE(radius); \
115     if (is_qnan(radius.z))radius.z = radius.x; \
116     if (is_qnan(radius.y))radius.y = radius.x; \
117     currentMesh.radius = radius; \
118     currentMesh.center = center;
119 
120 // ------------------------------------------------------------------------------------------------
121 #define AI_NFF2_GET_NEXT_TOKEN() \
122     do \
123     { \
124     if (!GetNextLine(buffer,line)) \
125         {DefaultLogger::get()->warn("NFF2: Unexpected EOF, can't read next token");break;} \
126     SkipSpaces(line,&sz); \
127     } \
128     while(IsLineEnd(*sz))
129 
130 
131 // ------------------------------------------------------------------------------------------------
132 // Loads the materail table for the NFF2 file format from an external file
LoadNFF2MaterialTable(std::vector<ShadingInfo> & output,const std::string & path,IOSystem * pIOHandler)133 void NFFImporter::LoadNFF2MaterialTable(std::vector<ShadingInfo>& output,
134     const std::string& path, IOSystem* pIOHandler)
135 {
136     std::unique_ptr<IOStream> file( pIOHandler->Open( path, "rb"));
137 
138     // Check whether we can read from the file
139     if( !file.get())    {
140         DefaultLogger::get()->error("NFF2: Unable to open material library " + path + ".");
141         return;
142     }
143 
144     // get the size of the file
145     const unsigned int m = (unsigned int)file->FileSize();
146 
147     // allocate storage and copy the contents of the file to a memory buffer
148     // (terminate it with zero)
149     std::vector<char> mBuffer2(m+1);
150     TextFileToBuffer(file.get(),mBuffer2);
151     const char* buffer = &mBuffer2[0];
152 
153     // First of all: remove all comments from the file
154     CommentRemover::RemoveLineComments("//",&mBuffer2[0]);
155 
156     // The file should start with the magic sequence "mat"
157     if (!TokenMatch(buffer,"mat",3))    {
158         DefaultLogger::get()->error("NFF2: Not a valid material library " + path + ".");
159         return;
160     }
161 
162     ShadingInfo* curShader = NULL;
163 
164     // No read the file line per line
165     char line[4096];
166     const char* sz;
167     while (GetNextLine(buffer,line))
168     {
169         SkipSpaces(line,&sz);
170 
171         // 'version' defines the version of the file format
172         if (TokenMatch(sz,"version",7))
173         {
174             DefaultLogger::get()->info("NFF (Sense8) material library file format: " + std::string(sz));
175         }
176         // 'matdef' starts a new material in the file
177         else if (TokenMatch(sz,"matdef",6))
178         {
179             // add a new material to the list
180             output.push_back( ShadingInfo() );
181             curShader = & output.back();
182 
183             // parse the name of the material
184         }
185         else if (!TokenMatch(sz,"valid",5))
186         {
187             // check whether we have an active material at the moment
188             if (!IsLineEnd(*sz))
189             {
190                 if (!curShader)
191                 {
192                     DefaultLogger::get()->error(std::string("NFF2 material library: Found element ") +
193                         sz + "but there is no active material");
194                     continue;
195                 }
196             }
197             else continue;
198 
199             // now read the material property and determine its type
200             aiColor3D c;
201             if (TokenMatch(sz,"ambient",7))
202             {
203                 AI_NFF_PARSE_TRIPLE(c);
204                 curShader->ambient = c;
205             }
206             else if (TokenMatch(sz,"diffuse",7) || TokenMatch(sz,"ambientdiffuse",14) /* correct? */)
207             {
208                 AI_NFF_PARSE_TRIPLE(c);
209                 curShader->diffuse = curShader->ambient = c;
210             }
211             else if (TokenMatch(sz,"specular",8))
212             {
213                 AI_NFF_PARSE_TRIPLE(c);
214                 curShader->specular = c;
215             }
216             else if (TokenMatch(sz,"emission",8))
217             {
218                 AI_NFF_PARSE_TRIPLE(c);
219                 curShader->emissive = c;
220             }
221             else if (TokenMatch(sz,"shininess",9))
222             {
223                 AI_NFF_PARSE_FLOAT(curShader->shininess);
224             }
225             else if (TokenMatch(sz,"opacity",7))
226             {
227                 AI_NFF_PARSE_FLOAT(curShader->opacity);
228             }
229         }
230     }
231 }
232 
233 // ------------------------------------------------------------------------------------------------
234 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)235 void NFFImporter::InternReadFile( const std::string& pFile,
236     aiScene* pScene, IOSystem* pIOHandler)
237 {
238     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
239 
240     // Check whether we can read from the file
241     if( !file.get())
242         throw DeadlyImportError( "Failed to open NFF file " + pFile + ".");
243 
244     unsigned int m = (unsigned int)file->FileSize();
245 
246     // allocate storage and copy the contents of the file to a memory buffer
247     // (terminate it with zero)
248     std::vector<char> mBuffer2;
249     TextFileToBuffer(file.get(),mBuffer2);
250     const char* buffer = &mBuffer2[0];
251 
252     // mesh arrays - separate here to make the handling of the pointers below easier.
253     std::vector<MeshInfo> meshes;
254     std::vector<MeshInfo> meshesWithNormals;
255     std::vector<MeshInfo> meshesWithUVCoords;
256     std::vector<MeshInfo> meshesLocked;
257 
258     char line[4096];
259     const char* sz;
260 
261     // camera parameters
262     aiVector3D camPos, camUp(0.f,1.f,0.f), camLookAt(0.f,0.f,1.f);
263     float angle = 45.f;
264     aiVector2D resolution;
265 
266     bool hasCam = false;
267 
268     MeshInfo* currentMeshWithNormals = NULL;
269     MeshInfo* currentMesh = NULL;
270     MeshInfo* currentMeshWithUVCoords = NULL;
271 
272     ShadingInfo s; // current material info
273 
274     // degree of tesselation
275     unsigned int iTesselation = 4;
276 
277     // some temporary variables we need to parse the file
278     unsigned int sphere     = 0,
279         cylinder            = 0,
280         cone                = 0,
281         numNamed            = 0,
282         dodecahedron        = 0,
283         octahedron          = 0,
284         tetrahedron         = 0,
285         hexahedron          = 0;
286 
287     // lights imported from the file
288     std::vector<Light> lights;
289 
290     // check whether this is the NFF2 file format
291     if (TokenMatch(buffer,"nff",3))
292     {
293         const float qnan = get_qnan();
294         const aiColor4D  cQNAN = aiColor4D (qnan,0.f,0.f,1.f);
295         const aiVector3D vQNAN = aiVector3D(qnan,0.f,0.f);
296 
297         // another NFF file format ... just a raw parser has been implemented
298         // no support for further details, I don't think it is worth the effort
299         // http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/nff/nff2.html
300         // http://www.netghost.narod.ru/gff/graphics/summary/sense8.htm
301 
302         // First of all: remove all comments from the file
303         CommentRemover::RemoveLineComments("//",&mBuffer2[0]);
304 
305         while (GetNextLine(buffer,line))
306         {
307             SkipSpaces(line,&sz);
308             if (TokenMatch(sz,"version",7))
309             {
310                 DefaultLogger::get()->info("NFF (Sense8) file format: " + std::string(sz));
311             }
312             else if (TokenMatch(sz,"viewpos",7))
313             {
314                 AI_NFF_PARSE_TRIPLE(camPos);
315                 hasCam = true;
316             }
317             else if (TokenMatch(sz,"viewdir",7))
318             {
319                 AI_NFF_PARSE_TRIPLE(camLookAt);
320                 hasCam = true;
321             }
322             // This starts a new object section
323             else if (!IsSpaceOrNewLine(*sz))
324             {
325                 unsigned int subMeshIdx = 0;
326 
327                 // read the name of the object, skip all spaces
328                 // at the end of it.
329                 const char* sz3 = sz;
330                 while (!IsSpaceOrNewLine(*sz))++sz;
331                 std::string objectName = std::string(sz3,(unsigned int)(sz-sz3));
332 
333                 const unsigned int objStart = (unsigned int)meshes.size();
334 
335                 // There could be a material table in a separate file
336                 std::vector<ShadingInfo> materialTable;
337                 while (true)
338                 {
339                     AI_NFF2_GET_NEXT_TOKEN();
340 
341                     // material table - an external file
342                     if (TokenMatch(sz,"mtable",6))
343                     {
344                         SkipSpaces(&sz);
345                         sz3 = sz;
346                         while (!IsSpaceOrNewLine(*sz))++sz;
347                         const unsigned int diff = (unsigned int)(sz-sz3);
348                         if (!diff)DefaultLogger::get()->warn("NFF2: Found empty mtable token");
349                         else
350                         {
351                             // The material table has the file extension .mat.
352                             // If it is not there, we need to append it
353                             std::string path = std::string(sz3,diff);
354                             if(std::string::npos == path.find_last_of(".mat"))
355                             {
356                                 path.append(".mat");
357                             }
358 
359                             // Now extract the working directory from the path to
360                             // this file and append the material library filename
361                             // to it.
362                             std::string::size_type s;
363                             if ((std::string::npos == (s = path.find_last_of('\\')) || !s) &&
364                                 (std::string::npos == (s = path.find_last_of('/'))  || !s) )
365                             {
366                                 s = pFile.find_last_of('\\');
367                                 if (std::string::npos == s)s = pFile.find_last_of('/');
368                                 if (std::string::npos != s)
369                                 {
370                                     path = pFile.substr(0,s+1) + path;
371                                 }
372                             }
373                             LoadNFF2MaterialTable(materialTable,path,pIOHandler);
374                         }
375                     }
376                     else break;
377                 }
378 
379                 // read the numbr of vertices
380                 unsigned int num = ::strtoul10(sz,&sz);
381 
382                 // temporary storage
383                 std::vector<aiColor4D>  tempColors;
384                 std::vector<aiVector3D> tempPositions,tempTextureCoords,tempNormals;
385 
386                 bool hasNormals = false,hasUVs = false,hasColor = false;
387 
388                 tempPositions.reserve      (num);
389                 tempColors.reserve         (num);
390                 tempNormals.reserve        (num);
391                 tempTextureCoords.reserve  (num);
392                 for (unsigned int i = 0; i < num; ++i)
393                 {
394                     AI_NFF2_GET_NEXT_TOKEN();
395                     aiVector3D v;
396                     AI_NFF_PARSE_TRIPLE(v);
397                     tempPositions.push_back(v);
398 
399                     // parse all other attributes in the line
400                     while (true)
401                     {
402                         SkipSpaces(&sz);
403                         if (IsLineEnd(*sz))break;
404 
405                         // color definition
406                         if (TokenMatch(sz,"0x",2))
407                         {
408                             hasColor = true;
409                             unsigned int numIdx = ::strtoul16(sz,&sz);
410                             aiColor4D clr;
411                             clr.a = 1.f;
412 
413                             // 0xRRGGBB
414                             clr.r = ((numIdx >> 16u) & 0xff) / 255.f;
415                             clr.g = ((numIdx >> 8u)  & 0xff) / 255.f;
416                             clr.b = ((numIdx)        & 0xff) / 255.f;
417                             tempColors.push_back(clr);
418                         }
419                         // normal vector
420                         else if (TokenMatch(sz,"norm",4))
421                         {
422                             hasNormals = true;
423                             AI_NFF_PARSE_TRIPLE(v);
424                             tempNormals.push_back(v);
425                         }
426                         // UV coordinate
427                         else if (TokenMatch(sz,"uv",2))
428                         {
429                             hasUVs = true;
430                             AI_NFF_PARSE_FLOAT(v.x);
431                             AI_NFF_PARSE_FLOAT(v.y);
432                             v.z = 0.f;
433                             tempTextureCoords.push_back(v);
434                         }
435                     }
436 
437                     // fill in dummies for all attributes that have not been set
438                     if (tempNormals.size() != tempPositions.size())
439                         tempNormals.push_back(vQNAN);
440 
441                     if (tempTextureCoords.size() != tempPositions.size())
442                         tempTextureCoords.push_back(vQNAN);
443 
444                     if (tempColors.size() != tempPositions.size())
445                         tempColors.push_back(cQNAN);
446                 }
447 
448                 AI_NFF2_GET_NEXT_TOKEN();
449                 if (!num)throw DeadlyImportError("NFF2: There are zero vertices");
450                 num = ::strtoul10(sz,&sz);
451 
452                 std::vector<unsigned int> tempIdx;
453                 tempIdx.reserve(10);
454                 for (unsigned int i = 0; i < num; ++i)
455                 {
456                     AI_NFF2_GET_NEXT_TOKEN();
457                     SkipSpaces(line,&sz);
458                     unsigned int numIdx = ::strtoul10(sz,&sz);
459 
460                     // read all faces indices
461                     if (numIdx)
462                     {
463                         // mesh.faces.push_back(numIdx);
464                         // tempIdx.erase(tempIdx.begin(),tempIdx.end());
465                         tempIdx.resize(numIdx);
466 
467                         for (unsigned int a = 0; a < numIdx;++a)
468                         {
469                             SkipSpaces(sz,&sz);
470                             m = ::strtoul10(sz,&sz);
471                             if (m >= (unsigned int)tempPositions.size())
472                             {
473                                 DefaultLogger::get()->error("NFF2: Vertex index overflow");
474                                 m= 0;
475                             }
476                             // mesh.vertices.push_back (tempPositions[idx]);
477                             tempIdx[a] = m;
478                         }
479                     }
480 
481                     // build a temporary shader object for the face.
482                     ShadingInfo shader;
483                     unsigned int matIdx = 0;
484 
485                     // white material color - we have vertex colors
486                     shader.color = aiColor3D(1.f,1.f,1.f);
487                     aiColor4D c  = aiColor4D(1.f,1.f,1.f,1.f);
488                     while (true)
489                     {
490                         SkipSpaces(sz,&sz);
491                         if(IsLineEnd(*sz))break;
492 
493                         // per-polygon colors
494                         if (TokenMatch(sz,"0x",2))
495                         {
496                             hasColor = true;
497                             const char* sz2 = sz;
498                             numIdx = ::strtoul16(sz,&sz);
499                             const unsigned int diff = (unsigned int)(sz-sz2);
500 
501                             // 0xRRGGBB
502                             if (diff > 3)
503                             {
504                                 c.r = ((numIdx >> 16u) & 0xff) / 255.f;
505                                 c.g = ((numIdx >> 8u)  & 0xff) / 255.f;
506                                 c.b = ((numIdx)        & 0xff) / 255.f;
507                             }
508                             // 0xRGB
509                             else
510                             {
511                                 c.r = ((numIdx >> 8u) & 0xf) / 16.f;
512                                 c.g = ((numIdx >> 4u) & 0xf) / 16.f;
513                                 c.b = ((numIdx)       & 0xf) / 16.f;
514                             }
515                         }
516                         // TODO - implement texture mapping here
517 #if 0
518                         // mirror vertex texture coordinate?
519                         else if (TokenMatch(sz,"mirror",6))
520                         {
521                         }
522                         // texture coordinate scaling
523                         else if (TokenMatch(sz,"scale",5))
524                         {
525                         }
526                         // texture coordinate translation
527                         else if (TokenMatch(sz,"trans",5))
528                         {
529                         }
530                         // texture coordinate rotation angle
531                         else if (TokenMatch(sz,"rot",3))
532                         {
533                         }
534 #endif
535 
536                         // texture file name for this polygon + mapping information
537                         else if ('_' == sz[0])
538                         {
539                             // get mapping information
540                             switch (sz[1])
541                             {
542                             case 'v':
543                             case 'V':
544 
545                                 shader.shaded = false;
546                                 break;
547 
548                             case 't':
549                             case 'T':
550                             case 'u':
551                             case 'U':
552 
553                                 DefaultLogger::get()->warn("Unsupported NFF2 texture attribute: trans");
554                             };
555                             if (!sz[1] || '_' != sz[2])
556                             {
557                                 DefaultLogger::get()->warn("NFF2: Expected underscore after texture attributes");
558                                 continue;
559                             }
560                             const char* sz2 = sz+3;
561                             while (!IsSpaceOrNewLine( *sz ))++sz;
562                             const unsigned int diff = (unsigned int)(sz-sz2);
563                             if (diff)shader.texFile = std::string(sz2,diff);
564                         }
565 
566                         // Two-sided material?
567                         else if (TokenMatch(sz,"both",4))
568                         {
569                             shader.twoSided = true;
570                         }
571 
572                         // Material ID?
573                         else if (!materialTable.empty() && TokenMatch(sz,"matid",5))
574                         {
575                             SkipSpaces(&sz);
576                             matIdx = ::strtoul10(sz,&sz);
577                             if (matIdx >= materialTable.size())
578                             {
579                                 DefaultLogger::get()->error("NFF2: Material index overflow.");
580                                 matIdx = 0;
581                             }
582 
583                             // now combine our current shader with the shader we
584                             // read from the material table.
585                             ShadingInfo& mat = materialTable[matIdx];
586                             shader.ambient   = mat.ambient;
587                             shader.diffuse   = mat.diffuse;
588                             shader.emissive  = mat.emissive;
589                             shader.opacity   = mat.opacity;
590                             shader.specular  = mat.specular;
591                             shader.shininess = mat.shininess;
592                         }
593                         else SkipToken(sz);
594                     }
595 
596                     // search the list of all shaders we have for this object whether
597                     // there is an identical one. In this case, we append our mesh
598                     // data to it.
599                     MeshInfo* mesh = NULL;
600                     for (std::vector<MeshInfo>::iterator it = meshes.begin() + objStart, end = meshes.end();
601                          it != end; ++it)
602                     {
603                         if ((*it).shader == shader && (*it).matIndex == matIdx)
604                         {
605                             // we have one, we can append our data to it
606                             mesh = &(*it);
607                         }
608                     }
609                     if (!mesh)
610                     {
611                         meshes.push_back(MeshInfo(PatchType_Simple,false));
612                         mesh = &meshes.back();
613                         mesh->matIndex = matIdx;
614 
615                         // We need to add a new mesh to the list. We assign
616                         // an unique name to it to make sure the scene will
617                         // pass the validation step for the moment.
618                         // TODO: fix naming of objects in the scenegraph later
619                         if (objectName.length())
620                         {
621                             ::strcpy(mesh->name,objectName.c_str());
622                             ASSIMP_itoa10(&mesh->name[objectName.length()],30,subMeshIdx++);
623                         }
624 
625                         // copy the shader to the mesh.
626                         mesh->shader = shader;
627                     }
628 
629                     // fill the mesh with data
630                     if (!tempIdx.empty())
631                     {
632                         mesh->faces.push_back((unsigned int)tempIdx.size());
633                         for (std::vector<unsigned int>::const_iterator it = tempIdx.begin(), end = tempIdx.end();
634                             it != end;++it)
635                         {
636                             m = *it;
637 
638                             // copy colors -vertex color specifications override polygon color specifications
639                             if (hasColor)
640                             {
641                                 const aiColor4D& clr = tempColors[m];
642                                 mesh->colors.push_back((is_qnan( clr.r ) ? c : clr));
643                             }
644 
645                             // positions should always be there
646                             mesh->vertices.push_back (tempPositions[m]);
647 
648                             // copy normal vectors
649                             if (hasNormals)
650                                 mesh->normals.push_back  (tempNormals[m]);
651 
652                             // copy texture coordinates
653                             if (hasUVs)
654                                 mesh->uvs.push_back      (tempTextureCoords[m]);
655                         }
656                     }
657                 }
658                 if (!num)throw DeadlyImportError("NFF2: There are zero faces");
659             }
660         }
661         camLookAt = camLookAt + camPos;
662     }
663     else // "Normal" Neutral file format that is quite more common
664     {
665         while (GetNextLine(buffer,line))
666         {
667             sz = line;
668             if ('p' == line[0] || TokenMatch(sz,"tpp",3))
669             {
670                 MeshInfo* out = NULL;
671 
672                 // 'tpp' - texture polygon patch primitive
673                 if ('t' == line[0])
674                 {
675                     currentMeshWithUVCoords = NULL;
676                     for (auto &mesh : meshesWithUVCoords)
677                     {
678                         if (mesh.shader == s)
679                         {
680                             currentMeshWithUVCoords = &mesh;
681                             break;
682                         }
683                     }
684 
685                     if (!currentMeshWithUVCoords)
686                     {
687                         meshesWithUVCoords.push_back(MeshInfo(PatchType_UVAndNormals));
688                         currentMeshWithUVCoords = &meshesWithUVCoords.back();
689                         currentMeshWithUVCoords->shader = s;
690                     }
691                     out = currentMeshWithUVCoords;
692                 }
693                 // 'pp' - polygon patch primitive
694                 else if ('p' == line[1])
695                 {
696                     currentMeshWithNormals = NULL;
697                     for (auto &mesh : meshesWithNormals)
698                     {
699                         if (mesh.shader == s)
700                         {
701                             currentMeshWithNormals = &mesh;
702                             break;
703                         }
704                     }
705 
706                     if (!currentMeshWithNormals)
707                     {
708                         meshesWithNormals.push_back(MeshInfo(PatchType_Normals));
709                         currentMeshWithNormals = &meshesWithNormals.back();
710                         currentMeshWithNormals->shader = s;
711                     }
712                     sz = &line[2];out = currentMeshWithNormals;
713                 }
714                 // 'p' - polygon primitive
715                 else
716                 {
717                     currentMesh = NULL;
718                     for (auto &mesh : meshes)
719                     {
720                         if (mesh.shader == s)
721                         {
722                             currentMesh = &mesh;
723                             break;
724                         }
725                     }
726 
727                     if (!currentMesh)
728                     {
729                         meshes.push_back(MeshInfo(PatchType_Simple));
730                         currentMesh = &meshes.back();
731                         currentMesh->shader = s;
732                     }
733                     sz = &line[1];out = currentMesh;
734                 }
735                 SkipSpaces(sz,&sz);
736                 m = strtoul10(sz);
737 
738                 // ---- flip the face order
739                 out->vertices.resize(out->vertices.size()+m);
740                 if (out != currentMesh)
741                 {
742                     out->normals.resize(out->vertices.size());
743                 }
744                 if (out == currentMeshWithUVCoords)
745                 {
746                     out->uvs.resize(out->vertices.size());
747                 }
748                 for (unsigned int n = 0; n < m;++n)
749                 {
750                     if(!GetNextLine(buffer,line))
751                     {
752                         DefaultLogger::get()->error("NFF: Unexpected EOF was encountered. Patch definition incomplete");
753                         continue;
754                     }
755 
756                     aiVector3D v; sz = &line[0];
757                     AI_NFF_PARSE_TRIPLE(v);
758                     out->vertices[out->vertices.size()-n-1] = v;
759 
760                     if (out != currentMesh)
761                     {
762                         AI_NFF_PARSE_TRIPLE(v);
763                         out->normals[out->vertices.size()-n-1] = v;
764                     }
765                     if (out == currentMeshWithUVCoords)
766                     {
767                         // FIX: in one test file this wraps over multiple lines
768                         SkipSpaces(&sz);
769                         if (IsLineEnd(*sz))
770                         {
771                             GetNextLine(buffer,line);
772                             sz = line;
773                         }
774                         AI_NFF_PARSE_FLOAT(v.x);
775                         SkipSpaces(&sz);
776                         if (IsLineEnd(*sz))
777                         {
778                             GetNextLine(buffer,line);
779                             sz = line;
780                         }
781                         AI_NFF_PARSE_FLOAT(v.y);
782                         v.y = 1.f - v.y;
783                         out->uvs[out->vertices.size()-n-1] = v;
784                     }
785                 }
786                 out->faces.push_back(m);
787             }
788             // 'f' - shading information block
789             else if (TokenMatch(sz,"f",1))
790             {
791                 float d;
792 
793                 // read the RGB colors
794                 AI_NFF_PARSE_TRIPLE(s.color);
795 
796                 // read the other properties
797                 AI_NFF_PARSE_FLOAT(s.diffuse.r);
798                 AI_NFF_PARSE_FLOAT(s.specular.r);
799                 AI_NFF_PARSE_FLOAT(d); // skip shininess and transmittance
800                 AI_NFF_PARSE_FLOAT(d);
801                 AI_NFF_PARSE_FLOAT(s.refracti);
802 
803                 // NFF2 uses full colors here so we need to use them too
804                 // although NFF uses simple scaling factors
805                 s.diffuse.g  = s.diffuse.b = s.diffuse.r;
806                 s.specular.g = s.specular.b = s.specular.r;
807 
808                 // if the next one is NOT a number we assume it is a texture file name
809                 // this feature is used by some NFF files on the internet and it has
810                 // been implemented as it can be really useful
811                 SkipSpaces(&sz);
812                 if (!IsNumeric(*sz))
813                 {
814                     // TODO: Support full file names with spaces and quotation marks ...
815                     const char* p = sz;
816                     while (!IsSpaceOrNewLine( *sz ))++sz;
817 
818                     unsigned int diff = (unsigned int)(sz-p);
819                     if (diff)
820                     {
821                         s.texFile = std::string(p,diff);
822                     }
823                 }
824                 else
825                 {
826                     AI_NFF_PARSE_FLOAT(s.ambient); // optional
827                 }
828             }
829             // 'shader' - other way to specify a texture
830             else if (TokenMatch(sz,"shader",6))
831             {
832                 SkipSpaces(&sz);
833                 const char* old = sz;
834                 while (!IsSpaceOrNewLine(*sz))++sz;
835                 s.texFile = std::string(old, (uintptr_t)sz - (uintptr_t)old);
836             }
837             // 'l' - light source
838             else if (TokenMatch(sz,"l",1))
839             {
840                 lights.push_back(Light());
841                 Light& light = lights.back();
842 
843                 AI_NFF_PARSE_TRIPLE(light.position);
844                 AI_NFF_PARSE_FLOAT (light.intensity);
845                 AI_NFF_PARSE_TRIPLE(light.color);
846             }
847             // 's' - sphere
848             else if (TokenMatch(sz,"s",1))
849             {
850                 meshesLocked.push_back(MeshInfo(PatchType_Simple,true));
851                 MeshInfo& currentMesh = meshesLocked.back();
852                 currentMesh.shader = s;
853                 currentMesh.shader.mapping = aiTextureMapping_SPHERE;
854 
855                 AI_NFF_PARSE_SHAPE_INFORMATION();
856 
857                 // we don't need scaling or translation here - we do it in the node's transform
858                 StandardShapes::MakeSphere(iTesselation, currentMesh.vertices);
859                 currentMesh.faces.resize(currentMesh.vertices.size()/3,3);
860 
861                 // generate a name for the mesh
862                 ::ai_snprintf(currentMesh.name,128,"sphere_%i",sphere++);
863             }
864             // 'dod' - dodecahedron
865             else if (TokenMatch(sz,"dod",3))
866             {
867                 meshesLocked.push_back(MeshInfo(PatchType_Simple,true));
868                 MeshInfo& currentMesh = meshesLocked.back();
869                 currentMesh.shader = s;
870                 currentMesh.shader.mapping = aiTextureMapping_SPHERE;
871 
872                 AI_NFF_PARSE_SHAPE_INFORMATION();
873 
874                 // we don't need scaling or translation here - we do it in the node's transform
875                 StandardShapes::MakeDodecahedron(currentMesh.vertices);
876                 currentMesh.faces.resize(currentMesh.vertices.size()/3,3);
877 
878                 // generate a name for the mesh
879                 ::ai_snprintf(currentMesh.name,128,"dodecahedron_%i",dodecahedron++);
880             }
881 
882             // 'oct' - octahedron
883             else if (TokenMatch(sz,"oct",3))
884             {
885                 meshesLocked.push_back(MeshInfo(PatchType_Simple,true));
886                 MeshInfo& currentMesh = meshesLocked.back();
887                 currentMesh.shader = s;
888                 currentMesh.shader.mapping = aiTextureMapping_SPHERE;
889 
890                 AI_NFF_PARSE_SHAPE_INFORMATION();
891 
892                 // we don't need scaling or translation here - we do it in the node's transform
893                 StandardShapes::MakeOctahedron(currentMesh.vertices);
894                 currentMesh.faces.resize(currentMesh.vertices.size()/3,3);
895 
896                 // generate a name for the mesh
897                 ::ai_snprintf(currentMesh.name,128,"octahedron_%i",octahedron++);
898             }
899 
900             // 'tet' - tetrahedron
901             else if (TokenMatch(sz,"tet",3))
902             {
903                 meshesLocked.push_back(MeshInfo(PatchType_Simple,true));
904                 MeshInfo& currentMesh = meshesLocked.back();
905                 currentMesh.shader = s;
906                 currentMesh.shader.mapping = aiTextureMapping_SPHERE;
907 
908                 AI_NFF_PARSE_SHAPE_INFORMATION();
909 
910                 // we don't need scaling or translation here - we do it in the node's transform
911                 StandardShapes::MakeTetrahedron(currentMesh.vertices);
912                 currentMesh.faces.resize(currentMesh.vertices.size()/3,3);
913 
914                 // generate a name for the mesh
915                 ::ai_snprintf(currentMesh.name,128,"tetrahedron_%i",tetrahedron++);
916             }
917 
918             // 'hex' - hexahedron
919             else if (TokenMatch(sz,"hex",3))
920             {
921                 meshesLocked.push_back(MeshInfo(PatchType_Simple,true));
922                 MeshInfo& currentMesh = meshesLocked.back();
923                 currentMesh.shader = s;
924                 currentMesh.shader.mapping = aiTextureMapping_BOX;
925 
926                 AI_NFF_PARSE_SHAPE_INFORMATION();
927 
928                 // we don't need scaling or translation here - we do it in the node's transform
929                 StandardShapes::MakeHexahedron(currentMesh.vertices);
930                 currentMesh.faces.resize(currentMesh.vertices.size()/3,3);
931 
932                 // generate a name for the mesh
933                 ::ai_snprintf(currentMesh.name,128,"hexahedron_%i",hexahedron++);
934             }
935             // 'c' - cone
936             else if (TokenMatch(sz,"c",1))
937             {
938                 meshesLocked.push_back(MeshInfo(PatchType_Simple,true));
939                 MeshInfo& currentMesh = meshesLocked.back();
940                 currentMesh.shader = s;
941                 currentMesh.shader.mapping = aiTextureMapping_CYLINDER;
942 
943                 if(!GetNextLine(buffer,line))
944                 {
945                     DefaultLogger::get()->error("NFF: Unexpected end of file (cone definition not complete)");
946                     break;
947                 }
948                 sz = line;
949 
950                 // read the two center points and the respective radii
951                 aiVector3D center1, center2; float radius1, radius2;
952                 AI_NFF_PARSE_TRIPLE(center1);
953                 AI_NFF_PARSE_FLOAT(radius1);
954 
955                 if(!GetNextLine(buffer,line))
956                 {
957                     DefaultLogger::get()->error("NFF: Unexpected end of file (cone definition not complete)");
958                     break;
959                 }
960                 sz = line;
961 
962                 AI_NFF_PARSE_TRIPLE(center2);
963                 AI_NFF_PARSE_FLOAT(radius2);
964 
965                 // compute the center point of the cone/cylinder -
966                 // it is its local transformation origin
967                 currentMesh.dir    =  center2-center1;
968                 currentMesh.center =  center1+currentMesh.dir/2.f;
969 
970                 float f;
971                 if (( f = currentMesh.dir.Length()) < 10e-3f )
972                 {
973                     DefaultLogger::get()->error("NFF: Cone height is close to zero");
974                     continue;
975                 }
976                 currentMesh.dir /= f; // normalize
977 
978                 // generate the cone - it consists of simple triangles
979                 StandardShapes::MakeCone(f, radius1, radius2,
980                     integer_pow(4, iTesselation), currentMesh.vertices);
981 
982                 // MakeCone() returns tris
983                 currentMesh.faces.resize(currentMesh.vertices.size()/3,3);
984 
985                 // generate a name for the mesh. 'cone' if it a cone,
986                 // 'cylinder' if it is a cylinder. Funny, isn't it?
987                 if (radius1 != radius2)
988                     ::ai_snprintf(currentMesh.name,128,"cone_%i",cone++);
989                 else ::ai_snprintf(currentMesh.name,128,"cylinder_%i",cylinder++);
990             }
991             // 'tess' - tesselation
992             else if (TokenMatch(sz,"tess",4))
993             {
994                 SkipSpaces(&sz);
995                 iTesselation = strtoul10(sz);
996             }
997             // 'from' - camera position
998             else if (TokenMatch(sz,"from",4))
999             {
1000                 AI_NFF_PARSE_TRIPLE(camPos);
1001                 hasCam = true;
1002             }
1003             // 'at' - camera look-at vector
1004             else if (TokenMatch(sz,"at",2))
1005             {
1006                 AI_NFF_PARSE_TRIPLE(camLookAt);
1007                 hasCam = true;
1008             }
1009             // 'up' - camera up vector
1010             else if (TokenMatch(sz,"up",2))
1011             {
1012                 AI_NFF_PARSE_TRIPLE(camUp);
1013                 hasCam = true;
1014             }
1015             // 'angle' - (half?) camera field of view
1016             else if (TokenMatch(sz,"angle",5))
1017             {
1018                 AI_NFF_PARSE_FLOAT(angle);
1019                 hasCam = true;
1020             }
1021             // 'resolution' - used to compute the screen aspect
1022             else if (TokenMatch(sz,"resolution",10))
1023             {
1024                 AI_NFF_PARSE_FLOAT(resolution.x);
1025                 AI_NFF_PARSE_FLOAT(resolution.y);
1026                 hasCam = true;
1027             }
1028             // 'pb' - bezier patch. Not supported yet
1029             else if (TokenMatch(sz,"pb",2))
1030             {
1031                 DefaultLogger::get()->error("NFF: Encountered unsupported ID: bezier patch");
1032             }
1033             // 'pn' - NURBS. Not supported yet
1034             else if (TokenMatch(sz,"pn",2) || TokenMatch(sz,"pnn",3))
1035             {
1036                 DefaultLogger::get()->error("NFF: Encountered unsupported ID: NURBS");
1037             }
1038             // '' - comment
1039             else if ('#' == line[0])
1040             {
1041                 const char* sz;SkipSpaces(&line[1],&sz);
1042                 if (!IsLineEnd(*sz))DefaultLogger::get()->info(sz);
1043             }
1044         }
1045     }
1046 
1047     // copy all arrays into one large
1048     meshes.reserve (meshes.size()+meshesLocked.size()+meshesWithNormals.size()+meshesWithUVCoords.size());
1049     meshes.insert  (meshes.end(),meshesLocked.begin(),meshesLocked.end());
1050     meshes.insert  (meshes.end(),meshesWithNormals.begin(),meshesWithNormals.end());
1051     meshes.insert  (meshes.end(),meshesWithUVCoords.begin(),meshesWithUVCoords.end());
1052 
1053     // now generate output meshes. first find out how many meshes we'll need
1054     std::vector<MeshInfo>::const_iterator it = meshes.begin(), end = meshes.end();
1055     for (;it != end;++it)
1056     {
1057         if (!(*it).faces.empty())
1058         {
1059             ++pScene->mNumMeshes;
1060             if ((*it).name[0])++numNamed;
1061         }
1062     }
1063 
1064     // generate a dummy root node - assign all unnamed elements such
1065     // as polygons and polygon patches to the root node and generate
1066     // sub nodes for named objects such as spheres and cones.
1067     aiNode* const root = new aiNode();
1068     root->mName.Set("<NFF_Root>");
1069     root->mNumChildren = numNamed + (hasCam ? 1 : 0) + (unsigned int) lights.size();
1070     root->mNumMeshes = pScene->mNumMeshes-numNamed;
1071 
1072     aiNode** ppcChildren = NULL;
1073     unsigned int* pMeshes = NULL;
1074     if (root->mNumMeshes)
1075         pMeshes = root->mMeshes = new unsigned int[root->mNumMeshes];
1076     if (root->mNumChildren)
1077         ppcChildren = root->mChildren = new aiNode*[root->mNumChildren];
1078 
1079     // generate the camera
1080     if (hasCam)
1081     {
1082         aiNode* nd = *ppcChildren = new aiNode();
1083         nd->mName.Set("<NFF_Camera>");
1084         nd->mParent = root;
1085 
1086         // allocate the camera in the scene
1087         pScene->mNumCameras = 1;
1088         pScene->mCameras = new aiCamera*[1];
1089         aiCamera* c = pScene->mCameras[0] = new aiCamera;
1090 
1091         c->mName = nd->mName; // make sure the names are identical
1092         c->mHorizontalFOV = AI_DEG_TO_RAD( angle );
1093         c->mLookAt      = camLookAt - camPos;
1094         c->mPosition    = camPos;
1095         c->mUp          = camUp;
1096 
1097         // If the resolution is not specified in the file, we
1098         // need to set 1.0 as aspect.
1099         c->mAspect      = (!resolution.y ? 0.f : resolution.x / resolution.y);
1100         ++ppcChildren;
1101     }
1102 
1103     // generate light sources
1104     if (!lights.empty())
1105     {
1106         pScene->mNumLights = (unsigned int)lights.size();
1107         pScene->mLights = new aiLight*[pScene->mNumLights];
1108         for (unsigned int i = 0; i < pScene->mNumLights;++i,++ppcChildren)
1109         {
1110             const Light& l = lights[i];
1111 
1112             aiNode* nd = *ppcChildren  = new aiNode();
1113             nd->mParent = root;
1114 
1115             nd->mName.length = ::ai_snprintf(nd->mName.data,1024,"<NFF_Light%u>",i);
1116 
1117             // allocate the light in the scene data structure
1118             aiLight* out = pScene->mLights[i] = new aiLight();
1119             out->mName = nd->mName; // make sure the names are identical
1120             out->mType = aiLightSource_POINT;
1121             out->mColorDiffuse = out->mColorSpecular = l.color * l.intensity;
1122             out->mPosition = l.position;
1123         }
1124     }
1125 
1126     if (!pScene->mNumMeshes)throw DeadlyImportError("NFF: No meshes loaded");
1127     pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
1128     pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials = pScene->mNumMeshes];
1129     for (it = meshes.begin(), m = 0; it != end;++it)
1130     {
1131         if ((*it).faces.empty())continue;
1132 
1133         const MeshInfo& src = *it;
1134         aiMesh* const mesh = pScene->mMeshes[m] = new aiMesh();
1135         mesh->mNumVertices = (unsigned int)src.vertices.size();
1136         mesh->mNumFaces = (unsigned int)src.faces.size();
1137 
1138         // Generate sub nodes for named meshes
1139         if ( src.name[ 0 ] && NULL != ppcChildren  ) {
1140             aiNode* const node = *ppcChildren = new aiNode();
1141             node->mParent = root;
1142             node->mNumMeshes = 1;
1143             node->mMeshes = new unsigned int[1];
1144             node->mMeshes[0] = m;
1145             node->mName.Set(src.name);
1146 
1147             // setup the transformation matrix of the node
1148             aiMatrix4x4::FromToMatrix(aiVector3D(0.f,1.f,0.f),
1149                 src.dir,node->mTransformation);
1150 
1151             aiMatrix4x4& mat = node->mTransformation;
1152             mat.a1 *= src.radius.x; mat.b1 *= src.radius.x; mat.c1 *= src.radius.x;
1153             mat.a2 *= src.radius.y; mat.b2 *= src.radius.y; mat.c2 *= src.radius.y;
1154             mat.a3 *= src.radius.z; mat.b3 *= src.radius.z; mat.c3 *= src.radius.z;
1155             mat.a4 = src.center.x;
1156             mat.b4 = src.center.y;
1157             mat.c4 = src.center.z;
1158 
1159             ++ppcChildren;
1160         } else {
1161             *pMeshes++ = m;
1162         }
1163 
1164         // copy vertex positions
1165         mesh->mVertices = new aiVector3D[mesh->mNumVertices];
1166         ::memcpy(mesh->mVertices,&src.vertices[0],
1167             sizeof(aiVector3D)*mesh->mNumVertices);
1168 
1169         // NFF2: there could be vertex colors
1170         if (!src.colors.empty())
1171         {
1172             ai_assert(src.colors.size() == src.vertices.size());
1173 
1174             // copy vertex colors
1175             mesh->mColors[0] = new aiColor4D[mesh->mNumVertices];
1176             ::memcpy(mesh->mColors[0],&src.colors[0],
1177                 sizeof(aiColor4D)*mesh->mNumVertices);
1178         }
1179 
1180         if (!src.normals.empty())
1181         {
1182             ai_assert(src.normals.size() == src.vertices.size());
1183 
1184             // copy normal vectors
1185             mesh->mNormals = new aiVector3D[mesh->mNumVertices];
1186             ::memcpy(mesh->mNormals,&src.normals[0],
1187                 sizeof(aiVector3D)*mesh->mNumVertices);
1188         }
1189 
1190         if (!src.uvs.empty())
1191         {
1192             ai_assert(src.uvs.size() == src.vertices.size());
1193 
1194             // copy texture coordinates
1195             mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
1196             ::memcpy(mesh->mTextureCoords[0],&src.uvs[0],
1197                 sizeof(aiVector3D)*mesh->mNumVertices);
1198         }
1199 
1200         // generate faces
1201         unsigned int p = 0;
1202         aiFace* pFace = mesh->mFaces = new aiFace[mesh->mNumFaces];
1203         for (std::vector<unsigned int>::const_iterator it2 = src.faces.begin(),
1204             end2 = src.faces.end();
1205             it2 != end2;++it2,++pFace)
1206         {
1207             pFace->mIndices = new unsigned int [ pFace->mNumIndices = *it2 ];
1208             for (unsigned int o = 0; o < pFace->mNumIndices;++o)
1209                 pFace->mIndices[o] = p++;
1210         }
1211 
1212         // generate a material for the mesh
1213         aiMaterial* pcMat = (aiMaterial*)(pScene->mMaterials[m] = new aiMaterial());
1214 
1215         mesh->mMaterialIndex = m++;
1216 
1217         aiString s;
1218         s.Set(AI_DEFAULT_MATERIAL_NAME);
1219         pcMat->AddProperty(&s, AI_MATKEY_NAME);
1220 
1221         // FIX: Ignore diffuse == 0
1222         aiColor3D c = src.shader.color * (src.shader.diffuse.r ?  src.shader.diffuse : aiColor3D(1.f,1.f,1.f));
1223         pcMat->AddProperty(&c,1,AI_MATKEY_COLOR_DIFFUSE);
1224         c = src.shader.color * src.shader.specular;
1225         pcMat->AddProperty(&c,1,AI_MATKEY_COLOR_SPECULAR);
1226 
1227         // NFF2 - default values for NFF
1228         pcMat->AddProperty(&src.shader.ambient, 1,AI_MATKEY_COLOR_AMBIENT);
1229         pcMat->AddProperty(&src.shader.emissive,1,AI_MATKEY_COLOR_EMISSIVE);
1230         pcMat->AddProperty(&src.shader.opacity, 1,AI_MATKEY_OPACITY);
1231 
1232         // setup the first texture layer, if existing
1233         if (src.shader.texFile.length())
1234         {
1235             s.Set(src.shader.texFile);
1236             pcMat->AddProperty(&s,AI_MATKEY_TEXTURE_DIFFUSE(0));
1237 
1238             if (aiTextureMapping_UV != src.shader.mapping) {
1239 
1240                 aiVector3D v(0.f,-1.f,0.f);
1241                 pcMat->AddProperty(&v, 1,AI_MATKEY_TEXMAP_AXIS_DIFFUSE(0));
1242                 pcMat->AddProperty((int*)&src.shader.mapping, 1,AI_MATKEY_MAPPING_DIFFUSE(0));
1243             }
1244         }
1245 
1246         // setup the name of the material
1247         if (src.shader.name.length())
1248         {
1249             s.Set(src.shader.texFile);
1250             pcMat->AddProperty(&s,AI_MATKEY_NAME);
1251         }
1252 
1253         // setup some more material properties that are specific to NFF2
1254         int i;
1255         if (src.shader.twoSided)
1256         {
1257             i = 1;
1258             pcMat->AddProperty(&i,1,AI_MATKEY_TWOSIDED);
1259         }
1260         i = (src.shader.shaded ? aiShadingMode_Gouraud : aiShadingMode_NoShading);
1261         if (src.shader.shininess)
1262         {
1263             i = aiShadingMode_Phong;
1264             pcMat->AddProperty(&src.shader.shininess,1,AI_MATKEY_SHININESS);
1265         }
1266         pcMat->AddProperty(&i,1,AI_MATKEY_SHADING_MODEL);
1267     }
1268     pScene->mRootNode = root;
1269 }
1270 
1271 #endif // !! ASSIMP_BUILD_NO_NFF_IMPORTER
1272