1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkOBJReader.cxx
5 
6   Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7   All rights reserved.
8   See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9 
10      This software is distributed WITHOUT ANY WARRANTY; without even
11      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12      PURPOSE.  See the above copyright notice for more information.
13 
14 =========================================================================*/
15 #include "vtkOBJReader.h"
16 
17 #include "vtkCellArray.h"
18 #include "vtkFloatArray.h"
19 #include "vtkInformation.h"
20 #include "vtkInformationVector.h"
21 #include "vtkObjectFactory.h"
22 #include "vtkPointData.h"
23 #include "vtkPolyData.h"
24 #include <ctype.h>
25 
26 vtkStandardNewMacro(vtkOBJReader);
27 
28 // Description:
29 // Instantiate object with NULL filename.
vtkOBJReader()30 vtkOBJReader::vtkOBJReader()
31 {
32   this->FileName = NULL;
33 
34   this->SetNumberOfInputPorts(0);
35 }
36 
~vtkOBJReader()37 vtkOBJReader::~vtkOBJReader()
38 {
39   delete [] this->FileName;
40   this->FileName = NULL;
41 }
42 
43 /*---------------------------------------------------------------------------*\
44 
45 This is only partial support for the OBJ format, which is quite complicated.
46 To find a full specification, search the net for "OBJ format", eg.:
47 
48     http://en.wikipedia.org/wiki/Obj
49     http://netghost.narod.ru/gff/graphics/summary/waveobj.htm
50 
51 We support the following types:
52 
53 v <x> <y> <z>
54 
55     vertex
56 
57 vn <x> <y> <z>
58 
59     vertex normal
60 
61 vt <x> <y>
62 
63     texture coordinate
64 
65 f <v_a> <v_b> <v_c> ...
66 
67     polygonal face linking vertices v_a, v_b, v_c, etc. which
68     are 1-based indices into the vertex list
69 
70 f <v_a>/<t_a> <v_b>/<t_b> ...
71 
72     polygonal face as above, but with texture coordinates for
73     each vertex. t_a etc. are 1-based indices into the texture
74     coordinates list (from the vt lines)
75 
76 f <v_a>/<t_a>/<n_a> <v_b>/<t_b>/<n_b> ...
77 
78     polygonal face as above, with a normal at each vertex, as a
79     1-based index into the normals list (from the vn lines)
80 
81 f <v_a>//<n_a> <v_b>//<n_b> ...
82 
83     polygonal face as above but without texture coordinates.
84 
85     Per-face tcoords and normals are supported by duplicating
86     the vertices on each face as necessary.
87 
88 l <v_a> <v_b> ...
89 
90     lines linking vertices v_a, v_b, etc. which are 1-based
91     indices into the vertex list
92 
93 p <v_a> <v_b> ...
94 
95     points located at the vertices v_a, v_b, etc. which are 1-based
96     indices into the vertex list
97 
98 \*---------------------------------------------------------------------------*/
99 
100 
RequestData(vtkInformation * vtkNotUsed (request),vtkInformationVector ** vtkNotUsed (inputVector),vtkInformationVector * outputVector)101 int vtkOBJReader::RequestData(
102   vtkInformation *vtkNotUsed(request),
103   vtkInformationVector **vtkNotUsed(inputVector),
104   vtkInformationVector *outputVector)
105 {
106   // get the info object
107   vtkInformation *outInfo = outputVector->GetInformationObject(0);
108 
109   // get the ouptut
110   vtkPolyData *output = vtkPolyData::SafeDownCast(
111     outInfo->Get(vtkDataObject::DATA_OBJECT()));
112 
113   if (!this->FileName)
114     {
115     vtkErrorMacro(<< "A FileName must be specified.");
116     return 0;
117     }
118 
119   FILE *in = fopen(this->FileName,"r");
120 
121   if (in == NULL)
122     {
123     vtkErrorMacro(<< "File " << this->FileName << " not found");
124     return 0;
125     }
126 
127   vtkDebugMacro(<<"Reading file");
128 
129   // intialise some structures to store the file contents in
130   vtkPoints *points = vtkPoints::New();
131   vtkFloatArray *tcoords = vtkFloatArray::New();
132   tcoords->SetNumberOfComponents(2);
133   tcoords->SetName("TCoords");
134   vtkFloatArray *normals = vtkFloatArray::New();
135   normals->SetNumberOfComponents(3);
136   normals->SetName("Normals");
137   vtkCellArray *polys = vtkCellArray::New();
138   vtkCellArray *tcoord_polys = vtkCellArray::New();
139 
140   vtkCellArray *pointElems = vtkCellArray::New();
141   vtkCellArray *lineElems = vtkCellArray::New();
142   vtkCellArray *normal_polys = vtkCellArray::New();
143 
144   bool hasTCoords = false;
145   bool hasNormals = false;
146   bool tcoords_same_as_verts = true;
147   bool normals_same_as_verts = true;
148   bool everything_ok = true; // (use of this flag avoids early return and associated memory leak)
149 
150   // -- work through the file line by line, assigning into the above 7 structures as appropriate --
151 
152   { // (make a local scope section to emphasise that the variables below are only used here)
153 
154   const int MAX_LINE = 1024;
155   char rawLine[MAX_LINE];
156   float xyz[3];
157 
158   int lineNr = 0;
159   int numPoints = 0;
160   while (everything_ok && fgets(rawLine, MAX_LINE, in) != NULL)
161     {
162     lineNr++;
163     char *pLine = rawLine;
164     char *pEnd = rawLine + strlen(rawLine);
165 
166     // find the first non-whitespace character
167     while (isspace(*pLine) && pLine < pEnd) { pLine++; }
168 
169     // this first non-whitespace is the command
170     const char *cmd = pLine;
171 
172     // skip over non-whitespace
173     while (!isspace(*pLine) && pLine < pEnd) { pLine++; }
174 
175     // terminate command
176     if (pLine < pEnd)
177       {
178       *pLine = '\0';
179       pLine++;
180       }
181 
182     // in the OBJ format the first characters determine how to interpret the line:
183     if (strcmp(cmd, "v") == 0)
184       {
185       // this is a vertex definition, expect three floats, separated by whitespace:
186       if (sscanf(pLine, "%f %f %f", xyz, xyz+1, xyz+2) == 3)
187         {
188         points->InsertNextPoint(xyz);
189         numPoints++;
190         }
191       else
192         {
193         vtkErrorMacro(<<"Error reading 'v' at line " << lineNr);
194         everything_ok = false;
195         }
196       }
197     else if (strcmp(cmd, "vt") == 0)
198       {
199       // this is a tcoord, expect two floats, separated by whitespace:
200       if (sscanf(pLine, "%f %f", xyz, xyz+1) == 2)
201         {
202         tcoords->InsertNextTuple(xyz);
203         }
204       else
205         {
206         vtkErrorMacro(<<"Error reading 'vt' at line " << lineNr);
207         everything_ok = false;
208         }
209       }
210     else if (strcmp(cmd, "vn") == 0)
211       {
212       // this is a normal, expect three floats, separated by whitespace:
213       if (sscanf(pLine, "%f %f %f", xyz, xyz+1, xyz+2) == 3)
214         {
215         normals->InsertNextTuple(xyz);
216         hasNormals = true;
217         }
218       else
219         {
220         vtkErrorMacro(<<"Error reading 'vn' at line " << lineNr);
221         everything_ok = false;
222         }
223       }
224     else if (strcmp(cmd, "p") == 0)
225       {
226       // this is a point definition, consisting of 1-based indices separated by whitespace and /
227       pointElems->InsertNextCell(0); // we don't yet know how many points are to come
228 
229       int nVerts=0; // keep a count of how many there are
230 
231       while (everything_ok && pLine < pEnd)
232         {
233         // find next non-whitespace character
234         while (isspace(*pLine) && pLine < pEnd) { pLine++; }
235 
236         if (pLine < pEnd)         // there is still data left on this line
237           {
238           int iVert;
239           if (sscanf(pLine, "%d", &iVert) == 1)
240             {
241             if (iVert < 0)
242               {
243               pointElems->InsertCellPoint(numPoints+iVert);
244               }
245             else
246               {
247               pointElems->InsertCellPoint(iVert-1);
248               }
249             nVerts++;
250             }
251           else if (strcmp(pLine, "\\\n") == 0)
252             {
253             // handle backslash-newline continuation
254             if (fgets(rawLine, MAX_LINE, in) != NULL)
255               {
256               lineNr++;
257               pLine = rawLine;
258               pEnd = rawLine + strlen(rawLine);
259               continue;
260               }
261             else
262               {
263               vtkErrorMacro(<<"Error reading continuation line at line " << lineNr);
264               everything_ok = false;
265               }
266             }
267           else
268             {
269             vtkErrorMacro(<<"Error reading 'p' at line " << lineNr);
270             everything_ok = false;
271             }
272           // skip over what we just sscanf'd
273           // (find the first whitespace character)
274           while (!isspace(*pLine) && pLine < pEnd) { pLine++; }
275           }
276         }
277 
278       if (nVerts < 1)
279         {
280         vtkErrorMacro
281         (
282             <<"Error reading file near line " << lineNr
283             << " while processing the 'p' command"
284         );
285         everything_ok = false;
286         }
287 
288       // now we know how many points there were in this cell
289       pointElems->UpdateCellCount(nVerts);
290       }
291     else if (strcmp(cmd, "l") == 0)
292       {
293       // this is a line definition, consisting of 1-based indices separated by whitespace and /
294       lineElems->InsertNextCell(0); // we don't yet know how many points are to come
295 
296       int nVerts=0; // keep a count of how many there are
297 
298       while (everything_ok && pLine < pEnd)
299         {
300         // find next non-whitespace character
301         while (isspace(*pLine) && pLine < pEnd) { pLine++; }
302 
303         if (pLine < pEnd)         // there is still data left on this line
304           {
305           int iVert, dummyInt;
306           if (sscanf(pLine, "%d/%d", &iVert, &dummyInt) == 2)
307             {
308             // we simply ignore texture information
309             if (iVert < 0)
310               {
311               lineElems->InsertCellPoint(numPoints+iVert);
312               }
313             else
314               {
315               lineElems->InsertCellPoint(iVert-1);
316               }
317             nVerts++;
318             }
319           else if (sscanf(pLine, "%d", &iVert) == 1)
320             {
321             if (iVert < 0)
322               {
323               lineElems->InsertCellPoint(numPoints+iVert);
324               }
325             else
326               {
327               lineElems->InsertCellPoint(iVert-1);
328               }
329             nVerts++;
330             }
331           else if (strcmp(pLine, "\\\n") == 0)
332             {
333             // handle backslash-newline continuation
334             if (fgets(rawLine, MAX_LINE, in) != NULL)
335               {
336               lineNr++;
337               pLine = rawLine;
338               pEnd = rawLine + strlen(rawLine);
339               continue;
340               }
341             else
342               {
343               vtkErrorMacro(<<"Error reading continuation line at line " << lineNr);
344               everything_ok = false;
345               }
346             }
347           else
348             {
349             vtkErrorMacro(<<"Error reading 'l' at line " << lineNr);
350             everything_ok = false;
351             }
352           // skip over what we just sscanf'd
353           // (find the first whitespace character)
354           while (!isspace(*pLine) && pLine < pEnd) { pLine++; }
355           }
356         }
357 
358       if (nVerts < 2)
359         {
360         vtkErrorMacro
361         (
362             <<"Error reading file near line " << lineNr
363             << " while processing the 'l' command"
364         );
365         everything_ok = false;
366         }
367 
368       // now we know how many points there were in this cell
369       lineElems->UpdateCellCount(nVerts);
370       }
371     else if (strcmp(cmd, "f") == 0)
372       {
373       // this is a face definition, consisting of 1-based indices separated by whitespace and /
374 
375       polys->InsertNextCell(0); // we don't yet know how many points are to come
376       tcoord_polys->InsertNextCell(0);
377       normal_polys->InsertNextCell(0);
378 
379       int nVerts=0, nTCoords=0, nNormals=0; // keep a count of how many of each there are
380 
381       while (everything_ok && pLine < pEnd)
382         {
383         // find the first non-whitespace character
384         while (isspace(*pLine) && pLine < pEnd) { pLine++; }
385 
386         if (pLine < pEnd)         // there is still data left on this line
387           {
388           int iVert,iTCoord,iNormal;
389           if (sscanf(pLine, "%d/%d/%d", &iVert, &iTCoord, &iNormal) == 3)
390             {
391             if (iVert < 0)
392               {
393               polys->InsertCellPoint(numPoints+iVert);
394               }
395             else
396               {
397               polys->InsertCellPoint(iVert-1);
398               }
399             nVerts++;
400             tcoord_polys->InsertCellPoint(iTCoord-1);
401             nTCoords++;
402             normal_polys->InsertCellPoint(iNormal-1);
403             nNormals++;
404             if (iTCoord != iVert)
405               tcoords_same_as_verts = false;
406             if (iNormal != iVert)
407               normals_same_as_verts = false;
408             }
409           else if (sscanf(pLine, "%d//%d", &iVert, &iNormal) == 2)
410             {
411             if (iVert < 0)
412               {
413               polys->InsertCellPoint(numPoints+iVert);
414               }
415             else
416               {
417               polys->InsertCellPoint(iVert-1);
418               }
419             nVerts++;
420             normal_polys->InsertCellPoint(iNormal-1);
421             nNormals++;
422             if (iNormal != iVert)
423               normals_same_as_verts = false;
424             }
425           else if (sscanf(pLine, "%d/%d", &iVert, &iTCoord) == 2)
426             {
427             if (iVert < 0)
428               {
429               polys->InsertCellPoint(numPoints+iVert);
430               }
431             else
432               {
433               polys->InsertCellPoint(iVert-1);
434               }
435             nVerts++;
436             tcoord_polys->InsertCellPoint(iTCoord-1);
437             nTCoords++;
438             if (iTCoord != iVert)
439               tcoords_same_as_verts = false;
440             }
441           else if (sscanf(pLine, "%d", &iVert) == 1)
442             {
443             if (iVert < 0)
444               {
445               polys->InsertCellPoint(numPoints+iVert);
446               }
447             else
448               {
449               polys->InsertCellPoint(iVert-1);
450               }
451             nVerts++;
452             }
453           else if (strcmp(pLine, "\\\n") == 0)
454             {
455             // handle backslash-newline continuation
456             if (fgets(rawLine, MAX_LINE, in) != NULL)
457               {
458               lineNr++;
459               pLine = rawLine;
460               pEnd = rawLine + strlen(rawLine);
461               continue;
462               }
463             else
464               {
465               vtkErrorMacro(<<"Error reading continuation line at line " << lineNr);
466               everything_ok = false;
467               }
468             }
469           else
470             {
471             vtkErrorMacro(<<"Error reading 'f' at line " << lineNr);
472             everything_ok = false;
473             }
474           // skip over what we just read
475           // (find the first whitespace character)
476           while (!isspace(*pLine) && pLine < pEnd) { pLine++; }
477           }
478         }
479 
480       // count of tcoords and normals must be equal to number of vertices or zero
481       if ( nVerts < 3 ||
482            (nTCoords > 0 && nTCoords != nVerts) ||
483            (nNormals > 0 && nNormals != nVerts)
484          )
485         {
486         vtkErrorMacro
487         (
488             <<"Error reading file near line " << lineNr
489             << " while processing the 'f' command"
490         );
491         everything_ok = false;
492         }
493 
494       // now we know how many points there were in this cell
495       polys->UpdateCellCount(nVerts);
496       tcoord_polys->UpdateCellCount(nTCoords);
497       normal_polys->UpdateCellCount(nNormals);
498 
499       // also make a note of whether any cells have tcoords, and whether any have normals
500       if (nTCoords > 0) { hasTCoords = true; }
501       if (nNormals > 0) { hasNormals = true; }
502       }
503     else
504       {
505       //vtkDebugMacro(<<"Ignoring line: "<<rawLine);
506       }
507 
508     } // (end of while loop)
509 
510   } // (end of local scope section)
511 
512   // we have finished with the file
513   fclose(in);
514 
515 
516   if (everything_ok)   // (otherwise just release allocated memory and return)
517     {
518     // -- now turn this lot into a useable vtkPolyData --
519 
520     // if there are no tcoords or normals or they match exactly
521     // then we can just copy the data into the output (easy!)
522     if (
523         (!hasTCoords || tcoords_same_as_verts) &&
524         (!hasNormals || normals_same_as_verts)
525        )
526       {
527       vtkDebugMacro(<<"Copying file data into the output directly");
528 
529       output->SetPoints(points);
530       if (pointElems->GetNumberOfCells())
531         {
532         output->SetVerts(pointElems);
533         }
534       if (lineElems->GetNumberOfCells())
535         {
536         output->SetLines(lineElems);
537         }
538       if (polys->GetNumberOfCells())
539         {
540         output->SetPolys(polys);
541         }
542 
543       // if there is an exact correspondence between tcoords and vertices then can simply
544       // assign the tcoords points as point data
545       if (hasTCoords && tcoords_same_as_verts)
546         {
547         output->GetPointData()->SetTCoords(tcoords);
548         }
549 
550       // if there is an exact correspondence between normals and vertices then can simply
551       // assign the normals as point data
552       if (hasNormals && normals_same_as_verts)
553         {
554         output->GetPointData()->SetNormals(normals);
555         }
556       output->Squeeze();
557       }
558     // otherwise we can duplicate the vertices as necessary (a bit slower)
559     else
560       {
561       vtkDebugMacro(<<"Duplicating vertices so that tcoords and normals are correct");
562 
563       vtkPoints *new_points = vtkPoints::New();
564       vtkFloatArray *new_tcoords = vtkFloatArray::New();
565       new_tcoords->SetName("TCoords");
566       new_tcoords->SetNumberOfComponents(2);
567       vtkFloatArray *new_normals = vtkFloatArray::New();
568       new_normals->SetNumberOfComponents(3);
569       new_normals->SetName("Normals");
570       vtkCellArray *new_polys = vtkCellArray::New();
571 
572       // for each poly, copy its vertices into new_points (and point at them)
573       // also copy its tcoords into new_tcoords
574       // also copy its normals into new_normals
575       polys->InitTraversal();
576       tcoord_polys->InitTraversal();
577       normal_polys->InitTraversal();
578 
579       vtkIdType dummy_warning_prevention_mechanism[1];
580       vtkIdType n_pts=-1,*pts=dummy_warning_prevention_mechanism;
581       vtkIdType n_tcoord_pts=-1,*tcoord_pts=dummy_warning_prevention_mechanism;
582       vtkIdType n_normal_pts=-1,*normal_pts=dummy_warning_prevention_mechanism;
583       for (int i=0; i<polys->GetNumberOfCells(); ++i)
584         {
585         polys->GetNextCell(n_pts,pts);
586         tcoord_polys->GetNextCell(n_tcoord_pts,tcoord_pts);
587         normal_polys->GetNextCell(n_normal_pts,normal_pts);
588 
589         // If some vertices have tcoords and not others (likewise normals)
590         // then we must do something else VTK will complain. (crash on render attempt)
591         // Easiest solution is to delete polys that don't have complete tcoords (if there
592         // are any tcoords in the dataset) or normals (if there are any normals in the dataset).
593 
594         if (
595             (n_pts != n_tcoord_pts && hasTCoords) ||
596             (n_pts != n_normal_pts && hasNormals)
597            )
598           {
599           // skip this poly
600           vtkDebugMacro(<<"Skipping poly "<<i+1<<" (1-based index)");
601           }
602         else
603           {
604           // copy the corresponding points, tcoords and normals across
605           for (int j=0; j<n_pts; ++j)
606             {
607             // copy the tcoord for this point across (if there is one)
608             if (n_tcoord_pts>0)
609               {
610               new_tcoords->InsertNextTuple(tcoords->GetTuple(tcoord_pts[j]));
611               }
612             // copy the normal for this point across (if there is one)
613             if (n_normal_pts>0)
614               {
615               new_normals->InsertNextTuple(normals->GetTuple(normal_pts[j]));
616               }
617             // copy the vertex into the new structure and update
618             // the vertex index in the polys structure (pts is a pointer into it)
619             pts[j] = new_points->InsertNextPoint(points->GetPoint(pts[j]));
620             }
621           // copy this poly (pointing at the new points) into the new polys list
622           new_polys->InsertNextCell(n_pts,pts);
623           }
624         }
625 
626       // use the new structures for the output
627       output->SetPoints(new_points);
628       output->SetPolys(new_polys);
629       if (hasTCoords)
630         {
631         output->GetPointData()->SetTCoords(new_tcoords);
632         }
633       if (hasNormals)
634         {
635         output->GetPointData()->SetNormals(new_normals);
636         }
637 
638       // TODO: fixup for pointElems and lineElems too
639 
640       output->Squeeze();
641 
642       new_points->Delete();
643       new_polys->Delete();
644       new_tcoords->Delete();
645       new_normals->Delete();
646       }
647     }
648 
649   points->Delete();
650   tcoords->Delete();
651   normals->Delete();
652   polys->Delete();
653   tcoord_polys->Delete();
654   normal_polys->Delete();
655 
656   lineElems->Delete();
657   pointElems->Delete();
658 
659   return 1;
660 }
661 
662 
PrintSelf(ostream & os,vtkIndent indent)663 void vtkOBJReader::PrintSelf(ostream& os, vtkIndent indent)
664 {
665   this->Superclass::PrintSelf(os,indent);
666 
667   os << indent << "File Name: "
668      << (this->FileName ? this->FileName : "(none)") << "\n";
669 
670 }
671 
672 
673 // ************************************************************************* //
674