From 46e53fb461700be7fbd9df407f4cd1d764416815 Mon Sep 17 00:00:00 2001 From: pahimar Date: Tue, 9 Apr 2013 18:31:42 -0400 Subject: [PATCH] Wavefront object importer. Imports Wavefront object and offers some simple render methods for rendering the models in game. Modders have complete access to all the data in the model to write their own rendering related code as well. --- .../minecraftforge/client/model/obj/Face.java | 84 ++++ .../client/model/obj/GroupObject.java | 54 +++ .../client/model/obj/TextureCoordinate.java | 23 + .../client/model/obj/Vertex.java | 23 + .../client/model/obj/WavefrontObject.java | 447 ++++++++++++++++++ 5 files changed, 631 insertions(+) create mode 100644 client/net/minecraftforge/client/model/obj/Face.java create mode 100644 client/net/minecraftforge/client/model/obj/GroupObject.java create mode 100644 client/net/minecraftforge/client/model/obj/TextureCoordinate.java create mode 100644 client/net/minecraftforge/client/model/obj/Vertex.java create mode 100644 client/net/minecraftforge/client/model/obj/WavefrontObject.java diff --git a/client/net/minecraftforge/client/model/obj/Face.java b/client/net/minecraftforge/client/model/obj/Face.java new file mode 100644 index 000000000..f47951b1d --- /dev/null +++ b/client/net/minecraftforge/client/model/obj/Face.java @@ -0,0 +1,84 @@ +package net.minecraftforge.client.model.obj; + +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.Vec3; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class Face +{ + + public Vertex[] vertices; + public Vertex[] vertexNormals; + public Vertex faceNormal; + public TextureCoordinate[] textureCoordinates; + + public void addFaceForRender(Tessellator tessellator) + { + addFaceForRender(tessellator, 0.0005F); + } + + public void addFaceForRender(Tessellator tessellator, float textureOffset) + { + if (faceNormal == null) + { + faceNormal = this.calculateFaceNormal(); + } + + tessellator.setNormal(faceNormal.x, faceNormal.y, faceNormal.z); + + float averageU = 0F; + float averageV = 0F; + + if (textureCoordinates.length != 0) + { + for (int i = 0; i < textureCoordinates.length; ++i) + { + averageU += textureCoordinates[i].u; + averageV += textureCoordinates[i].v; + } + + averageU = averageU / textureCoordinates.length; + averageV = averageV / textureCoordinates.length; + } + + float offsetU, offsetV; + + for (int i = 0; i < vertices.length; ++i) + { + + if (textureCoordinates.length != 0) + { + offsetU = textureOffset; + offsetV = textureOffset; + + if (textureCoordinates[i].u > averageU) + { + offsetU = -offsetU; + } + if (textureCoordinates[i].v > averageV) + { + offsetV = -offsetV; + } + + tessellator.addVertexWithUV(vertices[i].x, vertices[i].y, vertices[i].z, textureCoordinates[i].u + offsetU, textureCoordinates[i].v + offsetV); + } + else + { + tessellator.addVertex(vertices[i].x, vertices[i].y, vertices[i].z); + } + } + } + + public Vertex calculateFaceNormal() + { + Vec3 v1 = Vec3.createVectorHelper(vertices[1].x - vertices[0].x, vertices[1].y - vertices[0].y, vertices[1].z - vertices[0].z); + Vec3 v2 = Vec3.createVectorHelper(vertices[2].x - vertices[0].x, vertices[2].y - vertices[0].y, vertices[2].z - vertices[0].z); + Vec3 normalVector = null; + + normalVector = v1.crossProduct(v2).normalize(); + + return new Vertex((float) normalVector.xCoord, (float) normalVector.yCoord, (float) normalVector.zCoord); + } +} diff --git a/client/net/minecraftforge/client/model/obj/GroupObject.java b/client/net/minecraftforge/client/model/obj/GroupObject.java new file mode 100644 index 000000000..0bf36af46 --- /dev/null +++ b/client/net/minecraftforge/client/model/obj/GroupObject.java @@ -0,0 +1,54 @@ +package net.minecraftforge.client.model.obj; + +import java.util.ArrayList; + +import net.minecraft.client.renderer.Tessellator; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class GroupObject +{ + + public String name; + public ArrayList faces = new ArrayList(); + public int glDrawingMode; + + public GroupObject() + { + this(""); + } + + public GroupObject(String name) + { + this(name, -1); + } + + public GroupObject(String name, int glDrawingMode) + { + this.name = name; + this.glDrawingMode = glDrawingMode; + } + + public void render() + { + if (faces.size() > 0) + { + Tessellator tessellator = Tessellator.instance; + tessellator.startDrawing(glDrawingMode); + render(tessellator); + tessellator.draw(); + } + } + + public void render(Tessellator tessellator) + { + if (faces.size() > 0) + { + for (Face face : faces) + { + face.addFaceForRender(tessellator); + } + } + } +} diff --git a/client/net/minecraftforge/client/model/obj/TextureCoordinate.java b/client/net/minecraftforge/client/model/obj/TextureCoordinate.java new file mode 100644 index 000000000..85839055e --- /dev/null +++ b/client/net/minecraftforge/client/model/obj/TextureCoordinate.java @@ -0,0 +1,23 @@ +package net.minecraftforge.client.model.obj; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class TextureCoordinate +{ + + public float u, v, w; + + public TextureCoordinate(float u, float v) + { + this(u, v, 0F); + } + + public TextureCoordinate(float u, float v, float w) + { + this.u = u; + this.v = v; + this.w = w; + } +} diff --git a/client/net/minecraftforge/client/model/obj/Vertex.java b/client/net/minecraftforge/client/model/obj/Vertex.java new file mode 100644 index 000000000..c4ecf35e9 --- /dev/null +++ b/client/net/minecraftforge/client/model/obj/Vertex.java @@ -0,0 +1,23 @@ +package net.minecraftforge.client.model.obj; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class Vertex +{ + + public float x, y, z; + + public Vertex(float x, float y) + { + this(x, y, 0F); + } + + public Vertex(float x, float y, float z) + { + this.x = x; + this.y = y; + this.z = z; + } +} diff --git a/client/net/minecraftforge/client/model/obj/WavefrontObject.java b/client/net/minecraftforge/client/model/obj/WavefrontObject.java new file mode 100644 index 000000000..42934f8c7 --- /dev/null +++ b/client/net/minecraftforge/client/model/obj/WavefrontObject.java @@ -0,0 +1,447 @@ +package net.minecraftforge.client.model.obj; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.text.ParseException; +import java.util.ArrayList; + +import net.minecraft.client.renderer.Tessellator; + +import org.lwjgl.opengl.GL11; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class WavefrontObject +{ + + private static final String REGEX_VERTEX = "(v( (\\-){0,1}\\d+\\.\\d+){3,4} *\\n)|(v( (\\-){0,1}\\d+\\.\\d+){3,4} *$)"; + private static final String REGEX_VERTEX_NORMAL = "(vn( (\\-){0,1}\\d+\\.\\d+){3,4} *\\n)|(vn( (\\-){0,1}\\d+\\.\\d+){3,4} *$)"; + private static final String REGEX_TEXTURE_COORDINATE = "(vt( (\\-){0,1}\\d+\\.\\d+){3,4} *\\n)|(vt( (\\-){0,1}\\d+\\.\\d+){3,4} *$)"; + private static final String REGEX_FACE_VERTEX_TEXTURECOORD_VERTEXNORMAL = "(f( \\d+/\\d+/\\d+){3,4} *\\n)|(f( \\d+/\\d+/\\d+){3,4} *$)"; + private static final String REGEX_FACE_VERTEX_TEXTURECOORD = "(f( \\d+/\\d+){3,4} *\\n)|(f( \\d+/\\d+){3,4} *$)"; + private static final String REGEX_FACE_VERTEX_VERTEXNORMAL = "(f( \\d+//\\d+){3,4} *\\n)|(f( \\d+//\\d+){3,4} *$)"; + private static final String REGEX_FACE_VERTEX = "(f( \\d+){3,4} *\\n)|(f( \\d+){3,4} *$)"; + private static final String REGEX_GROUP_OBJECT = "([go]( [\\w\\d]+) *\\n)|([go]( [\\w\\d]+) *$)"; + + public ArrayList vertices = new ArrayList(); + public ArrayList vertexNormals = new ArrayList(); + public ArrayList textureCoordinates = new ArrayList(); + public ArrayList groupObjects = new ArrayList(); + private GroupObject currentGroupObject; + + public WavefrontObject(String fileName) + { + try + { + parseObjModel(this.getClass().getResource(fileName)); + } + catch (ParseException e) + { + e.printStackTrace(); + } + } + + public WavefrontObject(URL fileURL) + { + try + { + parseObjModel(fileURL); + } + catch (ParseException e) + { + e.printStackTrace(); + } + } + + private void parseObjModel(URL fileURL) throws ParseException + { + BufferedReader reader = null; + InputStream inputStream = null; + + String currentLine = null; + int lineCount = 0; + + try + { + inputStream = fileURL.openStream(); + reader = new BufferedReader(new InputStreamReader(inputStream)); + + while ((currentLine = reader.readLine()) != null) + { + lineCount++; + currentLine = currentLine.replaceAll("\\s+", " ").trim(); + + if (currentLine.startsWith("#") || currentLine.length() == 0) + { + continue; + } + else if (currentLine.startsWith("v ")) + { + Vertex vertex = parseVertex(currentLine); + if (vertex != null) + { + vertices.add(vertex); + } + else + { + throw new ParseException("Error parsing entry ('" + currentLine + "'" + ", line " + lineCount + ") in file '" + fileURL.getFile() + "'", lineCount); + } + } + else if (currentLine.startsWith("vn ")) + { + Vertex vertex = parseVertexNormal(currentLine); + if (vertex != null) + { + vertexNormals.add(vertex); + } + else + { + throw new ParseException("Error parsing entry ('" + currentLine + "'" + ", line " + lineCount + ") in file '" + fileURL.getFile() + "'", lineCount); + } + } + else if (currentLine.startsWith("vt ")) + { + TextureCoordinate textureCoordinate = parseTextureCoordinate(currentLine); + if (textureCoordinate != null) + { + textureCoordinates.add(textureCoordinate); + } + else + { + throw new ParseException("Error parsing entry ('" + currentLine + "'" + ", line " + lineCount + ") in file '" + fileURL.getFile() + "'", lineCount); + } + } + else if (currentLine.startsWith("f ")) + { + + if (currentGroupObject == null) + { + currentGroupObject = new GroupObject("Default"); + } + + Face face = parseFace(currentLine); + + if (face != null) + { + currentGroupObject.faces.add(face); + } + else + { + throw new ParseException("Error parsing entry ('" + currentLine + "'" + ", line " + lineCount + ") in file '" + fileURL.getFile() + "'", lineCount); + } + } + else if (currentLine.startsWith("g ") | currentLine.startsWith("o ")) + { + GroupObject group = parseGroupObject(currentLine); + + if (group != null) + { + if (currentGroupObject != null) + { + groupObjects.add(currentGroupObject); + } + } + else + { + throw new ParseException("Error parsing entry ('" + currentLine + "'" + ", line " + lineCount + ") in file '" + fileURL.getFile() + "'", lineCount); + } + + currentGroupObject = group; + } + } + + groupObjects.add(currentGroupObject); + } + catch (IOException e) + { + e.printStackTrace(); + } + finally + { + try + { + reader.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + try + { + inputStream.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + public void renderAll() + { + Tessellator tessellator = Tessellator.instance; + + if (currentGroupObject != null) + { + tessellator.startDrawing(currentGroupObject.glDrawingMode); + } + else + { + tessellator.startDrawing(GL11.GL_TRIANGLES); + } + + for (GroupObject groupObject : groupObjects) + { + groupObject.render(tessellator); + } + + tessellator.draw(); + } + + public void renderOnly(String... groupNames) + { + for (GroupObject groupObject : groupObjects) + { + for (String groupName : groupNames) + { + if (groupName.equalsIgnoreCase(groupObject.name)) + { + groupObject.render(); + } + } + } + } + + public void renderAllExcept(String... excludedGroupNames) + { + for (GroupObject groupObject : groupObjects) + { + for (String excludedGroupName : excludedGroupNames) + { + if (!excludedGroupName.equalsIgnoreCase(groupObject.name)) + { + groupObject.render(); + } + } + } + } + + private Vertex parseVertex(String line) + { + Vertex vertex = null; + + if (isValidVertexLine(line)) + { + line = line.substring(line.indexOf(" ") + 1); + String[] tokens = line.split(" "); + + try + { + if (tokens.length == 3) + return new Vertex(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])); + } + catch (NumberFormatException e) + { + e.printStackTrace(); + } + } + + return vertex; + } + + private Vertex parseVertexNormal(String line) + { + Vertex vertexNormal = null; + + if (isValidVertexNormalLine(line)) + { + line = line.substring(line.indexOf(" ") + 1); + String[] tokens = line.split(" "); + + try + { + if (tokens.length == 3) + return new Vertex(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])); + } + catch (NumberFormatException e) + { + e.printStackTrace(); + } + } + + return vertexNormal; + } + + private TextureCoordinate parseTextureCoordinate(String line) + { + TextureCoordinate textureCoordinate = null; + + if (isValidTextureCoordinateLine(line)) + { + line = line.substring(line.indexOf(" ") + 1); + String[] tokens = line.split(" "); + + try + { + if (tokens.length == 2) + return new TextureCoordinate(Float.parseFloat(tokens[0]), 1 - Float.parseFloat(tokens[1])); + else if (tokens.length == 3) + return new TextureCoordinate(Float.parseFloat(tokens[0]), 1 - Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])); + } + catch (NumberFormatException e) + { + e.printStackTrace(); + } + } + + return textureCoordinate; + } + + private Face parseFace(String line) + { + Face face = null; + + if (isValidFaceLine(line)) + { + face = new Face(); + + String trimmedLine = line.substring(line.indexOf(" ") + 1); + String[] tokens = trimmedLine.split(" "); + String[] subTokens = null; + + if (tokens.length == 3) + { + if (currentGroupObject.glDrawingMode == -1) + { + currentGroupObject.glDrawingMode = GL11.GL_TRIANGLES; + } + else if (currentGroupObject.glDrawingMode != GL11.GL_TRIANGLES) + { + return null; + } + } + else if (tokens.length == 4) + { + if (currentGroupObject.glDrawingMode == -1) + { + currentGroupObject.glDrawingMode = GL11.GL_QUADS; + } + else if (currentGroupObject.glDrawingMode != GL11.GL_QUADS) + { + return null; + } + } + + face.vertices = new Vertex[tokens.length]; + face.textureCoordinates = new TextureCoordinate[tokens.length]; + face.vertexNormals = new Vertex[tokens.length]; + + // f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ... + if (line.matches(REGEX_FACE_VERTEX_TEXTURECOORD_VERTEXNORMAL)) + { + for (int i = 0; i < tokens.length; ++i) + { + subTokens = tokens[i].split("/"); + + face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1); + face.textureCoordinates[i] = textureCoordinates.get(Integer.parseInt(subTokens[1]) - 1); + face.vertexNormals[i] = vertexNormals.get(Integer.parseInt(subTokens[2]) - 1); + } + + face.faceNormal = face.calculateFaceNormal(); + } + // f v1/vt1 v2/vt2 v3/vt3 ... + else if (line.matches(REGEX_FACE_VERTEX_TEXTURECOORD)) + { + for (int i = 0; i < tokens.length; ++i) + { + subTokens = tokens[i].split("/"); + + face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1); + face.textureCoordinates[i] = textureCoordinates.get(Integer.parseInt(subTokens[1]) - 1); + } + + face.faceNormal = face.calculateFaceNormal(); + } + // f v1//vn1 v2//vn2 v3//vn3 ... + else if (line.matches(REGEX_FACE_VERTEX_VERTEXNORMAL)) + { + for (int i = 0; i < tokens.length; ++i) + { + subTokens = tokens[i].split("//"); + + face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1); + face.vertexNormals[i] = vertexNormals.get(Integer.parseInt(subTokens[1]) - 1); + } + + face.faceNormal = face.calculateFaceNormal(); + } + // f v1 v2 v3 ... + else if (line.matches(REGEX_FACE_VERTEX)) + { + for (int i = 0; i < tokens.length; ++i) + { + face.vertices[i] = vertices.get(Integer.parseInt(tokens[i]) - 1); + } + + face.faceNormal = face.calculateFaceNormal(); + } + else + throw new IllegalArgumentException(); + } + + return face; + } + + private GroupObject parseGroupObject(String line) + { + GroupObject group = null; + + if (isValidGroupObjectLine(line)) + { + String trimmedLine = line.substring(line.indexOf(" ") + 1); + + if (trimmedLine.length() > 0) + { + group = new GroupObject(trimmedLine); + } + } + + return group; + } + + private static boolean isValidVertexLine(String line) + { + + return line.matches(REGEX_VERTEX); + } + + private static boolean isValidVertexNormalLine(String line) + { + + return line.matches(REGEX_VERTEX_NORMAL); + } + + private static boolean isValidTextureCoordinateLine(String line) + { + + return line.matches(REGEX_TEXTURE_COORDINATE); + } + + private static boolean isValidFaceLine(String line) + { + + return line.matches(REGEX_FACE_VERTEX_TEXTURECOORD_VERTEXNORMAL) || line.matches(REGEX_FACE_VERTEX_TEXTURECOORD) || line.matches(REGEX_FACE_VERTEX_VERTEXNORMAL) || line.matches(REGEX_FACE_VERTEX); + } + + private static boolean isValidGroupObjectLine(String line) + { + + return line.matches(REGEX_GROUP_OBJECT); + } +}