package com.bitwaffle.spaceguts.graphics.model; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.StringTokenizer; import javax.vecmath.Point2f; import javax.vecmath.Vector3f; import org.lwjgl.util.vector.Quaternion; import com.bitwaffle.spaceguts.util.QuaternionHelper; import com.bitwaffle.spaceout.resources.Textures; /** * This class loads in an obj file using a {@link ModelBuilder} and returns a {@link Model}. * There's a ton of different ways to call loadObjFile, you only need to use one that gives you the * options you need. * * The 'directory' String passed in to loadObjFile should point to a directory that contains * a .obj file and a .mtl file. It's assumed that the .obj and .mtl files have the same name * and that name matches the name of the directory. * * So, for example, a directory look like: * model/ * model.obj * model.mtl * model.png * * @author TranquilMarmot * @see Model * @see ModelBuilder * @see ModelPart * */ public class ModelLoader { public static Model loadObjFile(String directory, Textures texture){ return loadObjFile(directory, new Vector3f(1.0f, 1.0f, 1.0f), new Vector3f(0.0f, 0.0f, 0.0f), new Quaternion(0.0f, 0.0f, 0.0f, 1.0f), texture); } public static Model loadObjFile(String directory, float scale, Textures texture){ return loadObjFile(directory, new Vector3f(scale, scale, scale), new Vector3f(0.0f, 0.0f, 0.0f), new Quaternion(0.0f, 0.0f, 0.0f, 1.0f), texture); } public static Model loadObjFile(String directory, Quaternion rotation, Textures texture){ return loadObjFile(directory, new Vector3f(1.0f, 1.0f, 1.0f), new Vector3f(0.0f, 0.0f, 0.0f), rotation, texture); } public static Model loadObjFile(String directory, Vector3f scale, Textures texture){ return loadObjFile(directory, scale, new Vector3f(0.0f, 0.0f, 0.0f), new Quaternion(0.0f, 0.0f, 0.0f, 1.0f), texture); } public static Model loadObjFile(String directory, float scale, Quaternion rotation, Textures texture){ return loadObjFile(directory, new Vector3f(scale, scale, scale), new Vector3f(0.0f, 0.0f, 0.0f), rotation, texture); } /** * Get a model from an obj file * @param directory Directory to load the model in from. The directory must contain a .obj and a .mtl file with the same name as the directory. * @param scale Scale to load the model in at * @param offset Location offset to give each vertex being loaded * @param rotation Rotation offset to give each vertex being loaded * @param texture The texture from {@link Textures} to use for the model * @return A model loaded from the file */ public static Model loadObjFile(String directory, Vector3f scale, Vector3f offset, Quaternion rotation, Textures texture){ // our model Model model = null; // find out the name of the directory int lastSlash = 0; char[] chars = directory.toCharArray(); for(int i = chars.length - 1; i >= 0; i--){ if(chars[i] == '/'){ lastSlash = i; break; } } // get the name of the directory String name = directory.substring(lastSlash); // get a list of materials MaterialList materials = null; try{ // open the .obj file BufferedReader reader = new BufferedReader(new FileReader(directory + name + ".obj")); // current line String line; // our model builder ModelBuilder builder = new ModelBuilder(); while((line = reader.readLine()) != null){ // split the line up at spaces StringTokenizer toker = new StringTokenizer(line, " "); // grab the line's type String lineType = toker.nextToken(); // load material library if(lineType.equals("mtllib")){ try{ materials = loadMaterialList(directory + "/" + toker.nextToken()); } catch(FileNotFoundException e){ materials = loadMaterialList(directory + name + ".mtl"); } } // object name if (lineType.equals("o")) { //System.out.println("Loading " + toker.nextToken()); } // vertex if (lineType.equals("v")) { // grab the coordinates float x = (Float.parseFloat(toker.nextToken()) + offset.x) * scale.x; float y = (Float.parseFloat(toker.nextToken()) + offset.y) * scale.y; float z = (Float.parseFloat(toker.nextToken()) + offset.z) * scale.z; //org.lwjgl.util.vector.Vector3f rotated = QuaternionHelper.rotateVectorByQuaternion(new org.lwjgl.util.vector.Vector3f(x, y, z), rotation); //builder.addVertex(new Vector3f(rotated.x, rotated.y, rotated.z)); builder.addVertex(new Vector3f(x, y, z)); } // normal if (lineType.equals("vn")) { // grab the coordinates float x = Float.parseFloat(toker.nextToken()); float y = Float.parseFloat(toker.nextToken()); float z = Float.parseFloat(toker.nextToken()); org.lwjgl.util.vector.Vector3f rotated = QuaternionHelper.rotateVectorByQuaternion(new org.lwjgl.util.vector.Vector3f(x, y, z), rotation); builder.addNormal(new Vector3f(rotated.x, rotated.y, rotated.z)); } // texture coord if (line.startsWith("vt")) { float u = Float.parseFloat(toker.nextToken()); float v = Float.parseFloat(toker.nextToken()); builder.addTextureCoords(new Point2f(u, v)); } // new material if(line.startsWith("usemtl")){ // end the current material if we're on one //if(builder.isMakingModelPart()) // builder.endModelPart(); if(materials == null) materials = loadMaterialList(directory + name + ".mtl"); String mat = toker.nextToken(); builder.startModelPart(materials.getMaterial(mat)); } // face if (line.startsWith("f")) { // to see if we're dealing with a triangle or a quad (the ModelBuilder automatically splits quads into triangles) int numVertices = toker.countTokens(); int[] vertexIndices = new int[numVertices]; int[] normalIndices = new int[numVertices]; int[] textureIndices = new int[numVertices]; for(int i = 0; i < numVertices; i++){ String indices = toker.nextToken(); StringTokenizer split = new StringTokenizer(indices, "/"); // the obj file goes vertex/texture-coordinate/normal vertexIndices[i] = Integer.parseInt(split.nextToken()); textureIndices[i] = Integer.parseInt(split.nextToken()); normalIndices[i] = Integer.parseInt(split.nextToken()); } // add the indices to the model builder builder.addVertexIndices(vertexIndices); builder.addNormalIndices(normalIndices); builder.addTetxureIndices(textureIndices); } } model = builder.makeModel(texture); } catch(IOException e){ e.printStackTrace(); } return model; } /** * Get a material list for an obj file * @param .mtl file to load MaterialList from * @return List of materials from .mtl file */ private static MaterialList loadMaterialList(String file) throws FileNotFoundException{ // material list MaterialList list = new MaterialList(); try{ BufferedReader reader = new BufferedReader(new FileReader(file)); // current line String line; // name of the material (important!) String name = "NULL"; // vectors for ambient, diffuse, specular org.lwjgl.util.vector.Vector3f Ka = null, Kd = null, Ks = null; // shininess float Shininess = -1.0f; // whether or not we're loading a material right now boolean loadingMaterial = false; // whether or not a material has been loaded (ready to be added to list) boolean materialLoaded = false; // go through the whole .mtl file while((line = reader.readLine()) != null){ // new material if(line.startsWith("newmtl")){ name = line.substring(line.indexOf("newmtl") + 7, line.length()); Ka = null; Kd = null; Ks = null; Shininess = -1.0f; loadingMaterial = true; } else if(loadingMaterial){ // grab variable if(line.startsWith("Ns")){ StringTokenizer toker = new StringTokenizer(line, " "); toker.nextToken(); Shininess = Float.parseFloat(toker.nextToken()); } else if(line.startsWith("Ka")){ Ka = getColor(line); } else if(line.startsWith("Kd")){ Kd = getColor(line); } else if(line.startsWith("Ks")){ Ks = getColor(line); } // if we have all the necessary variables, we're done loading this material and it can be added to the list if(Shininess != -1.0f && Ka != null && Ks != null && Kd != null){ materialLoaded = true; } } // add material to list if it's loaded if(materialLoaded){ Material mat = new Material(Ka, Kd, Ks, Shininess); list.addMaterial(name, mat); } } } catch(FileNotFoundException e){ throw e; } catch(IOException e){ e.printStackTrace(); } return list; } /** * Get a vector representing a color from a string * @param line Line to get color from * @return Color from string */ private static org.lwjgl.util.vector.Vector3f getColor(String line){ StringTokenizer toker = new StringTokenizer(line, " " ); toker.nextToken(); float x = Float.parseFloat(toker.nextToken()); float y = Float.parseFloat(toker.nextToken()); float z = Float.parseFloat(toker.nextToken()); return new org.lwjgl.util.vector.Vector3f(x, y, z); } }