solved lwjgl 3d not sure how to translate .obj model in world space

Ok, so a week or two ago I decided to start learning lwjgl (without libraries such as slick2d, jpct, etc) and my simple digital world is populated with props which are generated using vector math - so I have absolutely no experience with any type of real model handling…
But I figured it would be nice touch to load at least a handful of real models to the game using .obj format models that I bought from turbosquid.com, and some that I made myself.
After some searching around online I managed to find a standalone .obj model loader which I was able to implement with ease, but now I’m stuck wondering how to manipulate the objects position in 3d world space because it renders at 0,0,0 and I have no experience with this.

Here’s what I mean:

If somebody could just explain to me how I could could go about translating the model to my own desired coordinates it would be very much appreciated as I’m new to lwjgl and have only really played with vector math, texturing, and other basic-basics so far.

In my game I am using this to initialize the model:
this.stall = new Loader("./res/stall.obj");

and this to render it in my existing scene:
stall.DrawModel();

The problem is that I just don’t understand how to set a forced position such as 32, 1, 32… instead of having it draw at 0,0,0 which I assume is the default location with the Loader I am using


package threed.obj;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.util.ArrayList;

import org.lwjgl.opengl.GL11;

import com.sun.prism.impl.BufferUtil;

public class Loader {
	
	private String OBJModelPath; // The path to the Model File
	private ArrayList vData = new ArrayList(); // List of Vertex Coordinates
	private ArrayList vtData = new ArrayList(); // List of Texture Coordinates
	private ArrayList vnData = new ArrayList(); // List of Normal Coordinates
	private ArrayList<int[]> fv = new ArrayList<int[]>(); // Face Vertex Indices
	private ArrayList<int[]> ft = new ArrayList<int[]>(); // Face Texture Indices
	private ArrayList<int[]> fn = new ArrayList<int[]>(); // Face Normal Indices
	private FloatBuffer modeldata; // Buffer which will contain vertice data
	private int FaceFormat; // Format of the Faces Triangles or Quads
	private int FaceMultiplier; // Number of possible coordinates per face
	private int PolyCount = 0; // The Models Polygon Count
	private boolean init = true;

	public Loader(String Modelpath) {
		OBJModelPath = Modelpath;
		LoadOBJModel(OBJModelPath);
		SetFaceRenderType();
	}

	private void LoadOBJModel(String ModelPath) {
		try {
			// Open a file handle and read the models data
			BufferedReader br = new BufferedReader(new FileReader(ModelPath));
			String line = null;
			while ((line = br.readLine()) != null) {
				if (line.startsWith("#")) { // Read Any Descriptor Data in the File
					System.out.println("Descriptor: "+line); //Uncomment to print out file descriptor data
				} else if (line.equals("")) {
					// Ignore whitespace data
				} else if (line.startsWith("v ")) { // Read in Vertex Data
					vData.add(ProcessData(line));
				} else if (line.startsWith("vt ")) { // Read Texture Coordinates
					vtData.add(ProcessData(line));
				} else if (line.startsWith("vn ")) { // Read Normal Coordinates
					vnData.add(ProcessData(line));
				} else if (line.startsWith("f ")) { // Read Face Data
					ProcessfData(line);
				}
			}
			br.close();
		} catch (IOException e) {
			System.out.println("Failed to find or read OBJ: " + ModelPath);
			System.err.println(e);
		}
	}

	private float[] ProcessData(String read) {
		final String s[] = read.split("\\s+");
		return (ProcessFloatData(s)); // returns an array of processed float data
	}

	private float[] ProcessFloatData(String sdata[]) {
		float data[] = new float[sdata.length - 1];
		for (int loop = 0; loop < data.length; loop++) {
			data[loop] = Float.parseFloat(sdata[loop + 1]);
		}
		return data; // return an array of floats
	}

	private void ProcessfData(String fread) {
		PolyCount++;
		String s[] = fread.split("\\s+");
		if (fread.contains("//")) { // Pattern is present if obj has only v and vn in face data
			for (int loop = 1; loop < s.length; loop++) {
				s[loop] = s[loop].replaceAll("//", "/0/"); // insert a zero for  missing vt data
			}
		}
		ProcessfIntData(s); // Pass in face data
	}

	private void ProcessfIntData(String sdata[]) {
		int vdata[] = new int[sdata.length - 1];
		int vtdata[] = new int[sdata.length - 1];
		int vndata[] = new int[sdata.length - 1];
		for (int loop = 1; loop < sdata.length; loop++) {
			String s = sdata[loop];
			String[] temp = s.split("/");
			vdata[loop - 1] = Integer.valueOf(temp[0]); // always add vertex indices
			if (temp.length > 1) { // we have v and vt data
				vtdata[loop - 1] = Integer.valueOf(temp[1]); // add in vt indices
			} else {
				vtdata[loop - 1] = 0; // if no vt data is present fill in zeros
			}
			if (temp.length > 2) { // we have v, vt, and vn data
				vndata[loop - 1] = Integer.valueOf(temp[2]); // add in vn indices
			} else {
				vndata[loop - 1] = 0; // if no vn data is present fill in zeros
			}
		}
		fv.add(vdata);
		ft.add(vtdata);
		fn.add(vndata);
	}

