Tutorial 18 - Carga de Modelos 3DS (Sólido)
Fecha Lunes, 05 noviembre a las 16:29:14
Tema DirectX/Opengl


En esta segunda parte sobre la carga de Modelos 3DS vermos cómo leer la información sobre los materiales.



Tenemos que redefinir nuevamente la información sobre la geometría, ya que debemos añadir más información tanto al vértice como a la gemotría (Mesh) para almacenar las caras que componen el modelo con el material asociado:


    // ********************************* NEW **************************************

    ///////////////////////////////////////////////////////////////////////////////
    /// struct   FACE Estructura del polígono que compone las geometrías.
    ///
    /// rief   
    /// author  Antonio Lucas Moreno version 1.0 date 26/08/2005 9:24:38
    ///////////////////////////////////////////////////////////////////////////////
    struct Face
    {
        unsigned int    vIndex[3];          ///< Contiene la referencia a los vértices dentro del array de vértices.
        Vector3         normal;             ///< Normal de la cara.
        Vector3         center;             ///< Centro de la cara.
        Material    *   pMaterial;          ///< Material usado.
    };

    // ****************************************************************************

    ///////////////////////////////////////////////////////////////////////////////
    /// struct   Mesh: Geometría 3D.
    ///
    /// rief   
    /// author  Antonio Lucas Moreno version 1.0 date 07/09/2007 12:33:21
    ///////////////////////////////////////////////////////////////////////////////
    struct Mesh
    {
        Vertex       *  pVertex;            ///< Lista de vértices.
        unsigned int    numVertex;          ///< Nº de vértices.
        unsigned int *  pIndexes;           ///< Lista de índices, en qué orden se renderizan los vértices.
        unsigned int    numIndexes;         ///< Nº de índices.
    // ********************************* NEW **************************************
        Face         *  pFaces;             ///< Lista de caras.
        unsigned int    numFaces;           ///< Nº de caras.
    // ****************************************************************************
    };

En la clase C3DSLoader añadimos una lista con los materias definidos en el modelo.


    typedef std::map < std::string, Material * >    MaterialList;

MaterialList m_MaterialList; ///< Lista de materiales.

Ahora tenemos que leer más información sobre la geometría, en este caso, los materiales asociados a cada cara (no cargaremos modelos con texturas hasta el siguiente tutorial).


    ///////////////////////////////////////////////////////////////////////////////
    ///     ReadMesh: Lee una geometría 3DS de un bloque de datos.
    ///
    ///     @param  unsigned int lenChunck: Tamaño en bytes del bloque de datos.
    ///     @param  Mesh *pMesh: Geometría creada.
    ///
    ///     @return  int: Tamaño del bytes del bloque.
    ///////////////////////////////////////////////////////////////////////////////
    int C3DSLoader::ReadMesh(unsigned int lenChunck, Mesh *pMesh)
    {
        int i,f;
        unsigned int bytesRead= 0;
        unsigned short numberElements;
        unsigned short faceFlags;
        unsigned short iVertex, numFaces;
        long beginChunckPos;

        beginChunckPos= ftell(m_pFile);

        do
        {
            ReadChunck();

            switch (m_Chunck.id)
            {
                case TRI_VERTEXL:

                    fread (&numberElements, sizeof (unsigned short), 1, m_pFile);

                    pMesh->numVertex= (unsigned int)numberElements;

                    pMesh->pVertex= new Vertex[pMesh->numVertex];
                    memset(pMesh->pVertex,0,pMesh->numVertex*sizeof(Vertex));

                    for (i=0; inumVertex; i++)
                    {
                        fread (&pMesh->pVertex[i].x, sizeof(float), 1, m_pFile);
                        fread (&pMesh->pVertex[i].z, sizeof(float), 1, m_pFile);
                        fread (&pMesh->pVertex[i].y, sizeof(float), 1, m_pFile);
                        pMesh->pVertex[i].z= -pMesh->pVertex[i].z;
                    }
                    break;

                case TRI_FACEL1:

                    fread (&numFaces, sizeof (unsigned short), 1, m_pFile);

                    pMesh->numFaces= numFaces;
                    pMesh->pFaces  = new Face[ numFaces ];

                    pMesh->pIndexes= new unsigned int[3*numFaces];
                    memset(pMesh->pIndexes,0,3*numFaces*sizeof(unsigned int));
                    pMesh->numIndexes= 0;

                    for (f=0; fsizeof (unsigned short), 1, m_pFile);
                        pMesh->pFaces[f].vIndex[0]            = iVertex;
                        pMesh->pIndexes[ pMesh->numIndexes++ ]= iVertex;

                        fread (&iVertex, sizeof (unsigned short), 1, m_pFile);
                        pMesh->pFaces[f].vIndex[1]            = iVertex;
                        pMesh->pIndexes[ pMesh->numIndexes++ ]= iVertex;

                        fread (&iVertex, sizeof (unsigned short), 1, m_pFile);
                        pMesh->pFaces[f].vIndex[2]            = iVertex;
                        pMesh->pIndexes[ pMesh->numIndexes++ ]= iVertex;

                        fread (&faceFlags, sizeof (unsigned short), 1, m_pFile);
                    }
                    break;

    // ********************************* NEW **************************************

                case TRI_MATERIAL:
                {
                    char materialName[64];
                    MaterialList::iterator itMat;
                    unsigned short iFace;

                    // Leemos el nombre del material
                    ReadString(materialName, sizeof(materialName));

                    itMat= m_MaterialList.find( std::string(materialName) );

                    // Leemos la asignación de materiales a las caras
                    fread(&numFaces, sizeof(unsigned short), 1, m_pFile);

                    for (i=0; isizeof(unsigned short), 1, m_pFile);
                        if (itMat != m_MaterialList.end())
                            pMesh->pFaces[iFace].pMaterial= itMat->second;
                        else
                            pMesh->pFaces[iFace].pMaterial= NULL;
                    }

                }   break;

    // ****************************************************************************

                default:
                    SkipChunck();
                    break;
            }
        }
        while ((ftell(m_pFile) - beginChunckPos) < (lenChunck-SIZE_OF_CHUCK-1));

        return lenChunck;
    }

