Introduction
Formats of keeping 3D models and stages in 3DStudio are very different. However, most of them are bound to the engine of 3DStudio. And only a few of them present data in form of simple rendering in OpenGL. For my project I chose 3DS format. I found a very powerfull code of reader of 3ds format - lib3ds on site http://www.lib3ds.org/. Nonetheless, the code on this site is presented on C language. As a result, in the original code of lib3ds there are much static function which have difficult links and there are not possibilities to use abilities of object languages. For my project on C++ I rewrote the original lib3ds 2.0.0-rc1 from C into C++ structure with using of classes and interfaces. As a result, I wrote the code, which separates of reading 3ds data from using of data for rendering. This decision can be used in COM-like technologies and give simple managment with data.
For testing of code I make special program 3dsViewer whic is presented here Download 3dsViewer_exe.zip
Background
For using of structure of interfaces I made 10 files which describ of structure of data which using in the interface. The interface is contented in seperated file ILib3ds_stage.h such as base class and contents all data and virtual functions. Other classes which inherit interface contents only functions. As a result, size of top inherit class is equal size of interface and enough to call delete for interface to free all memory, which selected for all data. Moreover, the date of different size such as vertexes and faces has very simple instrument of deleting. In the original code there is a difficult algorithm of managment data and deleting data. I changed them on simple classes: QString, QVector, QList, QMap - are analogy of Qt framework of std String, Vector, List, Map. As a result, data with non-fixed size is wrapped into stack object and is deleted automaticaly.
Using the code
All code is contented in 19 classes which have the inherited hierarchy. However, for using of code enough to call static function of makeILib3ds_stage of class Lib3ds_loader
with path to 3ds file. This function returns interface ILib3ds_stage
on data of 3D model.
class Lib3ds_loader {
public:
Lib3ds_loader();
static ILib3ds_stage *makeILib3ds_stage(QString filename); };
This code is example of using interface ILib3ds_stage
in form of pointer Istage
. When there is not camera in data 3ds after examination Istage->cameras.isEmpty()
in list Istage->cameras
add structure of camera. The same doing with list of source of light.
bool OpenGLWidget::LoadModel(QString filename)
{
if(Istage) delete Istage; Istage = Lib3ds_loader::makeILib3ds_stage(filename);
if(Istage != NULL)
{
Istage->lib3ds_stage_eval(1.0f);
Istage->lib3ds_stage_bounding_box_of_nodes(1, 0, 0, bmin, bmax, NULL);
sx = bmax[0] - bmin[0];
sy = bmax[1] - bmin[1];
sz = bmax[2] - bmin[2];
boxsize = MAX(sx, sy); boxsize = MAX(boxsize, sz);
cx = (bmin[0] + bmax[0])/2; cy = (bmin[1] + bmax[1])/2;
cz = (bmin[2] + bmax[2])/2;
if(Istage->cameras.isEmpty()) {
Lib3dsCamera camera;
camera.name = "Camera_Z";
memset(&camera.setting, 0, sizeof(Lib3dsCamera_setting));
camera.setting.fov = 45;
camera.setting.target[0] = cx;
camera.setting.target[1] = cy;
camera.setting.target[2] = cz;
camera.setting.position[0] = cx;
camera.setting.position[1] = cy;
camera.setting.position[2] = bmax[2] + 2 * MAX(sx,sy);
camera.setting.near_range = ( camera.setting.position[2] - bmax[2] ) * .5;
camera.setting.far_range = ( camera.setting.position[2] - bmin[2] ) * 2;
Istage->cameras[camera.name] = camera;
}
if (Istage->lights.isEmpty())
{
Lib3dsLight light;
memset(&light.setting, 0, sizeof(Lib3dsLight_setting));
light.name = "light0";
light.setting.spot_light = 0; light.setting.see_cone = 0; light.setting.color[0] = light.setting.color[1] = light.setting.color[2] = .6;
light.setting.position[0] = cx + boxsize * .75;
light.setting.position[1] = cy - boxsize * 1.;
light.setting.position[2] = cz + boxsize * 1.5;
light.setting.position[3] = 0.;
light.setting.outer_range = 100; light.setting.inner_range = 10; light.setting.multiplier = 1;
Istage->lights[light.name] = light; }
Istage->lib3ds_stage_eval(0.0f);
}
return (Istage != NULL);
}
The next code renders 3ds data by parsing of nodes of 3ds. Parsing is doing for root nodes and their children. Function of void OpenGLWidget::updateAnimation()
executes animation of the positions of vertexes.
QList<lib3dsnode>::iterator p = Istage->nodes.begin();
while(p!=Istage->nodes.end())
{
render_node(&(*p)); ++p;
}
void OpenGLWidget::render_node(Lib3dsNode *node)
{
{
QList<lib3dsnode>::iterator p = node->children.begin();
while(p!=node->children.end())
{
render_node(&(*p));
++p;
}
}
if (node->setting.type == LIB3DS_NODE_MESH_INSTANCE)
{
return;
}
if(Istage->meshes.contains(node->name))
mesh = &Istage->meshes[node->name];
}
if(mesh != NULL)
{
if (!mesh->setting.user_id)
{
mesh->setting.user_id=glGenLists(1);
glNewList(mesh->setting.user_id, GL_COMPILE);
QVector<lib3dsface>::iterator p = mesh->faces.begin();
float M[4][4];
Istage->lib3ds_matrix_copy(M, mesh->setting.matrix);
Istage->lib3ds_matrix_inv(M);
glMultMatrixf(&M[0][0]);
Lib3dsVector *normalL = new Lib3dsVector[3*mesh->faces.size()];
Istage->lib3ds_mesh_calculate_vertex_normals(mesh, normalL);
unsigned int f = 0;
while (p != mesh->faces.end())
{
Lib3dsMaterial *mat = NULL;
Lib3dsMaterial *oldmat = NULL;
if(Istage->materials.contains((*p).material))
{
mat = &Istage->materials[(*p).material];
}
if( mat)
{
if (mat != oldmat)
{
if( mat->setting.two_sided )
glDisable(GL_CULL_FACE);
else
glEnable(GL_CULL_FACE);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat->setting.ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat->setting.diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat->setting.specular);
glMaterialf(GL_FRONT, GL_SHININESS, pow(2, 10.0*
mat->setting.shininess));
}
oldmat = mat;
}
else
{
static const GLfloat a[]={0.7, 0.7, 0.7, 1.0};
static const GLfloat d[]={0.7, 0.7, 0.7, 1.0};
static const GLfloat s[]={1.0, 1.0, 1.0, 1.0};
glMaterialfv(GL_FRONT, GL_AMBIENT, a);
glMaterialfv(GL_FRONT, GL_DIFFUSE, d);
glMaterialfv(GL_FRONT, GL_SPECULAR, s);
glMaterialf(GL_FRONT, GL_SHININESS, pow(2, 10.0*0.5));
}
glBegin(GL_TRIANGLES);
glNormal3fv((*p).normal);
for (int i=0; i<3; ++i)
{
glNormal3fv(normalL[3*f+i]);
glVertex3f(mesh->vertices[(*p).index[i]].x(),
mesh->vertices[(*p).index[i]].y(),
mesh->vertices[(*p).index[i]].z());
}
glEnd();
++p;
f++;
}
delete []normalL;
glEndList();
}
if (mesh->setting.user_id)
{
glPushMatrix();
glMultMatrixf(&node->setting.matrix[0][0]);
glTranslatef(-node->MeshInstanceNode.pivot[0],
-node->MeshInstanceNode.pivot[1],
-node->MeshInstanceNode.pivot[2]);
glCallList(mesh->setting.user_id);
glPopMatrix();
}
}
}
else
{
}
}
void OpenGLWidget::updateAnimation()
{
if(Istage)
{
current_frame+=1.0;
if (current_frame>Istage->frames)
{
current_frame=0.0;
}
Istage->lib3ds_stage_eval(current_frame);
this->update();
}
}
This code is written on Qt framework, but all specific classes such as QString
, QList
, or QVector3D
can be rewritten in specific classes of almost all other platforms and languages.
Points of Interest
I spent much time on reading code of original lib3ds. For more clear understanding I commented almost all functions in classes. Alse I commented almost all application of flags for reading data. I hope my decision can help you to understand the structure of 3ds format.
Update for Qt5.3.2
I updated source code for lib3ds and Qt viewer for Qt5.3.2 - Download 3dsViewer.zip Source of viewer and lib3ds for Qt5.3.2 - 115 KB. 3ds model for testing - Download UEXTREM.zip.