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 /** @file Implementation of the STL importer class */
45 
46 
47 #ifndef ASSIMP_BUILD_NO_NFF_IMPORTER
48 
49 // internal headers
50 #include "NFFLoader.h"
51 #include <assimp/ParsingUtils.h>
52 #include <assimp/StandardShapes.h>
53 #include <assimp/qnan.h>
54 #include <assimp/fast_atof.h>
55 #include <assimp/RemoveComments.h>
56 #include <assimp/IOSystem.hpp>
57 #include <assimp/DefaultLogger.hpp>
58 #include <assimp/scene.h>
59 #include <assimp/importerdesc.h>
60 #include <memory>
61 
62 
63 using namespace Assimp;
64 
65 static const aiImporterDesc desc = {
66     "Neutral File Format Importer",
67     "",
68     "",
69     "",
70     aiImporterFlags_SupportBinaryFlavour,
71     0,
72     0,
73     0,
74     0,
75     "enff nff"
76 };
77 
78 // ------------------------------------------------------------------------------------------------
79 // Constructor to be privately used by Importer
NFFImporter()80 NFFImporter::NFFImporter()
81 {}
82 
83 // ------------------------------------------------------------------------------------------------
84 // Destructor, private as well
~NFFImporter()85 NFFImporter::~NFFImporter()
86 {}
87 
88 // ------------------------------------------------------------------------------------------------
89 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem *,bool) const90 bool NFFImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool /*checkSig*/) const
91 {
92     return SimpleExtensionCheck(pFile,"nff","enff");
93 }
94 
95 // ------------------------------------------------------------------------------------------------
96 // Get the list of all supported file extensions
GetInfo() const97 const aiImporterDesc* NFFImporter::GetInfo () const
98 {
99     return &desc;
100 }
101 
102 // ------------------------------------------------------------------------------------------------
103 #define AI_NFF_PARSE_FLOAT(f) \
104     SkipSpaces(&sz); \
105     if (!::IsLineEnd(*sz))sz = fast_atoreal_move<float>(sz, (float&)f);
106 
107 // ------------------------------------------------------------------------------------------------
108 #define AI_NFF_PARSE_TRIPLE(v) \
109     AI_NFF_PARSE_FLOAT(v[0]) \
110     AI_NFF_PARSE_FLOAT(v[1]) \
111     AI_NFF_PARSE_FLOAT(v[2])
112 
113 // ------------------------------------------------------------------------------------------------
114 #define AI_NFF_PARSE_SHAPE_INFORMATION() \
115     aiVector3D center, radius(1.0f,get_qnan(),get_qnan()); \
116     AI_NFF_PARSE_TRIPLE(center); \
117     AI_NFF_PARSE_TRIPLE(radius); \
118     if (is_qnan(radius.z))radius.z = radius.x; \
119     if (is_qnan(radius.y))radius.y = radius.x; \
120     currentMesh.radius = radius; \
121     currentMesh.center = center;
122 
123 // ------------------------------------------------------------------------------------------------
124 #define AI_NFF2_GET_NEXT_TOKEN() \
125     do \
126     { \
127     if (!GetNextLine(buffer,line)) \
128         {ASSIMP_LOG_WARN("NFF2: Unexpected EOF, can't read next token");break;} \
129     SkipSpaces(line,&sz); \
130     } \
131     while(IsLineEnd(*sz))
132 
133 
134 // ------------------------------------------------------------------------------------------------
135 // Loads the material table for the NFF2 file format from an external file
LoadNFF2MaterialTable(std::vector<ShadingInfo> & output,const std::string & path,IOSystem * pIOHandler)136 void NFFImporter::LoadNFF2MaterialTable(std::vector<ShadingInfo>& output,
137     const std::string& path, IOSystem* pIOHandler)
138 {
139     std::unique_ptr<IOStream> file( pIOHandler->Open( path, "rb"));
140 
141     // Check whether we can read from the file
142     if( !file.get())    {
143         ASSIMP_LOG_ERROR("NFF2: Unable to open material library " + path + ".");
144         return;
145     }
146 
147     // get the size of the file
148     const unsigned int m = (unsigned int)file->FileSize();
149 
150     // allocate storage and copy the contents of the file to a memory buffer
151     // (terminate it with zero)
152     std::vector<char> mBuffer2(m+1);
153     TextFileToBuffer(file.get(),mBuffer2);
154     const char* buffer = &mBuffer2[0];
155 
156     // First of all: remove all comments from the file
157     CommentRemover::RemoveLineComments("//",&mBuffer2[0]);
158 
159     // The file should start with the magic sequence "mat"
160     if (!TokenMatch(buffer,"mat",3))    {
161         ASSIMP_LOG_ERROR_F("NFF2: Not a valid material library ", path, ".");
162         return;
163     }
164 
165     ShadingInfo* curShader = NULL;
166 
167     // No read the file line per line
168     char line[4096];
169     const char* sz;
170     while (GetNextLine(buffer,line))
171     {
172         SkipSpaces(line,&sz);
173 
174         // 'version' defines the version of the file format
175         if (TokenMatch(sz,"version",7))
176         {
177             ASSIMP_LOG_INFO_F("NFF (Sense8) material library file format: ", std::string(sz));
178         }
179         // 'matdef' starts a new material in the file
180         else if (TokenMatch(sz,"matdef",6))
181         {
182             // add a new material to the list
183             output.push_back( ShadingInfo() );
184             curShader = & output.back();
185 
186             // parse the name of the material
187         }
188         else if (!TokenMatch(sz,"valid",5))
189         {
190             // check whether we have an active material at the moment
191             if (!IsLineEnd(*sz))
192             {
193                 if (!curShader)
194                 {
195                     ASSIMP_LOG_ERROR_F("NFF2 material library: Found element ", sz, "but there is no active material");
196                     continue;
197                 }
198             }
199             else continue;
200 
201             // now read the material property and determine its type
202             aiColor3D c;
203             if (TokenMatch(sz,"ambient",7))
204             {
205                 AI_NFF_PARSE_TRIPLE(c);
206                 curShader->ambient = c;
207             }
208             else if (TokenMatch(sz,"diffuse",7) || TokenMatch(sz,"ambientdiffuse",14) /* correct? */)
209             {
210                 AI_NFF_PARSE_TRIPLE(c);
211                 curShader->diffuse = curShader->ambient = c;
212             }
213             else if (TokenMatch(sz,"specular",8))
214             {
215                 AI_NFF_PARSE_TRIPLE(c);
216                 curShader->specular = c;
217             }
218             else if (TokenMatch(sz,"emission",8))
219             {
220                 AI_NFF_PARSE_TRIPLE(c);
221                 curShader->emissive = c;
222             }
223             else if (TokenMatch(sz,"shininess",9))
224             {
225                 AI_NFF_PARSE_FLOAT(curShader->shininess);
226             }
227             else if (TokenMatch(sz,"opacity",7))
228             {
229                 AI_NFF_PARSE_FLOAT(curShader->opacity);
230             }
231         }
232     }
233 }
234 
235 // ------------------------------------------------------------------------------------------------
236 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)237 void NFFImporter::InternReadFile( const std::string& pFile,
238     aiScene* pScene, IOSystem* pIOHandler)
239 {
240     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
241 
242     // Check whether we can read from the file
243     if( !file.get())
244         throw DeadlyImportError( "Failed to open NFF file " + pFile + ".");
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 tessellation
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                 ASSIMP_LOG_INFO_F("NFF (Sense8) file format: ", 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)ASSIMP_LOG_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                             unsigned int m = ::strtoul10(sz,&sz);
471                             if (m >= (unsigned int)tempPositions.size())
472                             {
473                                 ASSIMP_LOG_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                                 ASSIMP_LOG_WARN("Unsupported NFF2 texture attribute: trans");
554                             };
555                             if (!sz[1] || '_' != sz[2])
556                             {
557                                 ASSIMP_LOG_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                                 ASSIMP_LOG_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                             unsigned int 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                 unsigned int 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                         ASSIMP_LOG_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                     ASSIMP_LOG_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                     ASSIMP_LOG_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/(ai_real)2.0;
969 
970                 float f;
971                 if (( f = currentMesh.dir.Length()) < 10e-3f )
972                 {
973                     ASSIMP_LOG_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' - tessellation
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                 ASSIMP_LOG_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                 ASSIMP_LOG_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)) {
1043                     ASSIMP_LOG_INFO(sz);
1044                 }
1045             }
1046         }
1047     }
1048 
1049     // copy all arrays into one large
1050     meshes.reserve (meshes.size()+meshesLocked.size()+meshesWithNormals.size()+meshesWithUVCoords.size());
1051     meshes.insert  (meshes.end(),meshesLocked.begin(),meshesLocked.end());
1052     meshes.insert  (meshes.end(),meshesWithNormals.begin(),meshesWithNormals.end());
1053     meshes.insert  (meshes.end(),meshesWithUVCoords.begin(),meshesWithUVCoords.end());
1054 
1055     // now generate output meshes. first find out how many meshes we'll need
1056     std::vector<MeshInfo>::const_iterator it = meshes.begin(), end = meshes.end();
1057     for (;it != end;++it)
1058     {
1059         if (!(*it).faces.empty())
1060         {
1061             ++pScene->mNumMeshes;
1062             if ((*it).name[0])++numNamed;
1063         }
1064     }
1065 
1066     // generate a dummy root node - assign all unnamed elements such
1067     // as polygons and polygon patches to the root node and generate
1068     // sub nodes for named objects such as spheres and cones.
1069     aiNode* const root = new aiNode();
1070     root->mName.Set("<NFF_Root>");
1071     root->mNumChildren = numNamed + (hasCam ? 1 : 0) + (unsigned int) lights.size();
1072     root->mNumMeshes = pScene->mNumMeshes-numNamed;
1073 
1074     aiNode** ppcChildren = NULL;
1075     unsigned int* pMeshes = NULL;
1076     if (root->mNumMeshes)
1077         pMeshes = root->mMeshes = new unsigned int[root->mNumMeshes];
1078     if (root->mNumChildren)
1079         ppcChildren = root->mChildren = new aiNode*[root->mNumChildren];
1080 
1081     // generate the camera
1082     if (hasCam)
1083     {
1084         ai_assert(ppcChildren);
1085         aiNode* nd = new aiNode();
1086         *ppcChildren = nd;
1087         nd->mName.Set("<NFF_Camera>");
1088         nd->mParent = root;
1089 
1090         // allocate the camera in the scene
1091         pScene->mNumCameras = 1;
1092         pScene->mCameras = new aiCamera*[1];
1093         aiCamera* c = pScene->mCameras[0] = new aiCamera;
1094 
1095         c->mName = nd->mName; // make sure the names are identical
1096         c->mHorizontalFOV = AI_DEG_TO_RAD( angle );
1097         c->mLookAt      = camLookAt - camPos;
1098         c->mPosition    = camPos;
1099         c->mUp          = camUp;
1100 
1101         // If the resolution is not specified in the file, we
1102         // need to set 1.0 as aspect.
1103         c->mAspect      = (!resolution.y ? 0.f : resolution.x / resolution.y);
1104         ++ppcChildren;
1105     }
1106 
1107     // generate light sources
1108     if (!lights.empty())
1109     {
1110         ai_assert(ppcChildren);
1111         pScene->mNumLights = (unsigned int)lights.size();
1112         pScene->mLights = new aiLight*[pScene->mNumLights];
1113         for (unsigned int i = 0; i < pScene->mNumLights;++i,++ppcChildren)
1114         {
1115             const Light& l = lights[i];
1116 
1117             aiNode* nd = new aiNode();
1118             *ppcChildren = nd;
1119             nd->mParent = root;
1120 
1121             nd->mName.length = ::ai_snprintf(nd->mName.data,1024,"<NFF_Light%u>",i);
1122 
1123             // allocate the light in the scene data structure
1124             aiLight* out = pScene->mLights[i] = new aiLight();
1125             out->mName = nd->mName; // make sure the names are identical
1126             out->mType = aiLightSource_POINT;
1127             out->mColorDiffuse = out->mColorSpecular = l.color * l.intensity;
1128             out->mPosition = l.position;
1129         }
1130     }
1131 
1132     if (!pScene->mNumMeshes)throw DeadlyImportError("NFF: No meshes loaded");
1133     pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
1134     pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials = pScene->mNumMeshes];
1135     unsigned int m = 0;
1136     for (it = meshes.begin(); it != end;++it)
1137     {
1138         if ((*it).faces.empty())continue;
1139 
1140         const MeshInfo& src = *it;
1141         aiMesh* const mesh = pScene->mMeshes[m] = new aiMesh();
1142         mesh->mNumVertices = (unsigned int)src.vertices.size();
1143         mesh->mNumFaces = (unsigned int)src.faces.size();
1144 
1145         // Generate sub nodes for named meshes
1146         if ( src.name[ 0 ] && NULL != ppcChildren  ) {
1147             aiNode* const node = *ppcChildren = new aiNode();
1148             node->mParent = root;
1149             node->mNumMeshes = 1;
1150             node->mMeshes = new unsigned int[1];
1151             node->mMeshes[0] = m;
1152             node->mName.Set(src.name);
1153 
1154             // setup the transformation matrix of the node
1155             aiMatrix4x4::FromToMatrix(aiVector3D(0.f,1.f,0.f),
1156                 src.dir,node->mTransformation);
1157 
1158             aiMatrix4x4& mat = node->mTransformation;
1159             mat.a1 *= src.radius.x; mat.b1 *= src.radius.x; mat.c1 *= src.radius.x;
1160             mat.a2 *= src.radius.y; mat.b2 *= src.radius.y; mat.c2 *= src.radius.y;
1161             mat.a3 *= src.radius.z; mat.b3 *= src.radius.z; mat.c3 *= src.radius.z;
1162             mat.a4 = src.center.x;
1163             mat.b4 = src.center.y;
1164             mat.c4 = src.center.z;
1165 
1166             ++ppcChildren;
1167         } else {
1168             *pMeshes++ = m;
1169         }
1170 
1171         // copy vertex positions
1172         mesh->mVertices = new aiVector3D[mesh->mNumVertices];
1173         ::memcpy(mesh->mVertices,&src.vertices[0],
1174             sizeof(aiVector3D)*mesh->mNumVertices);
1175 
1176         // NFF2: there could be vertex colors
1177         if (!src.colors.empty())
1178         {
1179             ai_assert(src.colors.size() == src.vertices.size());
1180 
1181             // copy vertex colors
1182             mesh->mColors[0] = new aiColor4D[mesh->mNumVertices];
1183             ::memcpy(mesh->mColors[0],&src.colors[0],
1184                 sizeof(aiColor4D)*mesh->mNumVertices);
1185         }
1186 
1187         if (!src.normals.empty())
1188         {
1189             ai_assert(src.normals.size() == src.vertices.size());
1190 
1191             // copy normal vectors
1192             mesh->mNormals = new aiVector3D[mesh->mNumVertices];
1193             ::memcpy(mesh->mNormals,&src.normals[0],
1194                 sizeof(aiVector3D)*mesh->mNumVertices);
1195         }
1196 
1197         if (!src.uvs.empty())
1198         {
1199             ai_assert(src.uvs.size() == src.vertices.size());
1200 
1201             // copy texture coordinates
1202             mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
1203             ::memcpy(mesh->mTextureCoords[0],&src.uvs[0],
1204                 sizeof(aiVector3D)*mesh->mNumVertices);
1205         }
1206 
1207         // generate faces
1208         unsigned int p = 0;
1209         aiFace* pFace = mesh->mFaces = new aiFace[mesh->mNumFaces];
1210         for (std::vector<unsigned int>::const_iterator it2 = src.faces.begin(),
1211             end2 = src.faces.end();
1212             it2 != end2;++it2,++pFace)
1213         {
1214             pFace->mIndices = new unsigned int [ pFace->mNumIndices = *it2 ];
1215             for (unsigned int o = 0; o < pFace->mNumIndices;++o)
1216                 pFace->mIndices[o] = p++;
1217         }
1218 
1219         // generate a material for the mesh
1220         aiMaterial* pcMat = (aiMaterial*)(pScene->mMaterials[m] = new aiMaterial());
1221 
1222         mesh->mMaterialIndex = m++;
1223 
1224         aiString s;
1225         s.Set(AI_DEFAULT_MATERIAL_NAME);
1226         pcMat->AddProperty(&s, AI_MATKEY_NAME);
1227 
1228         // FIX: Ignore diffuse == 0
1229         aiColor3D c = src.shader.color * (src.shader.diffuse.r ?  src.shader.diffuse : aiColor3D(1.f,1.f,1.f));
1230         pcMat->AddProperty(&c,1,AI_MATKEY_COLOR_DIFFUSE);
1231         c = src.shader.color * src.shader.specular;
1232         pcMat->AddProperty(&c,1,AI_MATKEY_COLOR_SPECULAR);
1233 
1234         // NFF2 - default values for NFF
1235         pcMat->AddProperty(&src.shader.ambient, 1,AI_MATKEY_COLOR_AMBIENT);
1236         pcMat->AddProperty(&src.shader.emissive,1,AI_MATKEY_COLOR_EMISSIVE);
1237         pcMat->AddProperty(&src.shader.opacity, 1,AI_MATKEY_OPACITY);
1238 
1239         // setup the first texture layer, if existing
1240         if (src.shader.texFile.length())
1241         {
1242             s.Set(src.shader.texFile);
1243             pcMat->AddProperty(&s,AI_MATKEY_TEXTURE_DIFFUSE(0));
1244 
1245             if (aiTextureMapping_UV != src.shader.mapping) {
1246 
1247                 aiVector3D v(0.f,-1.f,0.f);
1248                 pcMat->AddProperty(&v, 1,AI_MATKEY_TEXMAP_AXIS_DIFFUSE(0));
1249                 pcMat->AddProperty((int*)&src.shader.mapping, 1,AI_MATKEY_MAPPING_DIFFUSE(0));
1250             }
1251         }
1252 
1253         // setup the name of the material
1254         if (src.shader.name.length())
1255         {
1256             s.Set(src.shader.texFile);
1257             pcMat->AddProperty(&s,AI_MATKEY_NAME);
1258         }
1259 
1260         // setup some more material properties that are specific to NFF2
1261         int i;
1262         if (src.shader.twoSided)
1263         {
1264             i = 1;
1265             pcMat->AddProperty(&i,1,AI_MATKEY_TWOSIDED);
1266         }
1267         i = (src.shader.shaded ? aiShadingMode_Gouraud : aiShadingMode_NoShading);
1268         if (src.shader.shininess)
1269         {
1270             i = aiShadingMode_Phong;
1271             pcMat->AddProperty(&src.shader.shininess,1,AI_MATKEY_SHININESS);
1272         }
1273         pcMat->AddProperty(&i,1,AI_MATKEY_SHADING_MODEL);
1274     }
1275     pScene->mRootNode = root;
1276 }
1277 
1278 #endif // !! ASSIMP_BUILD_NO_NFF_IMPORTER
1279