001/*
002 * Renderer Models. The MIT License.
003 * Copyright (c) 2022 rlkraft@pnw.edu
004 * See LICENSE for details.
005*/
006
007package renderer.models_L;
008
009import renderer.scene.*;
010import renderer.scene.primitives.*;
011
012import java.util.Scanner;
013import java.io.File;
014import java.io.FileInputStream;
015import java.io.IOException;
016import java.io.FileNotFoundException;
017import java.util.regex.*;
018
019/**
020<p>
021   A simple demonstration of loading and drawing a basic OBJ file.
022<p>
023   A basic OBJ file is a text file that contains three kinds of lines:
024   lines that begin with the character {@code 'v'}, lines that begin
025   with the character {@code 'f'}, and lines that begin with the
026   character {@code '#'}.
027<p>
028   A line in an OBJ file that begins with {@code '#'} is a comment line
029   and can be ignored.
030<p>
031   A line in an OBJ file that begins with {@code 'v'} is a line that
032   describes a vertex in 3-dimensional space. The {@code 'v'} will always
033   be followed on the line by three doubles, the {@code x}, {@code y},
034   and {@code z} coordinates of the vertex.
035<p>
036   A line in an OBJ file that begins with {@code 'f'} is a line that
037   describes a "face". The {@code 'f'} will be followed on the line by
038   a sequence of positive integers. The integers are the indices of the
039   vertices that make up the face. The "index" of a vertex is the order
040   in which the vertex was listed in the OBJ file. So a line like this
041<pre>{@code
042      f  2  4  1
043}</pre>
044   would represent a triangle made up of the 2nd vertex read from the file,
045   the 4th vertex read from the file, and the 1st vertex read from the file.
046   And a line like this
047<pre>{@code
048      f  2  4  3  5
049}</pre>
050   would represent a quadrilateral made up of the 2nd vertex read from the file,
051   the 4th vertex read from the file, the 3rd vertex read from the file, and
052   the 5th vertex read from the file.
053<p>
054   See <a href="https://en.wikipedia.org/wiki/Wavefront_.obj_file" target="_top">
055                https://en.wikipedia.org/wiki/Wavefront_.obj_file</a>
056*/
057public class ObjSimpleModel extends Model
058{
059   /**
060      Create a wireframe model from the contents of an OBJ file.
061
062      @param objFile  {@link File} object for the OBJ data file
063   */
064   public ObjSimpleModel(final File objFile)
065   {
066      super(objFile.getPath());
067
068      // Open the OBJ file.
069      FileInputStream fis = null;
070      try
071      {
072         fis = new FileInputStream( objFile );
073      }
074      catch (FileNotFoundException e)
075      {
076         e.printStackTrace(System.err);
077         System.err.printf("ERROR! Could not find OBJ file: %s\n", objFile);
078         System.exit(-1);
079      }
080
081      // Get the geometry from the OBJ file.
082      try
083      {
084         // Pattern for parsing lines that start with "f".
085         final Pattern p = Pattern.compile("^(\\d*)[/]?(\\d*)[/]?(\\d*)");
086
087         final Scanner scanner = new Scanner(fis);
088         while ( scanner.hasNext() )
089         {
090            final String token = scanner.next();
091            if ( token.startsWith("#")
092              || token.startsWith("vt")
093              || token.startsWith("vn")
094              || token.startsWith("s")
095              || token.startsWith("g")
096              || token.startsWith("o")
097              || token.startsWith("usemtl")
098              || token.startsWith("mtllib") )
099            {
100               scanner.nextLine(); // skip over these lines
101            }
102            else if ( token.startsWith("v") )
103            {
104               final double x = scanner.nextDouble();
105               final double y = scanner.nextDouble();
106               final double z = scanner.nextDouble();
107               addVertex( new Vertex(x, y, z) );
108            }// parse vertex
109            else if ( token.startsWith("f") )
110            {
111               // Tokenize the rest of the line.
112               final String restOfLine = scanner.nextLine();
113               final Scanner scanner2 = new Scanner( restOfLine );
114               // Parse three vertices and make two line segments.
115               final int[] vIndex = new int[3];
116               for (int i = 0; i < 3; ++i)
117               {
118                  // Parse a "v/vt/vn" group.
119                  final String faceGroup = scanner2.next();
120                  final Matcher m = p.matcher( faceGroup );
121                  if ( m.find() )
122                  {
123                     vIndex[i] = Integer.parseInt(m.group(1)) - 1;
124                     final String vt = m.group(2);  // don't need
125                     final String vn = m.group(3);  // don't need
126                  }
127                  else
128                     System.err.println("Error: bad face: " + faceGroup);
129               }
130               addPrimitive(new LineSegment(vIndex[0], vIndex[1]),
131                            new LineSegment(vIndex[1], vIndex[2]));
132
133               // Parse another vertex (if there is one) and make a line segment.
134               while (scanner2.hasNext())
135               {
136                  vIndex[1] = vIndex[2];
137                  final String faceGroup = scanner2.next();
138                  final Matcher m = p.matcher( faceGroup );
139                  if ( m.find() )
140                  {
141                     vIndex[2] = Integer.parseInt(m.group(1)) - 1;
142                     final String vt = m.group(2);  // don't need
143                     final String vn = m.group(3);  // don't need
144                  }
145                  else
146                     System.err.println("Error: bad face: " + faceGroup);
147
148                  addPrimitive(new LineSegment(vIndex[1], vIndex[2]));
149               }
150               // Close the line loop around this face.
151               addPrimitive(new LineSegment(vIndex[2], vIndex[0]));
152            }// parse face
153         }// parse one line
154         fis.close();
155      }
156      catch (Exception e)
157      {
158         e.printStackTrace(System.err);
159         System.err.printf("ERROR! Could not read OBJ file: %s\n", objFile);
160         System.exit(-1);
161      }
162   }
163}//ObjSimpleModel