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