www.jpct.net

jPCT - a 3d engine for Java => Support => Topic started by: cyberkilla on February 16, 2007, 02:19:28 pm

Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 02:19:28 pm
I have two rotations, similar to below.
Code: [Select]

<rotate angle="0.224545">
<axis x="0.999774" y="-0.019487" z="0.008553"/>
</rotate>


I need to "add" them together, to get the final rotation.
For some reason, it doesn't appear to work like this...

Code: [Select]

matrix1.matMul(matrix2);


The result appears to swap the actual axis.
It is hard to explain. Basically...

The first matrix is the bone's rotation axis, and the second is the rotation of the current keyframe.
It is my assumption that the keyframe is relative to the rest pose/rotation of the bone.

If I have a rest rotation that has an axis pointing 45 degrees up on the Y axis,
applying a keyframe rotation with an axis of 45 degrees up on y also, it is almost as if it stays 45 degrees up Y, but moves ~45 degrees along another axis.

Strange, huh?

EDIT:
Worth mentioning...
The skeletal system works BRILLIANT, if the rest post has no rotations.

If I make a snake, with all the bones in a line, i can animate it any way I want,
and it works fine.
It seems to be due to the adding of rest rotation with keyframe rotations.
Title: Adding Rotations
Post by: EgonOlsen on February 16, 2007, 03:16:34 pm
I'm not sure if the order is correct...what happens if you switch them? And how are matrix1 and -2 calculated? Via rotateAxis() or something?
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 03:26:22 pm
yes, both are rotateaxis
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 03:42:57 pm
Swapping doesnt work:)

Egon, if I give you the source code(commented),
and some model files, and how they are supposed to look,
could you check it out?

Actually, if you ran through a few of the methods, I am sure you would pick up on something, if it is an obvious mistake.

Its only really SimpleSkeleton's buildJoints(), and advanceAnimation()
that appear to be in error.

What is strange, is that the rest pose of the skeleton is perfect.
So is the movement of vertices in relation to their joint(s).

The problem is that the rotational keyframes are not being applied correctly.
They all move relative to the parent joint, but not in the correct direction!:)

As I mentioned above, IF I use a model that has NO rotation in its rest pose,
the keyframe rotations seem to work. NOT in the right direction - but they moved like they do in the modeler.

It is almost as if the Keyframes are rotated -90 degrees along y(?) axis, compared to the MESH, and the skeletons REST POSE.

This first bit DOES put the bones in the right REST pose.
Code: [Select]
/**
* Initializes the skeleton's bones, by setting up their
* transformation matrices.
*/
private void setupBones()
{
sortBones();

for (SimpleBone bone : bones)
{
bone.transRelative = new SimpleVector(bone.localTranslation);

Matrix localRotation = new Matrix();
localRotation.rotateAxis(bone.localRotationAxis,-bone.localRotationAngle);

bone.rotationAbsolute = localRotation;//.invert3x3();

if (bone.parent != null)
   {
//=========================================================
// If bone has a parent, apply its rotation matrix to
// the child bone.
//=========================================================
bone.rotationAbsolute.matMul(bone.parent.rotationAbsolute);

bone.transRelative.matMul(bone.parent.rotationAbsolute);
//---------------------------------------------------------
// Inherit the absolute transformation from the parent
// bone, adding it to this child bone's relative
// transformation, to get this bone's absolute
// transformation.
//---------------------------------------------------------
    bone.transAbsolute = new SimpleVector(bone.parent.transAbsolute);
    bone.transAbsolute.add(bone.transRelative);
   }
   else
   {
    //========================================================
    // This is a parent bone.
    // We do not inherit any transformations,
    // and do not have any parent rotation matrices to apply.
    //========================================================
       bone.transAbsolute = new SimpleVector(bone.transRelative);
   }
}
}


Code: [Select]

