Here's a picture demonstrating this. The model is just hacked-together pieces from a jeep model I found online, I added the propeller, nothing fancy it's just to test functionality of the code. In terms of data structures this model is made up of 7 meshes, each of which has its own local transformation matrix. There is another matrix which contains the transformation of the entire model in absolute coordinates.

I also compute the radius, bounding box and center the mesh. The center is with respect to the center of the model in design coordinates (the center of the entire model is computed, and all vertices are adjusted such that in design coordinates the center is the origin). You must know the center of the mesh to have the propeller properly spin. According to matrix concatenations I have to translate the propeller back to the origin, rotate it about it's z axis based on angular velocity, then translate it back to its original position. When rendering the entire model there is a matrix for the entire model, and each mesh's local matrix is concatenated with that (by the OpenGL matrix stack...this could be the start of a basic scene graph). Here's the Render() hook for a Hovercraft (just hacked together for now), the hook meaning that nothing is actually rendered here, just the appropriate data is added to the renderer and held until the final rendering is done (all together).

Note that I use 'my own' file format. Milkshape3D (.ms3d) files are optimized for saving file space, whereas my files are optimized for rendering (tightly-packed data structures). My files are subsequently about %30 larger, and called .ntmdl (Nuclear Toaster Model).

Code:

void Hovertank::Render()
{
// DebugPrintFloat("inputs",this->inputs,gpTextManager);
// DebugPrintInt("prop index",this->pNTModel->GetMeshByGroupComment("propeller"),gpTextManager);
if(gpFrustum->SphereInFrustum(CurrentState.mPosition,mRadius))
{
Matrix4x4 final_render_mat;
final_render_mat = CurrentState.mMat_Orientation;
final_render_mat.SetTranslation(&CurrentState.mPosition);
Matrix4x4 rotation,translation_inverse,translation;
translation.SetTranslation(&prop->DEBUG_CENTER);
translation_inverse.SetTranslation( &(prop->DEBUG_CENTER *-1) );
rotation.InitFromAngles(0,0,this->propellor_angle);
prop->local_mat = translation * rotation * translation_inverse;
// DebugPrint4x4Matrix("prop_local",prop->local_mat,gpTextManager);
pNTModel->mat = final_render_mat;
gpGLRenderer->AddNTRenderModelToRenderer(*pNTModel);
}
}