How can one load an OBJ file?

Started by elias4444, January 27, 2005, 00:13:05

Previous topic - Next topic

elias4444

I know you guys are probably tired of my constant questions, but is there a way to load an OBJ file, other than having to write my own loader/translator for the file format?
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

funsheep

you will probably have your own datastructure to represent 3d models, so you will have to write an specific obj loader.

you should adapt one of the various loader tutorials on the web.
some are collected here:
http://www.j3d.org/utilities/loaders.html

Matzon

or check out one of the game engines on this page:
http://lwjgl.org/links.php

elias4444

OK.... Here it is.
This is crude...
this is uncommented...
this has got to be some of the craziest and ugliest coding I've ever done.

BUT, I've wanted to find a way to say thank you to those on the board for all their help... SO, here's my simple object loader. It loads and assists in drawing .obj files. I can't guarantee anything, but it's worked for me on every obj file I've thrown at it so far. It supports coordinates, normals, and texture coordinates. Please be kind, as this has taken me all day to program (mostly trying to decipher the obj spec), and I'm too tired to go back through and clean-up/optimize it right now. Feel free to delete all the System.out.println commands.  :P

**Updated: August 8, 2005
Notice that now you pass a BufferedReader object into the class. This way you can handle your own file loading and error routines on a per program basis.
/*
 * Modified on August 8, 2005
 */
package tools;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;

import org.lwjgl.opengl.GL11;

/**
 * @author Jeremy Adams (elias4444)
 *
 * Use these lines if reading from a file
 * FileReader fr = new FileReader(ref);
 * BufferedReader br = new BufferedReader(fr);

 * Use these lines if reading from within a jar
 * InputStreamReader fr = new InputStreamReader(new BufferedInputStream(getClass().getClassLoader().getResourceAsStream(ref)));
 * BufferedReader br = new BufferedReader(fr);
 */

public class Object3D {
	
	
	private ArrayList vertexsets = new ArrayList(); // Vertex Coordinates
	private ArrayList vertexsetsnorms = new ArrayList(); // Vertex Coordinates Normals
	private ArrayList vertexsetstexs = new ArrayList(); // Vertex Coordinates Textures
	private ArrayList faces = new ArrayList(); // Array of Faces (vertex sets)
	private ArrayList facestexs = new ArrayList(); // Array of of Faces textures
	private ArrayList facesnorms = new ArrayList(); // Array of Faces normals
	
	private int objectlist;
	private int numpolys = 0;
	
	//// Statisitcs for drawing ////
	public float toppoint = 0;		// y+
	public float bottompoint = 0;	// y-
	public float leftpoint = 0;		// x-
	public float rightpoint = 0;	// x+
	public float farpoint = 0;		// z-
	public float nearpoint = 0;		// z+
	
	public Object3D(BufferedReader ref, boolean centerit) {
		loadobject(ref);
		if (centerit) {
			centerit();
		}
		opengldrawtolist();
		numpolys = faces.size();
		cleanup();
	}
	
	private void cleanup() {
		vertexsets.clear();
		vertexsetsnorms.clear();
		vertexsetstexs.clear();
		faces.clear();
		facestexs.clear();
		facesnorms.clear();
	}
	
