001package net.minecraftforge.client.model.obj; 002 003import java.io.BufferedReader; 004import java.io.IOException; 005import java.io.InputStream; 006import java.io.InputStreamReader; 007import java.net.URL; 008import java.util.ArrayList; 009import java.util.regex.Matcher; 010import java.util.regex.Pattern; 011 012import net.minecraft.client.renderer.Tessellator; 013import net.minecraftforge.client.model.IModelCustom; 014import net.minecraftforge.client.model.ModelFormatException; 015 016import org.lwjgl.opengl.GL11; 017 018import cpw.mods.fml.relauncher.Side; 019import cpw.mods.fml.relauncher.SideOnly; 020 021/** 022 * Wavefront Object importer 023 * Based heavily off of the specifications found at http://en.wikipedia.org/wiki/Wavefront_.obj_file 024 */ 025@SideOnly(Side.CLIENT) 026public class WavefrontObject implements IModelCustom 027{ 028 029 private static Pattern vertexPattern = Pattern.compile("(v( (\\-){0,1}\\d+\\.\\d+){3,4} *\\n)|(v( (\\-){0,1}\\d+\\.\\d+){3,4} *$)"); 030 private static Pattern vertexNormalPattern = Pattern.compile("(vn( (\\-){0,1}\\d+\\.\\d+){3,4} *\\n)|(vn( (\\-){0,1}\\d+\\.\\d+){3,4} *$)"); 031 private static Pattern textureCoordinatePattern = Pattern.compile("(vt( (\\-){0,1}\\d+\\.\\d+){2,3} *\\n)|(vt( (\\-){0,1}\\d+\\.\\d+){2,3} *$)"); 032 private static Pattern face_V_VT_VN_Pattern = Pattern.compile("(f( \\d+/\\d+/\\d+){3,4} *\\n)|(f( \\d+/\\d+/\\d+){3,4} *$)"); 033 private static Pattern face_V_VT_Pattern = Pattern.compile("(f( \\d+/\\d+){3,4} *\\n)|(f( \\d+/\\d+){3,4} *$)"); 034 private static Pattern face_V_VN_Pattern = Pattern.compile("(f( \\d+//\\d+){3,4} *\\n)|(f( \\d+//\\d+){3,4} *$)"); 035 private static Pattern face_V_Pattern = Pattern.compile("(f( \\d+){3,4} *\\n)|(f( \\d+){3,4} *$)"); 036 private static Pattern groupObjectPattern = Pattern.compile("([go]( [\\w\\d]+) *\\n)|([go]( [\\w\\d]+) *$)"); 037 038 private static Matcher vertexMatcher, vertexNormalMatcher, textureCoordinateMatcher; 039 private static Matcher face_V_VT_VN_Matcher, face_V_VT_Matcher, face_V_VN_Matcher, face_V_Matcher; 040 private static Matcher groupObjectMatcher; 041 042 public ArrayList<Vertex> vertices = new ArrayList<Vertex>(); 043 public ArrayList<Vertex> vertexNormals = new ArrayList<Vertex>(); 044 public ArrayList<TextureCoordinate> textureCoordinates = new ArrayList<TextureCoordinate>(); 045 public ArrayList<GroupObject> groupObjects = new ArrayList<GroupObject>(); 046 private GroupObject currentGroupObject; 047 private String fileName; 048 049 public WavefrontObject(String fileName, URL resource) throws ModelFormatException 050 { 051 this.fileName = fileName; 052 loadObjModel(resource); 053 } 054 055 private void loadObjModel(URL fileURL) throws ModelFormatException 056 { 057 BufferedReader reader = null; 058 InputStream inputStream = null; 059 060 String currentLine = null; 061 int lineCount = 0; 062 063 try 064 { 065 inputStream = fileURL.openStream(); 066 reader = new BufferedReader(new InputStreamReader(inputStream)); 067 068 while ((currentLine = reader.readLine()) != null) 069 { 070 lineCount++; 071 currentLine = currentLine.replaceAll("\\s+", " ").trim(); 072 073 if (currentLine.startsWith("#") || currentLine.length() == 0) 074 { 075 continue; 076 } 077 else if (currentLine.startsWith("v ")) 078 { 079 Vertex vertex = parseVertex(currentLine, lineCount); 080 if (vertex != null) 081 { 082 vertices.add(vertex); 083 } 084 } 085 else if (currentLine.startsWith("vn ")) 086 { 087 Vertex vertex = parseVertexNormal(currentLine, lineCount); 088 if (vertex != null) 089 { 090 vertexNormals.add(vertex); 091 } 092 } 093 else if (currentLine.startsWith("vt ")) 094 { 095 TextureCoordinate textureCoordinate = parseTextureCoordinate(currentLine, lineCount); 096 if (textureCoordinate != null) 097 { 098 textureCoordinates.add(textureCoordinate); 099 } 100 } 101 else if (currentLine.startsWith("f ")) 102 { 103 104 if (currentGroupObject == null) 105 { 106 currentGroupObject = new GroupObject("Default"); 107 } 108 109 Face face = parseFace(currentLine, lineCount); 110 111 if (face != null) 112 { 113 currentGroupObject.faces.add(face); 114 } 115 } 116 else if (currentLine.startsWith("g ") | currentLine.startsWith("o ")) 117 { 118 GroupObject group = parseGroupObject(currentLine, lineCount); 119 120 if (group != null) 121 { 122 if (currentGroupObject != null) 123 { 124 groupObjects.add(currentGroupObject); 125 } 126 } 127 128 currentGroupObject = group; 129 } 130 } 131 132 groupObjects.add(currentGroupObject); 133 } 134 catch (IOException e) 135 { 136 throw new ModelFormatException("IO Exception reading model format", e); 137 } 138 finally 139 { 140 try 141 { 142 reader.close(); 143 } 144 catch (IOException e) 145 { 146 // hush 147 } 148 149 try 150 { 151 inputStream.close(); 152 } 153 catch (IOException e) 154 { 155 // hush 156 } 157 } 158 } 159 160 public void renderAll() 161 { 162 Tessellator tessellator = Tessellator.instance; 163 164 if (currentGroupObject != null) 165 { 166 tessellator.startDrawing(currentGroupObject.glDrawingMode); 167 } 168 else 169 { 170 tessellator.startDrawing(GL11.GL_TRIANGLES); 171 } 172 173 for (GroupObject groupObject : groupObjects) 174 { 175 groupObject.render(tessellator); 176 } 177 178 tessellator.draw(); 179 } 180 181 public void renderOnly(String... groupNames) 182 { 183 for (GroupObject groupObject : groupObjects) 184 { 185 for (String groupName : groupNames) 186 { 187 if (groupName.equalsIgnoreCase(groupObject.name)) 188 { 189 groupObject.render(); 190 } 191 } 192 } 193 } 194 195 public void renderPart(String partName) 196 { 197 for (GroupObject groupObject : groupObjects) 198 { 199 if (partName.equalsIgnoreCase(groupObject.name)) 200 { 201 groupObject.render(); 202 } 203 } 204 } 205 206 public void renderAllExcept(String... excludedGroupNames) 207 { 208 for (GroupObject groupObject : groupObjects) 209 { 210 for (String excludedGroupName : excludedGroupNames) 211 { 212 if (!excludedGroupName.equalsIgnoreCase(groupObject.name)) 213 { 214 groupObject.render(); 215 } 216 } 217 } 218 } 219 220 private Vertex parseVertex(String line, int lineCount) throws ModelFormatException 221 { 222 Vertex vertex = null; 223 224 if (isValidVertexLine(line)) 225 { 226 line = line.substring(line.indexOf(" ") + 1); 227 String[] tokens = line.split(" "); 228 229 try 230 { 231 if (tokens.length == 2) 232 { 233 return new Vertex(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1])); 234 } 235 else if (tokens.length == 3) 236 { 237 return new Vertex(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])); 238 } 239 } 240 catch (NumberFormatException e) 241 { 242 throw new ModelFormatException(String.format("Number formatting error at line %d",lineCount), e); 243 } 244 } 245 else 246 { 247 throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); 248 } 249 250 return vertex; 251 } 252 253 private Vertex parseVertexNormal(String line, int lineCount) throws ModelFormatException 254 { 255 Vertex vertexNormal = null; 256 257 if (isValidVertexNormalLine(line)) 258 { 259 line = line.substring(line.indexOf(" ") + 1); 260 String[] tokens = line.split(" "); 261 262 try 263 { 264 if (tokens.length == 3) 265 return new Vertex(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])); 266 } 267 catch (NumberFormatException e) 268 { 269 throw new ModelFormatException(String.format("Number formatting error at line %d",lineCount), e); 270 } 271 } 272 else 273 { 274 throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); 275 } 276 277 return vertexNormal; 278 } 279 280 private TextureCoordinate parseTextureCoordinate(String line, int lineCount) throws ModelFormatException 281 { 282 TextureCoordinate textureCoordinate = null; 283 284 if (isValidTextureCoordinateLine(line)) 285 { 286 line = line.substring(line.indexOf(" ") + 1); 287 String[] tokens = line.split(" "); 288 289 try 290 { 291 if (tokens.length == 2) 292 return new TextureCoordinate(Float.parseFloat(tokens[0]), 1 - Float.parseFloat(tokens[1])); 293 else if (tokens.length == 3) 294 return new TextureCoordinate(Float.parseFloat(tokens[0]), 1 - Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])); 295 } 296 catch (NumberFormatException e) 297 { 298 throw new ModelFormatException(String.format("Number formatting error at line %d",lineCount), e); 299 } 300 } 301 else 302 { 303 throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); 304 } 305 306 return textureCoordinate; 307 } 308 309 private Face parseFace(String line, int lineCount) throws ModelFormatException 310 { 311 Face face = null; 312 313 if (isValidFaceLine(line)) 314 { 315 face = new Face(); 316 317 String trimmedLine = line.substring(line.indexOf(" ") + 1); 318 String[] tokens = trimmedLine.split(" "); 319 String[] subTokens = null; 320 321 if (tokens.length == 3) 322 { 323 if (currentGroupObject.glDrawingMode == -1) 324 { 325 currentGroupObject.glDrawingMode = GL11.GL_TRIANGLES; 326 } 327 else if (currentGroupObject.glDrawingMode != GL11.GL_TRIANGLES) 328 { 329 throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Invalid number of points for face (expected 4, found " + tokens.length + ")"); 330 } 331 } 332 else if (tokens.length == 4) 333 { 334 if (currentGroupObject.glDrawingMode == -1) 335 { 336 currentGroupObject.glDrawingMode = GL11.GL_QUADS; 337 } 338 else if (currentGroupObject.glDrawingMode != GL11.GL_QUADS) 339 { 340 throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Invalid number of points for face (expected 3, found " + tokens.length + ")"); 341 } 342 } 343 344 face.vertices = new Vertex[tokens.length]; 345 face.textureCoordinates = new TextureCoordinate[tokens.length]; 346 face.vertexNormals = new Vertex[tokens.length]; 347 348 // f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ... 349 if (isValidFace_V_VT_VN_Line(line)) 350 { 351 for (int i = 0; i < tokens.length; ++i) 352 { 353 subTokens = tokens[i].split("/"); 354 355 face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1); 356 face.textureCoordinates[i] = textureCoordinates.get(Integer.parseInt(subTokens[1]) - 1); 357 face.vertexNormals[i] = vertexNormals.get(Integer.parseInt(subTokens[2]) - 1); 358 } 359 360 face.faceNormal = face.calculateFaceNormal(); 361 } 362 // f v1/vt1 v2/vt2 v3/vt3 ... 363 else if (isValidFace_V_VT_Line(line)) 364 { 365 for (int i = 0; i < tokens.length; ++i) 366 { 367 subTokens = tokens[i].split("/"); 368 369 face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1); 370 face.textureCoordinates[i] = textureCoordinates.get(Integer.parseInt(subTokens[1]) - 1); 371 } 372 373 face.faceNormal = face.calculateFaceNormal(); 374 } 375 // f v1//vn1 v2//vn2 v3//vn3 ... 376 else if (isValidFace_V_VN_Line(line)) 377 { 378 for (int i = 0; i < tokens.length; ++i) 379 { 380 subTokens = tokens[i].split("//"); 381 382 face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1); 383 face.vertexNormals[i] = vertexNormals.get(Integer.parseInt(subTokens[1]) - 1); 384 } 385 386 face.faceNormal = face.calculateFaceNormal(); 387 } 388 // f v1 v2 v3 ... 389 else if (isValidFace_V_Line(line)) 390 { 391 for (int i = 0; i < tokens.length; ++i) 392 { 393 face.vertices[i] = vertices.get(Integer.parseInt(tokens[i]) - 1); 394 } 395 396 face.faceNormal = face.calculateFaceNormal(); 397 } 398 else 399 { 400 throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); 401 } 402 } 403 else 404 { 405 throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); 406 } 407 408 return face; 409 } 410 411 private GroupObject parseGroupObject(String line, int lineCount) throws ModelFormatException 412 { 413 GroupObject group = null; 414 415 if (isValidGroupObjectLine(line)) 416 { 417 String trimmedLine = line.substring(line.indexOf(" ") + 1); 418 419 if (trimmedLine.length() > 0) 420 { 421 group = new GroupObject(trimmedLine); 422 } 423 } 424 else 425 { 426 throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); 427 } 428 429 return group; 430 } 431 432 /*** 433 * Verifies that the given line from the model file is a valid vertex 434 * @param line the line being validated 435 * @return true if the line is a valid vertex, false otherwise 436 */ 437 private static boolean isValidVertexLine(String line) 438 { 439 if (vertexMatcher != null) 440 { 441 vertexMatcher.reset(); 442 } 443 444 vertexMatcher = vertexPattern.matcher(line); 445 return vertexMatcher.matches(); 446 } 447 448 /*** 449 * Verifies that the given line from the model file is a valid vertex normal 450 * @param line the line being validated 451 * @return true if the line is a valid vertex normal, false otherwise 452 */ 453 private static boolean isValidVertexNormalLine(String line) 454 { 455 if (vertexNormalMatcher != null) 456 { 457 vertexNormalMatcher.reset(); 458 } 459 460 vertexNormalMatcher = vertexNormalPattern.matcher(line); 461 return vertexNormalMatcher.matches(); 462 } 463 464 /*** 465 * Verifies that the given line from the model file is a valid texture coordinate 466 * @param line the line being validated 467 * @return true if the line is a valid texture coordinate, false otherwise 468 */ 469 private static boolean isValidTextureCoordinateLine(String line) 470 { 471 if (textureCoordinateMatcher != null) 472 { 473 textureCoordinateMatcher.reset(); 474 } 475 476 textureCoordinateMatcher = textureCoordinatePattern.matcher(line); 477 return textureCoordinateMatcher.matches(); 478 } 479 480 /*** 481 * Verifies that the given line from the model file is a valid face that is described by vertices, texture coordinates, and vertex normals 482 * @param line the line being validated 483 * @return true if the line is a valid face that matches the format "f v1/vt1/vn1 ..." (with a minimum of 3 points in the face, and a maximum of 4), false otherwise 484 */ 485 private static boolean isValidFace_V_VT_VN_Line(String line) 486 { 487 if (face_V_VT_VN_Matcher != null) 488 { 489 face_V_VT_VN_Matcher.reset(); 490 } 491 492 face_V_VT_VN_Matcher = face_V_VT_VN_Pattern.matcher(line); 493 return face_V_VT_VN_Matcher.matches(); 494 } 495 496 /*** 497 * Verifies that the given line from the model file is a valid face that is described by vertices and texture coordinates 498 * @param line the line being validated 499 * @return true if the line is a valid face that matches the format "f v1/vt1 ..." (with a minimum of 3 points in the face, and a maximum of 4), false otherwise 500 */ 501 private static boolean isValidFace_V_VT_Line(String line) 502 { 503 if (face_V_VT_Matcher != null) 504 { 505 face_V_VT_Matcher.reset(); 506 } 507 508 face_V_VT_Matcher = face_V_VT_Pattern.matcher(line); 509 return face_V_VT_Matcher.matches(); 510 } 511 512 /*** 513 * Verifies that the given line from the model file is a valid face that is described by vertices and vertex normals 514 * @param line the line being validated 515 * @return true if the line is a valid face that matches the format "f v1//vn1 ..." (with a minimum of 3 points in the face, and a maximum of 4), false otherwise 516 */ 517 private static boolean isValidFace_V_VN_Line(String line) 518 { 519 if (face_V_VN_Matcher != null) 520 { 521 face_V_VN_Matcher.reset(); 522 } 523 524 face_V_VN_Matcher = face_V_VN_Pattern.matcher(line); 525 return face_V_VN_Matcher.matches(); 526 } 527 528 /*** 529 * Verifies that the given line from the model file is a valid face that is described by only vertices 530 * @param line the line being validated 531 * @return true if the line is a valid face that matches the format "f v1 ..." (with a minimum of 3 points in the face, and a maximum of 4), false otherwise 532 */ 533 private static boolean isValidFace_V_Line(String line) 534 { 535 if (face_V_Matcher != null) 536 { 537 face_V_Matcher.reset(); 538 } 539 540 face_V_Matcher = face_V_Pattern.matcher(line); 541 return face_V_Matcher.matches(); 542 } 543 544 /*** 545 * Verifies that the given line from the model file is a valid face of any of the possible face formats 546 * @param line the line being validated 547 * @return true if the line is a valid face that matches any of the valid face formats, false otherwise 548 */ 549 private static boolean isValidFaceLine(String line) 550 { 551 return isValidFace_V_VT_VN_Line(line) || isValidFace_V_VT_Line(line) || isValidFace_V_VN_Line(line) || isValidFace_V_Line(line); 552 } 553 554 /*** 555 * Verifies that the given line from the model file is a valid group (or object) 556 * @param line the line being validated 557 * @return true if the line is a valid group (or object), false otherwise 558 */ 559 private static boolean isValidGroupObjectLine(String line) 560 { 561 if (groupObjectMatcher != null) 562 { 563 groupObjectMatcher.reset(); 564 } 565 566 groupObjectMatcher = groupObjectPattern.matcher(line); 567 return groupObjectMatcher.matches(); 568 } 569 570 @Override 571 public String getType() 572 { 573 return "obj"; 574 } 575}