Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / OpenGL

Modified version of lib3ds reader of .3ds format

4.78/5 (15 votes)
4 Oct 2012CPOL3 min read 44.5K   2.9K  
In this article I present version of lib3ds reader for C++ language

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

Image 1

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.

C++
class Lib3ds_loader // Class of loading
{
public:
    Lib3ds_loader();
 
    static ILib3ds_stage *makeILib3ds_stage(QString filename); // Static
// function of making interface of 3ds data
};

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.

C++
bool OpenGLWidget::LoadModel(QString filename)
{
 
    if(Istage) delete Istage; // Deleting data
    Istage = Lib3ds_loader::makeILib3ds_stage(filename); // Getting interface on data

    if(Istage != NULL) 
    /*Checking of existence of inteface of 3ds data*/
    {
        Istage->lib3ds_stage_eval(1.0f); // Setting first frame animation

        Istage->lib3ds_stage_bounding_box_of_nodes(1, 0, 0, bmin, bmax, NULL); //
        // finding of borders in node structure

        // Computing of size of box which surround of scene
        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; // Computing of centres of box
        cy = (bmin[1] + bmax[1])/2;
        cz = (bmin[2] + bmax[2])/2;
 
        if(Istage->cameras.isEmpty()) // Checking of list of cameras in 3ds data 
        {
            // If thre is not any camera in 3ds data than created camera
            Lib3dsCamera camera; 
            camera.name = "Camera_Z"; // Naming new camera
 
            memset(&camera.setting, 0, sizeof(Lib3dsCamera_setting));
 
            // setting of angle of view
            camera.setting.fov = 45;

            // Setting point of target of camera in center of scene 
            camera.setting.target[0] = cx;
            camera.setting.target[1] = cy;
            camera.setting.target[2] = cz;

            // Setting position camera out of scene
            camera.setting.position[0] = cx;
            camera.setting.position[1] = cy;
            camera.setting.position[2] = bmax[2] + 2 * MAX(sx,sy);

            // Setting near and far borders of camera
            camera.setting.near_range = ( camera.setting.position[2] - bmax[2] ) * .5;
            camera.setting.far_range = ( camera.setting.position[2] - bmin[2] ) * 2;
 
            // adding camera into list of cameras of 3ds data
            Istage->cameras[camera.name] = camera;  
        }
 
        if (Istage->lights.isEmpty())// Checking of list of lights in 3ds data 
        // If thre is not any light source in 3ds data than created light source

        {
          Lib3dsLight light;
 
          memset(&light.setting, 0, sizeof(Lib3dsLight_setting));
 
          light.name = "light0"; // Named light source
 
          light.setting.spot_light = 0; // Setting of 3DStudio
          light.setting.see_cone = 0;// Setting of 3DStudio
          // Setting of color of light
          light.setting.color[0] = light.setting.color[1] = light.setting.color[2] = .6;
          // Setting position of light source out of scene

          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;// Setting of 3DStudio
          light.setting.inner_range = 10;// Setting of 3DStudio
          light.setting.multiplier = 1;// Setting of 3DStudio
 
          Istage->lights[light.name] = light; // adding light
        }
 
        Istage->lib3ds_stage_eval(0.0f); // setting in start position
 
    }

    // If there is 3ds data than return true;
    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.

C++
QList<lib3dsnode>::iterator p = Istage->nodes.begin();
while(p!=Istage->nodes.end())
// Enumiration of root nodes in 3ds data which do not have parents
{
  render_node(&(*p)); // Function of rendering of node
  ++p;
}

void OpenGLWidget::render_node(Lib3dsNode *node)
{
     {
       QList<lib3dsnode>::iterator p = node->children.begin();
 
       while(p!=node->children.end())
       // Enumiration of children nodes
       {
         render_node(&(*p)); // Rendering of children nodes
 
         ++p;
       }
     }
 
     if (node->setting.type == LIB3DS_NODE_MESH_INSTANCE)
     /*Checking type of node. if it equals flag LIB3DS_NODE_MESH_INSTANCE then
     execute rendering of mesh's vertexes
     {
       Lib3dsMesh *mesh = NULL; // Pointer on mesh
 
       if(node->name == "$$$DUMMY")
/*Checking name on valid*/
       {
         return;
       }
 
       if(Istage->meshes.contains(node->name))
/*Checking of name node in list of meses in 3ds model
       {
/*Getting addres on mesh*/
           mesh = &Istage->meshes[node->name];
       }
 
       if(mesh != NULL)
       {

/*Checking the binding of mesh with list rendering of OpenGL*/
           if (!mesh->setting.user_id)
           {
               // Creating of binding with mesh
               mesh->setting.user_id=glGenLists(1);
               glNewList(mesh->setting.user_id, GL_COMPILE);
 
 
// Iterator of trangles primitives in mesh
               QVector<lib3dsface>::iterator p = mesh->faces.begin();
    
           
// Setting movement mesh in initial position
               float M[4][4];
               Istage->lib3ds_matrix_copy(M, mesh->setting.matrix);
               Istage->lib3ds_matrix_inv(M);
               glMultMatrixf(&M[0][0]);
 
// Allocated memory for normals for smooth mesh
               Lib3dsVector *normalL = new Lib3dsVector[3*mesh->faces.size()];
 
// Calculation of smooth normals for vertexes
               Istage->lib3ds_mesh_calculate_vertex_normals(mesh, normalL);
 
               unsigned int f = 0;
 
 
 // Enumiration of trangles primitives in mesh
               while (p != mesh->faces.end())
               {
                   Lib3dsMaterial *mat = NULL; // Material of triangle primitive
 
                   Lib3dsMaterial *oldmat = NULL;//Material of old triangle primitive

 
// Finding material in list of materials of 3ds data
                   if(Istage->materials.contains((*p).material))
                   {
                       mat = &Istage->materials[(*p).material];
                   }
 
                   if( mat)
                   {
// If material is founded than reading of data
                     if (mat != oldmat)
                     {
 
                         if( mat->setting.two_sided )
                           glDisable(GL_CULL_FACE);
                         else
                           glEnable(GL_CULL_FACE);

// Setting colors for lightning
                         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
                   {
// Setting default colors in cases of there is not material

                     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);// Start drawing primitive
 
                   glNormal3fv((*p).normal);
 
                   for (int i=0; i<3; ++i)
                   {
// Setting normal for vertex
                       glNormal3fv(normalL[3*f+i]);

// Drawing of vertex
                       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();
           }
 
//Rendering of list of calls of OpenGL
           if (mesh->setting.user_id)
           {
             glPushMatrix();
//Multiplication of mesh position with current matrix
             glMultMatrixf(&node->setting.matrix[0][0]);
//Movement mesh in point rotation 
             glTranslatef(-node->MeshInstanceNode.pivot[0],
 -node->MeshInstanceNode.pivot[1],
 -node->MeshInstanceNode.pivot[2]);
// Calling of list compiled commands
             glCallList(mesh->setting.user_id);
             glPopMatrix();
 
           }
 
       }
     }
     else
     {
// There can be executed animation of other objects such as camera or light source
 
     }
 }
void OpenGLWidget::updateAnimation()
{
     if(Istage)
     {
         current_frame+=1.0;

         // Current frame cannot be more maximum frames of 3ds model
         if (current_frame>Istage->frames)
         {
           current_frame=0.0;
         }
 
         // Function of execution computing matrexes nodes in current frame
         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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)