/**
* Advances the animation sequence.
* The faster this method is called, the higher the
* fidelity of the animation.
*
* The keyframes are self timed, and will not be
* made faster by more calls to this method.
*/
public void advanceAnimation()
{
SkeletalAnimation animation = animations[currentAnimation];

//=================================================================
// Ensure that the animation time has not been exceeded.
// If so, restart animation sequence, or clip to end time,
// if the animation is non-looping.
//================================================================
double time = timer.getTime();

if (time > animation.length)
{
if (animation.looping)
{
restartAnimation();
time = 0;
}
else
{
       time = animation.length;
}
}
//================================================================
// Calculate the final transform of all bones at this time
// in the animation.
//
// Inbetween keyframes are interpolated for smoother transition.
//================================================================
for( int i = 0; i < bones.length; i++)
{
SimpleVector transVec = new SimpleVector();
   SimpleBone   bone     = bones[i];
   int frame;
   
   //------------------------------------------------------------
   // If the bone has no keyframes, then skip it.
   //------------------------------------------------------------
   if (bone.rotationKeyframes[currentAnimation] == null &&
       bone.translationKeyframes[currentAnimation] == null)
   {
    bone.transFinal.set(bone.transAbsolute);
       continue;
   }
   //------------------------------------------------------------
   // Ensure we are at the correct keyframe for this time
   // in the animation.
   //------------------------------------------------------------
   frame = bone.currentTranslationKeyframe;
   while(frame < bone.translationKeyframes[currentAnimation].length &&
        bone.translationKeyframes[currentAnimation][frame].time < time)
   {
    frame++;
   }
   bone.currentTranslationKeyframe = frame;
   //------------------------------------------------------------
   // Find the correct translation vector for this time in the
   // animation, for this bone.
   //
   // If the frame is at the start, or the end, then the
   // vector is taken directly from the keyframes.
   // However, if it is neither, the vector is interpolated
   // from the current keyframe, and the previous keyframe.
   //------------------------------------------------------------
   if (frame == 0)
   {
    transVec.set(bone.translationKeyframes[currentAnimation][0].translation);
   }
   else if (frame == bone.translationKeyframes[currentAnimation].length)
   {
    transVec.set(bone.translationKeyframes[currentAnimation][frame-1].translation);
   }
   else
   {
    TranslationKeyframe curFrame  = bone.translationKeyframes[currentAnimation][frame];
    TranslationKeyframe prevFrame = bone.translationKeyframes[currentAnimation][frame-1];
    float timeDelta = (curFrame.time)-(prevFrame.time);
       float interpValue = (float)(time-(prevFrame.time))/timeDelta;

       SimpleVector curTranslation  = curFrame.translation;
       SimpleVector prevTranslation = prevFrame.translation;
         
       transVec.x = prevTranslation.x + (curTranslation.x - prevTranslation.x) * interpValue;
       transVec.y = prevTranslation.y + (curTranslation.y - prevTranslation.y) * interpValue;
       transVec.z = prevTranslation.z + (curTranslation.z - prevTranslation.z) * interpValue;
   }
   //-------------------------------------------------------------
   // Ensure we are at the correct rotational keyframe for this
   // time in the animation sequence.
   //-------------------------------------------------------------
   frame = bone.currentRotationKeyframe;
   while(frame < bone.rotationKeyframes[currentAnimation].length &&
        bone.rotationKeyframes[currentAnimation][frame].time < time)
   {
    frame++;
   }
   bone.currentRotationKeyframe = frame;
   //-------------------------------------------------------------
   // Same as above, but for the rotational keyframes instead.
   //-------------------------------------------------------------
   if (frame == 0)
   {
    bone.rotationFinal = bone.rotationKeyframes[currentAnimation][0].rotationMatrix;
   }
   else if (frame == bone.rotationKeyframes[currentAnimation].length)
   {
        bone.rotationFinal = bone.rotationKeyframes[currentAnimation][frame-1].rotationMatrix;
   }
   else
   {
    RotationKeyframe curFrame  = bone.rotationKeyframes[currentAnimation][frame];
    RotationKeyframe prevFrame = bone.rotationKeyframes[currentAnimation][frame-1];
       
    Matrix curMatrix  = curFrame.rotationMatrix;
    Matrix prevMatrix = prevFrame.rotationMatrix;
       
    float timeDelta   = (curFrame.time)-(prevFrame.time);
       float interpValue = (float)(time-(prevFrame.time))/timeDelta;

       Matrix matrix = new Matrix();
       matrix.interpolate(prevMatrix,curMatrix, interpValue);
       
       //---------------------------------------------------------
       // Set the bones rotationFinal matrix, for use by
       // vertices, and child bones.
       //---------------------------------------------------------
       bone.rotationFinal = matrix;//prevMatrix.cloneMatrix();
   }
   //-------------------------------------------------------------
   // Apply the transformation vector to the relativeFinal
   // transformation of this bone.
   //-------------------------------------------------------------
   SimpleVector relativeFinal = new SimpleVector(bone.transRelative);
   relativeFinal.add(transVec);
   
   if(bone.parent == null)
   {
    //---------------------------------------------------------
    // We are a parent bone, so just use the relative final
    // as the final transform.
    // There are no rotations, or transformations to inherit.
    //---------------------------------------------------------
    bone.transFinal = relativeFinal;
   }
   else
   {
    //---------------------------------------------------------
    // We are a child bone, so inherit any rotations,
    // and inherit the parent bone's final transformation
    // to get our own.
    //---------------------------------------------------------
    bone.rotationFinal.matMul(bone.parent.rotationFinal);
   
    relativeFinal.matMul(bone.parent.rotationFinal);
       bone.transFinal.set(bone.parent.transFinal);
       bone.transFinal.add(relativeFinal);
   }
}
}
Title: Adding Rotations
Post by: EgonOlsen on February 16, 2007, 04:12:23 pm
You may give me the code, but i'm not sure if i manage to have a closer look before wednesday. I think you'll be faster by checking this out yourself. Anyway, jkust to make sure that i understand this correctly: You mean that something like this:

