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