Confusion Of Assimp Sdk, Animation System Design Need Advice
Hellow !
I 'm going to make an animation system for game......
I will export models to custom format model using the assimp sdk. I will only export joint nodes and mesh nodes, and animation data.
The output scene file format will look like these:
<ModelNode>
<JointNode> lcltransform .....
<meshNode> lcltransform.... <\meshNode>
<\JointNode>
......many joints to omit.
<\ModelNode>
The JointNode holds inverse bind pose matrix .
As for the MeshNode, I know , I know, the static mesh is not completely static, the "static" means it can not be deformed,
it can also have motions, so mesh will be implemented as MeshNode. It will inherit transform from ancestors, so it will move!!
As for the ModelNode, why it inherit from SceneNode is that it act as the root node of all joint nodes and model nodes, somewhat look like assimp root node.
All these node will hold local transform.
The question is, assimp scene nodes are not just joint nodes and mesh nodes, and "pure node", which is used to pass transform, the root is the most apparent one.
I 'm confusing that whether it will go wrong, if I don't export these nodes, because I'm afraid that some "pure nodes" would have a non-identity transform !!
In the animation export part , I will use these code:
std::map<std::string, int> name2Joint;
struct animation
{
void export()
{
export(duration, tickPersecond);
for(node in influenced nodes)
{
jointID = name2Joint[node.name];
export(jointID, frame.pos, frame.rot, frame.scale);
}
}
};
My animation system is the one of the most naivest animation system in the world , it can only import, and display .
I have not begin to code, and I don't know if it will work, so
I will show my code, and if someone find any mistake or have a better implementation, please tell.
Here's are my data structure regarding animation in the engine side:
struct Frame
{
_pos, _rot, _scale; // all are reference to parent
};
struct JointFrame
{
_jointID; // will be used to find joint node at update time
vector<Frame> _frames;
};
struct Animation
{
vector<JointFrame> _jointFrames;
void update();
};
And here's the data structure of Model in engine side:
struct SceneNode
{
SceneNode *_parent;
pos, rot, scale; // all are reference to parent
_globalTransform;
virtual void update();
virtual void postUpdate()=0;
};
struct Joint : public SceneNode
{
Matrix4f _invBindMat; // inverse bind matrix
};
struct Model : public SceneNode
//why it inherit from SceneNode is that
// it act as the root node of all joint nodes and model nodes, somewhat look like assimp root node
{
vector<Mesh> _meshes;
vector<Joint*> _joints;
vector<Matrix4f> _JointFinalMats
void PostUpdate(); // use to update _JointFinalMats after all Joints' GlobalTransform have updated, will be called by Scene
};
struct Scene
{
vector<SceneNode*> _nodes;
void update();
};
The flowchart of big big updates :
scene::update()
{
animation.update(); // update joint's local transform by keyframes
foreach(node in _nodes) node.update(); // update joint's global transform
foreach(node in _nodes) node.postUpdate(); // update jointFinalMat array
}
void Animation ::update()
{
for(jointFrame in _jointFrames)
{
frame = jointFrame.findFrame(gametime);
joint = this->parentModel.findJoint(jointFrame._jointID);
// all are reference to parent
joint->setPos(frame._pos);
joint->setRot(frame._rot);
joint->setScale(frame._scale);
}
}
void SceneNode::update()
{
_globalTransform = localTransform(_pos, _rot, _scale) * _parent->_globalTransform ;
}
void Model::postUpdate()
{
foreach(joint in _joints)
{
_JointFinalMats[joint.ID] = joint._invBindMat * joint._globalTransform;
}
}
After I have attached joints to scene graph, and Scene::update will call joint::update, where jointFinalMat = inverseBindposeMat * jointGlobalTransform
will take place. Since the jointGlobalTransform has contain ToRootMat, I will pass a identity matrix to shader as a world matrix.
The code will look like :
if(mesh.hasBone)
{
worldMat = Identity;
}
else
{
worldMat = mesh.globalTransfom
}
the shader code:
cbuffer cbPerObject : register(b0)
{
row_major float4x4 worldMat;
row_major float4x4 worldInvTransposeMat;
row_major float4x4 worldviewprojMat;
};
cbuffer cbSkinned : register(b1)
{
row_major float4x4 boneMat[96];
};
struct SkinnedVertexIn
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
float3 TangentL : TANGENT;
float2 Tex : TEXCOORD;
uint4 BoneIndices : BONEINDICES;
float4 Weights : WEIGHTS;
};
VertexOut vsmain(SkinnedVertexIn vin)
{
VertexOut vout;
float weights[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
weights[0] = vin.Weights.x;
weights[1] = vin.Weights.y;
weights[2] = vin.Weights.z;
weights[3] = 1.0f - weights[0] - weights[1] - weights[2];
float3 posL = float3(0.0f, 0.0f, 0.0f);
float3 normalL = float3(0.0f, 0.0f, 0.0f);
float3 tangentL = float3(0.0f, 0.0f, 0.0f);
for (int i = 0; i < 4; ++i)
{
posL += weights[i] * mul(float4(vin.PosL, 1.0f), boneMat[vin.BoneIndices[i]]).xyz;
normalL += weights[i] * mul(vin.NormalL, (float3x3)boneMat[vin.BoneIndices[i]]);
tangentL += weights[i] * mul(vin.TangentL.xyz, (float3x3)boneMat[vin.BoneIndices[i]]);
}
vout.PosW = mul(float4(posL, 1.0f), worldMat).xyz;
vout.NormalW = mul(normalL, (float3x3) worldInvTransposeMat);
vout.TangentW = mul(tangentL, (float3x3)worldMat);
vout.PosH = mul(float4(posL, 1.0f), worldviewprojMat);
vout.Tex = vin.Tex;
return vout;
}