How can one load an OBJ file?

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

Previous topic - Next topic

jam007

Great :D

Reading this I realised that your loader class together with Java XML support will be perfect to make new ice boat types in our game without having to make any new classes or code. Just filling in a XML-file and making 3D-model Objects.

It really solved a big problem for us in making the ice boats modifiable by non java/OpenGL "enabled" users.

Anders

ordoc

you can eliminate casting in the 1.5 JDK with generic types.
ArrayList<float[]> vertexsets = new ArrayList<float[]>();

also if any of the ArrayLists are gonna have a good number of elements in them, you might want to instantiate them with something other than the default value of 10.

However I am not sure how much faster this will make your code if at all :) You can always test it out though.

tomb

Quote from: "ordoc"you can eliminate casting in the 1.5 JDK with generic types.
ArrayList<float[]> vertexsets = new ArrayList<float[]>();

No, you don't eliminate the cast. It executes exactly as 1.4 code.

ordoc

Right, you eliminate the cast in your code to make it more readable, but it just becomes implicit.


I was more refering to the arraylist instantiations when it came to speed.

jam007

and use the Scanner class to read the file. (Java 1.5)
using 1.5 types eliminates the irritating warning about unsafe bla bla
Still, elias4444 has made a great job!

Anders

elias4444

Good comments everyone! I love feedback!  :D

I've been trying to avoid being locked into Java 1.5, but I'm seriously considering a full on move to it (the guy who does my graphics is on a Mac though, which doesn't have 1.5 available yet). Once Apple decides to go 1.5, I don't think I'll have an excuse anymore.

At one point, I actually switched the code over to using Arrays rather than ArrayLists, but it made it more complex, and I realized that it didn't make any difference in speed once I started using OpenGL Display Lists to accelerate rendering. My only other thought was to use Vertex Buffer Objects rather than Display Lists, but I learned that there wasn't any speed improvement with that either.  :P

Things left that I'm considering:
*Manually calculating Vertex Normals - but as I've learned, graphic artists sometimes like to do "weird" things with their lighting normals, so thus far, I've left it out.

*Read in material groups - although this would require the user to pass in an array of textures to use for the materials as well. Is it just easier to create a seperate object with a different texture mapped onto it? My sprites can actually accept an array of objects and textures, and then "glue" them all together, so I'm not sure this is needed (which is also why I made the auto-centering of the object optional with the boolean flag).

...So, anyway... Anyone know about Apple's plans to move to Java 1.5? Also, if there's anything else you guys are interested in, please let me know and I'll take my best shot at it.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

ordoc


jam007

Just that your class (when you think itÃ,´s ready) will be part of the lwjgl.util package. So I wont have to paste copy and ... it :wink:

Anders

elias4444

Ok, just a quick refresh of the OBJ loader. I've set it up now so that you pass the constructor your own BufferedReader object (the sample code is in the comments at the top of the class). This way, I'm doing things closer to the "right" way, and you can handle your own file-loading and error routines the way you'd like.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

kappa

does the loader support textures?

elias4444

It supports texture coordinates. I didn't want to build the texturing into the loader, as some people (including myself) like to do "funny" things with textures. Basically, you can use the class something like a GLU object:

GL11.glPushMatrix();
		texture.bind();
		GL11.glTranslatef(x, y, z);	
		GL11.glColor4f(1,1,1,1);
		theobject.opengldraw();
		GL11.glPopMatrix();


I prefer keeping things as componentized as possible to allow for the greatest amount of flexibility.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

oNyx

Ok... fixed the code... if you ever write test code... be sure that it actually checks the correct stuff :lol:

I just took your loader and removed all regex stuff in that loadobject method... String.split and String.replaceAll are using regex under the hood and are therefore somewhat slow.

[old] 4.940987751 seconds
[new] 2.025095927 seconds
[old] 4.930706557 seconds
[new] 1.948526977 seconds
[old] 4.904319683 seconds
[new] 1.940505009 seconds


See? Quite the difference huh? (I used a 2mb file for benching) And all I did was replacing String.split with StringTokenizer (the api says one should use String.split instead yadda yadda... I don't care... it's slower). And that replaceAll was replaced with some "toCharArray loop StringBuffer"-voodoo (and voodoo it is... guess where the bug was hidden :D).

Here is the method... from my testing the in-mem-data was exactly the same (now I'm sure). Feel free to give it a try ;)

private void loadobject2(BufferedReader br) {
	try {

		String newline;
		boolean firstpass = true;

		while (((newline = br.readLine()) != null)) {
			if (newline.length() > 0) {
				newline = newline.trim();
				if (newline.startsWith("v ")){
					float[] coords = new float[4];
					String[] coordstext = new String[4];
					newline = newline.substring(2,newline.length());
					StringTokenizer st=new StringTokenizer(newline," ");
					for(int i=0;st.hasMoreTokens();i++)
						coords[i] = Float.parseFloat(st.nextToken());
					//// 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);
				}
				else if (newline.startsWith("vt")){
					float[] coords = new float[4];
					String[] coordstext = new String[4];
					newline = newline.substring(3,newline.length());
					StringTokenizer st=new StringTokenizer(newline," ");
					for(int i=0;st.hasMoreTokens();i++)
						coords[i] = Float.parseFloat(st.nextToken());
					vertexsetstexs.add(coords);
				}
				else if (newline.startsWith("vn")){
					float[] coords = new float[4];
					String[] coordstext = new String[4];
					newline = newline.substring(3,newline.length());
					StringTokenizer st=new StringTokenizer(newline," ");
					for(int i=0;st.hasMoreTokens();i++)
						coords[i] = Float.parseFloat(st.nextToken());
					vertexsetsnorms.add(coords);
				}
				else if (newline.startsWith("f ")){

					newline = newline.substring(2,newline.length());
					StringTokenizer st=new StringTokenizer(newline," ");
					int count=st.countTokens();
					int[] v = new int[count];
					int[] vt = new int[count];
					int[] vn = new int[count];

					for(int i=0;i<count;i++) {
						char []chars=st.nextToken().toCharArray();
						StringBuffer sb=new StringBuffer();
						char lc='x';
						for(int k=0;k<chars.length;k++) {
							if(chars[k]=='/') {
								if(lc=='/')
									sb.append('0');
							}
							lc=chars[k];
							sb.append(lc);
						}
						StringTokenizer st2=new StringTokenizer(sb.toString(),"/");
						int num=st2.countTokens();
						v[i] = Integer.parseInt(st2.nextToken());
						if (num > 1) {
							vt[i] = Integer.parseInt(st2.nextToken());
						} else {
							vt[i] = 0;
						}
						if (num > 2) {
							vn[i] = Integer.parseInt(st2.nextToken());
						} else {
							vn[i] = 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 file: " + br.toString() + "\r \r" + e.getMessage());
		//System.exit(0);
	}

}

oNyx

FYI "fixed the code" referred to my code (as seen above). I edited that post, but in retrospect it looks a bit misleading...

Well, there is something weird with that loader...

*poke* *poke*

Hm... ok... if I change this line:

GL11.glTexCoord3f(textempx, textempy, textempz);

like this:

GL11.glTexCoord3f(textempx, 1f-textempy, textempz);

Then the texturing seems to be ok.

The simplest test model+texture:
http://kaioa.com/k/dice.zip (~2kb)

elias4444

I think it just depends on how you implement your texturing system. But I'm glad to see someone using it/having fun with it.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

oNyx

Ye, its either wrong way up or not... dunno how to avoid that whole issue, but it isn't really a problem with the loader itself. I think I should have pointed that out somehow.

Tried my alternate loadobject method?