Need some help to load Quake MDL Model files

Hello guys, i’m new to this forum, but i started OpenGL since few weeks. This week, i need to end my quake mdl viewer, but even after following this tutorial: MDL file format specifications (Quake's models)
i’m always blocked, nothing of interesting appears on the screen. I wrote that with Java, and i use code::blocks next to eclipse, to compare values, but scale and translate vectors seems to be unsynchronized. Here are my classes, if you need the 3d model, tell me, i’ll upload it. Thanks in advance !

package fr.plaigon.mdlloader;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.lwjgl.util.vector.Vector3f;

import com.google.common.io.LittleEndianDataInputStream;

public class MDLReader
{
	public String filepath = "";
	public String filename = "";
	public MDLModel model = new MDLModel();
	
	public MDLReader(String fileName)
	{
		loadobject(fileName);
	}

	public void loadobject(String objfilename)
	{
		if (objfilename != null && objfilename.length() > 0)
		{
			String[] pathParts = GLApp.getPathAndFile(objfilename);
			filepath = pathParts[0];
			filename = pathParts[1];

			try
			{
				loadobject(GLApp.getInputStream(objfilename));
			}
			catch (Exception e)
			{
				System.out.println("MDLReader.loadobject(): Failed to read file: " + objfilename + " " + e);
			}
		}
	}

	public void loadobject(InputStream in)
	{
		if (in != null)
		{
			int numVertices, numTris, numFrames;
			int skinWidth, skinHeight;
			FileInputStream fis = (FileInputStream)in;
			try
			{
				LittleEndianDataInputStream dis = new LittleEndianDataInputStream(fis);
				
				//Header start
				this.model.header.ident = dis.readInt();
				this.model.header.version = dis.readInt();
				
				this.model.header.scale = new Vector3f(dis.readFloat(), dis.readFloat(), dis.readFloat());
				this.model.header.translate = new Vector3f(dis.readFloat(), dis.readFloat(), dis.readFloat());
				
				this.model.header.boundingRadius = dis.readFloat();
				
				this.model.header.eyePosition = new Vector3f(dis.readFloat(), dis.readFloat(), dis.readFloat());
				
				this.model.header.numSkins = dis.readInt();
				this.model.header.skinWidth = dis.readInt();
				this.model.header.skinHeight = dis.readInt();
				this.model.header.numVertices = dis.readInt();
				this.model.header.numTriangles = dis.readInt();
				this.model.header.numFrames = dis.readInt();
				
				this.model.header.syncType = dis.readInt();
				this.model.header.flags = dis.readInt();
				this.model.header.size = dis.readFloat();
				//Header end
				
				this.model.texturesID = new int[this.model.header.numSkins];
				this.model.skinInfo.group = dis.readInt();
				byte[] dataArray = new byte[this.model.header.skinWidth * this.model.header.skinHeight];
				dis.readFully(dataArray);
				this.model.skinInfo.dataArray = dataArray;
//				this.model.texturesID[0] = this.model.makeTextureFromSkin();

				TexCoord[] texCoordsArray = new TexCoord[this.model.header.numVertices];
				for(int i = 0; i < texCoordsArray.length; i++)
				{
					int onSeam = dis.readInt();
					int s = dis.readInt();
					int t = dis.readInt();
					TexCoord texCoord = new TexCoord(onSeam, s, t);
					texCoordsArray[i] = texCoord;
					
					
				}
				this.model.textureCoordinates = texCoordsArray;
				
				Triangle[] trianglesArray = new Triangle[this.model.header.numTriangles];
				for(int i = 0; i < trianglesArray.length; i++)
				{
					int facesFront = dis.readInt();
					int[] verticesIndices = new int[] {dis.readInt(), dis.readInt(), dis.readInt()};
					Triangle triangle = new Triangle(facesFront, verticesIndices);
					trianglesArray[i] = triangle;
				}
				this.model.triangles = trianglesArray;
				
				Frame[] framesArray = new Frame[this.model.header.numFrames];
				for(int i = 0; i < framesArray.length; i++)
				{
					dis.readInt();//type ( 0 = simple, !0 = groupe ==> mdl_groupframe_t)
					
					int[] bboxMinCoordinates = new int[] {dis.read(), dis.read(), dis.read()};
//					System.out.println(new StringBuilder("bboxmin vertex coordinates: ").append("x: " + bboxMinCoordinates[0]).append(" y: " + bboxMinCoordinates[1]).append(" z: " + bboxMinCoordinates[2]).toString());
					Vertex bboxMin = new Vertex(bboxMinCoordinates, dis.read());
					int[] bboxMaxCoordinates = new int[] {dis.read(), dis.read(), dis.read()};
//					System.out.println(new StringBuilder("bboxmax vertex coordinates: ").append("x: " + bboxMaxCoordinates[0]).append(" y: " + bboxMaxCoordinates[1]).append(" z: " + bboxMaxCoordinates[2]).toString());
					Vertex bboxMax = new Vertex(bboxMaxCoordinates, dis.read());
					
					byte[] name = new byte[16];
					dis.readFully(name);
					String frameName = new String(name);//frame's name
					
					Vertex[] verticesArray = new Vertex[this.model.header.numVertices];
					for(int i2 = 0; i2 < verticesArray.length; i2++)
					{
						int coordinates[] = new int[] {dis.read(), dis.read(), dis.read()};
						verticesArray[i2] = new Vertex(coordinates, dis.read());
					}
					framesArray[i] = new Frame(bboxMin, bboxMax, frameName, verticesArray);
				}
				this.model.frames = framesArray;
				
				dis.close();
				fis.close();
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}
	}
}

Main class:

package fr.plaigon;

import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.vector.Vector3f;

import fr.plaigon.mdlloader.MDLImporter;
import fr.plaigon.mdlloader.MDLReader;
import static org.lwjgl.opengl.GL11.*;

import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;

public class MDLMainFrame
{
	private long lastFrame;
	private Vector3f rotation = new Vector3f();
	private Vector3f location = new Vector3f();
	
	private final MDLImporter modelImporter = new MDLImporter();
	
	public MDLMainFrame()
	{
		this.init();
		setUpDisplay();

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		GLU.gluPerspective(30f, (float) (640 / 480), 0.3f, 100f);
		glMatrixMode(GL_MODELVIEW);
		glEnable(GL_DEPTH_TEST);

		getDelta();
		while (!Display.isCloseRequested()) {
			int delta = getDelta();

			render(delta);
			checkInput();
			Display.update();
			Display.sync(60);
		}
		System.exit(0);
	}
	
	public static void main(String[] args)
	{
		new MDLMainFrame();
	}
	
	private void setUpDisplay()
	{
		try
		{
			Display.setDisplayMode(new DisplayMode(640, 480));
			Display.setVSyncEnabled(true);
			Display.setResizable(true);
			Display.setTitle("Happy Easter!");
			Display.create();
		}
		catch (LWJGLException e)
		{
			System.err.println("The display wasn't initialized correctly. :(");
			Display.destroy();
			System.exit(1);
		}
	}

    /** 
     * Calculate how many milliseconds have passed 
     * since last frame.
     * 
     * @return milliseconds passed since last frame 
     */
    public int getDelta() {
        long time = getTime();
        int delta = (int) (time - lastFrame);
        lastFrame = time;
      
        return delta;
    }
     
    /**
     * Get the accurate system time
     * 
     * @return The system time in milliseconds
     */
    public long getTime() {
        return (Sys.getTime() * 1000) / Sys.getTimerResolution();
    }
	
    private void render(int delta)
	{
    	glPushMatrix();
		glEnable(GL_CULL_FACE);
	    glCullFace(GL_BACK);
		glEnable(GL_DEPTH_TEST);
    	glEnable(GL_ALPHA_TEST);
//    	glAlphaFunc(GL_GREATER, 0.5F);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glColor3f(1f, 1f, 1f);
		
		glEnable(GL_TEXTURE_2D);                     

//	    glShadeModel(GL_SMOOTH);
//	    glDepthFunc(GL_LESS);
//	    glDepthMask(true);
//	    glEnable(GL_NORMALIZE); 
	    //enable lighting
//	    glEnable(GL_LIGHTING);
		
//		glRotatef(rotation.x, 1, 0, 0);
//		glRotatef(rotation.y, 0, 1, 0);
//		glRotatef(rotation.z, 0, 0, 1);
//		glTranslatef(location.x, location.y, location.z);
		this.modelImporter.reader.model.renderFrame(0);
//		this.RenderCube();
		glPopMatrix();
	}
    
	private void checkInput() 
	{	
		boolean up = Keyboard.isKeyDown(Keyboard.KEY_W);
		boolean down = Keyboard.isKeyDown(Keyboard.KEY_S);
		boolean left = Keyboard.isKeyDown(Keyboard.KEY_A);
		boolean right = Keyboard.isKeyDown(Keyboard.KEY_D);
		boolean flyUp = Keyboard.isKeyDown(Keyboard.KEY_E);
		boolean flyDown = Keyboard.isKeyDown(Keyboard.KEY_Q);
		boolean speedUp = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT);
		boolean slowDown = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL);
		float walkSpeed = 0.15F;
		
		float mx = Mouse.getDX();
		float my = Mouse.getDY();
		mx *= 0.25F;
		my *= 0.25F;
		rotation.y += mx;
		if(rotation.y > 360)
			rotation.y -= 360;
		
		rotation.x -= my;
		if(rotation.x > 85)
			rotation.x = 85;
		if(rotation.x < -85)
			rotation.x = -85;
		
		if(speedUp && !slowDown)
			walkSpeed = 0.25F;
		if(slowDown && !speedUp)
			walkSpeed = 0.10F;
		
		if(up && !down)
		{
			float cz  = (float) (walkSpeed * 2 * Math.cos(Math.toRadians(rotation.y)));
			float cx  = (float) (walkSpeed * Math.sin(Math.toRadians(rotation.y)));
			location.z += cz;
			location.x -= cx;
		}
		if(down && !up)
		{
			float cz  = (float) (walkSpeed * 2 * Math.cos(Math.toRadians(rotation.y)));
			float cx  = (float) (walkSpeed * Math.sin(Math.toRadians(rotation.y)));
			location.z -= cz;
			location.x += cx;
		}
		if(right && !left)
		{
			float cz  = (float) (walkSpeed * 2 * Math.cos(Math.toRadians(rotation.y + 90)));
			float cx  = (float) (walkSpeed * Math.sin(Math.toRadians(rotation.y)));
			location.z += cz;
			location.x -= cx;
		}
		if(left && !right)
		{
			float cz  = (float) (walkSpeed * 2 * Math.cos(Math.toRadians(rotation.y + 90)));
			float cx  = (float) (walkSpeed * Math.sin(Math.toRadians(rotation.y)));
			location.z -= cz;
			location.x += cx;
		}
		
		if(flyUp && !flyDown)
			location.y -= walkSpeed;
		if(flyDown && !flyUp)
			location.y += walkSpeed;
	}
    
	private void init()
	{
		this.modelImporter.load("res/mdl/B_BB110out.mdl");
		System.out.println(this.modelImporter.reader.model.header);
//		-48
//		-497
//		-321
		
//		1073741824
//		536870912
//		-1610612736
	}
	
	private void RenderCube() {

		glTranslatef(0f + location.x, 0.0f + location.y, -7f + location.z);
		glRotatef(45f, 0.0f, 1.0f, 0.0f);
		glColor3f(0.5f, 0.5f, 1.0f);

		glBegin(GL_QUADS);
		glColor3f(1.0f, 1.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, -1.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glVertex3f(-1.0f, 1.0f, 1.0f);
		glVertex3f(1.0f, 1.0f, 1.0f);
		glColor3f(1.0f, 0.5f, 0.0f);
		glVertex3f(1.0f, -1.0f, 1.0f);
		glVertex3f(-1.0f, -1.0f, 1.0f);
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glVertex3f(1.0f, -1.0f, -1.0f);
		glColor3f(1.0f, 0.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, 1.0f);
		glVertex3f(-1.0f, 1.0f, 1.0f);
		glVertex3f(-1.0f, -1.0f, 1.0f);
		glVertex3f(1.0f, -1.0f, 1.0f);
		glColor3f(1.0f, 1.0f, 0.0f);
		glVertex3f(1.0f, -1.0f, -1.0f);
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glVertex3f(1.0f, 1.0f, -1.0f);
		glColor3f(0.0f, 0.0f, 1.0f);
		glVertex3f(-1.0f, 1.0f, 1.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glVertex3f(-1.0f, -1.0f, 1.0f);
		glColor3f(1.0f, 0.0f, 1.0f);
		glVertex3f(1.0f, 1.0f, -1.0f);
		glVertex3f(1.0f, 1.0f, 1.0f);
		glVertex3f(1.0f, -1.0f, 1.0f);
		glVertex3f(1.0f, -1.0f, -1.0f);
		glEnd();

	}
}

And MDLModel:

package fr.plaigon.mdlloader;

import static org.lwjgl.opengl.GL11.GL_LINEAR;
import static org.lwjgl.opengl.GL11.GL_REPEAT;
import static org.lwjgl.opengl.GL11.GL_RGB;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL11.glBegin;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glColor3f;
import static org.lwjgl.opengl.GL11.glEnd;
import static org.lwjgl.opengl.GL11.glGenTextures;
import static org.lwjgl.opengl.GL11.glTexParameteri;
import static org.lwjgl.opengl.GL11.glVertex3f;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;

import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.vector.Vector3f;

public class MDLModel
{
	public static final File COLOR_MAP = new File("res/mdl/colormap.txt");
	
	public TexCoord[] textureCoordinates;
	public int[] texturesID;
	public Triangle[] triangles;
	public Frame[] frames;
	public Header header = new Header();
	public SkinTextInformation skinInfo = new SkinTextInformation();
	
	public class Header
	{
		public int ident;//identifier or "magical number"
		public int version;//usually 6
		public Vector3f scale;//Scale model vector
		public Vector3f translate;//Translation model vector
		public float boundingRadius;//Sphere radius from a minimum radius in which the model can be put
		public Vector3f eyePosition;//Eyes model vector
		public int numSkins, skinWidth, skinHeight;
		public int numVertices, numTriangles, numFrames;
		public int syncType, flags;
		public float size;//average size of triangles
		
		@Override
		public String toString()
		{
			StringBuilder result = new StringBuilder("Header vars from requested mdl quake model are:").append("
 -ident or magic number: " + this.ident).append("
 -version: " + this.version)
					.append("
 -scale vector: " + this.scale).append("
 -translate vector: " + this.translate).append("
 -bounding radius: " + this.boundingRadius).append("
 -eyes position vector: " + this.eyePosition)
					.append("
 -numSkins: " + this.numSkins).append("
 -skinWidth: " + this.skinWidth).append("
 -skin height: " + this.skinHeight).append("
 -numVertices: " + this.numVertices)
					.append("
 -numTriangles: " + this.numTriangles).append("
 -numFrames: " + this.numFrames).append("
 -sync type: " + this.syncType).append("
 -flags: " + this.flags).append("
 -size: " + this.size);
			return result.toString();
		}
	}
	
	public class SkinTextInformation
	{
		public int group;//0 = simple, !0 = groupe
		public byte[] dataArray;
	}
	
	public void renderFrame(int n)
	{
		if ((n < 0) || (n > this.header.numFrames - 1))//Car n démarre à 0
		{
			System.out.println("n in invalid range !");
			return;
		}
		
		glBindTexture(GL_TEXTURE_2D, this.texturesID[0]);
		
		glBegin(GL_TRIANGLES);
		for(int i = 0; i < this.header.numTriangles; i++)
		{
			for(int j = 0; j < 3; j++)
			{
				Vertex vert = this.frames[0].verticesArray[this.triangles[i].verticesIndices[j]];
				
//				vert.coordinates[0] = (int)(-1610612736 * vert.coordinates[0]) + 1073741824;
//				vert.coordinates[1] = (int)(536870912 * vert.coordinates[1]) + 536870912;
//				vert.coordinates[2] = (int)(1610612736 * vert.coordinates[2]) + -1610612736;

				glColor3f((-1610612736 * vert.coordinates[0]) + 1073741824, (536870912 * vert.coordinates[1]) + 536870912, (1610612736 * vert.coordinates[2]) + -1610612736);
				glVertex3f((-1610612736 * vert.coordinates[0]) + 1073741824, (536870912 * vert.coordinates[1]) + 536870912, (1610612736 * vert.coordinates[2]) + -1610612736);
			}
		}
		glEnd();
	}
	
	public int makeTextureFromSkin()
	{
		int[] pixels = new int[this.header.skinWidth * this.header.skinHeight * 3];
		
		//Convert indexed 8 bits texture to RGB 24 bits
		for(int i = 0; i < this.header.skinWidth * this.header.skinHeight; i++)
		{
			pixels[(i * 3) + 0] = this.getRGBValueFromPalette( ((i + 1) % 4) == 0 ? (i + 1) / 4 : ( (int)(i / 4) + 1), ((i + 1)% 4) == 0 ? 3 : ((int)i % 4))[0];
			pixels[(i * 3) + 1] = this.getRGBValueFromPalette( ((i + 1) % 4) == 0 ? (i + 1) / 4 : ( (int)(i / 4) + 1), ((i + 1)% 4) == 0 ? 3 : ((int)i % 4))[1];
			pixels[(i * 3) + 2] = this.getRGBValueFromPalette( ((i + 1) % 4) == 0 ? (i + 1) / 4 : ( (int)(i / 4) + 1), ((i + 1)% 4) == 0 ? 3 : ((int)i % 4))[2];
		}
		this.texturesID[0] = glGenTextures();
		glBindTexture(GL_TEXTURE_2D, this.texturesID[0]);
		
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		
		ByteBuffer buffer = ByteBuffer.wrap(this.integersToBytes(pixels));
		GLU.gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, this.header.skinWidth, this.header.skinHeight, GL_RGB, GL_UNSIGNED_BYTE, buffer);
		return 0;
	}
	
	private byte[] integersToBytes(int[] values)
	{
	   ByteArrayOutputStream baos = new ByteArrayOutputStream();
	   DataOutputStream dos = new DataOutputStream(baos);
	   for(int i=0; i < values.length; ++i)
	   {
	        try
	        {
				dos.writeInt(values[i]);
			}
	        catch (IOException e)
	        {
				e.printStackTrace();
			}
	   }

	   return baos.toByteArray();
	}  
	
	public int[] getRGBValueFromPalette(int l, int c)
	{
		if(l < 1 || l > 64)
		{
			System.out.println("line out of palette text file");
			return new int[] {0, 0, 0};
		}
		if(c < 0 || c > 3)
		{
			System.out.println("column out of palette text file");
			return new int[] {0, 0, 0};
		}
		
		int[] rgbValues = new int[3];
		try
		{
			BufferedReader reader = new BufferedReader(new InputStreamReader(new DataInputStream(new FileInputStream(MDLModel.COLOR_MAP))));
			try
			{
				String line = "";
				for(int i = 0; i < l; i++)
					line = reader.readLine();
				
				rgbValues[0] = Integer.valueOf(splitInEqualParts(line, 4)[c].replaceAll(",", " ").substring(1, 4).trim());
				rgbValues[1] = Integer.valueOf(splitInEqualParts(line, 4)[c].replaceAll(",", " ").substring(5, 9).trim());
				rgbValues[2] = 	Integer.valueOf(splitInEqualParts(line, 4)[c].replaceAll(",", " ").substring(10, 14).trim());
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}
		catch (FileNotFoundException e)
		{
			e.printStackTrace();
		}
		return rgbValues;
	}
	
	private String[] splitInEqualParts(final String s, final int n)
	{
	    if(s == null)
	        return null;
	    final int strlen = s.length();
	    if(strlen < n)
	    {
	        // this could be handled differently
	        throw new IllegalArgumentException("String too short");
	    }
	    final String[] arr = new String[n];
	    final int tokensize = strlen / n + (strlen % n == 0 ? 0 : 1);
	    for(int i = 0; i < n; i++)
	    {
	        arr[i] = s.substring(i * tokensize, Math.min((i + 1) * tokensize, strlen));
	    }
	    return arr;
	}
}

Have you looked at the Quake source code? It might be a helpful reference. Quake/gl_model.c at master · id-Software/Quake · GitHub

Thanks for your answer, but the tutorial explains all of that, all of reading binary mdl file. I juste need help about rendering vertices. I know that’s very easy, but i started a while ago, so i’m not able to see what i did wrong :confused: