Ok so i’ve been working on and off at getting the nuget version of assimp.net to load up a model into MonoGame correctly for the past month or so. I have the basics working as you can see in the gif below though its far from done and its still got tons of problems.
Edit i just fixed this up a bit and made a github repo for it were all the work i do on it will go.
I moved the google drive full project zipped link to the below post further down but that will probably go out of data since i can’t edit posts after some time expire.
Ill accept any input or help on fixing it up or if you want to chip in.
Im however having a problem which is making me scratch my head.
When i look at how other viewers loop animations it seems they know something i don’t.
For example with the dude.fbx when i play the animation in my project and loop it.
I see a poping motion on the hand from the last frames to the first.
But other viewers like the internal vs studio don’t seem to get that effect.
Though it seems to have a stutter itself.
Now i am interpolating rotations to build new frames in total for a set amount of frames determined by a fps value i pass to the loader. So the final frames are just look ups it all looks right in the debuging and i would think its the model but it seems other viewers are handling looping differently or i have a bug.
But i cannot see anything wrong with the interpolation this part has been bugging me for a few days now,
this is the interpolation im doing which looks ok to me though this is probably the biggest method in the class.
public void SetAnimationFpsCreateFrames(int animationFramesPerSecond, RiggedModel model)
{
fps = animationFramesPerSecond;
TotalFrames = (int)(DurationInSeconds * (double)(animationFramesPerSecond));
TicksPerFramePerSecond = TicksPerSecond / (double)(animationFramesPerSecond);
SecondsPerFrame = (1d / (animationFramesPerSecond));
CalculateNewInterpolatedAnimationFrames(model);
}
private void CalculateNewInterpolatedAnimationFrames(RiggedModel model)
{
// Loop nodes.
for (int i = 0; i < animatedNodes.Count; i++)
{
// Make sure we have enough frame orientations alloted for the number of frames.
animatedNodes[i].frameOrientations = new Matrix[TotalFrames];
animatedNodes[i].frameOrientationTimes = new double[TotalFrames];
// print name of node as we loop
Console.WriteLine("name " + animatedNodes[i].nodeName);
// Loop destination frames.
for (int j = 0; j < TotalFrames; j++)
{
// Find and set the interpolated value from the s r t elements based on time.
var frameTime = j * SecondsPerFrame + .0001d;
animatedNodes[i].frameOrientations[j] = Interpolate(frameTime, animatedNodes[i]);
animatedNodes[i].frameOrientationTimes[j] = frameTime;
}
Console.WriteLine("");
}
}
/// <summary>
/// Hummm its just a little bit off it seems..
/// </summary>
public Matrix Interpolate(double animTime, RiggedAnimationNodes n)
{
var nodeAnim = n;
//
Microsoft.Xna.Framework.Quaternion q1 = nodeAnim.qrot[0];
Vector3 p1 = nodeAnim.position[0];
Vector3 s1 = nodeAnim.scale[0];
//
double tq1 = nodeAnim.qrotTime[0]; // =0;
double tp1 = nodeAnim.positionTime[0]; ; // =0;
double ts1 = nodeAnim.scaleTime[0]; //=0;
//
int qindex1 = 0; int qindex2 = 0; // for output to console only
int pindex1 = 0; int pindex2 = 0; // for output to console only
//
int i2 = 0;
if (nodeAnim.qrot.Count > 1)
{
i2 = 1;
}
Microsoft.Xna.Framework.Quaternion q2 = nodeAnim.qrot[i2];
double tq2 = nodeAnim.qrotTime[i2];
i2 = 0;
if (nodeAnim.position.Count > 1)
{
i2 = 1;
}
Vector3 p2 = nodeAnim.position[i2];
double tp2 = nodeAnim.positionTime[i2];
i2 = 0;
if (nodeAnim.scale.Count > 1)
{
i2 = 1;
}
Vector3 s2 = nodeAnim.scale[i2];
double ts2 = nodeAnim.scaleTime[i2];
//
for (int frame = 0; frame < nodeAnim.qrot.Count; frame++)
{
var t = nodeAnim.qrotTime[frame];
if (t <= animTime)
{
//1___
q1 = nodeAnim.qrot[frame];
tq1 = t;
qindex1 = frame; // for output to console only
//2___
var frame2 = frame;
if (nodeAnim.qrot.Count > 1)
{
frame2 += 1;
}
if (frame2 >= nodeAnim.qrot.Count)
{
frame2 = 0;
q2 = nodeAnim.qrot[frame2];
tq2 = nodeAnim.qrotTime[frame2] + DurationInSeconds;
qindex2 = frame2; // for output to console only
}
else
{
q2 = nodeAnim.qrot[frame2];
tq2 = nodeAnim.qrotTime[frame2];
qindex2 = frame2; // for output to console only
}
}
}
//
for (int frame = 0; frame < nodeAnim.position.Count; frame++)
{
var t = nodeAnim.positionTime[frame];
if (t <= animTime)
{
//1___
p1 = nodeAnim.position[frame];
tp1 = t;
pindex1 = frame; // for output to console only
//2___
var frame2 = frame;
if (nodeAnim.position.Count > 1)
{
frame2 += 1;
}
if (frame2 >= nodeAnim.position.Count)
{
frame2 = 0;
p2 = nodeAnim.position[frame2];
tp2 = nodeAnim.positionTime[frame2] + DurationInSeconds;
pindex2 = frame2; // for output to console only
}
else
{
p2 = nodeAnim.position[frame2];
tp2 = nodeAnim.positionTime[frame2];
pindex2 = frame2; // for output to console only
}
}
}
Microsoft.Xna.Framework.Quaternion q;
if (tq1 != tq2)
q = Microsoft.Xna.Framework.Quaternion.Slerp(q1, q2, (float)GetInterpolationTimeRatio(tq1, tq2, animTime));
else
q = q1;
Vector3 p;
if (tp1 != tp2)
p = Vector3.Lerp(p1, p2, (float)GetInterpolationTimeRatio(tp1, tp2, animTime));
else
p = p1;
Console.WriteLine("" + " animTime: " + animTime);
Console.WriteLine(" q : " + " index1: " + qindex1 + " index2: " + qindex2 + " time1: " + tq1 + " time2: " + tq2 + " quaternion: " + q.ToString());
Console.WriteLine(" p : " + " index1: " + pindex1 + " index2: " + pindex2 + " time1: " + tp1 + " time2: " + tp2 + " position: " + p.ToString());
var r = Matrix.CreateFromQuaternion(q);
r.Translation = p;
return r; // * Matrix.CreateTranslation(p); // * Matrix.CreateScale(s);
}
public double GetInterpolationTimeRatio(double s, double e, double val)
{
if (e < s)
s = s + e;
return (val - s) / (e - s);
}
Id like to say i do have the basics flushed out and it does basically work for a rigged model.
This has 2 class files … a loader and a model class.
Though the Rigged model class has 4 nested classes i actually had them separate and then nested them on purpose i figured they are so specific to the model they belong to it and as part of it.
Then game1 uses the loader to load a model from the fbx to the riggedmodel.
// load the model
RiggedModelLoader modelReader = new RiggedModelLoader(Content, riggedEffect);
int generatedAnimationFramesPerSecond = 24;
model = modelReader.LoadAsset("dude.fbx", generatedAnimationFramesPerSecond);
It is somewhat sloppy as this is basically still a sort of rough draft even after all this time
I guess im running out of steam on this and im really looking for some suggestions or direction as well.
I did quite a bit so far.
I think the overall model structural ideology is pretty solid most of it just follows assimps ideology, it has both a tree node linked list and a flat node list of references that point to the tree nodes because for somethings its simpler to iterate a list.
I added the basic stuff so far , and a very basic shader to load it and show it as seen in the above pic.
I got the animations weights and meshes loading and the textures are added to the content pipeline for now.
I pass the content manager to the model and let it load the textures it finds in the fbx or dae or whatever to its own meshes if they are present in the content folder nothing fancy beyond that it doesn’t search content folders or subfolders ect yet.
I made a custom shader for it and its pretty basic atm but i guess that’s the easier part.
I still can’t see how the mesh transform is supposed to fit in and when either if anyone knows the idea behind how that is supposed to work exactly please let me know, it seems to be a scalar sometimes and other times its just i don’t even know what.