/**
* Copyright (C) 2013 Gundog Studios LLC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.gundogstudios.models.md2;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import com.gundogstudios.models.CombinedModel;
public class MD2Parser {
private static final String DIRECTORY = "D:/Users/Joe/Documents/TowerDefenseWars/Graphics/Processed 3D Models/MD2/";
private static void checkCreature(String baseName) throws Exception {
Model idleModel = MD2Parser.parse(new FileInputStream("data/" + baseName + "_idle.md2"));
// Model walkModel = MD2Parser.parse(new FileInputStream("data/" + baseName + "_walk.md2"));
Model attackModel = MD2Parser.parse(new FileInputStream("data/" + baseName + "_attack.md2"));
// Model deathModel = MD2Parser.parse(new FileInputStream("data/" + baseName + "_death.md2"));
float[] uvs = idleModel.getUvs();
float[] otherUVs = attackModel.getUvs();
if (uvs.length != otherUVs.length)
System.out.println("Different Length UVs");
System.out.println("Total UVs: " + uvs.length / 2);
for (int i = 0; i < uvs.length; i += 2) {
if (Float.floatToIntBits(uvs[i]) != Float.floatToIntBits(otherUVs[i])
&& Float.floatToIntBits(uvs[i + 1]) != Float.floatToIntBits(otherUVs[i + 1])) {
System.out.println("UV's not equal: " + i + " " + uvs[i] + " vs " + otherUVs[i] + " and " + uvs[i + 1]
+ " vs " + otherUVs[i + 1]);
}
}
short[] indices = idleModel.getIndices();
short[] otherIndices = attackModel.getIndices();
if (indices.length != otherIndices.length)
System.out.println("Different Length Indices");
for (int i = 0; i < indices.length; i++) {
if (indices[i] != otherIndices[i])
System.out.println("INDEX: " + i + " is different " + indices[i] + " vs " + otherIndices[i]);
}
}
public static void main(String[] argv) throws Exception {
// parseReduced("Pillars");
// CombinedModel model = parseReduced("Soldiers");
// System.out.println(model.getIdleVertices()[0].length / 3);
checkCreature("Cannons");
}
public static CombinedModel parseReduced(String baseName) {
CombinedModel model = parse(baseName);
ArrayList<Vertex> vertices = new ArrayList<Vertex>();
HashMap<Vertex, Short> table = new HashMap<Vertex, Short>();
float[] verts = model.getFloatIdleVertices()[0];
float[] uvs = model.getFloatUVs();
short[] indices = new short[verts.length / 3];
// System.out.println(verts.length / 3 + " " + uvs.length / 2 + " " + indices.length);
int index = 0;
for (int v = 0, u = 0; v < verts.length; v += 3, u += 2) {
Vertex temp = new Vertex(verts[v], verts[v + 1], verts[v + 2], uvs[u], uvs[u + 1]);
Short loc = table.get(temp);
if (loc == null) {
loc = (short) vertices.size();
table.put(temp, loc);
vertices.add(temp);
}
indices[index++] = loc;
}
uvs = new float[vertices.size() * 2];
int curr = 0;
for (Vertex v : vertices) {
uvs[curr++] = v.u;
uvs[curr++] = v.v;
}
float[][] idleVertices = generateReducedVertices(model.getFloatIdleVertices(), indices, vertices.size() * 3);
float[][] moveVertices = generateReducedVertices(model.getFloatMoveVertices(), indices, vertices.size() * 3);
float[][] attackVertices = generateReducedVertices(model.getFloatAttackVertices(), indices, vertices.size() * 3);
float[][] deathVertices = generateReducedVertices(model.getFloatDeathVertices(), indices, vertices.size() * 3);
return new CombinedModel(indices, uvs, idleVertices, moveVertices, attackVertices, deathVertices);
}
private static float[][] generateReducedVertices(float[][] vertices, short[] indices, int length) {
if (vertices == null)
return null;
float[][] newVertices = new float[vertices.length][];
for (int i = 0; i < newVertices.length; i++) {
newVertices[i] = new float[length];
int nextVertex = 0;
int curr = 0;
int j = 0;
for (int currentIndex = 0; currentIndex < indices.length;) {
if (indices[currentIndex++] == nextVertex) {
newVertices[i][curr++] = vertices[i][j++];
newVertices[i][curr++] = vertices[i][j++];
newVertices[i][curr++] = vertices[i][j++];
nextVertex++;
} else {
j += 3;
}
}
}
return newVertices;
}
public static CombinedModel parse(String baseName) {
Model idleModel = tryParse(DIRECTORY + baseName + "_idle.md2");
Model moveModel = tryParse(DIRECTORY + baseName + "_walk.md2");
Model attackModel = tryParse(DIRECTORY + baseName + "_attack.md2");
Model deathModel = tryParse(DIRECTORY + baseName + "_death.md2");
return new CombinedModel(idleModel.getIndices(), idleModel.getUvs(), idleModel.getVertices(),
moveModel.getVertices(), attackModel.getVertices(), deathModel.getVertices());
}
private static Model tryParse(String location) {
try {
return MD2Parser.parse(new FileInputStream(location));
} catch (IOException e) {
System.out.println(location + " does not exist");
}
return new Model();
}
public static Model parse(InputStream fileIn) throws IOException {
BufferedInputStream stream = new BufferedInputStream(fileIn);
MD2Header header = new MD2Header();
header.parse(stream);
byte[] bytes = new byte[header.offsetEnd - 68];
stream.read(bytes);
// getMaterials(stream, bytes);
float[] uvs = getTexCoords(bytes, header);
KeyFrame[] frames = getFrames(bytes, header);
ArrayList<Float> correctUVs = new ArrayList<Float>(uvs.length);
int[] compressedIndices = getTriangles(bytes, uvs, correctUVs, header);
for (int j = 0; j < frames.length; j++) {
frames[j].setIndices(compressedIndices);
}
for (int i = 0; i < correctUVs.size(); i++) {
uvs[i] = correctUVs.get(i);
}
float[][] vertices = new float[frames.length][];
for (int i = 0; i < frames.length; i++) {
vertices[i] = frames[i].getVertices();
}
int length = vertices[0].length / 3;
short[] indices = new short[length];
for (short i = 0; i < length; i++) {
indices[i] = i;
}
return new Model(uvs, indices, vertices);
}
// private static void getMaterials(BufferedInputStream stream, byte[] bytes, MD2Header header) throws IOException {
// ByteArrayInputStream ba = new ByteArrayInputStream(bytes, header.offsetSkins - 68, bytes.length
// - header.offsetSkins);
// LittleEndianDataInputStream is = new LittleEndianDataInputStream(ba);
//
// for (int i = 0; i < header.numSkins; i++) {
// String skinPath = is.readString(64);
// }
// }
private static float[] getTexCoords(byte[] bytes, MD2Header header) throws IOException {
ByteArrayInputStream ba = new ByteArrayInputStream(bytes, header.offsetTexCoord - 68, bytes.length
- header.offsetTexCoord);
LittleEndianDataInputStream is = new LittleEndianDataInputStream(ba);
float[] uvs = new float[header.numTexCoord * 2];
for (int i = 0; i < uvs.length;) {
uvs[i++] = (float) is.readShort() / (float) header.skinWidth;
uvs[i++] = (float) is.readShort() / (float) header.skinHeight;
}
is.close();
return uvs;
}
private static KeyFrame[] getFrames(byte[] bytes, MD2Header header) throws IOException {
ByteArrayInputStream ba = new ByteArrayInputStream(bytes, header.offsetFrames - 68, bytes.length
- header.offsetFrames);
LittleEndianDataInputStream is = new LittleEndianDataInputStream(ba);
KeyFrame[] frames = new KeyFrame[header.numFrames];
for (int i = 0; i < header.numFrames; i++) {
float scaleX = is.readFloat();
float scaleY = is.readFloat();
float scaleZ = is.readFloat();
float translateX = is.readFloat();
float translateY = is.readFloat();
float translateZ = is.readFloat();
// String name =
is.readString(16);
float[] vertices = new float[header.numVerts * 3];
for (int j = 0; j < vertices.length;) {
vertices[j++] = scaleX * is.readUnsignedByte() + translateX;
vertices[j++] = scaleY * is.readUnsignedByte() + translateY;
vertices[j++] = scaleZ * is.readUnsignedByte() + translateZ;
// what is this for?
// int normalIndex =
is.readUnsignedByte();
}
frames[i] = new KeyFrame(vertices);
}
is.close();
return frames;
}
private static int[] getTriangles(byte[] bytes, float[] uvs, ArrayList<Float> correctUVs, MD2Header header)
throws IOException {
ByteArrayInputStream ba = new ByteArrayInputStream(bytes, header.offsetTriangles - 68, bytes.length
- header.offsetTriangles);
LittleEndianDataInputStream is = new LittleEndianDataInputStream(ba);
int[] indices = new int[header.numTriangles * 3];
int index = 0;
for (int i = 0; i < header.numTriangles; i++) {
int[] vertexIDs = new int[3];
int[] uvIDS = new int[3];
indices[index + 2] = vertexIDs[2] = is.readUnsignedShort();
indices[index + 1] = vertexIDs[1] = is.readUnsignedShort();
indices[index] = vertexIDs[0] = is.readUnsignedShort();
index += 3;
uvIDS[2] = is.readUnsignedShort();
uvIDS[1] = is.readUnsignedShort();
uvIDS[0] = is.readUnsignedShort();
correctUVs.add(uvs[uvIDS[0] * 2]);
correctUVs.add(uvs[uvIDS[0] * 2 + 1]);
correctUVs.add(uvs[uvIDS[1] * 2]);
correctUVs.add(uvs[uvIDS[1] * 2 + 1]);
correctUVs.add(uvs[uvIDS[2] * 2]);
correctUVs.add(uvs[uvIDS[2] * 2 + 1]);
}
is.close();
return indices;
}
// private static String readString(InputStream stream) throws IOException {
// String result = new String();
// byte inByte;
// while ((inByte = (byte) stream.read()) != 0)
// result += (char) inByte;
// return result;
// }
private static int readInt(InputStream stream) throws IOException {
return stream.read() | (stream.read() << 8) | (stream.read() << 16) | (stream.read() << 24);
}
// private static int readShort(InputStream stream) throws IOException {
// return (stream.read() | (stream.read() << 8));
// }
//
// private static float readFloat(InputStream stream) throws IOException {
// return Float.intBitsToFloat(readInt(stream));
// }
private static class Vertex {
public float x, y, z;
public float u, v;
public Vertex(float x, float y, float z, float u, float v) {
this.x = x;
this.y = y;
this.z = z;
this.u = u;
this.v = v;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Float.floatToIntBits(u);
result = prime * result + Float.floatToIntBits(v);
result = prime * result + Float.floatToIntBits(x);
result = prime * result + Float.floatToIntBits(y);
result = prime * result + Float.floatToIntBits(z);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Vertex other = (Vertex) obj;
if (Float.floatToIntBits(u) != Float.floatToIntBits(other.u))
return false;
if (Float.floatToIntBits(v) != Float.floatToIntBits(other.v))
return false;
if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x))
return false;
if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y))
return false;
if (Float.floatToIntBits(z) != Float.floatToIntBits(other.z))
return false;
return true;
}
}
private static class MD2Header {
public int id;
public int version;
public int skinWidth;
public int skinHeight;
// public int frameSize;
// public int numSkins;
public int numVerts;
public int numTexCoord;
public int numTriangles;
// public int numGLCommands;
public int numFrames;
// public int offsetSkins;
public int offsetTexCoord;
public int offsetTriangles;
public int offsetFrames;
// public int offsetGLCommands;
public int offsetEnd;
public void parse(InputStream stream) throws IOException {
id = readInt(stream);
version = readInt(stream);
if (id != 844121161 || version != 8)
throw new IOException("This is not a valid MD2 file.");
skinWidth = readInt(stream);
if (skinWidth == 0)
skinWidth = 512;
skinHeight = readInt(stream);
if (skinHeight == 0)
skinHeight = 512;
// frameSize =
readInt(stream);
// numSkins =
readInt(stream);
numVerts = readInt(stream);
numTexCoord = readInt(stream);
numTriangles = readInt(stream);
// numGLCommands =
readInt(stream);
numFrames = readInt(stream);
// offsetSkins =
readInt(stream);
offsetTexCoord = readInt(stream);
offsetTriangles = readInt(stream);
offsetFrames = readInt(stream);
// offsetGLCommands =
readInt(stream);
offsetEnd = readInt(stream);
}
}
}