package rajawali.parser.md5;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
import rajawali.animation.mesh.AAnimationObject3D;
import rajawali.animation.mesh.AnimationSkeleton;
import rajawali.animation.mesh.BoneAnimationObject3D;
import rajawali.animation.mesh.SkeletonJoint;
import rajawali.materials.AMaterial;
import rajawali.materials.DiffuseMaterial;
import rajawali.materials.TextureManager;
import rajawali.math.Number3D;
import rajawali.parser.AMeshParser;
import rajawali.parser.IAnimatedMeshParser;
import rajawali.renderer.RajawaliRenderer;
import rajawali.util.RajLog;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.Matrix;
public class MD5MeshParser extends AMeshParser implements IAnimatedMeshParser {
private static final String MD5_VERSION = "MD5Version";
private static final String COMMAND_LINE = "commandline";
private static final String NUM_JOINTS = "numJoints";
private static final String NUM_MESHES = "numMeshes";
private static final String NUM_VERTS = "numverts";
private static final String NUM_TRIS = "numtris";
private static final String NUM_WEIGHTS = "numweights";
private static final String JOINTS = "joints";
private static final String MESH = "mesh";
private static final String SHADER = "shader";
private static final String VERT = "vert";
private static final String TRI = "tri";
private static final String WEIGHT = "weight";
private int mNumJoints;
private int mNumMeshes;
private int mMeshIndex = 0;
private MD5Mesh[] mMeshes;
private SkeletonJoint[] mJoints;
public float[] mBindPoseMatrix;
public float[][] mInverseBindPoseMatrix;
public MD5MeshParser(RajawaliRenderer renderer, String fileOnSDCard) {
super(renderer, fileOnSDCard);
}
public MD5MeshParser(RajawaliRenderer renderer, int resourceId) {
this(renderer.getContext().getResources(), renderer.getTextureManager(), resourceId);
}
public MD5MeshParser(Resources resources, TextureManager textureManager, int resourceId) {
super(resources, textureManager, resourceId);
}
public AAnimationObject3D getParsedAnimationObject() {
return (AAnimationObject3D)mRootObject;
}
@Override
public MD5MeshParser parse() throws ParsingException {
super.parse();
BufferedReader buffer = null;
if(mFile == null) {
InputStream fileIn = mResources.openRawResource(mResourceId);
buffer = new BufferedReader(new InputStreamReader(fileIn));
} else {
try {
buffer = new BufferedReader(new FileReader(mFile));
} catch (FileNotFoundException e) {
RajLog.e("["+getClass().getCanonicalName()+"] Could not find file.");
throw new ParsingException(e);
}
}
String line;
try {
while((line = buffer.readLine()) != null) {
StringTokenizer parts = new StringTokenizer(line, " ");
int numTokens = parts.countTokens();
if(numTokens == 0)
continue;
String type = parts.nextToken();
if(type.equalsIgnoreCase(MD5_VERSION)) {
RajLog.d("MD5 Version: " + parts.nextToken());
} else if(type.equalsIgnoreCase(COMMAND_LINE)) {
} else if(type.equalsIgnoreCase(NUM_JOINTS)) {
mNumJoints = Integer.parseInt(parts.nextToken());
mJoints = new SkeletonJoint[mNumJoints];
} else if(type.equalsIgnoreCase(NUM_MESHES)) {
mNumMeshes = Integer.parseInt(parts.nextToken());
mMeshes = new MD5Mesh[mNumMeshes];
} else if(type.equalsIgnoreCase(JOINTS)) {
parseJoints(buffer);
} else if(type.equals(MESH)) {
parseMesh(buffer);
}
}
buffer.close();
buildBindPose();
buildMeshes();
calculateNormals();
createObjects();
} catch (IOException e) {
try {
buffer.close();
} catch(Exception ex) {}
throw new ParsingException(e);
}
return this;
}
private void parseJoints(BufferedReader buffer) {
try {
String line;
int count = 0;
while((line = buffer.readLine()) != null) {
SkeletonJoint joint = new SkeletonJoint();
if(line.length() == 0)
continue;
if(line.indexOf('}') > -1) {
return;
}
line = line.replace('\t', ' ');
// -- Bone Name
int offset = line.lastIndexOf('"');
joint.setName(line.substring(line.indexOf('"') + 1, offset));
// -- Parent Index
offset += 2;
joint.setParentIndex(Integer.parseInt(line.substring(offset, line.indexOf(' ', offset))));
// -- position
offset = line.indexOf(')');
String[] p = line.substring(line.indexOf('(') + 2, offset).split(" ");
joint.setPosition(Float.parseFloat(p[0]), Float.parseFloat(p[2]), Float.parseFloat(p[1]));
// -- orientation
p = line.substring(line.indexOf('(', offset) + 2, line.lastIndexOf(')')).split(" ");
joint.setOrientation(Float.parseFloat(p[0]), Float.parseFloat(p[2]), Float.parseFloat(p[1]));
joint.getOrientation().computeW();
mJoints[count++] = joint;
}
} catch(Exception e) {
e.printStackTrace();
}
}
private void parseMesh(BufferedReader buffer) {
try {
String line;
MD5Mesh mesh = new MD5Mesh();
while((line = buffer.readLine()) != null) {
line = line.replace("\t", "");
StringTokenizer parts = new StringTokenizer(line, " ");
int numTokens = parts.countTokens();
if(line.indexOf('}') > -1) {
mMeshes[mMeshIndex++] = mesh;
return;
}
if(numTokens == 0 || line.indexOf('}') > -1)
continue;
String type = parts.nextToken();
if(type.equalsIgnoreCase(SHADER)) {
mesh.shader = parts.nextToken();
mesh.shader = mesh.shader.replace("\"", "");
if(mesh.shader.length() == 0) continue;
int lastDelim = mesh.shader.lastIndexOf("/");
if(lastDelim == -1)
lastDelim = mesh.shader.lastIndexOf("\\");
if(lastDelim > -1)
mesh.shader = mesh.shader.substring(lastDelim + 1, mesh.shader.length());
int dot = mesh.shader.lastIndexOf(".");
if(dot > -1)
mesh.shader = mesh.shader.substring(0, dot);
} else if(type.equalsIgnoreCase(NUM_VERTS)) {
mesh.numVerts = Integer.parseInt(parts.nextToken());
mesh.verts = new MD5Vert[mesh.numVerts];
} else if(type.equalsIgnoreCase(VERT)) {
int index = Integer.parseInt(parts.nextToken());
MD5Vert vert = new MD5Vert();
// -- ignore '('
parts.nextToken();
vert.texU = Float.parseFloat(parts.nextToken());
vert.texV = Float.parseFloat(parts.nextToken());
// -- ignore ')'
parts.nextToken();
vert.weightIndex = Integer.parseInt(parts.nextToken());
vert.weightElem = Integer.parseInt(parts.nextToken());
mesh.numWeightElems += vert.weightElem;
mesh.maxNumWeights = Math.max(mesh.maxNumWeights, vert.weightElem);// MAXIMUM
mesh.verts[index] = vert;
} else if(type.equalsIgnoreCase(NUM_TRIS)) {
mesh.numTris = Integer.parseInt(parts.nextToken());
mesh.tris = new int[mesh.numTris][];
} else if(type.equalsIgnoreCase(TRI)) {
int index = Integer.parseInt(parts.nextToken());
mesh.tris[index] = new int[] { Integer.parseInt(parts.nextToken()), Integer.parseInt(parts.nextToken()), Integer.parseInt(parts.nextToken()) };
} else if(type.equalsIgnoreCase(NUM_WEIGHTS)) {
mesh.numWeights = Integer.parseInt(parts.nextToken());
mesh.weights = new MD5Weight[mesh.numWeights];
} else if(type.equalsIgnoreCase(WEIGHT)) {
int index = Integer.parseInt(parts.nextToken());
MD5Weight weight = new MD5Weight();
weight.jointIndex = Integer.parseInt(parts.nextToken());
weight.weightValue = Float.parseFloat(parts.nextToken());
mesh.weights[index] = weight;
// -- ignore '('
parts.nextToken();
weight.position.x = Float.parseFloat(parts.nextToken());
weight.position.z = Float.parseFloat(parts.nextToken());
weight.position.y = Float.parseFloat(parts.nextToken());
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
private void buildMeshes() {
for(int i=0; i<mNumMeshes; ++i) {
int boneIndex = 0;
MD5Mesh mesh = mMeshes[i];
mesh.vertices = new float[mesh.numVerts * 3];
mesh.boneIndices = new int[mesh.numWeightElems];
mesh.boneWeights = new float[mesh.numWeightElems];
mesh.texCoords = new float[mesh.numVerts * 2];
for(int j=0; j<mesh.numVerts; ++j) {
MD5Vert vert = mesh.verts[j];
Number3D position = new Number3D();
for(int k=0; k<vert.weightElem; ++k) {
MD5Weight weight = mesh.weights[vert.weightIndex + k];
SkeletonJoint joint = mJoints[weight.jointIndex];
Number3D rotPos = joint.getOrientation().multiply(weight.position);
Number3D pos = Number3D.add(joint.getPosition(), rotPos);
pos.multiply(weight.weightValue);
position.add(pos);
mesh.boneIndices[boneIndex] = weight.jointIndex;
mesh.boneWeights[boneIndex++] = weight.weightValue;
}
int vertIndex = j * 3;
mesh.vertices[vertIndex] = position.x;
mesh.vertices[vertIndex+1] = position.y;
mesh.vertices[vertIndex+2] = position.z;
int uvIndex = j * 2;
mesh.texCoords[uvIndex] = vert.texU;
mesh.texCoords[uvIndex + 1] = vert.texV;
}
}
}
private void calculateNormals() {
for(int i=0; i<mNumMeshes; ++i) {
MD5Mesh mesh = mMeshes[i];
mesh.indices = new int[mesh.numTris * 3];
int index = 0;
for(int j=0; j<mesh.numTris; ++j) {
int index0 = mesh.tris[j][0];
int index1 = mesh.tris[j][1];
int index2 = mesh.tris[j][2];
mesh.indices[index++] = index0;
mesh.indices[index++] = index1;
mesh.indices[index++] = index2;
int index03 = index0 * 3;
int index13 = index1 * 3;
int index23 = index2 * 3;
Number3D v0 = new Number3D(mesh.vertices[index03], mesh.vertices[index03 + 1], mesh.vertices[index03 + 2]);
Number3D v1 = new Number3D(mesh.vertices[index13], mesh.vertices[index13 + 1], mesh.vertices[index13 + 2]);
Number3D v2 = new Number3D(mesh.vertices[index23], mesh.vertices[index23 + 1], mesh.vertices[index23 + 2]);
Number3D normal = Number3D.cross(Number3D.subtract(v2, v0), Number3D.subtract(v1, v0));
mesh.verts[index0].normal.add(normal);
mesh.verts[index1].normal.add(normal);
mesh.verts[index2].normal.add(normal);
}
if(mesh.normals == null) mesh.normals = new float[mesh.numVerts * 3];
for(int j=0; j<mesh.numVerts; ++j) {
MD5Vert vert = mesh.verts[j];
Number3D normal = vert.normal.clone();
vert.normal.normalize();
normal.normalize();
int normIndex = j * 3;
mesh.normals[normIndex] = normal.x;
mesh.normals[normIndex+1] = normal.y;
mesh.normals[normIndex+2] = normal.z;
vert.normal.setAll(0, 0, 0);
// -- bind-pose normal to joint-local
// so the animated normal can be computed faster
for(int k=0; k<vert.weightElem; ++k) {
MD5Weight weight = mesh.weights[vert.weightIndex + k];
SkeletonJoint joint = mJoints[weight.jointIndex];
vert.normal.add(Number3D.multiply(joint.getOrientation().multiply(normal), weight.weightValue));
}
}
}
}
private void buildBindPose() {
mBindPoseMatrix = new float[mNumJoints*16];
mInverseBindPoseMatrix = new float[mNumJoints][];
for(int i=0; i<mNumJoints; ++i) {
SkeletonJoint joint = mJoints[i];
float[] boneTranslation = new float[16];
float[] boneRotation = new float[16];
float[] boneMatrix = new float[16];
float[] inverseBoneMatrix = new float[16];
Matrix.setIdentityM(boneTranslation, 0);
Matrix.setIdentityM(boneRotation, 0);
Number3D jointPos = joint.getPosition();
Matrix.translateM(boneTranslation, 0, jointPos.x, jointPos.y, jointPos.z);
joint.getOrientation().toRotationMatrix(boneRotation);
Matrix.multiplyMM(boneMatrix, 0, boneTranslation, 0, boneRotation, 0);
Matrix.invertM(inverseBoneMatrix, 0, boneMatrix, 0);
for(int j=0; j<16; j++){
mBindPoseMatrix[i + j] = boneMatrix[j];
}
mInverseBindPoseMatrix[i] = inverseBoneMatrix;
}
}
private void createObjects() {
AnimationSkeleton root = new AnimationSkeleton();
root.uBoneMatrix = mBindPoseMatrix;
root.mInverseBindPoseMatrix = mInverseBindPoseMatrix;
root.setJoints(mJoints);
mRootObject = root;
for(int i=0; i<mNumMeshes; ++i) {
MD5Mesh mesh = mMeshes[i];
BoneAnimationObject3D o = new BoneAnimationObject3D();
o.setData(
mesh.vertices, GLES20.GL_STREAM_DRAW,
mesh.normals, GLES20.GL_STREAM_DRAW,
mesh.texCoords, GLES20.GL_STATIC_DRAW,
null, GLES20.GL_STATIC_DRAW,
mesh.indices, GLES20.GL_STATIC_DRAW
);
o.setMD5Mesh(mesh);
o.setName("MD5Mesh_" + i);
o.setSkeleton(mRootObject);
boolean hasTexture = mesh.shader != null && mesh.shader.length() > 0;
DiffuseMaterial mat = new DiffuseMaterial(AMaterial.SKELETAL_ANIMATION);
mat.setNumJoints(mNumJoints);
mat.setMaxWeights(mesh.maxNumWeights);
o.setMaterial(mat);
if(!hasTexture) {
mat.setUseColor(!hasTexture);
o.setColor(0xff000000 + (int)(Math.random() * 0xffffff));
} else {
int identifier = mResources.getIdentifier(mesh.shader, "drawable", mResources.getResourcePackageName(mResourceId));
if(identifier == 0) {
RajLog.e("Couldn't find texture " + mesh.shader);
break;
}
o.addTexture(mTextureManager.addTexture(BitmapFactory.decodeResource(mResources, identifier)));
}
mRootObject.addChild(o);
}
}
public class MD5Mesh {
public String shader;
public int numVerts;
public int numTris;
public int numWeights;
public int numWeightElems = 0;
public int maxNumWeights=0;
public MD5Vert[] verts;
public int[][] tris;
public MD5Weight[] weights;
public float[] vertices;
public float[] normals;
public int[] indices;
public int[] boneIndices;
public float[] boneWeights;
public float[] texCoords;
}
public class MD5Vert {
public float texU;
public float texV;
public int weightIndex;
public int weightElem;
public Number3D normal = new Number3D();
public String toString() {
return texU + ", " + texV + ", " + weightIndex + ", " + weightElem;
}
}
public class MD5Weight {
public int jointIndex;
public float weightValue;
public Number3D position = new Number3D();
public String toString() {
return jointIndex + ", " + weightValue + ", " + position.toString();
}
}
}