Code: [Select]

    Matrix m1=new Matrix();
    Matrix m2=new Matrix();
   
    m1.rotateAxis(new SimpleVector(1,0,0), -(float) Math.PI/4);
    m2.rotateAxis(new SimpleVector(1,0,0), -(float) Math.PI/4);
   
    SimpleVector s=new SimpleVector(0,0,1);
    m1.matMul(m2);
    s.matMul(m1);
    System.out.println(s);


doesn't create a point (0,-1,0) but something like (0.5,-0.7,0.5) instead?
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 04:23:14 pm
Possibly. I know it isnt the most accurate thing in the world.

But this is almost as if it is working properly, but in the WRONG direction.

I have a snake model. It moves its tail up in the air.
In my skeletal api, it's tail rotates the right amount, but across the ground.

Almost like the keyframes are 90/-90 degrees rotated, in comparison to the actual model.

The rest pose of the bone(without any keyframes), is orientated correctly.

Do you understand me now? Strange, isnt it!
Title: Adding Rotations
Post by: EgonOlsen on February 16, 2007, 05:04:35 pm
Could it be that the axis of the keyframe's rotation has to be transformed into "rest space" first? I.e. if the rest space's rotation is 90° around X and the keyframe's axis is (0,0,1), that the actual rotation of the keyframe has to be around (0,0,1)"*x90°"=(0,-1,0) and not around (0,0,1)?
I've no clue...i'm just guessing here, and that's the first thing that came to my mind.

Edit: Fixed mixed up vectors..
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 05:07:41 pm
Could this be so, even if the rotation keyframes "appear" relative?
Ill try this.
I know i had to apply the invert of the bones rest position to the vertices to  "undo" the rest position. I am not sure it that would for for the bones themselves, but, worth a try.
Title: Adding Rotations
Post by: EgonOlsen on February 16, 2007, 05:12:43 pm
I don't mean to undo the "rest rotation" on the keyframe's axis but to apply it. But both things could be possible and both are worth a try. When you don't know how these rotations are actucally related, there's not much left than trying.
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 05:36:53 pm
I know very little about matrices, and the more I read, the less I feel I can predict the outcome of math involving them.

The rest position is already applied to the keyframe, so I dont think it could be this.
When I remove it, I get strange results.

It is quite frustrating that I cannot understand the problem.
If I could at least understand it, I could find some kind of answer.
Title: Adding Rotations
Post by: EgonOlsen on February 16, 2007, 05:41:42 pm
Does "applied to the keyframe" mean that it's applied to the rotation axis of it? I guess so...
Matrix math IS terrible. It always twists my head after some time of thinking.
 