Definimos una nueva función para leer la información sobre un material, cada vez que nos encontremos con el identificador EDIT_MATERIAL:


    ///////////////////////////////////////////////////////////////////////////////
    ///     ReadMaterial: Lee la información sobre el material.
    ///
    ///     @param  unsigned int lenChunck: Tamaño en bytes del bloque de datos.
    ///
    ///     @return  int: Tamaño del bytes del bloque.
    ///////////////////////////////////////////////////////////////////////////////
    int C3DSLoader::ReadMaterial(unsigned int lenChunck)
    {
        unsigned int bytesRead= 0;
        long beginChunckPos;

        Material * pMaterial= new Material;
        if (!pMaterial)
            return -1;

        memset(pMaterial,0,sizeof(Material));

        beginChunckPos= ftell(m_pFile);

        do
        {
            ReadChunck();

            switch (m_Chunck.id)
            {
                // nombre del material
                case MAT_NAME:
                    ReadString(pMaterial->name, sizeof(pMaterial->name));
                    break;

                case MAT_AMBIENT:
                    // a continuación viene el chunck con el color (lo leemos)
                    ReadChunck();
                    pMaterial->ambient= ReadColor();
                    break;

                case MAT_DIFFUSE:
                    // a continuación viene el chunck con el color (lo leemos)
                    ReadChunck();
                    pMaterial->diffuse= ReadColor();
                    break;

                case MAT_SPECULAR:
                    // a continuación viene el chunck con el color (lo leemos)
                    ReadChunck();
                    pMaterial->specular= ReadColor();
                    break;

                case MAT_TEXMAP:
                    break;

                default:
                    SkipChunck();
                    break;
            }
        }
        // hasta que lleguemos al final del chunck
        while ((ftell(m_pFile) - beginChunckPos) < (lenChunck-SIZE_OF_CHUCK-1));

        if ( pMaterial )
            m_MaterialList[ std::string(pMaterial->name) ]= pMaterial;

        return lenChunck;
    }