	private void loadobject(BufferedReader br) {
		int linecounter = 0;
		try {
			
			String newline;
			boolean firstpass = true;
			
			while (((newline = br.readLine()) != null)) {
				linecounter++;
				newline = newline.trim();
				if (newline.length() > 0) {
					if (newline.charAt(0) == 'v' && newline.charAt(1) == ' ') {
						float[] coords = new float[4];
						String[] coordstext = new String[4];
						coordstext = newline.split("\\s+");
						for (int i = 1;i < coordstext.length;i++) {
							coords[i-1] = Float.valueOf(coordstext[i]).floatValue();
						}
						//// check for farpoints ////
						if (firstpass) {
							rightpoint = coords[0];
							leftpoint = coords[0];
							toppoint = coords[1];
							bottompoint = coords[1];
							nearpoint = coords[2];
							farpoint = coords[2];
							firstpass = false;
						}
						if (coords[0] > rightpoint) {
							rightpoint = coords[0];
						}
						if (coords[0] < leftpoint) {
							leftpoint = coords[0];
						}
						if (coords[1] > toppoint) {
							toppoint = coords[1];
						}
						if (coords[1] < bottompoint) {
							bottompoint = coords[1];
						}
						if (coords[2] > nearpoint) {
							nearpoint = coords[2];
						}
						if (coords[2] < farpoint) {
							farpoint = coords[2];
						}
						/////////////////////////////
						vertexsets.add(coords);
					}
					if (newline.charAt(0) == 'v' && newline.charAt(1) == 't') {
						float[] coords = new float[4];
						String[] coordstext = new String[4];
						coordstext = newline.split("\\s+");
						for (int i = 1;i < coordstext.length;i++) {
							coords[i-1] = Float.valueOf(coordstext[i]).floatValue();
						}
						vertexsetstexs.add(coords);
					}
					if (newline.charAt(0) == 'v' && newline.charAt(1) == 'n') {
						float[] coords = new float[4];
						String[] coordstext = new String[4];
						coordstext = newline.split("\\s+");
						for (int i = 1;i < coordstext.length;i++) {
							coords[i-1] = Float.valueOf(coordstext[i]).floatValue();
						}
						vertexsetsnorms.add(coords);
					}
					if (newline.charAt(0) == 'f' && newline.charAt(1) == ' ') {
						String[] coordstext = newline.split("\\s+");
						int[] v = new int[coordstext.length - 1];
						int[] vt = new int[coordstext.length - 1];
						int[] vn = new int[coordstext.length - 1];
						
						for (int i = 1;i < coordstext.length;i++) {
							String fixstring = coordstext[i].replaceAll("//","/0/");
							String[] tempstring = fixstring.split("/");
							v[i-1] = Integer.valueOf(tempstring[0]).intValue();
							if (tempstring.length > 1) {
								vt[i-1] = Integer.valueOf(tempstring[1]).intValue();
							} else {
								vt[i-1] = 0;
							}
							if (tempstring.length > 2) {
								vn[i-1] = Integer.valueOf(tempstring[2]).intValue();
							} else {
								vn[i-1] = 0;
							}
						}
						faces.add(v);
						facestexs.add(vt);
						facesnorms.add(vn);
					}
				}
			}
			
		} catch (IOException e) {
			System.out.println("Failed to read file: " + br.toString());
			//System.exit(0);			
		} catch (NumberFormatException e) {
			System.out.println("Malformed OBJ (on line " + linecounter + "): " + br.toString() + "\r \r" + e.getMessage());
			//System.exit(0);
		}
		
	}
	
	private void centerit() {
		float xshift = (rightpoint-leftpoint) /2f;
		float yshift = (toppoint - bottompoint) /2f;
		float zshift = (nearpoint - farpoint) /2f;
		
		for (int i=0; i < vertexsets.size(); i++) {
			float[] coords = new float[4];
			
			coords[0] = ((float[])(vertexsets.get(i)))[0] - leftpoint - xshift;
			coords[1] = ((float[])(vertexsets.get(i)))[1] - bottompoint - yshift;
			coords[2] = ((float[])(vertexsets.get(i)))[2] - farpoint - zshift;
			
			vertexsets.set(i,coords); // = coords;
		}
		
	}
	
	public float getXWidth() {
		float returnval = 0;
		returnval = rightpoint - leftpoint;
		return returnval;
	}
	
	public float getYHeight() {
		float returnval = 0;
		returnval = toppoint - bottompoint;
		return returnval;
	}
	
	public float getZDepth() {
		float returnval = 0;
		returnval = nearpoint - farpoint;
		return returnval;
	}
	
	public int numpolygons() {
		return numpolys;
	}
	
	public void opengldrawtolist() {
		
		this.objectlist = GL11.glGenLists(1);
		
		GL11.glNewList(objectlist,GL11.GL_COMPILE);
		for (int i=0;i<faces.size();i++) {
			int[] tempfaces = (int[])(faces.get(i));
			int[] tempfacesnorms = (int[])(facesnorms.get(i));
			int[] tempfacestexs = (int[])(facestexs.get(i));
			
			//// Quad Begin Header ////
			int polytype;
			if (tempfaces.length == 3) {
				polytype = GL11.GL_TRIANGLES;
			} else if (tempfaces.length == 4) {
				polytype = GL11.GL_QUADS;
			} else {
				polytype = GL11.GL_POLYGON;
			}
			GL11.glBegin(polytype);
			////////////////////////////
			
			for (int w=0;w<tempfaces.length;w++) {
				if (tempfacesnorms[w] != 0) {
					float normtempx = ((float[])vertexsetsnorms.get(tempfacesnorms[w] - 1))[0];
					float normtempy = ((float[])vertexsetsnorms.get(tempfacesnorms[w] - 1))[1];
					float normtempz = ((float[])vertexsetsnorms.get(tempfacesnorms[w] - 1))[2];
					GL11.glNormal3f(normtempx, normtempy, normtempz);
				}
				
				if (tempfacestexs[w] != 0) {
					float textempx = ((float[])vertexsetstexs.get(tempfacestexs[w] - 1))[0];
					float textempy = ((float[])vertexsetstexs.get(tempfacestexs[w] - 1))[1];
					float textempz = ((float[])vertexsetstexs.get(tempfacestexs[w] - 1))[2];
					GL11.glTexCoord3f(textempx,1f-textempy,textempz);
				}
				
				float tempx = ((float[])vertexsets.get(tempfaces[w] - 1))[0];
				float tempy = ((float[])vertexsets.get(tempfaces[w] - 1))[1];
				float tempz = ((float[])vertexsets.get(tempfaces[w] - 1))[2];
				GL11.glVertex3f(tempx,tempy,tempz);
			}
			
			
			//// Quad End Footer /////
			GL11.glEnd();
			///////////////////////////
			
			
		}
		GL11.glEndList();
	}
	
