65.9K
CodeProject 正在变化。 阅读更多。
Home

修改后的 lib3ds 阅读器,用于 .3ds 格式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (15投票s)

2012年6月20日

CPOL

3分钟阅读

viewsIcon

45481

downloadIcon

3035

在本文中, 我将介绍 C++ 语言的 lib3ds 读取器版本

引言

3DStudio 中存储 3D 模型和场景的格式差异很大。然而,它们中的大多数都与 3DStudio 的引擎绑定。只有少数以 OpenGL 中简单渲染的形式呈现数据。对于我的项目,我选择了 3DS 格式。我找到了一个非常强大的 3ds 格式阅读器代码 - lib3ds,在 http://www.lib3ds.org/ 网站上。尽管如此,该网站上的代码是用 C 语言编写的。因此,在 lib3ds 的原始代码中有很多静态函数,它们有复杂的链接,并且无法使用面向对象语言的功能。对于我的 C++ 项目,我将原始的 lib3ds 2.0.0-rc1 从 C 语言重写为 C++ 结构,并使用了类和接口。因此,我编写了代码,将读取 3ds 数据与使用数据进行渲染分离开来。这个决定可以用于 COM 类型的技术,并提供简单的数据管理。

为了测试代码,我制作了一个特殊的程序 3dsViewer,在这里可以找到 下载 3dsViewer_exe.zip

背景

为了使用接口结构,我制作了 10 个文件,描述了接口中使用的数据结构。接口包含在独立文件 ILib3ds_stage.h 中,如基类,并包含所有数据和虚拟函数。其他继承接口的类只包含函数。因此,顶级继承类的大小等于接口的大小,足以调用接口的删除以释放为所有数据选择的所有内存。此外,不同大小的数据,例如顶点和面,具有非常简单的删除工具。在原始代码中,有一个复杂的数据管理和数据删除算法。我将它们更改为简单的类:QString, QVector, QList, QMap - 相当于 Qt 框架的 std String, Vector, List, Map。因此,具有非固定大小的数据被包装在栈对象中并自动删除。

使用代码

所有代码都包含在 19 个类中,这些类具有继承的层次结构。但是,为了使用代码,只需使用类 Lib3ds_loader 的静态函数 makeILib3ds_stage,并传入 3ds 文件的路径即可。该函数返回接口 ILib3ds_stage,其中包含 3D 模型的数据。

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

此代码是使用接口 ILib3ds_stage 的一个例子,使用指针 Istage 的形式。当 3ds 数据中没有相机时,在检查 Istage->cameras.isEmpty() 后,在列表 Istage->cameras 中添加相机结构。对光源列表也执行相同的操作。

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);
}

下一个代码通过解析 3ds 的节点来渲染 3ds 数据。解析是对根节点及其子节点进行的。函数 void OpenGLWidget::updateAnimation() 执行顶点位置的动画。

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();
     }
}

此代码是用 Qt 框架编写的,但所有特定类,如 QStringQListQVector3D,都可以用几乎所有其他平台和语言的特定类重写。

关注点

我花了很多时间阅读原始的 lib3ds 代码。为了更清楚的理解,我注释了类中的几乎所有函数。我还注释了读取数据时几乎所有标志的应用。我希望我的决定可以帮助您理解 3ds 格式的结构。

更新 Qt5.3.2

我更新了 lib3ds 和 Qt 查看器的源代码,用于 Qt5.3.2 - 下载 3dsViewer.zip,用于 Qt5.3.2 的查看器和 lib3ds 源代码 - 115 KB。 用于测试的 3ds 模型 - 下载 UEXTREM.zip

© . All rights reserved.