Se define una función auxiliar, que calcula los vectores normales de cada vértice, calculando primero un vector normal a la cara y posteriormente la normal de cada vértice como la suma de las normales de las caras que comparten dicho vértice.


    ///////////////////////////////////////////////////////////////////////////////
    ///     CalculateNormals: Calcula los vectores normales de los vértices.
    ///
    ///     @param  Mesh * pMesh: Lista de triángulos/caras.
    ///
    ///     @return  int:   
    ///                     - -1  : error.
    ///                     .
    ///                     - otro: correcto.
    ///                     .
    ///////////////////////////////////////////////////////////////////////////////
    int C3DSLoader::CalculateNormals(Mesh * pMesh)
    {
        unsigned int f, i;
        Vector3 vVector1, vVector2, vNormal, vFace[3], vSum;

        // Este array almacenará el número de caras que comparten el vértice.
        unsigned int * sharedFaces;

        if (pMesh)
        {
            sharedFaces= new unsigned int[pMesh->numVertex];
            if (!sharedFaces)
                return -1;

            memset( sharedFaces,0, pMesh->numVertex * sizeof(unsigned int) );

            // Primero calculamos la normal de cada cara y vamos tomando nota de las caras
            // compartidas por cada vértice y calculando el acumulado de la normal para cada vértice.
            for (f=0; fnumFaces ; f++)
            {
                // Creamos un vector para cada vértice de la cara
                for (i=0; i<3; i++)
                {
                    vFace[i]= Vector3( pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].x ,
                                       pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].y ,
                                       pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].z );
                }
                // Creamos dos vectores coplanares a la cara 
                // (recordemos que las caras se recorren en sentido antihorario).
                //
                //  v0 *------->* v2
                //        F   .
                //         .
                //        v
                //         * v1
                //
                vVector1= vFace[0] - vFace[1];
                vVector2= vFace[0] - vFace[2];

                // Multiplicamos v1*v2 y nos dará un vector perpendicular a la cara
                // (regla de la mano derecha).
                //
                //    vNormal
                //     ^
                //     |
                //     |
                //  v0 *------->* v2
                //        F   .
                //         .
                //        v
                //         * v1
                //
                vNormal= vVector1 * vVector2;

                Vector3 vCenter(0.0f,0.0f,0.0f);

                // Vamos acumulando la normal de la cara a la que pertenece el vértice
                for (i=0; i<3; i++)
                {
                    vSum.x= pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].nx;
                    vSum.y= pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].ny;
                    vSum.z= pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].nz;

                    vSum= vSum + vNormal;

                    pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].nx= vSum.x;
                    pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].ny= vSum.y;
                    pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].nz= vSum.z;

                    // Incrementamos el número de caras compartidas por el vértice
                    sharedFaces[ pMesh->pFaces[f].vIndex[i] ]++;

                    vCenter= vCenter + Vector3( pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].x ,
                                                pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].y ,
                                                pMesh->pVertex[ pMesh->pFaces[f].vIndex[i] ].z );
                }

                // Normalizamos
                vNormal.Normalize();

                // Asignamos también la normal a la cara
                pMesh->pFaces[f].normal= vNormal;

                // Fijamos el centro de la cara
                pMesh->pFaces[f].center= vCenter/3;
            }

            // Tras calcular las normales de cada cara y las acumuladas en los vértices,
            // normalizamos las de los vértices.
            for (i=0; inumVertex; i++)
            {
                vSum.x= pMesh->pVertex[i].nx;
                vSum.y= pMesh->pVertex[i].ny;
                vSum.z= pMesh->pVertex[i].nz;

                vNormal= vSum / (float)sharedFaces[i];

                vNormal.Normalize();

                pMesh->pVertex[i].nx= vNormal.x;
                pMesh->pVertex[i].ny= vNormal.y;
                pMesh->pVertex[i].nz= vNormal.z;
            }
            delete [] sharedFaces;

            return 0;
        }
        else
            return -1;
    }

El método para renderizar una geometría añade información para renderizar según el material de la cara, siempre que el material varíe, ya que cambiar de material o de textura es una operación costosa.


    ///////////////////////////////////////////////////////////////////////////////
    ///     Render: Dibuja el modelo 3DS.
    ///
    ///     @param   nada
    ///
    ///     @return  nada
    ///////////////////////////////////////////////////////////////////////////////
    void C3DSLoader::Render()
    {
        MeshList::iterator itMesh;
        Mesh * pMesh= NULL;
    // ********************************* NEW **************************************
        unsigned int i, f, startIndex, endIndex;
        Material * pLastMaterial= NULL;

        IVideoDriver *pVideoDriver= IVideoDriver::GetVideoDriver();
        if (!pVideoDriver)
            return;
    // ****************************************************************************

        for (itMesh= m_MeshList.begin(); itMesh!=m_MeshList.end(); itMesh++)
        {
            pMesh= itMesh->second;
            if (pMesh)
            {
    // ********************************* NEW **************************************

                // Renderizamos sus caras
                i            = 0;
                startIndex   = 0;
                endIndex     = 0;
                pLastMaterial= pMesh->pFaces[0].pMaterial;

                for ( f=0; fnumFaces; f++ )
                {
                    // Si cambia el material, renderizamos...
                    if ( pMesh->pFaces[f].pMaterial != pLastMaterial && endIndex > startIndex )
                    {
                        // Fijamos el material
                        pVideoDriver->SetMaterial( *pLastMaterial );

                        pVideoDriver->RenderIndexedVertex( eTriangleList,
                                                        (BYTE*)pMesh->pVertex, pMesh->numVertex,
                                                        sizeof(Vertex), pMesh->pIndexes,
                                                        startIndex, endIndex-startIndex,
                                                        eFVF_XYZ|eFVF_NORMAL );

                        startIndex   = endIndex;
                        pLastMaterial= pMesh->pFaces[f].pMaterial;
                    }

                    endIndex += 3;

                } // end for

                // Falta renderizar el último material
                if ( endIndex > startIndex )
                {
                    pVideoDriver->SetMaterial( *pLastMaterial );
                    pVideoDriver->RenderIndexedVertex( eTriangleList,
                                                    (BYTE*)pMesh->pVertex, pMesh->numVertex,
                                                    sizeof(Vertex), pMesh->pIndexes,
                                                    startIndex, endIndex-startIndex,
                                                    eFVF_XYZ|eFVF_NORMAL );
                }
    // ****************************************************************************
            }
        }
    }

Podéis descargaros el ejemplo: tutorial-18.zip



Compartir en Facebook



Este artículo proviene de Programacion Grafica: Desarrollo 2D/3D con C++ y DirectX/OpenGL/GLUT/SDL - Windows/Linux
http://www.programaciongrafica.com

La dirección de esta noticia es:
http://www.programaciongrafica.com/modules.php?name=News&file=article&sid=19