	private void SetFaceRenderType() {
		final int temp[] = fv.get(0);
		if (temp.length == 3) {
			FaceFormat = GL11.GL_TRIANGLES; // The faces come in sets of 3 so we have triangular faces
			FaceMultiplier = 3;
		} else if (temp.length == 4) {
			FaceFormat = GL11.GL_QUADS; // The faces come in sets of 4 so we have quadrilateral faces
			FaceMultiplier = 4;
		} else {
			FaceFormat = GL11.GL_POLYGON; // Fall back to render as free form polygons
		}
	}

	private void ConstructInterleavedArray() {
		final int tv[] = fv.get(0);
		final int tt[] = ft.get(0);
		final int tn[] = fn.get(0);
		// If a value of zero is found that it tells us we don't have that type of data
		if ((tv[0] != 0) && (tt[0] != 0) && (tn[0] != 0)) {
			ConstructTNV(); // We have Vertex, 2D Texture, and Normal Data
			GL11.glInterleavedArrays(GL11.GL_T2F_N3F_V3F, 0, modeldata);
		} else if ((tv[0] != 0) && (tt[0] != 0) && (tn[0] == 0)) {
			ConstructTV(); // We have just vertex and 2D texture Data
			GL11.glInterleavedArrays(GL11.GL_T2F_V3F, 0, modeldata);
		} else if ((tv[0] != 0) && (tt[0] == 0) && (tn[0] != 0)) {
			ConstructNV(); // We have just vertex and normal Data
			GL11.glInterleavedArrays(GL11.GL_N3F_V3F, 0, modeldata);
		} else if ((tv[0] != 0) && (tt[0] == 0) && (tn[0] == 0)) {
			ConstructV();
			GL11.glInterleavedArrays(GL11.GL_V3F, 0, modeldata);
		}
	}

	private void ConstructTNV() {
		int[] v, t, n;
		float tcoords[] = new float[2]; // Only T2F is supported in
										// InterLeavedArrays!!
		float coords[] = new float[3];
		int fbSize = PolyCount * (FaceMultiplier * 8); // 3v Per Poly, 2vt Per Poly, 3vn Per Poly
		modeldata = BufferUtil.newFloatBuffer(fbSize);
		modeldata.position(0);
		for (int oloop = 0; oloop < fv.size(); oloop++) {
			v = (fv.get(oloop));
			t = (ft.get(oloop));
			n = (fn.get(oloop));
			for (int iloop = 0; iloop < v.length; iloop++) {
				// Fill in the texture coordinate data
				for (int tloop = 0; tloop < tcoords.length; tloop++)
					// Only T2F is supported in InterLeavedArrays!!
					tcoords[tloop] = ((float[]) vtData.get(t[iloop] - 1))[tloop];
				modeldata.put(tcoords);
				// Fill in the normal coordinate data
				for (int vnloop = 0; vnloop < coords.length; vnloop++)
					coords[vnloop] = ((float[]) vnData.get(n[iloop] - 1))[vnloop];
				modeldata.put(coords);
				// Fill in the vertex coordinate data
				for (int vloop = 0; vloop < coords.length; vloop++)
					coords[vloop] = ((float[]) vData.get(v[iloop] - 1))[vloop];
				modeldata.put(coords);
			}
		}
		modeldata.position(0);
	}

	private void ConstructTV() {
		int[] v, t;
		float tcoords[] = new float[2]; // Only T2F is supported in
										// InterLeavedArrays!!
		float coords[] = new float[3];
		int fbSize = PolyCount * (FaceMultiplier * 5); // 3v Per Poly, 2vt Per Poly
		modeldata = BufferUtil.newFloatBuffer(fbSize);
		modeldata.position(0);
		for (int oloop = 0; oloop < fv.size(); oloop++) {
			v = (fv.get(oloop));
			t = (ft.get(oloop));
			for (int iloop = 0; iloop < v.length; iloop++) {
				// Fill in the texture coordinate data
				for (int tloop = 0; tloop < tcoords.length; tloop++)
					// Only T2F is supported in InterLeavedArrays!!
					tcoords[tloop] = ((float[]) vtData.get(t[iloop] - 1))[tloop];
				modeldata.put(tcoords);
				// Fill in the vertex coordinate data
				for (int vloop = 0; vloop < coords.length; vloop++)
					coords[vloop] = ((float[]) vData.get(v[iloop] - 1))[vloop];
				modeldata.put(coords);
			}
		}
		modeldata.position(0);
	}

	private void ConstructNV() {
		int[] v, n;
		float coords[] = new float[3];
		int fbSize = PolyCount * (FaceMultiplier * 6); // 3v Per Poly, 3vn Per Poly
		modeldata = BufferUtil.newFloatBuffer(fbSize);
		modeldata.position(0);
		for (int oloop = 0; oloop < fv.size(); oloop++) {
			v = (fv.get(oloop));
			n = (fn.get(oloop));
			for (int iloop = 0; iloop < v.length; iloop++) {
				// Fill in the normal coordinate data
				for (int vnloop = 0; vnloop < coords.length; vnloop++)
					coords[vnloop] = ((float[]) vnData.get(n[iloop] - 1))[vnloop];
				modeldata.put(coords);
				// Fill in the vertex coordinate data
				for (int vloop = 0; vloop < coords.length; vloop++)
					coords[vloop] = ((float[]) vData.get(v[iloop] - 1))[vloop];
				modeldata.put(coords);
			}
		}
		modeldata.position(0);
	}