So we have a rest rotation around an axis in object space(?) and a keyframe rotation on top of this in...object space(?), rest space(?)...? And while the rest rotation is fine, the keyframe rotation is wrong, i.e. it goes in the wrong direction? Did i get it?
Title: Adding Rotations
Post by: raft on February 16, 2007, 05:47:35 pm
excuse me if this is a silly question but did you check if ogre3d's and jPCT's coordinate systems differ ?

for instance 3d max's is different from jPCT's so during loading, one needs to rotate objects -90 degrees around X axis. only after this they look as they did in 3d max

Code: [Select]
Object3D.rotateX((float)-Math.PI/2)
Title: Adding Rotations
Post by: EgonOlsen on February 16, 2007, 05:48:47 pm
Quote from: "raft"
excuse me if this is a silly question but did you check if ogre3d's and jPCT's coordinate systems differ ?

for instance 3d max's is different from jPCT's so during loading, one needs to rotate objects -90 degrees around X axis. only after this they look as they did in 3d max
Good point. That's worth a look too. They differ for sure, because no other engine that i know of uses my strange system... :wink:
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 06:17:11 pm
Ogre is Y up.

I did not think it would matter though, because all keyframes, are supposed to be in object space?

Yes, I think you have it Egon:).

The c++ code Im basing this off, doesnt have all of the strange absoluteRotation matrices I have.

Their implementation of matrix appears to "store" an axis.
Observe....
Code: [Select]

joint.m_relative.setRotationRadians( joint.m_localRotation );
 joint.m_relative.setTranslation( joint.m_localTranslation );

They apply it straight to the relative matrix.
This makes no sense, unless milkshape format stores bones very different to ogre.

EDIT:
I do rotate the object by 180 degrees to correct the Y axis, but do not call rotateMesh, because that misaligns the vertices in object space, and makes it even worse:)
Title: Adding Rotations
Post by: EgonOlsen on February 16, 2007, 06:24:10 pm
Quote from: "cyberkilla"
Ogre is Y up.

I did not think it would matter though, because all keyframes, are supposed to be in object space?
It will still matter, because object space uses the same system. Just to make this crystal clear to everyone: In jPCT, y is down, z goes INTO the screen and x to the left. Others, where y goes up, have z pointing out of the screen.
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 06:29:04 pm
So I have to rotate everything?

If everything is using a different coordinate system, then
shouldnt it work fine, but the whole model will be upside down?

Apologies for my ignorance:)

EDIT:
Also, given that the coordinate system is affected in object space,
why is it that only the keyframes are wrong, yet the mesh, and the rest pose of the bones work? I think I'm at the very pinnacle of confusion now :wink:
Title: Adding Rotations
Post by: raft on February 16, 2007, 06:41:32 pm
there is one more point to consider: Object3D.rotateX,Y,Z methods behave differently. rotateX and rotateZ rotates object counter-clockwise while rotateY rotates clockwise

egon you will remember, i once discovered this and you said you didnt noticed it before. it was also strange to discover that 3d max does the same too. so it may be some sort of standard

this may be relevant to the case
Code: [Select]
r a f t
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 06:45:09 pm
Interesting:)

Yeh, I do not believe it would affect the model in the way it seems to be.

Even without applying any object3D rotations, it does the same trick.
I wonder if its a blender export issue.
I think id be wise to try someone elses model.
Title: Adding Rotations
Post by: EgonOlsen on February 16, 2007, 07:53:10 pm
Quote from: "raft"
egon you will remember, i once discovered this and you said you didnt noticed it before. it was also strange to discover that 3d max does the same too. so it may be some sort of standard
Yes. I've recently (re-)discovered the corresponding parts in the Javadocs and thought by myself what causes this and if it's really a correct behaviour. I came to the conclusion that it's caused by the way the rotations matrices are build. I'm not doing anything special there, so i doesn't make me wonder that 3ds shows the same effect...but it eases me off... :wink:
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 07:54:26 pm
Still nothing.

It is most strange how only keyframes are being affected.
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 10:14:08 pm
Fixed it!
It was blender :shock:  :?  :roll: !

