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