/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. ******************************************************************************/ package com.badlogic.gdx.graphics.g3d.loaders.md2; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.Mesh; import com.badlogic.gdx.graphics.Mesh.VertexDataType; import com.badlogic.gdx.graphics.VertexAttribute; import com.badlogic.gdx.graphics.VertexAttributes.Usage; import com.badlogic.gdx.graphics.g3d.ModelLoaderHints; import com.badlogic.gdx.graphics.g3d.loaders.KeyframedModelLoader; import com.badlogic.gdx.graphics.g3d.model.keyframe.Keyframe; import com.badlogic.gdx.graphics.g3d.model.keyframe.KeyframedAnimation; import com.badlogic.gdx.graphics.g3d.model.keyframe.KeyframedModel; import com.badlogic.gdx.graphics.g3d.model.keyframe.KeyframedSubMesh; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.utils.LittleEndianInputStream; import com.badlogic.gdx.utils.ObjectMap; public class MD2Loader implements KeyframedModelLoader { public KeyframedModel load(FileHandle file, ModelLoaderHints hints) { float frameDuration = 0.2f; if (hints instanceof MD2LoaderHints) { frameDuration = ((MD2LoaderHints) hints).frameDuration; } return load(file, frameDuration); } public KeyframedModel load(FileHandle fileHandle, float frameDuration) { InputStream in = fileHandle.read(); try { return load(in, frameDuration); } finally { if (in != null) try { in.close(); } catch (IOException e) { } ; } } public KeyframedModel load(InputStream in, float frameDuration) { try { byte[] bytes = loadBytes(in); MD2Header header = loadHeader(bytes); float[] texCoords = loadTexCoords(header, bytes); MD2Triangle[] triangles = loadTriangles(header, bytes); MD2Frame[] frames = loadFrames(header, bytes); return buildModel(header, triangles, texCoords, frames, frameDuration); } catch (Exception ex) { ex.printStackTrace(); return null; } } private KeyframedModel buildModel(MD2Header header, MD2Triangle[] triangles, float[] texCoords, MD2Frame[] frames, float frameDuration) { ArrayList<VertexIndices> vertCombos = new ArrayList<VertexIndices>(); short[] indices = new short[triangles.length * 3]; int idx = 0; short vertIdx = 0; for (int i = 0; i < triangles.length; i++) { MD2Triangle triangle = triangles[i]; for (int j = 0; j < 3; j++) { VertexIndices vert = null; boolean contains = false; for (int k = 0; k < vertCombos.size(); k++) { VertexIndices vIdx = vertCombos.get(k); if (vIdx.vIdx == triangle.vertices[j] && vIdx.tIdx == triangle.texCoords[j]) { vert = vIdx; contains = true; break; } } if (!contains) { vert = new VertexIndices(triangle.vertices[j], triangle.texCoords[j], vertIdx); vertCombos.add(vert); vertIdx++; } indices[idx++] = vert.nIdx; } } idx = 0; float[] uvs = new float[vertCombos.size() * 2]; for (int i = 0; i < vertCombos.size(); i++) { VertexIndices vtI = vertCombos.get(i); uvs[idx++] = texCoords[vtI.tIdx * 2]; uvs[idx++] = texCoords[vtI.tIdx * 2 + 1]; } for (int i = 0; i < frames.length; i++) { MD2Frame frame = frames[i]; idx = 0; float[] newVerts = new float[vertCombos.size() * 6]; for (int j = 0; j < vertCombos.size(); j++) { VertexIndices vIdx = vertCombos.get(j); newVerts[idx++] = frame.vertices[vIdx.vIdx * 3]; newVerts[idx++] = frame.vertices[vIdx.vIdx * 3 + 1]; newVerts[idx++] = frame.vertices[vIdx.vIdx * 3 + 2]; newVerts[idx++] = MD2Normals.normals[frame.normalIndices[vIdx.vIdx]][1]; newVerts[idx++] = MD2Normals.normals[frame.normalIndices[vIdx.vIdx]][2]; newVerts[idx++] = MD2Normals.normals[frame.normalIndices[vIdx.vIdx]][0]; } frame.vertices = newVerts; } header.numVertices = vertCombos.size(); float[] blendedVertices = new float[header.numVertices * 8]; MD2Frame frame = frames[0]; idx = 0; int idxV = 0; int idxT = 0; for (int i = 0; i < header.numVertices; i++) { VertexIndices vIdx = vertCombos.get(i); blendedVertices[idx++] = frame.vertices[idxV++]; blendedVertices[idx++] = frame.vertices[idxV++]; blendedVertices[idx++] = frame.vertices[idxV++]; blendedVertices[idx++] = frame.vertices[idxV++]; blendedVertices[idx++] = frame.vertices[idxV++]; blendedVertices[idx++] = frame.vertices[idxV++]; blendedVertices[idx++] = uvs[idxT++]; blendedVertices[idx++] = uvs[idxT++]; } ObjectMap<String, KeyframedAnimation> animations = new ObjectMap<String, KeyframedAnimation>(); String lastName = frames[0].name; int beginFrame = 0; for (int frameNum = 1; frameNum < frames.length; frameNum++) { if (!frames[frameNum].name.equals(lastName) || frameNum == frames.length - 1) { int subAnimLen = frameNum - beginFrame; KeyframedAnimation subAnim = new KeyframedAnimation(lastName, frameDuration, new Keyframe[subAnimLen]); for (int subFrame = beginFrame; subFrame < frameNum; subFrame++) { int absFrameNum = subFrame - beginFrame; frame = frames[subFrame]; float[] vertices = new float[header.numVertices * 6]; idx = 0; idxV = 0; for (int i = 0; i < header.numVertices; i++) { vertices[idx++] = frame.vertices[idxV++]; vertices[idx++] = frame.vertices[idxV++]; vertices[idx++] = frame.vertices[idxV++]; vertices[idx++] = frame.vertices[idxV++]; vertices[idx++] = frame.vertices[idxV++]; vertices[idx++] = frame.vertices[idxV++]; } Keyframe keyFrame = new Keyframe(absFrameNum * frameDuration, vertices); subAnim.keyframes[absFrameNum] = keyFrame; animations.put(subAnim.name, subAnim); } lastName = frames[frameNum].name; beginFrame = frameNum; } } KeyframedAnimation animation = new KeyframedAnimation("all", frameDuration, new Keyframe[frames.length]); float timeStamp = 0; for (int frameNum = 0; frameNum < frames.length; frameNum++) { frame = frames[frameNum]; float[] vertices = new float[header.numVertices * 6]; idx = 0; idxV = 0; for (int i = 0; i < header.numVertices; i++) { vertices[idx++] = frame.vertices[idxV++]; vertices[idx++] = frame.vertices[idxV++]; vertices[idx++] = frame.vertices[idxV++]; vertices[idx++] = frame.vertices[idxV++]; vertices[idx++] = frame.vertices[idxV++]; vertices[idx++] = frame.vertices[idxV++]; } Keyframe keyFrame = new Keyframe(frameNum * frameDuration, vertices); animation.keyframes[frameNum] = keyFrame; } Mesh mesh = new Mesh(VertexDataType.VertexArray, false, header.numVertices, indices.length, new VertexAttribute(Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE), new VertexAttribute( Usage.Normal, 3, ShaderProgram.NORMAL_ATTRIBUTE), new VertexAttribute(Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + "0")); mesh.setIndices(indices); animations.put("all", animation); KeyframedSubMesh subMesh = new KeyframedSubMesh("md2-mesh", mesh, blendedVertices, animations, 6, GL10.GL_TRIANGLES); KeyframedModel model = new KeyframedModel(new KeyframedSubMesh[] { subMesh }); model.setAnimation("all", 0, false); return model; } private float[] buildTexCoords(MD2Header header, MD2Triangle[] triangles, float[] texCoords) { float[] uvs = new float[header.numVertices * 2]; for (int i = 0; i < triangles.length; i++) { MD2Triangle triangle = triangles[i]; for (int j = 0; j < 3; j++) { int vertIdx = triangle.vertices[j]; int uvIdx = vertIdx * 2; uvs[uvIdx] = texCoords[triangle.texCoords[j] * 2]; uvs[uvIdx + 1] = texCoords[triangle.texCoords[j] * 2 + 1]; } } return uvs; } private short[] buildIndices(MD2Triangle[] triangles) { short[] indices = new short[triangles.length * 3]; int idx = 0; for (int i = 0; i < triangles.length; i++) { MD2Triangle triangle = triangles[i]; indices[idx++] = triangle.vertices[0]; indices[idx++] = triangle.vertices[1]; indices[idx++] = triangle.vertices[2]; } return indices; } private MD2Frame[] loadFrames(MD2Header header, byte[] bytes) throws IOException { LittleEndianInputStream in = new LittleEndianInputStream(new ByteArrayInputStream(bytes)); in.skip(header.offsetFrames); MD2Frame[] frames = new MD2Frame[header.numFrames]; for (int i = 0; i < header.numFrames; i++) { frames[i] = loadFrame(header, in); } in.close(); return frames; } private final byte[] charBuffer = new byte[16]; private MD2Frame loadFrame(MD2Header header, LittleEndianInputStream in) throws IOException { MD2Frame frame = new MD2Frame(); frame.vertices = new float[header.numVertices * 3]; frame.normalIndices = new int[header.numVertices]; float scaleX = in.readFloat(), scaleY = in.readFloat(), scaleZ = in.readFloat(); float transX = in.readFloat(), transY = in.readFloat(), transZ = in.readFloat(); in.read(charBuffer); int len = 0; for (int i = 0; i < charBuffer.length; i++) if (charBuffer[i] == 0) { len = i; break; } frame.name = new String(charBuffer, 0, len); int vertIdx = 0; for (int i = 0; i < header.numVertices; i++) { float x = in.read() * scaleX + transX; float y = in.read() * scaleY + transY; float z = in.read() * scaleZ + transZ; frame.vertices[vertIdx++] = y; frame.vertices[vertIdx++] = z; frame.vertices[vertIdx++] = x; frame.normalIndices[i] = in.read(); // normal index } return frame; } private MD2Triangle[] loadTriangles(MD2Header header, byte[] bytes) throws IOException { LittleEndianInputStream in = new LittleEndianInputStream(new ByteArrayInputStream(bytes)); in.skip(header.offsetTriangles); MD2Triangle[] triangles = new MD2Triangle[header.numTriangles]; for (int i = 0; i < header.numTriangles; i++) { MD2Triangle triangle = new MD2Triangle(); triangle.vertices[0] = in.readShort(); triangle.vertices[1] = in.readShort(); triangle.vertices[2] = in.readShort(); triangle.texCoords[0] = in.readShort(); triangle.texCoords[1] = in.readShort(); triangle.texCoords[2] = in.readShort(); triangles[i] = triangle; } in.close(); return triangles; } private float[] loadTexCoords(MD2Header header, byte[] bytes) throws IOException { LittleEndianInputStream in = new LittleEndianInputStream(new ByteArrayInputStream(bytes)); in.skip(header.offsetTexCoords); float[] texCoords = new float[header.numTexCoords * 2]; float width = header.skinWidth; float height = header.skinHeight; for (int i = 0; i < header.numTexCoords * 2; i += 2) { short u = in.readShort(); short v = in.readShort(); texCoords[i] = u / width; texCoords[i + 1] = v / height; } in.close(); return texCoords; } private MD2Header loadHeader(byte[] bytes) throws IOException { LittleEndianInputStream in = new LittleEndianInputStream(new ByteArrayInputStream(bytes)); MD2Header header = new MD2Header(); header.ident = in.readInt(); header.version = in.readInt(); header.skinWidth = in.readInt(); header.skinHeight = in.readInt(); header.frameSize = in.readInt(); header.numSkins = in.readInt(); header.numVertices = in.readInt(); header.numTexCoords = in.readInt(); header.numTriangles = in.readInt(); header.numGLCommands = in.readInt(); header.numFrames = in.readInt(); header.offsetSkin = in.readInt(); header.offsetTexCoords = in.readInt(); header.offsetTriangles = in.readInt(); header.offsetFrames = in.readInt(); header.offsetGLCommands = in.readInt(); header.offsetEnd = in.readInt(); in.close(); return header; } private byte[] loadBytes(InputStream in) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int readBytes = 0; while ((readBytes = in.read(buffer)) > 0) { out.write(buffer, 0, readBytes); } out.close(); return out.toByteArray(); } public class VertexIndices { public VertexIndices(short vIdx, short tIdx, short nIdx) { this.vIdx = vIdx; this.tIdx = tIdx; this.nIdx = nIdx; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + tIdx; result = prime * result + vIdx; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; VertexIndices other = (VertexIndices) obj; if (tIdx != other.tIdx) return false; if (vIdx != other.vIdx) return false; return true; } public short vIdx; public short tIdx; public short nIdx; } public static class MD2LoaderHints extends ModelLoaderHints { public final float frameDuration; public MD2LoaderHints(float frameDuration) { super(false); this.frameDuration = frameDuration; } } }