Its still a bit wrong, but it moves pretty well now.
Title: Adding Rotations
Post by: EgonOlsen on February 16, 2007, 10:33:06 pm
What exactly was it?
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 10:55:50 pm
I cannot explain very well.

Apparently, some of the scaling, and translations were done in world space, when they were supposed to be done in object space.

To fix it, you must enter Object Mode, select the object(skeleton), and press CTRL+A.

This makes all translations and scales local(object space).

Apparently it is a bit source of confusion for people.
I was convinced id only used object space, but, reality begs to differ.
Title: Adding Rotations
Post by: cyberkilla on February 16, 2007, 11:53:24 pm
Quote from: "raft"
there is one more point to consider: Object3D.rotateX,Y,Z methods behave differently. rotateX and rotateZ rotates object counter-clockwise while rotateY rotates clockwise

egon you will remember, i once discovered this and you said you didnt noticed it before. it was also strange to discover that 3d max does the same too. so it may be some sort of standard

this may be relevant to the case
Code: [Select]
r a f t


Wait, this is affecting it! It must be! Only the Y axis is inverted now!
Thanks raft;) No idea how ill "hack" it into an inverted axis.
Title: Adding Rotations
Post by: cyberkilla on February 17, 2007, 12:39:38 am
It seems to work better now. Granted, it isnt an identical copy of the Blender version, but it is pretty good.

Edit:

Nope, still broken :roll:

Egon, is your implementation of matrices standard? I know the loader
im basing this all from uses a different matrix format to you, as you said.

I just wonder if there is something in the matMul that is different in jPCT to c++.

I really want this fixed. I have so much to finish on my FutureRP project.
The annoying thing is that the hardest, most perplexing problem, is the most documented, fundamental skeletal construct.

the c++ model doesnt use ANY invert3x3 on the transforms.
It uses it once, on the vertices, but the keyframe rotations are not subject to rotations.

http://rsn.gamedev.net/tutorials/ms3danim.asp

Thats the website.

The c++ version seems to be column major...
Code: [Select]

// Matrix data, stored in column-major order

float m_matrix[16];



Whoever provides the solution to my problem, will be held in very high regard by me:)
Title: Adding Rotations
Post by: EgonOlsen on February 17, 2007, 11:02:08 am
Order differs from row to column major. Maybe this causes some of the problems. Have look here: http://steve.hollasch.net/cgindex/math/matrix/column-vec.html

Quote
Egon, is your implementation of matrices standard?
Yes, standard for row major.
Title: Adding Rotations
Post by: EgonOlsen on February 17, 2007, 11:08:05 am
Maybe you can write a small test case, i.e. something that does the same thing like your skeletal code on one or two SimpleVectors and that compares the outcome to what was actually expected as a result.
Title: Adding Rotations
Post by: cyberkilla on February 17, 2007, 12:28:47 pm
Okay, I will do that:).

I am currently converting the matrix class into java.
Interestingly, there is a similar problem with this too.

I think I had better add support for multiple bone assignments,
because the strange effects *may* be related.
Title: Adding Rotations
Post by: raft on February 17, 2007, 03:42:20 pm
cyberkilla, i just wanna be sure what you're doing exactly: you load both mesh and skeleton from ogre's xml format right ? (you dont use any of jPCT's loaders) then i guess you're right: coordinate system and clockwise/counter-clockwise issue shouldnt matter since all are in the same object space. all mechanism should work, possibly in wrong direction/axis, but it should work, isnt it?

so may it be that you miss-interpret ogre3d format ? for instance something related to quaternion-matrix issue ?
Title: Adding Rotations
Post by: cyberkilla on February 17, 2007, 04:17:07 pm
Yes, you understand perfectly;).
I do not think it could be a problem either.

I have managed to make it move in the "general" correct direction, but it is  bu no means accurate.

I think I will use quaternions too, because the euler to matrix, to rotation vector is subject to a strange 90 degree issue(flips axis after a certain rotation).

EDIT:
Yup, im going to use quaternions with angle axis, because it looks like im being hit be "gimbal lock". I wondered what this meant, so I read about it, and it looks familiar.
Title: Adding Rotations
Post by: cyberkilla on February 17, 2007, 05:42:00 pm
I hate to jinx it again, but it is working!!!!!!
Honestly! Its arms move out, the head turns left, the legs spread forwards:D

