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