	public void opengldraw() {
		GL11.glCallList(objectlist);
	}
	
}
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

funsheep

we are also using obj files. and it had turned out, that in java 1.4 the switch case blocks are for such things faster than the if then else if then else ... statements.

and

when you are going to read mtl files there are several commands, beginning with the same character, what do you do then?
ok: use switch case blocks to determine the first character and than proove with if else statements which command it is :)

happy hacking

elias4444

I'm actually not too worried about the if-then statements. They're primarily used in the loading sequence, which I traditionally do before the game actually begins. The drawing sequence doesn't seem to be effected as much as the information is already in memory and is simply looped through.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

napier

Hey elias4444,

I need to load obj files too and have played some with your loader.  

I found a few models that look like they're not loading right (I don't think I'm messing them up in my code).  A few of the triangles drop out, leaving holes in the model.  

If you're interested try the obj file here: http://potatoland.com/code/objloader/p51_mustang.obj

I'm planning to debug but thought I'd let you know.

I also found a loader by Karl Berg that looks decent but is written in C++. Might be useful for reference:

http://potatoland.com/code/objloader/ModelType.cpp
http://potatoland.com/code/objloader/ModelType.h

napier
penGL/Java/LWJGL demos and code: http://potatoland.org/code/gl

elias4444

Right... make sure you grab the code again from the previous post (I've updated it a couple of times). I hadn't done anything with textures, so I didn't realize I was setting the texture coordinates out of order. It seems to work now with just about everything I've thrown at it (including custom objects exported from Maya). Keep in mind though, I'm only using faces (no curves or anything yet - I just don't know how to do those in openGL yet). BTW, that's a cool p51 model.  :)
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

elias4444

Here's a site with some fun free objects to test with:

http://o.ffrench.free.fr/meshbank/

Also for fun, I've made what I call a "texture tester" for the guy doing my 3D graphics for me. Feel free to use it (whenever my webserver is actually up):

//www.tommytwisters.com/texture/texture.jnlp
To use it, find the "tommytwisters" folder under your user directory (on windows - c:\documents and settings\userid\tommytwisters\), and drop in an object file named "object.obj" and a texture file named "texture.png" (you can use other image types, but it must be named "texture.png" - even if it's a gif or something) to see what it looks like. It'll tell you how many faces the object has and a simple FPS benchmark for it. Also, you can use the following keys for testing:

L = turn dynamic lighting on and off
S = turn spheremapping on and off
R = lock the light to the camera position (and unlock)
I = "zoom in" = scale your object larger
O = "zoom out" = scale your object smaller
T,F,G,H = move the object around.
Arrow Keys = move the camera around.

It uses my most up-to-date version of the object reader code above. I also experienced missing triangles, but it turned out to be an older version of my code and the way I had the texture mapping setup. It seems to be fixed now.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

napier

Hmmm.....  I grabbed the most recent code above but get the same result (several triangles are missing).   The model has verts, normals and faces.  It also has smoothing groups (I thing that's what 'g' and 's' are about).  Maybe that's related.  

QuoteBTW, that's a cool p51 model.

I'd love to take credit but can't.  I found it on the web, I think it's part of the Java 3D demos.  If it was my own I'd be more suspicious that I screwed up the model and that's what's causing render problems  :)

Anyhoo I can't debug for two days or so.  If you do get to it let me know and I'll test.  

BTW Thanks for the loader!
penGL/Java/LWJGL demos and code: http://potatoland.org/code/gl

elias4444

I'm noticing some missing triangles in the cockpit windshield for the model, although I'm not sure it's a bug in my code (although it could be that the object file is using something I haven't implemented yet). Are you having problems with any other models? I've been downloading some pretty complex models from the web and haven't found any with the same problem yet.

I'll have a friend load it into Maya and see what he gets.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

elias4444

Mmmm... Yummy humble pie!!!

Yep, I left a line out of my code up there. It should work just fine for you now.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

napier

Very cool.  Now it works.  

Thanks!
penGL/Java/LWJGL demos and code: http://potatoland.org/code/gl

elias4444

Figured I better point out that I updated the code (posted above). It's about 30% faster now. I convert the arraylists to arrays in order to avoid casting everything on every draw. Also, the constructor now supports a boolean variable that specifies whether you want the object automatically centered around an origin or not. There's also some extra functions to get the different dimensions and number of polygons used.

Hope that helps!
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

elias4444

Well, the joy keeps coming with this one... I just implemented genlists... with a 35,000 face count, I went from 35fps to 120+ fps in my tester app. Enjoy the upgrade!
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com