And the snake model moves in a robotic manner, just like blender!

I dont believe it!

Quaternions made this possible for me Egon.
Axis angle, the opposite matrices style(forgotten name:) column-...),
quaternions, and 5 days work!

It was a mixture of blender being cryptic, problems with post/pre multiply of matrices, gimbal lock, and ignoring multiple bone assignments(causing incorrect vertex warping)
Title: Adding Rotations
Post by: EgonOlsen on February 17, 2007, 05:51:27 pm
Great! I'm glad that you've made it. The user of your API isn't confronted with the fact that matrices are column major internally or is he? Because that would be quite confusing...an API with row major matrices and an extension that uses column major.
BTW: I never understood why people are using column major at all. At least in germany, everyone does it the row major style in school and at universities. When reading a book or tutorial that uses column major this can be very confusing, because most of the time the writers don't mention which way they are using...and then you look at it and think: "What the **** are they doing there?", because everything looks just plain wrong... :wink:

Oh, and using matrices all the way doesn't lead to gimbal lock. Gimbal lock is caused by using euler angles. If you are using eulers internally and are creating matrices from them at each iteration, you'll run into it...if you are using matrices all the way, you won't. However, it's working now, so... :D
Title: Adding Rotations
Post by: raft on February 17, 2007, 05:58:36 pm
great ;) i'm looking forward for a demo :D
Title: Adding Rotations
Post by: cyberkilla on February 17, 2007, 06:04:07 pm
There is no problem with conflicts in the api;)
I am not that crazy;)

Actually, I believe the reason it works now, is mainly due to preMultiplication.

I was confused with bone axis inheritance, and this matrix implementation(from c++) allowed me to set a rotation, which could be applied to things after it has been applied.

I cant really understand it, so I cant explain it fully, but it works now, and it is almost definately because of this post/pre multiplication.

Btw, here is the source....
Its very simple api right now. I think it will be expanded on greatly, but it works, so Im very, very happy.

http://rpwar.com/cyber3/jpctSkeletalAPI.jar

That should work for your average ogre xml file.
I am testing uv right now, so I cant guarantee that working.
Im also adding a few lines to rotate the normals:) Just the same as vertex rotation, so it will be fine.

I would love someone to test it for me.

API:
http://rpwar.com/cyber3/doc/

Sample Models(x2)
http://rpwar.com/cyber3/models.zip
Note: these are weird animations:) Also, bare in mind I havent done normals yet.
Title: Adding Rotations
Post by: EgonOlsen on February 17, 2007, 06:10:24 pm
Quote from: "cyberkilla"
Actually, I believe the reason it works now, is mainly due to preMultiplication.
Ah yes...i remember reading a tutorial done in column major manner (undocumented of course) and i was totally confused because every multiplication looked sooo wrong. It took me a few hours to realize that the problem was caused by column major vs. row major.
Title: Adding Rotations
Post by: cyberkilla on February 17, 2007, 06:36:23 pm
Complicated stuff, but well worth the experience. Im just glad I managed to finish it.

I think I will have uv and bone blending finished in a few hours.

EDIT:
I have done UV, and ive done normal inversion.

Ive tried bone blending, but it was a little more then adding vectors, like I thought it was, so I havent finished it yet.

Usage:
Code: [Select]

SkeletalObject3D[]  objects = loadOgreXML(java.io.InputStream in, java.io.InputStream skeletonIn, float newScale);

objects[0].advanceAnimation();

Thats it really;)
Title: Adding Rotations
Post by: raft on February 20, 2007, 07:30:45 pm
Quote from: "cyberkilla"
Code: [Select]

SkeletalObject3D[]  objects = loadOgreXML(java.io.InputStream in, java.io.InputStream skeletonIn, float newScale);

objects[0].advanceAnimation();


seems as quite simple :) is it possible to specify how much the animation will be advanced ? how long the animation lasts ?
Title: Adding Rotations
Post by: cyberkilla on February 20, 2007, 11:24:17 pm
That is specified in the files themselves.

I will add a scaling method though, so you can proportionately alter the timeframe of the animation.