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