Hey Guys,
How can I load an GMMOD File with LWJGL and render it the screen ?
I did some searching, and found a solution for this. The GMMOD file is a text based file, and contains model in the GAME MAKER 3D MODEL format. The format seems to have changed, and modern GM versions now use OBJ instead of this file format.
However, there is some editor to create models in this file format: http://www.maartenbaert.be/model-creator/
Iâve tried to create a simple triangle with it (itâs not at all easy to use, Blender is the right tool for this job) and exported it into a GMMOD file, and it created the following contents.
100
5 GMMC 5.0 1.0000 1 1.0000 0.0000 1 1 0 0 0
0 4.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
9 0.0000 4.0000 23.0000 0.0000 -1.0000 0.0000 0.0000 0.0000 16777215.0000 1.0000
9 -21.0000 4.0000 0.0000 0.0000 -1.0000 0.0000 0.0000 0.0000 16777215.0000 1.0000
9 19.0000 4.0000 0.0000 0.0000 -1.0000 0.0000 0.0000 0.0000 16777215.0000 1.0000
1 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
From my limited understanding (and guesses), the first part seems to be the instruction type and the rest are arguments. My assumption is as follows:
[]100 seems to be file format version, or some magic number.
[]9 seems to be vertex definition, followed by 3 floats for position, 3 floats for normal and 4 floats for color.
[*]I didnât understand what 0 and 1 commands stand for.
It is extremely difficult to progress, since there exist no documentation for this file format. And then, I found a cool function in this program. It can export GMMOD files into OBJ models. Use the settings icon in View 2 to export as OBJ.
And this one works fine, I can load the OBJ model in Blender and it rendered exactly as it is in the GMMC editor. Definitely recommend to go use the OBJ export feature and update all your models to this format. BTW out of curiosity, from where did you get GMMOD models?
Just FYI I found this old forum thread. Here it looks like this is just one âfunction callâ per line, with the first number being the function ID, followed by exactly ten arguments, which are used - or simply ignored - as function parameters.
I find this format because I searched on the Internet for an alternative because I donât like Blender that much.So I found the Game Maker Model Creator and I like the Design of it because this Program is very easy to use.So I want to implement a GMMOD Support in my 3D Engine
I already tried to use the OBJ Export in Game Make Model Creator but always when I try to load a .obj exported from the Model Creator my Program gives an Error.
Iâve worked with that format a bunch, as I originally came to Java from Game Maker.
SHC, youâre mostly correct
line 0 is the magic number
the FIRST number on line 1 is the amount lines after line 1. So if you have 5, youâll have a start line, 3 vertices, and an end line. You can ignore every other number on this line. For the most part they are meaningless.
However, the start and end lines are technically meaningless, as I donât think they were ever implemented as they were intended. So when reading the file, you can ignore them.
This is also a little annoying, because sometimes game maker models (When saved from older versions of Game Maker) have a start and an end line every 600-1000 lines or so, so the model is being split for no reason. However Model Creator just uses 1 on the start and end of each vertex type.
I wrote a little bit of java code maybe 3-4 years ago to load gmmod models when I first started with LWJGL. Here it is:
public void loadFromString(String[] loadStrings, boolean flipX) {
int pointer = 0;
int white = Color.white.getRGB();
if (loadStrings[0].equals("100")) {
String header = loadStrings[1];
String[] headerStrings = header.split(" ");
int amtPolys = Integer.parseInt(headerStrings[0]);
if (amtPolys >= 5) {
this.vertices = new Vertex[amtPolys];
for (int i = 2; i < loadStrings.length; i++) {
String[] dat = loadStrings[i].split(" "); //read the line of the D3D model, and split it into an array
// This is the triangle type. Most cases this is equal to 9.
float polyType = Integer.parseInt(dat[0]);
if (polyType <= 1)
continue;
// Vertex position
float vx = Float.parseFloat(dat[1]);
float vy = Float.parseFloat(dat[2]);
float vz = Float.parseFloat(dat[3]);
// Vertex normal
float nx = Float.parseFloat(dat[4]);
float ny = Float.parseFloat(dat[5]);
float nz = Float.parseFloat(dat[6]);
// Texture coordinates are not always defined. Default to 0.
float tx = 0;
float ty = 0;
// Color and alpha are not always defined. Default to 0.
float c = (int) white;
float a = 1;
if ( polyType == 3 ) {
// Just simple triangle. No color or texture data.
}else if ( polyType == 8 ) { // Polygon type 8
tx = Float.parseFloat(dat[7]);
ty = Float.parseFloat(dat[8]);
}else if ( polyType == 9 ) { //Polygon type 9
tx = Float.parseFloat(dat[7]);
ty = Float.parseFloat(dat[8]);
float col = Float.parseFloat(dat[9]);
float alp = Float.parseFloat(dat[10]);
if (alp > 0) {
c = (int) col;
a = alp;
}
} else if ( polyType == 5 || polyType == 13 ) {
continue;
}
// Flip vertex and normals
if (flipX) {
vx = -vx;
ny = -ny;
nz = -nz;
}
// Add vertex to model
addVertex(pointer++, vx, vy, vz, nx, ny, nz, tx, ty);
}
// Clip the model as it might has some blank vertices
clip();
if (flipX) {
// Flip the model, as the x component was inverted
flipFaces();
}
}
}
}
Itâs pretty messy, but then again, so is the Game Maker Model format. It only loads the polygons from a GMMOD file. It will not load things like shape definitions (cube shapes, sphere shapes, ect). You need to convert those to triangles first before saving.
The gmmod format is still quite nice to be honest. Itâs a very lightweight model, with no other files. However, it is also extremely basic. No multi-texturing, or material support. And Model Creator will always flat-shade your model.
Thanks I will try it out Orange451
But first I tried out to model it with Game Maker Model Creator export it as .obj then load it with blender and export it as .obj again with the given Settings from ThinMatrix because I use his OBJ Loader.Now I get an Exception
java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at engine.rendering.OBJLoader.processVertex(OBJLoader.java:90)
at engine.rendering.OBJLoader.loadObjModel(OBJLoader.java:59)
at Game.engineTester.init(engineTester.java:50)
at Game.engineTester.run(engineTester.java:60)
at java.lang.Thread.run(Unknown Source)
OBJ Loader:
public class OBJLoader {
public static VBOModel loadObjModel(String fileName, ModelLoader loader) {
FileReader fr = null;
try {
fr = new FileReader(new File("res/Models/" + fileName + ".obj"));
} catch (FileNotFoundException e) {
System.err.println("Couldn't load file!");
e.printStackTrace();
}
BufferedReader reader = new BufferedReader(fr);
String line;
List<Vector3f> vertices = new ArrayList<Vector3f>();
List<Vector2f> textures = new ArrayList<Vector2f>();
List<Vector3f> normals = new ArrayList<Vector3f>();
List<Integer> indices = new ArrayList<Integer>();
float[] verticesArray = null;
float[] normalsArray = null;
float[] textureArray = null;
int[] indicesArray = null;
try {
while (true) {
line = reader.readLine();
String[] currentLine = line.split(" ");
if (line.startsWith("v ")) {
Vector3f vertex = new Vector3f(Float.parseFloat(currentLine[1]), Float.parseFloat(currentLine[2]), Float.parseFloat(currentLine[3]));
vertices.add(vertex);
} else if (line.startsWith("vt ")) {
Vector2f texture = new Vector2f(Float.parseFloat(currentLine[1]), Float.parseFloat(currentLine[2]));
textures.add(texture);
} else if (line.startsWith("vn ")) {
Vector3f normal = new Vector3f(Float.parseFloat(currentLine[1]), Float.parseFloat(currentLine[2]), Float.parseFloat(currentLine[3]));
normals.add(normal);
} else if (line.startsWith("f ")) {
textureArray = new float[vertices.size() * 2];
normalsArray = new float[vertices.size() * 3];
break;
}
}
while (line != null) {
if (!line.startsWith("f ")) {
line = reader.readLine();
continue;
}
String[] currentLine = line.split(" ");
String[] vertex1 = currentLine[1].split("/");
String[] vertex2 = currentLine[2].split("/");
String[] vertex3 = currentLine[3].split("/");
processVertex(vertex1, indices, textures, normals, textureArray, normalsArray);
processVertex(vertex2, indices, textures, normals, textureArray, normalsArray);
processVertex(vertex3, indices, textures, normals, textureArray, normalsArray);
line = reader.readLine();
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
verticesArray = new float[vertices.size() * 3];
indicesArray = new int[indices.size()];
int vertexPointer = 0;
for (Vector3f vertex : vertices) {
verticesArray[vertexPointer++] = vertex.x1;
verticesArray[vertexPointer++] = vertex.x2;
verticesArray[vertexPointer++] = vertex.x3;
}
for (int i = 0; i < indices.size(); i++) {
indicesArray[i] = indices.get(i);
}
return loader.loadToVAO(verticesArray, indicesArray, textureArray);
}
private static void processVertex(String[] vertexData, List<Integer> indices, List<Vector2f> textures, List<Vector3f> normals, float[] textureArray, float[] normalsArray) {
int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
indices.add(currentVertexPointer);
Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
textureArray[currentVertexPointer * 2] = currentTex.x1;
textureArray[currentVertexPointer * 2 + 1] = 1 - currentTex.x2;
Vector3f currentNorm = normals.get(Integer.parseInt(vertexData[2]) - 1);
normalsArray[currentVertexPointer * 3] = currentNorm.x1;
normalsArray[currentVertexPointer * 3 + 1] = currentNorm.x2;
normalsArray[currentVertexPointer * 3 + 2] = currentNorm.x3;
}
}
EDIT: It worked I just forget to bind the Texture in Blender