Ok, I have completely rewritten the loader. I’m now loading the mode and displaying it fine. I’m going to start loading the joints now. Using the ByteBuffer has been much easier. I’m able to follow the spec directly and read just as it says. Hitting the joints now…
great job guy
Alright! Works. Here is the complete loading code. My apologies for the length. You may notice I changed some things, mostly I am trying to move away from using the MS3D* classes in favor of just the generic ones. Which means I changes the generic ones a little bit.
public void initialize() {
//read the byte data
byte data[] = null;
File file = new File(modelFile);
path = file.getAbsolutePath().substring(
0,
file.getAbsolutePath().length() - file.getName().length());
int length = (int)file.length();
data = new byte[length];
FileInputStream fis;
try {
fis = new FileInputStream(file);
fis.read(data);
fis.close();
} catch (FileNotFoundException e) {
LoggingSystem.getLoggingSystem().getLogger().log(
Level.WARNING,
"Could not find model file " + modelFile);
return;
} catch (IOException e) {
LoggingSystem.getLoggingSystem().getLogger().log(
Level.WARNING,
"Could not read model file " + modelFile);
return;
}
buffer = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
//read header information
//the ID is 10 bytes
byte idBuffer[] = new byte[10];
for (int i = 0; i < 10; i++) {
idBuffer[i] = buffer.get();
}
id = Conversion.byte2String(idBuffer);
version = buffer.getInt();
if (!id.equals("MS3D000000")) {
LoggingSystem.getLoggingSystem().getLogger().log(
Level.WARNING,
modelFile + " is not a valid Milkshape3D model file.");
return;
}
if (version < 3) {
LoggingSystem.getLoggingSystem().getLogger().log(
Level.WARNING,
"Bad " + modelFile + " version.");
return;
}
//Read the vertices
numVertices = buffer.getShort();
vertices = new Vertex[numVertices];
MS3DVertex vertex = null;
for (int i = 0; i < numVertices; i++) {
vertex = new MS3DVertex();
vertex.flags = buffer.get();
vertices[i] = new Vertex();
vertices[i].point[0] = buffer.getFloat();
vertices[i].point[1] = buffer.getFloat();
vertices[i].point[2] = buffer.getFloat();
vertices[i].boneId = buffer.get();
vertex.refCount = buffer.get();
}
//Read the Triangles
numTriangles = buffer.getShort();
triangles = new Triangle[numTriangles];
MS3DTriangle tri = new MS3DTriangle();
for (int i = 0; i < numTriangles; i++) {
tri = new MS3DTriangle();
tri.flags = buffer.getShort();
triangles[i] = new Triangle();
for (int j = 0; j < 3; j++) {
triangles[i].vertexIndices[j] = buffer.getShort();
}
for (int j = 0; j < 3; j++) {
triangles[i].vertexNormals[j][0] = buffer.getFloat();
triangles[i].vertexNormals[j][1] = buffer.getFloat();
triangles[i].vertexNormals[j][2] = buffer.getFloat();
}
for (int j = 0; j < 3; j++) {
triangles[i].s[j] = buffer.getFloat();
}
for (int j = 0; j < 3; j++) {
triangles[i].t[j] = 1.0f - buffer.getFloat();
}
tri.smoothingGroup = buffer.get();
tri.groupIndex = buffer.get();
}
//Read the meshes
numMeshes = buffer.getShort();
meshes = new Mesh[numMeshes];
for (int i = 0; i < numMeshes; i++) {
meshes[i] = new Mesh();
meshes[i].flags = buffer.get();
//get the name 32 bytes
byte nameBuffer[] = new byte[32];
for (int j = 0; j < 32; j++) {
nameBuffer[j] = buffer.get();
}
meshes[i].name = Conversion.byte2String(nameBuffer);
meshes[i].numTriangles = buffer.getShort();
meshes[i].triangleIndices = new int[meshes[i].numTriangles];
for (int j = 0; j < meshes[i].numTriangles; j++) {
meshes[i].triangleIndices[j] = buffer.getShort();
}
meshes[i].materialIndex = buffer.get();
}
//read materials
numMaterials = buffer.getShort();
materials = new Material[numMaterials];
for (int i = 0; i < numMaterials; i++) {
materials[i] = new Material();
//read the material name 32 bytes
byte nameBuffer[] = new byte[32];
for (int j = 0; j < 32; j++) {
nameBuffer[j] = buffer.get();
}
materials[i].name = Conversion.byte2String(nameBuffer);
materials[i].ambient[0] = buffer.getFloat();
materials[i].ambient[1] = buffer.getFloat();
materials[i].ambient[2] = buffer.getFloat();
materials[i].ambient[3] = buffer.getFloat();
materials[i].diffuse[0] = buffer.getFloat();
materials[i].diffuse[1] = buffer.getFloat();
materials[i].diffuse[2] = buffer.getFloat();
materials[i].diffuse[3] = buffer.getFloat();
materials[i].specular[0] = buffer.getFloat();
materials[i].specular[1] = buffer.getFloat();
materials[i].specular[2] = buffer.getFloat();
materials[i].specular[3] = buffer.getFloat();
materials[i].emissive[0] = buffer.getFloat();
materials[i].emissive[1] = buffer.getFloat();
materials[i].emissive[2] = buffer.getFloat();
materials[i].emissive[3] = buffer.getFloat();
materials[i].shininess = buffer.getFloat();
materials[i].transparency = buffer.getFloat();
materials[i].mode = buffer.get();
//get texture
byte texBuffer[] = new byte[128];
for(int j = 0; j < 128; j++) {
texBuffer[j] = buffer.get();
}
materials[i].textureFilename = Conversion.byte2String(texBuffer);
byte alphaBuffer[] = new byte[128];
for(int j = 0; j < 128; j++) {
alphaBuffer[j] = buffer.get();
}
materials[i].alphaFilename = Conversion.byte2String(texBuffer);
}
loadTextures();
//Read key frames
animationFPS = buffer.getFloat();
currentTime = (buffer.getFloat() * 1000);
totalFrames = buffer.getInt();
numJoints = buffer.getShort();
joints = new Joint[numJoints];
for(int i = 0; i < numJoints; i++) {
joints[i] = new Joint();
joints[i].flags = buffer.get();
//get name 32 bytes
byte[] nameBuffer = new byte[32];
for(int j = 0; j < 32; j++) {
nameBuffer[j] = buffer.get();
}
joints[i].name = Conversion.byte2String(nameBuffer);
//get parent name 32 bytes
byte[] parentBuffer = new byte[32];
for(int j = 0; j < 32; j++) {
parentBuffer[j] = buffer.get();
}
joints[i].parentName = Conversion.byte2String(parentBuffer);
joints[i].rotation[0] = buffer.getFloat();
joints[i].rotation[1] = buffer.getFloat();
joints[i].rotation[2] = buffer.getFloat();
joints[i].translation[0] = buffer.getFloat();
joints[i].translation[1] = buffer.getFloat();
joints[i].translation[2] = buffer.getFloat();
joints[i].numRotationKeyframes = buffer.getShort();
joints[i].rotationKeyframes =
new Keyframe[joints[i].numRotationKeyframes];
joints[i].numTranslationKeyframes = buffer.getShort();
joints[i].translationKeyframes =
new Keyframe[joints[i].numTranslationKeyframes];
for(int j = 0; j < joints[i].numRotationKeyframes; j++) {
joints[i].rotationKeyframes[j] = new Keyframe();
joints[i].rotationKeyframes[j].time =
(buffer.getFloat() * 1000);
joints[i].rotationKeyframes[j].parameter[0] =
buffer.getFloat();
joints[i].rotationKeyframes[j].parameter[1] =
buffer.getFloat();
joints[i].rotationKeyframes[j].parameter[2] =
buffer.getFloat();
}
for(int j = 0; j < joints[i].numTranslationKeyframes; j++) {
joints[i].translationKeyframes[j] = new Keyframe();
joints[i].translationKeyframes[j].time =
(buffer.getFloat() * 1000);
joints[i].translationKeyframes[j].parameter[0] =
buffer.getFloat();
joints[i].translationKeyframes[j].parameter[1] =
buffer.getFloat();
joints[i].translationKeyframes[j].parameter[2] =
buffer.getFloat();
}
}
}
Ok, while loading the data is now correct, the setupJoints method is totally whacked.
Hey Mik,
Don’t know if you are still interested, but I’ve put aside the Milkshape Animation for a moment and I am moving on to Quake 3 Loading and Animating. I am successfully loading all the Milkshape data (joints and all) but having trouble getting them to animate. My hope is to write a Quake 3 loader and get that animating and that may help me with the Milkshape. My design is such that the loaders build a common model anyways.
I’d still be interested in your progress though. So, let me know if you have any luck.
Hi mik and mojo.
I’ve also started on a bones animation project for ms3d.
Did u guys finish your or did you move on to mdl loaders? Anyway my program loads all the data and setups the joints correctly. Just gona battle it out some more with the Quartenions and the timer stuff and hopefully it will work. Right now it displays the image but no animation. Also I want to separate the animation from the model file and insted load in animation files like smd.
/dodgy
I got as far as loading the joints, and then moved on to MD3. I was having problems with my joint translations, causing the vertices to be totally put out of whack. If you get a loader done, I’d love to see what you did for animation.
I used the tutorial at http://rsn.gamedev.net/tutorials/ms3danim.asp and got it to work. Since the whole class is to long I’ll just post four methods:
public void setupJoints() {
for (int i = 0; i < numJoints; i++) {
Joint joint = joints[i];
joint.relative.setRotationRadians(joint.rotation);
joint.relative.setTranslation(joint.translation);
joint.parent = -1;
for (int j = 0; j < numJoints; j++) {
if (joints[j].name.compareTo(joint.parentName) == 0)
joint.parent = j;
}
if (joint.parent != -1) {
joint.absolute.set(joints[joint.parent].absolute.getMatrix());
joint.absolute.postMultiply(joint.relative);
} else {
joint.absolute.set(joint.relative.getMatrix());
}
}
for (int i = 0; i < numVertices; i++) {
Vertex vertex = vertices[i];
if (vertex.boneId != -1) {
Matrix matrix = joints[vertex.boneId].absolute;
vertex.point = matrix.inverseTranslateVect(vertex.point);
vertex.point = matrix.inverseRotateVect(vertex.point);
}
}
for (int i = 0; i < numTriangles; i++) {
Triangle triangle = triangles[i];
for (int j = 0; j < 3; j++) {
Vertex vertex = vertices[triangle.vertexIndices[j]];
if (vertex.boneId != -1) {
Matrix matrix = joints[vertex.boneId].absolute;
triangle.vertexNormals[j] = matrix.inverseRotateVect(triangle.vertexNormals[j]);
}
}
}
}
void advanceAnimation() {
double time = timer.getTime();
if (time > totalTime) {
if (looping) {
restart();
time = 0;
} else
time = totalTime;
}
for (int i = 0; i < numJoints; i++) {
float[] transVec = new float[3];
Matrix transform = new Matrix();
int frame;
Joint joint = joints[i];
if (joint.numRotationKeyframes == 0 && joint.numTranslationKeyframes == 0) {
joint.m_final.set(joint.absolute.getMatrix());
continue; // OBS!
}
frame = joint.currentTranslationkeyframe;
while (frame < joint.numTranslationKeyframes &&
joint.translationKeyframes[frame].time < time) {
frame++;
}
joint.currentTranslationkeyframe = frame;
if (frame == 0)
transVec = joint.translationKeyframes[0].parameter;
// memcpy( transVec, joint.translationKeyframes[0].parameter, sizeof ( float )*3 );
else if (frame == joint.numTranslationKeyframes)
transVec = joint.translationKeyframes[frame - 1].parameter;
// memcpy( transVec, joint.translationKeyframes[frame-1].parameter, sizeof ( float )*3 );
else {
// assert( frame > 0 && frame < joint.numTranslationKeyframes );
Keyframe curFrame = joint.translationKeyframes[frame];
Keyframe prevFrame = joint.translationKeyframes[frame - 1];
float timeDelta = curFrame.time - prevFrame.time;
float interpValue = (float) ((time - prevFrame.time) / timeDelta);
transVec[0] = prevFrame.parameter[0] + (curFrame.parameter[0] - prevFrame.parameter[0]) * interpValue;
transVec[1] = prevFrame.parameter[1] + (curFrame.parameter[1] - prevFrame.parameter[1]) * interpValue;
transVec[2] = prevFrame.parameter[2] + (curFrame.parameter[2] - prevFrame.parameter[2]) * interpValue;
}
frame = joint.currentRotationkeyframe;
while (frame < joint.numRotationKeyframes &&
joint.rotationKeyframes[frame].time < time) {
frame++;
}
joint.currentRotationkeyframe = frame;
if (frame == 0)
transform.setRotationRadians(joint.rotationKeyframes[0].parameter);
else if (frame == joint.numRotationKeyframes)
transform.setRotationRadians(joint.rotationKeyframes[frame - 1].parameter);
else {
// assert( frame > 0 && frame < pJoint->m_numRotationKeyframes );
Keyframe curFrame = joint.rotationKeyframes[frame];
Keyframe prevFrame = joint.rotationKeyframes[frame - 1];
float timeDelta = curFrame.time - prevFrame.time;
float interpValue = (float) ((time - prevFrame.time) / timeDelta);
// assert( interpValue >= 0 && interpValue <= 1 );
Quaternion qPrev = new Quaternion(prevFrame.parameter);
Quaternion qCur = new Quaternion(curFrame.parameter);
Quaternion qFinal = new Quaternion(qPrev, qCur, interpValue);
transform.setRotationQuaternion(qFinal);
}
transform.setTranslation(transVec);
Matrix relativeFinal = new Matrix(joint.relative);
relativeFinal.postMultiply(transform);
if (joint.parent == -1)
joint.m_final.set(relativeFinal.getMatrix());
else {
joint.m_final.set(joints[joint.parent].m_final.getMatrix());
joint.m_final.postMultiply(relativeFinal);
}
}
}
public void draw() {
advanceAnimation();
boolean texEnabled = gl.glIsEnabled(GL.GL_TEXTURE_2D);
// Draw by group
for (int i = 0; i < numMeshes; i++) {
int materialIndex = meshes[i].materialIndex;
if (materialIndex >= 0) {
gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, materials[materialIndex].ambient);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, materials[materialIndex].diffuse);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, materials[materialIndex].specular);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_EMISSION, materials[materialIndex].emissive);
gl.glMaterialf(GL.GL_FRONT, GL.GL_SHININESS, materials[materialIndex].shininess);
if (materials[i].textureFilename.length() > 0) {
textureManager.bindTexture(materials[i].textureFilename);
gl.glEnable(GL.GL_TEXTURE_2D);
} else
gl.glDisable(GL.GL_TEXTURE_2D);
} else {
// Material properties?
gl.glDisable(GL.GL_TEXTURE_2D);
}
gl.glBegin(GL.GL_TRIANGLES);
{
for (int j = 0; j < meshes[i].numTriangles; j++) {
int triangleIndex = meshes[i].triangleIndices[j];
Triangle pTri = (Triangle) (triangles[triangleIndex]);
for (int k = 0; k < 3; k++) {
int index = pTri.vertexIndices[k];
if (vertices[index].boneId == -1) {
// same as before
gl.glTexCoord2f(pTri.s[k], pTri.t[k]);
gl.glNormal3fv(pTri.vertexNormals[k]);
gl.glVertex3fv(vertices[index].point);
} else {
// rotate according to transformation matrix
Matrix finalMatrix = joints[vertices[index].boneId].m_final;
gl.glTexCoord2f(pTri.s[k], pTri.t[k]);
Vector newNormal = new Vector(pTri.vertexNormals[k]);
newNormal.transform3(finalMatrix);
newNormal.normalize();
gl.glNormal3fv(newNormal.getVector3());
Vector newVertex = new Vector(vertices[index].point);
newVertex.transform(finalMatrix);
gl.glVertex3fv(newVertex.getVector3());
}
}
}
}
gl.glEnd();
}
if (texEnabled)
gl.glEnable(GL.GL_TEXTURE_2D);
else
gl.glDisable(GL.GL_TEXTURE_2D);
}
public void restart() {
for (int i = 0; i < numJoints; i++) {
joints[i].currentRotationkeyframe = joints[i].currentTranslationkeyframe = 0;
joints[i].m_final.set(joints[i].absolute.getMatrix());
}
timer.reset();
}
I got this error running the loader
F:\JOGL\MS3D Loader>java -jar nehe31.jar
INIT GL IS: net.java.games.jogl.impl.windows.WindowsGLImpl
Force TGA : data/Wood.TGA
java.lang.NoClassDefFoundError: net/java/games/util/TGAImage
at MilkshapeModel.LoadGLTextures(MilkshapeModel.java:690)
at MilkshapeModel.reloadTextures(MilkshapeModel.java:660)
at MilkshapeModel.loadModelData(MilkshapeModel.java:555)
at MilkshapeModel.loadModelData(MilkshapeModel.java:399)
at Nehe31$Renderer.init(Nehe31.java:116)
at net.java.games.jogl.impl.GLDrawableHelper.init(GLDrawableHelper.java:
68)
at net.java.games.jogl.GLCanvas$InitAction.run(GLCanvas.java:201)
at net.java.games.jogl.impl.windows.WindowsGLContext.makeCurrent(Windows
GLContext.java:144)
at net.java.games.jogl.impl.windows.WindowsOnscreenGLContext.makeCurrent
(WindowsOnscreenGLContext.java:110)
at net.java.games.jogl.impl.GLContext.setRenderingThread(GLContext.java:
253)
at net.java.games.jogl.GLCanvas.setRenderingThread(GLCanvas.java:162)
at net.java.games.jogl.Animator$1.run(Animator.java:89)
at java.lang.Thread.run(Unknown Source)
I used the same tutorial Tarm. Wich Timer did you use?
My app runs but its looks like shit… vertices and stuff everywhere…
I wonder if it is the timer that is wrong. Which model have you tested it on?
[quote]vertices and stuff everywhere…
[/quote]
sounds exactly like what I am experiencing. :-/
Any help with that TGAImage class, or shall I code my own :’(
I tried it on “bert.ms3d”. I think it’s included when you download milkshape.
Here is my timer class
public class Timer {
double timerStart;
int pauseCount = 0;
double pauseTime = 0;
public Timer() {
timerStart = System.currentTimeMillis();
pauseCount = 0;
pauseTime = 0;
}
public void reset() {
timerStart = System.currentTimeMillis();
}
public double getTime() {
if (pauseCount > 0)
return pauseTime;
return (System.currentTimeMillis() - timerStart);
}
void pause() {
if (pauseCount == 0)
pauseTime = System.currentTimeMillis();
pauseCount++;
}
void unpause() {
if (pauseCount > 0) ;
pauseCount--;
if (pauseCount == 0) {
timerStart = (System.currentTimeMillis() - pauseTime);
}
}
}
Remember that c++ uses pointers, porting from c++ to java isn’t always very easy. If you get vertices at very odd places it might be because you are passing on a reference to a method when you need to copy the value instead.
Im using a model called fantasy. Its basicly just a head with headspikes growing out of it as an animation. Its good for testing as it is quit simple. The animation works but the spikes seem to grow in the wrong direction… coming out under the head instead of ontop of it.
This could surely be a reference problem that Tarm was talking about. God knows I have been known to get into trouble with pointers.
I could also be an matematichal error, maybee my slerp funtion in Quarteninons is faulty or something.
Anyway, gona look at it tonight when I come home from work.
Be well guys.
/d
Can I get some help please?
The loader won’t work for me as it’s missing some TGAImage class…
Do I really need to reinvent the wheel and code my own loader:’(
TGAImage is a part of the jogl utils jar which I believe is here
https://jogl.dev.java.net/servlets/ProjectDocumentList?folderID=159
in the jogl-demos.tar.gz
I messed a little with my postMulty method and now my models look good except that nothing gets animated… very strange.
Maybee there are some strange stuff in my Matrix/Quart classes. idont know.
/d
Just reviving an old thread here ;D
I am currently in the process of creating a loader for JOGL using the GL4JAVA port of Nehe lesson31 as a basis for my loader.
I have completed the loader but when I try to animate the Model, the Model seems to explode outwards with vertices every where. When I remove the animate function the model loads thing with no Animation.
Did anybody complete their Milkshape Loader with Skeletal Animation ?
If not can anybody have a wee look at my Animation code and see if i have made any mistakes.
void advancedAnimation(){
double timer=time.getTime();
if(timer>m_totalTime){
if(m_looping){
restart();
timer=0;
}
else{
timer=m_totalTime;
}
}
for (int i=0;i<m_numJoints;i++){
float[] transvec= new float[3];
Matrix transform=new Matrix();
int frame;
Joint joint=m_pJoints[i];
if (joint.m_numTranslationKeyframes==0 && joint.m_numTranslationKeyframes==0){
joint.m_final.set(joint.m_absolute.getMatrix());
continue;
}
frame=joint.m_currentTranslationkeyframe;
while (frame < joint.m_numTranslationKeyframes &&
joint.m_pTranslationkeyframes[frame].m_time < timer) {
frame++;
}
joint.m_currentTranslationkeyframe = frame;
if (frame==0){
transvec=joint.m_pTranslationkeyframes[0].m_parameters;
}
else if (frame==joint.m_numTranslationKeyframes)
transvec=joint.m_pTranslationkeyframes[frame-1].m_parameters;
else{
Keyframe curFrame=joint.m_pTranslationkeyframes[frame];
Keyframe prevFrame=joint.m_pTranslationkeyframes[frame-1];
float timeDelta=curFrame.m_time-prevFrame.m_time;
float interpvalue=(float)((timer-prevFrame.m_time)/timeDelta);
transvec[0]=prevFrame.m_parameters[0]+(curFrame.m_parameters[0]
-prevFrame.m_parameters[0])*interpvalue;
transvec[1]=prevFrame.m_parameters[1]+(curFrame.m_parameters[1]
-prevFrame.m_parameters[1])*interpvalue;
transvec[2]=prevFrame.m_parameters[2]+(curFrame.m_parameters[2]
-prevFrame.m_parameters[2])*interpvalue;
}
frame=joint.m_currentRotationkeyframe;
while (frame < joint.m_numRotationKeyframes &&
joint.m_pRotationkeyframes[frame].m_time < timer) {
frame++;
}
joint.m_currentRotationkeyframe=frame;
if (frame==0){
transform.setRotationRadians(joint.m_pRotationkeyframes[0].m_parameters);
}
else if (frame==joint.m_numRotationKeyframes)
transform.setRotationRadians(joint.m_pRotationkeyframes[frame-1].m_parameters);
else{
Keyframe curFrame=joint.m_pRotationkeyframes[frame];
Keyframe prevFrame=joint.m_pRotationkeyframes[frame-1];
float timeDelta=curFrame.m_time-prevFrame.m_time;
float interpvalue=(float)((timer-prevFrame.m_time)/timeDelta);
Quaternion qPrev= new Quaternion(prevFrame.m_parameters);
Quaternion qCur= new Quaternion(curFrame.m_parameters);
Quaternion qFinal = new Quaternion(qPrev,qCur,interpvalue);
transform.setRotationQuaternion(qFinal);
}
transform.setTranslation(transvec);
Matrix relativeFinal=new Matrix(joint.m_relative.getMatrix());
relativeFinal.postMultiply(transform);
if (joint.m_parent==-1){
joint.m_final.set(relativeFinal.getMatrix());
}
else{
joint.m_final.set(m_pJoints[joint.m_parent].m_final.getMatrix());
joint.m_final.postMultiply(relativeFinal);
}
}
}
Thanks in advance
EDIT*
Heres a screen capture of my app running with the Animation function taken out
http://www.honours.pwp.blueyonder.co.uk/noAnim.jpg
With the Animation function put in
http://www.honours.pwp.blueyonder.co.uk/probVertsEverywhere.jpg
***Another edit
Messed about with my animation function, got some sort of animation but it seems like the had is coming out of the models chest and the arms and legs look weird
http://www.honours.pwp.blueyonder.co.uk/probsweirdAnim.jpg
Maybe a problem with my Matrix class or Quaternion class