	private void ConstructV() {
		int[] v;
		float coords[] = new float[3];
		int fbSize = PolyCount * (FaceMultiplier * 3); // 3v Per Poly
		modeldata = BufferUtil.newFloatBuffer(fbSize);
		modeldata.position(0);
		for (int oloop = 0; oloop < fv.size(); oloop++) {
			v = (fv.get(oloop));
			for (int iloop = 0; iloop < v.length; iloop++) {
				// Fill in the vertex coordinate data
				for (int vloop = 0; vloop < coords.length; vloop++)
					coords[vloop] = ((float[]) vData.get(v[iloop] - 1))[vloop];
				modeldata.put(coords);
			}
		}
		modeldata.position(0);
	}

	public void DrawModel() {
		if (init) {
			ConstructInterleavedArray();
			cleanup();
			init = false;
		}
		GL11.glEnable(GL11.GL_CULL_FACE);
		GL11.glCullFace(GL11.GL_BACK);
		// GL11.glPolygonMode(GL11.GL_FRONT,GL11.GL_LINE);
		GL11.glDrawArrays(FaceFormat, 0, PolyCount * FaceMultiplier);
		GL11.glDisable(GL11.GL_CULL_FACE);
	}

	public String Polycount() {
		String pc = Integer.toString(PolyCount);
		return pc;
	}

	private void cleanup() {
		vData.clear();
		vtData.clear();
		vnData.clear();
		fv.clear();
		ft.clear();
		fn.clear();
		modeldata.clear();
		System.gc();
	}

}

Have a read up on matrices and transformations, e.g. http://www.swiftless.com/tutorials/opengl/rotation.html
It’s not as complicated as it might first appear :wink:

i guess the model itself is offset.

you could “center” the mesh after you loaded it :

  1. compute mesh center point by computing a AABB enclosing all vertices :
  2. loop all vertices and add center point. [icode]vec3 vertex; vertex += center;[/icode]

in your code it looks like should do this after [icode]modeldata[/icode] is filled up. tho’ its a interleaved array which makes it a bit complicated.

you need to loop over so it matches one of the formats picked : GL11.GL_T2F_N3F_V3F, GL11.GL_T2F_V3F, GL11.GL_N3F_V3F or GL11.GL_V3F.

It’s perfectly normal that the model is at 0, 0, 0.

If you’re using modern OpenGL (3+), then in your shader you need to specify a uniform matrix “the model matrix”, which is a mat4. This model matrix gets multiplied with the view and projection matrix (M * V * P) to find the vertex position.

Because the model matrix is a uniform, you can specify it just before each draw call. So in lwjgl, you would use the matrix class, create a new matrix, translate it to your desired location, and upload the model matrix uniform to your shader.

If you’re not using modern openGL, then you can use the built in openGL matrix functions; however I’m afraid I don’t know them as I don’t use the older opengl :x

I would advise against offsetting the actual vertices of your OBJ, as this will lead to undesired results when rotating the model, or dynamically translating it.

Thank you all for your replies, each one was carefully read until I understood what each of you had said.
However, I humbly admit that I think the obj loader I am using is too complex for my current understanding of lwjgl.

So… Here’s what I do know: translations, rotations, and general 3D world space - but this is only useful for the code generated voxel structures I currently use (an example is the closing/opening doors in my “game”, or the random generated trees and ore resource functions I have developed)

Shamefully I will ask, if somebody would be generous enough to provide me with a bare bones .obj or .3ds model loader, one which is very very VERY basic, so that I could interpret each line of code and build onto it from there? If so, I will gladly release the source code back to the public for other beginners such as myself.

Edit: Any .obj or .3ds models I load would be static content, such as a fire pit, or some model that cannot be aesthetically generated using vector math. So dynamic translations aren’t important in this case, as long as I can spawn the object to a set position.

yea, .obj loading should be done simpler than this.

my point was, if the .obj file itself contains vertices way off the center, or already rotated, or scaled to a incompatible with your world unit-size, then even drawing it at the center will draw it “off”. cleaning up after loading, before drawing can fix that.

Do you have any websites or blog pages in mind you wouldn’t mind linking me to? I really want this feature in my project, but I want code that I actually understand otherwise I will be posting for help over and over… and I’d rather deal with code where, if I have a problem, I can fix it myself through research opposed to begging the internet to spoon feed me code that I wont be able to call my own.

Edit: I have a set of static prop models for my game which I created some months ago, while this project was still a growing idea. They’re centered to a “tile” and scaled appropriately, so if I had my own model loader it would be a cake walk to implement them into the 3D scene :slight_smile:

Your obj loader was fine! You just needed to apply a transformation matrix when drawing it! :slight_smile:

:wink: