Announcement

Collapse
No announcement yet.

C++ to PB again

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • C++ to PB again

    I am unable to translate correctly this piece of C++ code to PB,
    because of my lack of undesrtanding of the syntax being use in:
    "map class", the ".end" method, the "vertices.begin"/"vertices.end", and the "make_pair".

    Note: In my PB translation m_vertexCache() is a global array of long,
    and to retrieve the iter value, i am using the PB syntax "ARRAY SCAN gnm_vertexCache(), = nHash, TO nIter"

    Any help would be much appreciated.

    Code:
    int ModelOBJ::addVertex(int hash, const Vertex *pVertex)
    {
        int index = -1;
        std::map<int, std::vector<int> >::const_iterator iter = m_vertexCache.find(hash);
    
        if (iter == m_vertexCache.end())
        {
            // Vertex hash doesn't exist in the cache.
    
            index = static_cast<int>(m_vertexBuffer.size());
            m_vertexBuffer.push_back(*pVertex);
            m_vertexCache.insert(std::make_pair(hash, std::vector<int>(1, index)));
        }
        else
        {
            // One or more vertices have been hashed to this entry in the cache.
    
            const std::vector<int> &vertices = iter->second;
            const Vertex *pCachedVertex = 0;
            bool found = false;
    
            for (std::vector<int>::const_iterator i = vertices.begin(); i != vertices.end(); ++i)
            {
                index = *i;
                pCachedVertex = &m_vertexBuffer[index];
    
                if (memcmp(pCachedVertex, pVertex, sizeof(Vertex)) == 0)
                {
                    found = true;
                    break;
                }
            }
    
            if (!found)
            {
                index = static_cast<int>(m_vertexBuffer.size());
                m_vertexBuffer.push_back(*pVertex);
                m_vertexCache[hash].push_back(index);
            }
        }
    
        return index;
    }
    Last edited by Patrice Terrier; 13 Jun 2012, 04:11 AM.
    Patrice Terrier
    www.zapsolution.com
    www.objreader.com
    Addons: GDImage.DLL 32/64-bit (Graphic library), WinLIFT.DLL 32/64-bit (Skin Engine).

  • #2
    At a guess the ".end method" is a property which returns a conventional value to indicate that the search has completed.

    Comment


    • #3
      if (iter == mvertextCache.end()) is just checking if the item was not found. vertex.begin is the first element, vertex.end is just past the last element.

      the for loop is iterating from the first element (.begin) to the last element (.end)

      the vertexCache is a map collection of integers (the keys) and arrays (the vectors i.e. integer array).

      make_pair is creating an integer (key), integer array (data) pair to insert into the map.

      Think of the pair (key, data) as the "thing" the map collection stores.
      LarryC
      Website
      Sometimes life's a dream, sometimes it's a scream

      Comment


      • #4
        Here is my translation, as i have understood the C++ code.

        However i have an array bound error, thus i know it is not good

        Code:
        FUNCTION Mobj_addVertex(BYVAL nHash AS LONG, pVertex AS VertexT) AS LONG
        
            LOCAL nIndex, nIter AS LONG
            nIndex = -1
        
            ARRAY SCAN gnm_vertexCache(), = nHash, TO nIter
        
            IF nIter = UBOUND(gnm_vertexCache()) + 1 THEN
               '// Vertex nHash doesn't exist in the cache.
               nIndex = ARRAYATTR(gtm_vertexBuffer(), 4) + 1
               REDIM PRESERVE gtm_vertexBuffer(nIndex)
               gtm_vertexBuffer(nIndex) = pVertex
        
               REDIM PRESERVE gnm_vertexCache(nIndex)
               gnm_vertexCache(nIndex) = nHash
            ELSE 
               '// One or more vertices have been hashed to this entry in the cache.
               LOCAL pCachedVertex AS VertexT PTR
               LOCAL nI, nFound AS LONG
        
               FOR nI = nIter + 1 TO UBOUND(gnm_vertexCache())
                   nIndex = gnm_vertexCache(nI)
        
        '//////////////////////////////////////////
        IF nIndex > UBOUND(gtm_vertexBuffer()) THEN
            Msgbox "Array bound error" + str$(nIndex)+str$(UBOUND(gtm_vertexBuffer()))+str$(nI)
            EXIT FOR
        END IF
        '//////////////////////////////////////////
        
                   pCachedVertex = VARPTR(gtm_vertexBuffer(nIndex))
        
                   IF memcmp(@pCachedVertex, pVertex, SIZEOF(VertexT)) = 0 THEN
                      nFound = -1
                      EXIT FOR
                   END IF
               NEXT
        
               IF NOT nFound  THEN
                  nIndex = ARRAYATTR(gtm_vertexBuffer(), 4) + 1
                  REDIM PRESERVE gtm_vertexBuffer(nIndex)
                  gtm_vertexBuffer(nIndex) = pVertex
        
                  nIndex = ARRAYATTR(gnm_vertexCache(), 4) + 1
                  REDIM PRESERVE gnm_vertexCache(nIndex)
                  gnm_vertexCache(nIndex) = nHash
               END IF
            END IF
        
            FUNCTION = nIndex
        END FUNCTION
        Patrice Terrier
        www.zapsolution.com
        www.objreader.com
        Addons: GDImage.DLL 32/64-bit (Graphic library), WinLIFT.DLL 32/64-bit (Skin Engine).

        Comment


        • #5
          Ok, i think i have solved my ARRAY bound error.

          And i have now a better understanding on how to handle quickly the iterator to find for a specific hash value.

          So far i was using ARRAY SCAN, but it is crawling compared to C++ because C++ is performing a Tree search that is done almost instantaneously.
          I will use my own dvBtree to perform the same task, then no more need to read the whole ARRAY to perform the scan.

          ...
          Patrice Terrier
          www.zapsolution.com
          www.objreader.com
          Addons: GDImage.DLL 32/64-bit (Graphic library), WinLIFT.DLL 32/64-bit (Skin Engine).

          Comment


          • #6
            will use my own dvBtree to perform the same task, then no more need to read the whole ARRAY to perform the scan
            You may not have to go that far.

            If the values can be sorted and have no duplicates a binary search might be all you need to use. (Disk or memory).

            Or maybe the new PowerCollection Object (CONTAINS method) can find stuff real fast.

            Lots of options.
            Michael Mattias
            Tal Systems (retired)
            Port Washington WI USA
            [email protected]
            http://www.talsystems.com

            Comment


            • #7
              C++ map::find is really fast, i still have to find a way to do the same in PB

              ...
              Patrice Terrier
              www.zapsolution.com
              www.objreader.com
              Addons: GDImage.DLL 32/64-bit (Graphic library), WinLIFT.DLL 32/64-bit (Skin Engine).

              Comment


              • #8
                Hash index table maybe.

                Comment


                • #9
                  If you have to do it 'really fast', why are you REDIM PRESERVE'ing two arrays one element at a time?

                  If you have to resize, add 100 or 1000 elements and keep track of how many are actually currently valid and limit your ARRAY SCAN accordingly.

                  Or, instead of appending new elements to the array, INSERT them in sorted order (create original to support "worst case scenario" number of elements) and use a binary search for hit testing.


                  MCM
                  Last edited by Michael Mattias; 13 Jun 2012, 04:29 PM.
                  Michael Mattias
                  Tal Systems (retired)
                  Port Washington WI USA
                  [email protected]
                  http://www.talsystems.com

                  Comment


                  • #10
                    I think you're missing the essence. Essentially they are using a hash table.

                    Haven't done any testing but would be something roughly along these lines:

                    Code:
                    #Compile Exe
                    #Dim All
                     
                    %MAX_ITEMS = 1000: ' Maximum number of vertexes we allow
                    %CACHE_SIZE = %MAX_ITEMS * 1.3: ' Number of items a cache needs (you can make this smaller but you end up with more collisions)
                     
                    Type Vertex
                     points(3) As Long : ' Change to suit vertex values
                    End Type
                    Type VertexList
                     dta As Vertex: ' A vertex
                    nextVertex As Long: ' Index of the next vertex in the list
                    End Type
                     
                    Global m_VertexCache() As Long: ' Hash table, indexes to the first entry in each list
                    Global m_vertexBuffer() As VertexList: ' Array containing listS <- yes that's plural
                    Global m_count As Long: ' Number of items put in the m_vertexBuffer
                    Function PBMain () As Long
                     Local i As Long
                    ' Initialize m_VertexCach and m_vertexBuffer once
                    ReDim m_VertexCache( %CACHE_SIZE-1 )
                    ReDim m_vertexBuffer( %MAX_ITEMS-1 )
                    ' Could do a MEMORY FILL to &FF also.
                    For i=0 To %CACHE_SIZE-1
                       m_VertexCache = -1
                    Next
                     
                    End Function
                     
                    Function ModelOBJ_addVertex( ByVal hash As Long, pVertex As Vertex ) As Long
                     Register i As Long
                     Register index As Long
                     Local p As Byte Ptr
                    ' Calculate index using hash, we want a nice distribution
                    p = VarPtr( hash )
                    For i=0 To 3
                       index = 1610612741 * index + @p[i]
                    Next
                    index And= &H7FFFFFFF: ' Remove sign
                    index Mod= CACHE_SIZE: ' 0 - CACHE_SIZE-1
                    If m_vertexCache( index ) = -1 Then
                    ' Nothing already at that location
                    m_vertexCache( index ) = m_count: ' Location we're placing item at
                    m_vertexBuffer( m_count ).nextVertex = 0: ' No next item
                    m_vertexBuffer( m_count ).dta = pVertex: ' Place data
                    Function = m_count: ' Index of pVertex (that we added)
                    Incr m_count: ' Get ready to add another item
                    Else
                       Do
                         If m_vertexBuffer( index ).dta = pVertex Then Function = index: Exit Function: ' Index of pVertex (that we found)
                    i = index: ' Save index of last good value
                    index = m_vertexBuffer( index ).nextVertex
                    Loop While index>-1
                       m_vertexBuffer( i ).nextVertex = m_count: ' Update the last entry in list to point to the next "slot"
                    m_vertexBuffer( m_Count ).nextVertex = 0: ' This is now the last entry in the list
                    m_vertexBuffer( m_count ).dta = pVertex: ' Set the data
                    Function = m_count: ' Index of pVertex (that we added)
                    Incr m_count: ' Get ready to add another item
                    End If
                    End Function
                    For obtaining an index
                    index = hash MOD CACHE_SIZE
                    might be worth testing. It would be marginally faster to compute the hash, but would likely result in more collisions (so there might be more list searching). Also it would rock if CACHE_SIZE was actually a prime number.
                    Last edited by Larry Charlton; 13 Jun 2012, 06:00 PM. Reason: Added some more comments
                    LarryC
                    Website
                    Sometimes life's a dream, sometimes it's a scream

                    Comment


                    • #11
                      Larry, Michael, Chris, and others

                      For your information, the C++ code i am translating imports 3D OBJ models into OpenGL, some OBJ files are rather complex and use a huge number of vertex.

                      The code i have translated, already use a hash table, based on Paul Squires's code posted here in 2007.

                      However the result of my benchmark shows that C++ is still 4 times faster than my PB's translation with the hash table, and 20 times faster than the version without the hash table. I think i will follow MCM suggestion to reduce the number of REDIM PRESERVE, and i will also make some test with new Larry's code.

                      Thank you all for your suggestions.

                      Here is the latest version of my translation of the "addVertex" procedure.

                      Code:
                      #INCLUDE "stdio.inc"
                      #INCLUDE "memory.inc"
                      
                      #Include "LinkedList.inc"
                      #Include "HashTable.inc"
                      
                      FUNCTION HashFind (sUseString AS STRING) AS LONG
                          LOCAL nValue AS LONG
                          nValue = 0
                          IF gnm_handle THEN
                             IF hash_find( gnm_handle, sUseString, nValue) = 0 THEN nValue = 0
                          END IF
                      
                          FUNCTION = nValue
                      END FUNCTION
                      
                      SUB HashAdd (sUseString AS STRING, BYVAL nValue AS LONG)
                          IF gnm_handle = 0 THEN
                             gnm_handle = hash_create(200000, %FALSE )
                          END IF
                          IF gnm_handle THEN
                             hash_add(gnm_handle, sUseString, nValue) 
                          END IF
                      END SUB
                      
                      FUNCTION Mobj_addVertex(BYVAL nHash AS LONG, pVertex AS VertexT) AS LONG
                      
                          REGISTER nI AS LONG
                          LOCAL nIndex, nIter AS LONG
                          nIndex = -1
                      
                          'ARRAY SCAN gnm_vertexCache(), FROM 1 TO 4, = MKL$(nHash), TO nIter
                          nIter = HashFind(MKL$(nHash))
                      
                          IF nIter = 0 THEN ' 
                             '// Vertex nHash doesn't exist in the cache.
                             nIndex = ARRAYATTR(gtm_vertexBuffer(), 4) + 1
                             REDIM PRESERVE gtm_vertexBuffer(nIndex)
                             gtm_vertexBuffer(nIndex) = pVertex
                      
                             nIter = ARRAYATTR(gnm_vertexCache(), 4) + 1
                             REDIM PRESERVE gnm_vertexCache(nIter)
                             gnm_vertexCache(nIter).nHash = nHash
                             gnm_vertexCache(nIter).nIndex = nIndex
                             HashAdd(MKL$(nHash), nIndex)
                             
                          ELSE 
                             '// One or more vertices have been hashed to this entry in the cache.
                             LOCAL pCachedVertex AS VertexT PTR
                             LOCAL nFound AS LONG
                             FOR nI = LBOUND(gnm_vertexCache()) TO UBOUND(gnm_vertexCache())
                                 nIndex = gnm_vertexCache(nI).nIndex
                                 pCachedVertex = VARPTR(gtm_vertexBuffer(nIndex))
                                 IF memcmp(@pCachedVertex, pVertex, SIZEOF(VertexT)) = 0 THEN
                                    nFound = -1
                      'incr gnFound
                                    EXIT FOR
                                 END IF
                             NEXT
                      
                             IF NOT nFound  THEN
                                nIndex = ARRAYATTR(gtm_vertexBuffer(), 4) + 1
                                REDIM PRESERVE gtm_vertexBuffer(nIndex)
                                gtm_vertexBuffer(nIndex) = pVertex
                      
                                nIter = ARRAYATTR(gnm_vertexCache(), 4) + 1
                                REDIM PRESERVE gnm_vertexCache(nIter)
                                gnm_vertexCache(nIter).nHash = nHash
                                gnm_vertexCache(nIter).nIndex = nIndex
                                HashAdd(MKL$(nHash), nIndex)
                      
                             END IF
                          END IF
                      
                          FUNCTION = nIndex
                      END FUNCTION
                      And here is the original C++ class code i am translating to PB's flat API.

                      Code:
                      void ModelOBJ::destroy()
                      {
                          m_hasPositions = false;
                          m_hasTextureCoords = false;
                          m_hasNormals = false;
                          m_hasTangents = false;
                      
                          m_numberOfVertexCoords = 0;
                          m_numberOfTextureCoords = 0;
                          m_numberOfNormals = 0;
                          m_numberOfTriangles = 0;
                          m_numberOfMaterials = 0;
                          m_numberOfMeshes = 0;
                      
                          m_center[0] = m_center[1] = m_center[2] = 0.0f;
                          m_width = m_height = m_length = m_radius = 0.0f;
                      
                          m_directoryPath.clear();
                      
                          m_meshes.clear();
                          m_materials.clear();
                          m_vertexBuffer.clear();
                          m_indexBuffer.clear();
                          m_attributeBuffer.clear();
                      
                          m_vertexCoords.clear();
                          m_textureCoords.clear();
                          m_normals.clear();
                      
                          m_materialCache.clear();
                          m_vertexCache.clear();
                      }
                      
                      bool ModelOBJ::import(const char *pszFilename, bool rebuildNormals)
                      {
                          FILE *pFile = fopen(pszFilename, "r");
                      
                          if (!pFile)
                              return false;
                      
                          // Extract the directory the OBJ file is in from the file name.
                          // This directory path will be used to load the OBJ's associated MTL file.
                      
                          m_directoryPath.clear();
                      
                          std::string filename = pszFilename;
                          std::string::size_type offset = filename.find_last_of('\\');
                      
                          if (offset != std::string::npos)
                          {
                              m_directoryPath = filename.substr(0, ++offset);
                          }
                          else
                          {
                              offset = filename.find_last_of('/');
                      
                              if (offset != std::string::npos)
                                  m_directoryPath = filename.substr(0, ++offset);
                          }
                      
                          // Import the OBJ file.
                      
                          importGeometryFirstPass(pFile);
                          rewind(pFile);
                          importGeometrySecondPass(pFile);
                          fclose(pFile);
                      
                          // Perform post import tasks.
                      
                          buildMeshes();
                          bounds(m_center, m_width, m_height, m_length, m_radius);
                      
                          // Build vertex normals if required.
                      
                          if (rebuildNormals)
                          {
                              generateNormals();
                          }
                          else
                          {
                              if (!hasNormals())
                                  generateNormals();
                          }
                      
                          // Build tangents is required.
                      
                          for (int i = 0; i < m_numberOfMaterials; ++i)
                          {
                              if (!m_materials[i].bumpMapFilename.empty())
                              {
                                  generateTangents();
                                  break;
                              }
                          }
                      
                          return true;
                      }
                      
                      void ModelOBJ::normalize(float scaleTo, bool center)
                      {
                          float width = 0.0f;
                          float height = 0.0f;
                          float length = 0.0f;
                          float radius = 0.0f;
                          float centerPos[3] = {0.0f};
                      
                          bounds(centerPos, width, height, length, radius);
                      
                          float scalingFactor = scaleTo / radius;
                          float offset[3] = {0.0f};
                      
                          if (center)
                          {
                              offset[0] = -centerPos[0];
                              offset[1] = -centerPos[1];
                              offset[2] = -centerPos[2];
                          }
                          else
                          {
                              offset[0] = 0.0f;
                              offset[1] = 0.0f;
                              offset[2] = 0.0f;
                          }
                      
                          scale(scalingFactor, offset);
                          bounds(m_center, m_width, m_height, m_length, m_radius);
                      }
                      
                      void ModelOBJ::reverseWinding()
                      {
                          int swap = 0;
                      
                          // Reverse face winding.
                          for (int i = 0; i < static_cast<int>(m_indexBuffer.size()); i += 3)
                          {
                              swap = m_indexBuffer[i + 1];
                              m_indexBuffer[i + 1] = m_indexBuffer[i + 2];
                              m_indexBuffer[i + 2] = swap;
                          }
                      
                          float *pNormal = 0;
                          float *pTangent = 0;
                      
                          // Invert normals and tangents.
                          for (int i = 0; i < static_cast<int>(m_vertexBuffer.size()); ++i)
                          {
                              pNormal = m_vertexBuffer[i].normal;
                              pNormal[0] = -pNormal[0];
                              pNormal[1] = -pNormal[1];
                              pNormal[2] = -pNormal[2];
                      
                              pTangent = m_vertexBuffer[i].tangent;
                              pTangent[0] = -pTangent[0];
                              pTangent[1] = -pTangent[1];
                              pTangent[2] = -pTangent[2];
                          }
                      }
                      
                      void ModelOBJ::scale(float scaleFactor, float offset[3])
                      {
                          float *pPosition = 0;
                      
                          for (int i = 0; i < static_cast<int>(m_vertexBuffer.size()); ++i)
                          {
                              pPosition = m_vertexBuffer[i].position;
                      
                              pPosition[0] += offset[0];
                              pPosition[1] += offset[1];
                              pPosition[2] += offset[2];
                      
                              pPosition[0] *= scaleFactor;
                              pPosition[1] *= scaleFactor;
                              pPosition[2] *= scaleFactor;
                          }
                      }
                      
                      void ModelOBJ::addTrianglePos(int index, int material, int v0, int v1, int v2)
                      {
                          Vertex vertex =
                          {
                              0.0f, 0.0f, 0.0f,
                              0.0f, 0.0f,
                              0.0f, 0.0f, 0.0f
                          };
                      
                          m_attributeBuffer[index] = material;
                      
                          vertex.position[0] = m_vertexCoords[v0 * 3];
                          vertex.position[1] = m_vertexCoords[v0 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v0 * 3 + 2];
                          m_indexBuffer[index * 3] = addVertex(v0, &vertex);
                      
                          vertex.position[0] = m_vertexCoords[v1 * 3];
                          vertex.position[1] = m_vertexCoords[v1 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v1 * 3 + 2];
                          m_indexBuffer[index * 3 + 1] = addVertex(v1, &vertex);
                      
                          vertex.position[0] = m_vertexCoords[v2 * 3];
                          vertex.position[1] = m_vertexCoords[v2 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v2 * 3 + 2];
                          m_indexBuffer[index * 3 + 2] = addVertex(v2, &vertex);
                      }
                      
                      void ModelOBJ::addTrianglePosNormal(int index, int material, int v0, int v1,
                                                          int v2, int vn0, int vn1, int vn2)
                      {
                          Vertex vertex =
                          {
                              0.0f, 0.0f, 0.0f,
                              0.0f, 0.0f,
                              0.0f, 0.0f, 0.0f,
                              0.0f, 0.0f, 0.0f
                          };
                      
                          m_attributeBuffer[index] = material;
                      
                          vertex.position[0] = m_vertexCoords[v0 * 3];
                          vertex.position[1] = m_vertexCoords[v0 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v0 * 3 + 2];
                          vertex.normal[0] = m_normals[vn0 * 3];
                          vertex.normal[1] = m_normals[vn0 * 3 + 1];
                          vertex.normal[2] = m_normals[vn0 * 3 + 2];
                          m_indexBuffer[index * 3] = addVertex(v0, &vertex);
                      
                          vertex.position[0] = m_vertexCoords[v1 * 3];
                          vertex.position[1] = m_vertexCoords[v1 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v1 * 3 + 2];
                          vertex.normal[0] = m_normals[vn1 * 3];
                          vertex.normal[1] = m_normals[vn1 * 3 + 1];
                          vertex.normal[2] = m_normals[vn1 * 3 + 2];
                          m_indexBuffer[index * 3 + 1] = addVertex(v1, &vertex);
                      
                          vertex.position[0] = m_vertexCoords[v2 * 3];
                          vertex.position[1] = m_vertexCoords[v2 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v2 * 3 + 2];
                          vertex.normal[0] = m_normals[vn2 * 3];
                          vertex.normal[1] = m_normals[vn2 * 3 + 1];
                          vertex.normal[2] = m_normals[vn2 * 3 + 2];
                          m_indexBuffer[index * 3 + 2] = addVertex(v2, &vertex);
                      }
                      
                      void ModelOBJ::addTrianglePosTexCoord(int index, int material, int v0, int v1,
                                                            int v2, int vt0, int vt1, int vt2)
                      {
                          Vertex vertex =
                          {
                              0.0f, 0.0f, 0.0f,
                              0.0f, 0.0f,
                              0.0f, 0.0f, 0.0f,
                              0.0f, 0.0f, 0.0f
                          };
                      
                          m_attributeBuffer[index] = material;
                      
                          vertex.position[0] = m_vertexCoords[v0 * 3];
                          vertex.position[1] = m_vertexCoords[v0 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v0 * 3 + 2];
                          vertex.texCoord[0] = m_textureCoords[vt0 * 2];
                          vertex.texCoord[1] = m_textureCoords[vt0 * 2 + 1];
                          m_indexBuffer[index * 3] = addVertex(v0, &vertex);
                      
                          vertex.position[0] = m_vertexCoords[v1 * 3];
                          vertex.position[1] = m_vertexCoords[v1 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v1 * 3 + 2];
                          vertex.texCoord[0] = m_textureCoords[vt1 * 2];
                          vertex.texCoord[1] = m_textureCoords[vt1 * 2 + 1];
                          m_indexBuffer[index * 3 + 1] = addVertex(v1, &vertex);
                      
                          vertex.position[0] = m_vertexCoords[v2 * 3];
                          vertex.position[1] = m_vertexCoords[v2 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v2 * 3 + 2];
                          vertex.texCoord[0] = m_textureCoords[vt2 * 2];
                          vertex.texCoord[1] = m_textureCoords[vt2 * 2 + 1];
                          m_indexBuffer[index * 3 + 2] = addVertex(v2, &vertex);
                      }
                      
                      void ModelOBJ::addTrianglePosTexCoordNormal(int index, int material, int v0,
                                                                  int v1, int v2, int vt0, int vt1,
                                                                  int vt2, int vn0, int vn1, int vn2)
                      {
                          Vertex vertex =
                          {
                              0.0f, 0.0f, 0.0f,
                              0.0f, 0.0f,
                              0.0f, 0.0f, 0.0f,
                              0.0f, 0.0f, 0.0f
                          };
                      
                          m_attributeBuffer[index] = material;
                      
                          vertex.position[0] = m_vertexCoords[v0 * 3];
                          vertex.position[1] = m_vertexCoords[v0 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v0 * 3 + 2];
                          vertex.texCoord[0] = m_textureCoords[vt0 * 2];
                          vertex.texCoord[1] = m_textureCoords[vt0 * 2 + 1];
                          vertex.normal[0] = m_normals[vn0 * 3];
                          vertex.normal[1] = m_normals[vn0 * 3 + 1];
                          vertex.normal[2] = m_normals[vn0 * 3 + 2];
                          m_indexBuffer[index * 3] = addVertex(v0, &vertex);
                      
                          vertex.position[0] = m_vertexCoords[v1 * 3];
                          vertex.position[1] = m_vertexCoords[v1 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v1 * 3 + 2];
                          vertex.texCoord[0] = m_textureCoords[vt1 * 2];
                          vertex.texCoord[1] = m_textureCoords[vt1 * 2 + 1];
                          vertex.normal[0] = m_normals[vn1 * 3];
                          vertex.normal[1] = m_normals[vn1 * 3 + 1];
                          vertex.normal[2] = m_normals[vn1 * 3 + 2];
                          m_indexBuffer[index * 3 + 1] = addVertex(v1, &vertex);
                      
                          vertex.position[0] = m_vertexCoords[v2 * 3];
                          vertex.position[1] = m_vertexCoords[v2 * 3 + 1];
                          vertex.position[2] = m_vertexCoords[v2 * 3 + 2];
                          vertex.texCoord[0] = m_textureCoords[vt2 * 2];
                          vertex.texCoord[1] = m_textureCoords[vt2 * 2 + 1];
                          vertex.normal[0] = m_normals[vn2 * 3];
                          vertex.normal[1] = m_normals[vn2 * 3 + 1];
                          vertex.normal[2] = m_normals[vn2 * 3 + 2];
                          m_indexBuffer[index * 3 + 2] = addVertex(v2, &vertex);
                      }
                      
                      int ModelOBJ::addVertex(int hash, const Vertex *pVertex)
                      {
                          int index = -1;
                          std::map<int, std::vector<int> >::const_iterator iter = m_vertexCache.find(hash);
                      
                          if (iter == m_vertexCache.end())
                          {
                              // Vertex hash doesn't exist in the cache.
                      
                              index = static_cast<int>(m_vertexBuffer.size());
                              m_vertexBuffer.push_back(*pVertex);
                              m_vertexCache.insert(std::make_pair(hash, std::vector<int>(1, index)));
                          }
                          else
                          {
                              // One or more vertices have been hashed to this entry in the cache.
                      
                              const std::vector<int> &vertices = iter->second;
                              const Vertex *pCachedVertex = 0;
                              bool found = false;
                      
                              for (std::vector<int>::const_iterator i = vertices.begin(); i != vertices.end(); ++i)
                              {
                                  index = *i;
                                  pCachedVertex = &m_vertexBuffer[index];
                      
                                  if (memcmp(pCachedVertex, pVertex, sizeof(Vertex)) == 0)
                                  {
                                      found = true;
                                      ++m_count;
                                      break;
                                  }
                              }
                      
                              if (!found)
                              {
                                  index = static_cast<int>(m_vertexBuffer.size());
                                  m_vertexBuffer.push_back(*pVertex);
                                  m_vertexCache[hash].push_back(index);
                              }
                          }
                      
                          return index;
                      }
                      
                      void ModelOBJ::buildMeshes()
                      {
                          // Group the model's triangles based on material type.
                      
                          Mesh *pMesh = 0;
                          int materialId = -1;
                          int numMeshes = 0;
                      
                          // Count the number of meshes.
                          for (int i = 0; i < static_cast<int>(m_attributeBuffer.size()); ++i)
                          {
                              if (m_attributeBuffer[i] != materialId)
                              {
                                  materialId = m_attributeBuffer[i];
                                  ++numMeshes;
                              }
                          }
                      
                          // Allocate memory for the meshes and reset counters.
                          m_numberOfMeshes = numMeshes;
                          m_meshes.resize(m_numberOfMeshes);
                          numMeshes = 0;
                          materialId = -1;
                      
                          // Build the meshes. One mesh for each unique material.
                          for (int i = 0; i < static_cast<int>(m_attributeBuffer.size()); ++i)
                          {
                              if (m_attributeBuffer[i] != materialId)
                              {
                                  materialId = m_attributeBuffer[i];
                                  pMesh = &m_meshes[numMeshes++];            
                                  pMesh->pMaterial = &m_materials[materialId];
                                  pMesh->startIndex = i * 3;
                                  ++pMesh->triangleCount;
                              }
                              else
                              {
                                  ++pMesh->triangleCount;
                              }
                          }
                      
                          // Sort the meshes based on its material alpha. Fully opaque meshes
                          // towards the front and fully transparent towards the back.
                          std::sort(m_meshes.begin(), m_meshes.end(), MeshCompFunc);
                      }
                      
                      void ModelOBJ::generateNormals()
                      {
                          const int *pTriangle = 0;
                          Vertex *pVertex0 = 0;
                          Vertex *pVertex1 = 0;
                          Vertex *pVertex2 = 0;
                          float edge1[3] = {0.0f, 0.0f, 0.0f};
                          float edge2[3] = {0.0f, 0.0f, 0.0f};
                          float normal[3] = {0.0f, 0.0f, 0.0f};
                          float length = 0.0f;
                          int totalVertices = getNumberOfVertices();
                          int totalTriangles = getNumberOfTriangles();
                      
                          // Initialize all the vertex normals.
                          for (int i = 0; i < totalVertices; ++i)
                          {
                              pVertex0 = &m_vertexBuffer[i];
                              pVertex0->normal[0] = 0.0f;
                              pVertex0->normal[1] = 0.0f;
                              pVertex0->normal[2] = 0.0f;
                          }
                      
                          // Calculate the vertex normals.
                          for (int i = 0; i < totalTriangles; ++i)
                          {
                              pTriangle = &m_indexBuffer[i * 3];
                      
                              pVertex0 = &m_vertexBuffer[pTriangle[0]];
                              pVertex1 = &m_vertexBuffer[pTriangle[1]];
                              pVertex2 = &m_vertexBuffer[pTriangle[2]];
                      
                              // Calculate triangle face normal.
                      
                              edge1[0] = pVertex1->position[0] - pVertex0->position[0];
                              edge1[1] = pVertex1->position[1] - pVertex0->position[1];
                              edge1[2] = pVertex1->position[2] - pVertex0->position[2];
                      
                              edge2[0] = pVertex2->position[0] - pVertex0->position[0];
                              edge2[1] = pVertex2->position[1] - pVertex0->position[1];
                              edge2[2] = pVertex2->position[2] - pVertex0->position[2];
                      
                              normal[0] = (edge1[1] * edge2[2]) - (edge1[2] * edge2[1]);
                              normal[1] = (edge1[2] * edge2[0]) - (edge1[0] * edge2[2]);
                              normal[2] = (edge1[0] * edge2[1]) - (edge1[1] * edge2[0]);
                      
                              // Accumulate the normals.
                      
                              pVertex0->normal[0] += normal[0];
                              pVertex0->normal[1] += normal[1];
                              pVertex0->normal[2] += normal[2];
                      
                              pVertex1->normal[0] += normal[0];
                              pVertex1->normal[1] += normal[1];
                              pVertex1->normal[2] += normal[2];
                      
                              pVertex2->normal[0] += normal[0];
                              pVertex2->normal[1] += normal[1];
                              pVertex2->normal[2] += normal[2];
                          }
                      
                          // Normalize the vertex normals.
                          for (int i = 0; i < totalVertices; ++i)
                          {
                              pVertex0 = &m_vertexBuffer[i];
                      
                              length = 1.0f / sqrtf(pVertex0->normal[0] * pVertex0->normal[0] +
                                  pVertex0->normal[1] * pVertex0->normal[1] +
                                  pVertex0->normal[2] * pVertex0->normal[2]);
                      
                              pVertex0->normal[0] *= length;
                              pVertex0->normal[1] *= length;
                              pVertex0->normal[2] *= length;
                          }
                      
                          m_hasNormals = true;
                      }
                      
                      void ModelOBJ::generateTangents()
                      {
                          const int *pTriangle = 0;
                          Vertex *pVertex0 = 0;
                          Vertex *pVertex1 = 0;
                          Vertex *pVertex2 = 0;
                          float edge1[3] = {0.0f, 0.0f, 0.0f};
                          float edge2[3] = {0.0f, 0.0f, 0.0f};
                          float texEdge1[2] = {0.0f, 0.0f};
                          float texEdge2[2] = {0.0f, 0.0f};
                          float tangent[3] = {0.0f, 0.0f, 0.0f};
                          float bitangent[3] = {0.0f, 0.0f, 0.0f};
                          float det = 0.0f;
                          float nDotT = 0.0f;
                          float bDotB = 0.0f;
                          float length = 0.0f;
                          int totalVertices = getNumberOfVertices();
                          int totalTriangles = getNumberOfTriangles();
                      
                          // Initialize all the vertex tangents and bitangents.
                          for (int i = 0; i < totalVertices; ++i)
                          {
                              pVertex0 = &m_vertexBuffer[i];
                      
                              pVertex0->tangent[0] = 0.0f;
                              pVertex0->tangent[1] = 0.0f;
                              pVertex0->tangent[2] = 0.0f;
                              pVertex0->tangent[3] = 0.0f;
                      
                              pVertex0->bitangent[0] = 0.0f;
                              pVertex0->bitangent[1] = 0.0f;
                              pVertex0->bitangent[2] = 0.0f;
                          }
                      
                          // Calculate the vertex tangents and bitangents.
                          for (int i = 0; i < totalTriangles; ++i)
                          {
                              pTriangle = &m_indexBuffer[i * 3];
                      
                              pVertex0 = &m_vertexBuffer[pTriangle[0]];
                              pVertex1 = &m_vertexBuffer[pTriangle[1]];
                              pVertex2 = &m_vertexBuffer[pTriangle[2]];
                      
                              // Calculate the triangle face tangent and bitangent.
                      
                              edge1[0] = pVertex1->position[0] - pVertex0->position[0];
                              edge1[1] = pVertex1->position[1] - pVertex0->position[1];
                              edge1[2] = pVertex1->position[2] - pVertex0->position[2];
                      
                              edge2[0] = pVertex2->position[0] - pVertex0->position[0];
                              edge2[1] = pVertex2->position[1] - pVertex0->position[1];
                              edge2[2] = pVertex2->position[2] - pVertex0->position[2];
                      
                              texEdge1[0] = pVertex1->texCoord[0] - pVertex0->texCoord[0];
                              texEdge1[1] = pVertex1->texCoord[1] - pVertex0->texCoord[1];
                      
                              texEdge2[0] = pVertex2->texCoord[0] - pVertex0->texCoord[0];
                              texEdge2[1] = pVertex2->texCoord[1] - pVertex0->texCoord[1];
                      
                              det = texEdge1[0] * texEdge2[1] - texEdge2[0] * texEdge1[1];
                      
                              if (fabs(det) < 1e-6f)
                              {
                                  tangent[0] = 1.0f;
                                  tangent[1] = 0.0f;
                                  tangent[2] = 0.0f;
                      
                                  bitangent[0] = 0.0f;
                                  bitangent[1] = 1.0f;
                                  bitangent[2] = 0.0f;
                              }
                              else
                              {
                                  det = 1.0f / det;
                      
                                  tangent[0] = (texEdge2[1] * edge1[0] - texEdge1[1] * edge2[0]) * det;
                                  tangent[1] = (texEdge2[1] * edge1[1] - texEdge1[1] * edge2[1]) * det;
                                  tangent[2] = (texEdge2[1] * edge1[2] - texEdge1[1] * edge2[2]) * det;
                      
                                  bitangent[0] = (-texEdge2[0] * edge1[0] + texEdge1[0] * edge2[0]) * det;
                                  bitangent[1] = (-texEdge2[0] * edge1[1] + texEdge1[0] * edge2[1]) * det;
                                  bitangent[2] = (-texEdge2[0] * edge1[2] + texEdge1[0] * edge2[2]) * det;
                              }
                      
                              // Accumulate the tangents and bitangents.
                      
                              pVertex0->tangent[0] += tangent[0];
                              pVertex0->tangent[1] += tangent[1];
                              pVertex0->tangent[2] += tangent[2];
                              pVertex0->bitangent[0] += bitangent[0];
                              pVertex0->bitangent[1] += bitangent[1];
                              pVertex0->bitangent[2] += bitangent[2];
                      
                              pVertex1->tangent[0] += tangent[0];
                              pVertex1->tangent[1] += tangent[1];
                              pVertex1->tangent[2] += tangent[2];
                              pVertex1->bitangent[0] += bitangent[0];
                              pVertex1->bitangent[1] += bitangent[1];
                              pVertex1->bitangent[2] += bitangent[2];
                      
                              pVertex2->tangent[0] += tangent[0];
                              pVertex2->tangent[1] += tangent[1];
                              pVertex2->tangent[2] += tangent[2];
                              pVertex2->bitangent[0] += bitangent[0];
                              pVertex2->bitangent[1] += bitangent[1];
                              pVertex2->bitangent[2] += bitangent[2];
                          }
                      
                          // Orthogonalize and normalize the vertex tangents.
                          for (int i = 0; i < totalVertices; ++i)
                          {
                              pVertex0 = &m_vertexBuffer[i];
                      
                              // Gram-Schmidt orthogonalize tangent with normal.
                      
                              nDotT = pVertex0->normal[0] * pVertex0->tangent[0] +
                                      pVertex0->normal[1] * pVertex0->tangent[1] +
                                      pVertex0->normal[2] * pVertex0->tangent[2];
                      
                              pVertex0->tangent[0] -= pVertex0->normal[0] * nDotT;
                              pVertex0->tangent[1] -= pVertex0->normal[1] * nDotT;
                              pVertex0->tangent[2] -= pVertex0->normal[2] * nDotT;
                      
                              // Normalize the tangent.
                      
                              length = 1.0f / sqrtf(pVertex0->tangent[0] * pVertex0->tangent[0] +
                                                    pVertex0->tangent[1] * pVertex0->tangent[1] +
                                                    pVertex0->tangent[2] * pVertex0->tangent[2]);
                      
                              pVertex0->tangent[0] *= length;
                              pVertex0->tangent[1] *= length;
                              pVertex0->tangent[2] *= length;
                      
                              // Calculate the handedness of the local tangent space.
                              // The bitangent vector is the cross product between the triangle face
                              // normal vector and the calculated tangent vector. The resulting
                              // bitangent vector should be the same as the bitangent vector
                              // calculated from the set of linear equations above. If they point in
                              // different directions then we need to invert the cross product
                              // calculated bitangent vector. We store this scalar multiplier in the
                              // tangent vector's 'w' component so that the correct bitangent vector
                              // can be generated in the normal mapping shader's vertex shader.
                              //
                              // Normal maps have a left handed coordinate system with the origin
                              // located at the top left of the normal map texture. The x coordinates
                              // run horizontally from left to right. The y coordinates run
                              // vertically from top to bottom. The z coordinates run out of the
                              // normal map texture towards the viewer. Our handedness calculations
                              // must take this fact into account as well so that the normal mapping
                              // shader's vertex shader will generate the correct bitangent vectors.
                              // Some normal map authoring tools such as Crazybump
                              // (http://www.crazybump.com/) includes options to allow you to control
                              // the orientation of the normal map normal's y-axis.
                      
                              bitangent[0] = (pVertex0->normal[1] * pVertex0->tangent[2]) - 
                                             (pVertex0->normal[2] * pVertex0->tangent[1]);
                              bitangent[1] = (pVertex0->normal[2] * pVertex0->tangent[0]) -
                                             (pVertex0->normal[0] * pVertex0->tangent[2]);
                              bitangent[2] = (pVertex0->normal[0] * pVertex0->tangent[1]) - 
                                             (pVertex0->normal[1] * pVertex0->tangent[0]);
                      
                              bDotB = bitangent[0] * pVertex0->bitangent[0] + 
                                      bitangent[1] * pVertex0->bitangent[1] + 
                                      bitangent[2] * pVertex0->bitangent[2];
                      
                              pVertex0->tangent[3] = (bDotB < 0.0f) ? 1.0f : -1.0f;
                      
                              pVertex0->bitangent[0] = bitangent[0];
                              pVertex0->bitangent[1] = bitangent[1];
                              pVertex0->bitangent[2] = bitangent[2];
                          }
                      
                          m_hasTangents = true;
                      }
                      
                      void ModelOBJ::importGeometryFirstPass(FILE *pFile)
                      {
                          m_hasTextureCoords = false;
                          m_hasNormals = false;
                      
                          m_numberOfVertexCoords = 0;
                          m_numberOfTextureCoords = 0;
                          m_numberOfNormals = 0;
                          m_numberOfTriangles = 0;
                      
                          int v = 0;
                          int vt = 0;
                          int vn = 0;
                          char buffer[256] = {0};
                          std::string name;
                      
                          while (fscanf(pFile, "%s", buffer) != EOF)
                          {
                              switch (buffer[0])
                              {
                              case 'f':   // v, v//vn, v/vt, v/vt/vn.
                                  fscanf(pFile, "%s", buffer);
                      
                                  if (strstr(buffer, "//")) // v//vn
                                  {
                                      sscanf(buffer, "%d//%d", &v, &vn);
                                      fscanf(pFile, "%d//%d", &v, &vn);
                                      fscanf(pFile, "%d//%d", &v, &vn);
                                      ++m_numberOfTriangles;
                      
                                      while (fscanf(pFile, "%d//%d", &v, &vn) > 0)
                                          ++m_numberOfTriangles;
                                  }
                                  else if (sscanf(buffer, "%d/%d/%d", &v, &vt, &vn) == 3) // v/vt/vn
                                  {
                                      fscanf(pFile, "%d/%d/%d", &v, &vt, &vn);
                                      fscanf(pFile, "%d/%d/%d", &v, &vt, &vn);
                                      ++m_numberOfTriangles;
                      
                                      while (fscanf(pFile, "%d/%d/%d", &v, &vt, &vn) > 0)
                                          ++m_numberOfTriangles;
                                  }
                                  else if (sscanf(buffer, "%d/%d", &v, &vt) == 2) // v/vt
                                  {
                                      fscanf(pFile, "%d/%d", &v, &vt);
                                      fscanf(pFile, "%d/%d", &v, &vt);
                                      ++m_numberOfTriangles;
                      
                                      while (fscanf(pFile, "%d/%d", &v, &vt) > 0)
                                          ++m_numberOfTriangles;
                                  }
                                  else // v
                                  {
                                      fscanf(pFile, "%d", &v);
                                      fscanf(pFile, "%d", &v);
                                      ++m_numberOfTriangles;
                      
                                      while (fscanf(pFile, "%d", &v) > 0)
                                          ++m_numberOfTriangles;
                                  }
                                  break;
                      
                              case 'm':   // mtllib
                                  fgets(buffer, sizeof(buffer), pFile);
                                  sscanf(buffer, "%s %s", buffer, buffer);
                                  name = m_directoryPath;
                                  name += buffer;
                                  importMaterials(name.c_str());
                                  break;
                      
                              case 'v':   // v, vt, or vn
                                  switch (buffer[1])
                                  {
                                  case '\0':
                                      fgets(buffer, sizeof(buffer), pFile);
                                      ++m_numberOfVertexCoords;
                                      break;
                      
                                  case 'n':
                                      fgets(buffer, sizeof(buffer), pFile);
                                      ++m_numberOfNormals;
                                      break;
                      
                                  case 't':
                                      fgets(buffer, sizeof(buffer), pFile);
                                      ++m_numberOfTextureCoords;
                      
                                  default:
                                      break;
                                  }
                                  break;
                      
                              default:
                                  fgets(buffer, sizeof(buffer), pFile);
                                  break;
                              }
                          }
                      
                          m_hasPositions = m_numberOfVertexCoords > 0;
                          m_hasNormals = m_numberOfNormals > 0;
                          m_hasTextureCoords = m_numberOfTextureCoords > 0;
                      
                          // Allocate memory for the OBJ model data.
                          m_vertexCoords.resize(m_numberOfVertexCoords * 3);
                          m_textureCoords.resize(m_numberOfTextureCoords * 2);
                          m_normals.resize(m_numberOfNormals * 3);
                          m_indexBuffer.resize(m_numberOfTriangles * 3);
                          m_attributeBuffer.resize(m_numberOfTriangles);
                      
                          // Define a default material if no materials were loaded.
                          if (m_numberOfMaterials == 0)
                          {
                              Material defaultMaterial =
                              {
                                  0.2f, 0.2f, 0.2f, 1.0f,
                                  0.8f, 0.8f, 0.8f, 1.0f,
                                  0.0f, 0.0f, 0.0f, 1.0f,
                                  0.0f,
                                  1.0f,
                                  std::string("default"),
                                  std::string(),
                                  std::string()
                              };
                      
                              m_materials.push_back(defaultMaterial);
                              m_materialCache[defaultMaterial.name] = 0;
                          }
                      }
                      
                      void ModelOBJ::importGeometrySecondPass(FILE *pFile)
                      {
                          int v[3] = {0};
                          int vt[3] = {0};
                          int vn[3] = {0};
                          int numVertices = 0;
                          int numTexCoords = 0;
                          int numNormals = 0;
                          int numTriangles = 0;
                          int activeMaterial = 0;
                          char buffer[256] = {0};
                          std::string name;
                          std::map<std::string, int>::const_iterator iter;
                      
                          while (fscanf(pFile, "%s", buffer) != EOF)
                          {
                              switch (buffer[0])
                              {
                              case 'f': // v, v//vn, v/vt, or v/vt/vn.
                                  v[0]  = v[1]  = v[2]  = 0;
                                  vt[0] = vt[1] = vt[2] = 0;
                                  vn[0] = vn[1] = vn[2] = 0;
                      
                                  fscanf(pFile, "%s", buffer);
                      
                                  if (strstr(buffer, "//")) // v//vn
                                  {
                                      sscanf(buffer, "%d//%d", &v[0], &vn[0]);
                                      fscanf(pFile, "%d//%d", &v[1], &vn[1]);
                                      fscanf(pFile, "%d//%d", &v[2], &vn[2]);
                      
                                      v[0] = (v[0] < 0) ? v[0] + numVertices - 1 : v[0] - 1;
                                      v[1] = (v[1] < 0) ? v[1] + numVertices - 1 : v[1] - 1;
                                      v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
                      
                                      vn[0] = (vn[0] < 0) ? vn[0] + numNormals - 1 : vn[0] - 1;
                                      vn[1] = (vn[1] < 0) ? vn[1] + numNormals - 1 : vn[1] - 1;
                                      vn[2] = (vn[2] < 0) ? vn[2] + numNormals - 1 : vn[2] - 1;
                      
                                      addTrianglePosNormal(numTriangles++, activeMaterial,
                                          v[0], v[1], v[2], vn[0], vn[1], vn[2]);
                      
                                      v[1] = v[2];
                                      vn[1] = vn[2];
                      
                                      while (fscanf(pFile, "%d//%d", &v[2], &vn[2]) > 0)
                                      {
                                          v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
                                          vn[2] = (vn[2] < 0) ? vn[2] + numNormals - 1 : vn[2] - 1;
                      
                                          addTrianglePosNormal(numTriangles++, activeMaterial,
                                              v[0], v[1], v[2], vn[0], vn[1], vn[2]);
                      
                                          v[1] = v[2];
                                          vn[1] = vn[2];
                                      }
                                  }
                                  else if (sscanf(buffer, "%d/%d/%d", &v[0], &vt[0], &vn[0]) == 3) // v/vt/vn
                                  {
                                      fscanf(pFile, "%d/%d/%d", &v[1], &vt[1], &vn[1]);
                                      fscanf(pFile, "%d/%d/%d", &v[2], &vt[2], &vn[2]);
                      
                                      v[0] = (v[0] < 0) ? v[0] + numVertices - 1 : v[0] - 1;
                                      v[1] = (v[1] < 0) ? v[1] + numVertices - 1 : v[1] - 1;
                                      v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
                      
                                      vt[0] = (vt[0] < 0) ? vt[0] + numTexCoords - 1 : vt[0] - 1;
                                      vt[1] = (vt[1] < 0) ? vt[1] + numTexCoords - 1 : vt[1] - 1;
                                      vt[2] = (vt[2] < 0) ? vt[2] + numTexCoords - 1 : vt[2] - 1;
                      
                                      vn[0] = (vn[0] < 0) ? vn[0] + numNormals - 1 : vn[0] - 1;
                                      vn[1] = (vn[1] < 0) ? vn[1] + numNormals - 1 : vn[1] - 1;
                                      vn[2] = (vn[2] < 0) ? vn[2] + numNormals - 1 : vn[2] - 1;
                      
                                      addTrianglePosTexCoordNormal(numTriangles++, activeMaterial,
                                          v[0], v[1], v[2], vt[0], vt[1], vt[2], vn[0], vn[1], vn[2]);
                      
                                      v[1] = v[2];
                                      vt[1] = vt[2];
                                      vn[1] = vn[2];
                      
                                      while (fscanf(pFile, "%d/%d/%d", &v[2], &vt[2], &vn[2]) > 0)
                                      {
                                          v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
                                          vt[2] = (vt[2] < 0) ? vt[2] + numTexCoords - 1 : vt[2] - 1;
                                          vn[2] = (vn[2] < 0) ? vn[2] + numNormals - 1 : vn[2] - 1;
                      
                                          addTrianglePosTexCoordNormal(numTriangles++, activeMaterial,
                                              v[0], v[1], v[2], vt[0], vt[1], vt[2], vn[0], vn[1], vn[2]);
                      
                                          v[1] = v[2];
                                          vt[1] = vt[2];
                                          vn[1] = vn[2];
                                      }
                                  }
                                  else if (sscanf(buffer, "%d/%d", &v[0], &vt[0]) == 2) // v/vt
                                  {
                                      fscanf(pFile, "%d/%d", &v[1], &vt[1]);
                                      fscanf(pFile, "%d/%d", &v[2], &vt[2]);
                      
                                      v[0] = (v[0] < 0) ? v[0] + numVertices - 1 : v[0] - 1;
                                      v[1] = (v[1] < 0) ? v[1] + numVertices - 1 : v[1] - 1;
                                      v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
                      
                                      vt[0] = (vt[0] < 0) ? vt[0] + numTexCoords - 1 : vt[0] - 1;
                                      vt[1] = (vt[1] < 0) ? vt[1] + numTexCoords - 1 : vt[1] - 1;
                                      vt[2] = (vt[2] < 0) ? vt[2] + numTexCoords - 1 : vt[2] - 1;
                      
                                      addTrianglePosTexCoord(numTriangles++, activeMaterial,
                                          v[0], v[1], v[2], vt[0], vt[1], vt[2]);
                      
                                      v[1] = v[2];
                                      vt[1] = vt[2];
                      
                                      while (fscanf(pFile, "%d/%d", &v[2], &vt[2]) > 0)
                                      {
                                          v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
                                          vt[2] = (vt[2] < 0) ? vt[2] + numTexCoords - 1 : vt[2] - 1;
                      
                                          addTrianglePosTexCoord(numTriangles++, activeMaterial,
                                              v[0], v[1], v[2], vt[0], vt[1], vt[2]);
                      
                                          v[1] = v[2];
                                          vt[1] = vt[2];
                                      }
                                  }
                                  else // v
                                  {
                                      sscanf(buffer, "%d", &v[0]);
                                      fscanf(pFile, "%d", &v[1]);
                                      fscanf(pFile, "%d", &v[2]);
                      
                                      v[0] = (v[0] < 0) ? v[0] + numVertices - 1 : v[0] - 1;
                                      v[1] = (v[1] < 0) ? v[1] + numVertices - 1 : v[1] - 1;
                                      v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
                      
                                      addTrianglePos(numTriangles++, activeMaterial, v[0], v[1], v[2]);
                      
                                      v[1] = v[2];
                      
                                      while (fscanf(pFile, "%d", &v[2]) > 0)
                                      {
                                          v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
                      
                                          addTrianglePos(numTriangles++, activeMaterial, v[0], v[1], v[2]);
                      
                                          v[1] = v[2];
                                      }
                                  }
                                  break;
                      
                              case 'u': // usemtl
                                  fgets(buffer, sizeof(buffer), pFile);
                                  sscanf(buffer, "%s %s", buffer, buffer);
                                  name = buffer;
                                  iter = m_materialCache.find(buffer);
                                  activeMaterial = (iter == m_materialCache.end()) ? 0 : iter->second;
                                  break;
                      
                              case 'v': // v, vn, or vt.
                                  switch (buffer[1])
                                  {
                                  case '\0': // v
                                      fscanf(pFile, "%f %f %f",
                                          &m_vertexCoords[3 * numVertices],
                                          &m_vertexCoords[3 * numVertices + 1],
                                          &m_vertexCoords[3 * numVertices + 2]);
                                      ++numVertices;
                                      break;
                      
                                  case 'n': // vn
                                      fscanf(pFile, "%f %f %f",
                                          &m_normals[3 * numNormals],
                                          &m_normals[3 * numNormals + 1],
                                          &m_normals[3 * numNormals + 2]);
                                      ++numNormals;
                                      break;
                      
                                  case 't': // vt
                                      fscanf(pFile, "%f %f",
                                          &m_textureCoords[2 * numTexCoords],
                                          &m_textureCoords[2 * numTexCoords + 1]);
                                      ++numTexCoords;
                                      break;
                      
                                  default:
                                      break;
                                  }
                                  break;
                      
                              default:
                                  fgets(buffer, sizeof(buffer), pFile);
                                  break;
                              }
                          }
                      }
                      
                      bool ModelOBJ::importMaterials(const char *pszFilename)
                      {
                          FILE *pFile = fopen(pszFilename, "r");
                      
                          if (!pFile)
                              return false;
                      
                          Material *pMaterial = 0;
                          int illum = 0;
                          int numMaterials = 0;
                          char buffer[256] = {0};
                      
                          // Count the number of materials in the MTL file.
                          while (fscanf(pFile, "%s", buffer) != EOF)
                          {
                              switch (buffer[0])
                              {
                              case 'n': // newmtl
                                  ++numMaterials;
                                  fgets(buffer, sizeof(buffer), pFile);
                                  sscanf(buffer, "%s %s", buffer, buffer);
                                  break;
                      
                              default:
                                  fgets(buffer, sizeof(buffer), pFile);
                                  break;
                              }
                          }
                      
                          rewind(pFile);
                      
                          m_numberOfMaterials = numMaterials;
                          numMaterials = 0;
                          m_materials.resize(m_numberOfMaterials);
                      
                          // Load the materials in the MTL file.
                          while (fscanf(pFile, "%s", buffer) != EOF)
                          {
                              switch (buffer[0])
                              {
                              case 'N': // Ns
                                  fscanf(pFile, "%f", &pMaterial->shininess);
                      
                                  // Wavefront .MTL file shininess is from [0,1000].
                                  // Scale back to a generic [0,1] range.
                                  pMaterial->shininess /= 1000.0f;
                                  break;
                      
                              case 'K': // Ka, Kd, or Ks
                                  switch (buffer[1])
                                  {
                                  case 'a': // Ka
                                      fscanf(pFile, "%f %f %f",
                                          &pMaterial->ambient[0],
                                          &pMaterial->ambient[1],
                                          &pMaterial->ambient[2]);
                                      pMaterial->ambient[3] = 1.0f;
                                      break;
                      
                                  case 'd': // Kd
                                      fscanf(pFile, "%f %f %f",
                                          &pMaterial->diffuse[0],
                                          &pMaterial->diffuse[1],
                                          &pMaterial->diffuse[2]);
                                      pMaterial->diffuse[3] = 1.0f;
                                      break;
                      
                                  case 's': // Ks
                                      fscanf(pFile, "%f %f %f",
                                          &pMaterial->specular[0],
                                          &pMaterial->specular[1],
                                          &pMaterial->specular[2]);
                                      pMaterial->specular[3] = 1.0f;
                                      break;
                      
                                  default:
                                      fgets(buffer, sizeof(buffer), pFile);
                                      break;
                                  }
                                  break;
                      
                              case 'T': // Tr
                                  switch (buffer[1])
                                  {
                                  case 'r': // Tr
                                      fscanf(pFile, "%f", &pMaterial->alpha);
                                      pMaterial->alpha = 1.0f - pMaterial->alpha;
                                      break;
                      
                                  default:
                                      fgets(buffer, sizeof(buffer), pFile);
                                      break;
                                  }
                                  break;
                      
                              case 'd':
                                  fscanf(pFile, "%f", &pMaterial->alpha);
                                  break;
                      
                              case 'i': // illum
                                  fscanf(pFile, "%d", &illum);
                      
                                  if (illum == 1)
                                  {
                                      pMaterial->specular[0] = 0.0f;
                                      pMaterial->specular[1] = 0.0f;
                                      pMaterial->specular[2] = 0.0f;
                                      pMaterial->specular[3] = 1.0f;
                                  }
                                  break;
                      
                              case 'm': // map_Kd, map_bump
                                  if (strstr(buffer, "map_Kd") != 0)
                                  {
                                      fgets(buffer, sizeof(buffer), pFile);
                                      sscanf(buffer, "%s %s", buffer, buffer);
                                      pMaterial->colorMapFilename = buffer;
                                  }
                                  else if (strstr(buffer, "map_bump") != 0)
                                  {
                                      fgets(buffer, sizeof(buffer), pFile);
                                      sscanf(buffer, "%s %s", buffer, buffer);
                                      pMaterial->bumpMapFilename = buffer;
                                  }
                                  else
                                  {
                                      fgets(buffer, sizeof(buffer), pFile);
                                  }
                                  break;
                      
                              case 'n': // newmtl
                                  fgets(buffer, sizeof(buffer), pFile);
                                  sscanf(buffer, "%s %s", buffer, buffer);
                      
                                  pMaterial = &m_materials[numMaterials];
                                  pMaterial->ambient[0] = 0.2f;
                                  pMaterial->ambient[1] = 0.2f;
                                  pMaterial->ambient[2] = 0.2f;
                                  pMaterial->ambient[3] = 1.0f;
                                  pMaterial->diffuse[0] = 0.8f;
                                  pMaterial->diffuse[1] = 0.8f;
                                  pMaterial->diffuse[2] = 0.8f;
                                  pMaterial->diffuse[3] = 1.0f;
                                  pMaterial->specular[0] = 0.0f;
                                  pMaterial->specular[1] = 0.0f;
                                  pMaterial->specular[2] = 0.0f;
                                  pMaterial->specular[3] = 1.0f;
                                  pMaterial->shininess = 0.0f;
                                  pMaterial->alpha = 1.0f;
                                  pMaterial->name = buffer;
                                  pMaterial->colorMapFilename.clear();
                                  pMaterial->bumpMapFilename.clear();
                      
                                  m_materialCache[pMaterial->name] = numMaterials;
                                  ++numMaterials;
                                  break;
                      
                              default:
                                  fgets(buffer, sizeof(buffer), pFile);
                                  break;
                              }
                          }
                      
                          fclose(pFile);
                          return true;
                      }
                      Last edited by Patrice Terrier; 14 Jun 2012, 01:59 AM.
                      Patrice Terrier
                      www.zapsolution.com
                      www.objreader.com
                      Addons: GDImage.DLL 32/64-bit (Graphic library), WinLIFT.DLL 32/64-bit (Skin Engine).

                      Comment


                      • #12
                        Originally posted by Patrice Terrier View Post
                        Code:
                            LOCAL nIndex, nIter AS LONG
                            nIndex = -1
                        
                            ARRAY SCAN gnm_vertexCache(), = nHash, TO nIter
                        
                            IF nIter = UBOUND(gnm_vertexCache()) + 1 THEN
                               '// Vertex nHash doesn't exist in the cache.
                        From PowerBASIC HELP:
                        ARRAY SCAN array([index]) [FOR count], expression, TO lvar&
                        If none of the scanned elements satisfy expression, zero will be stored in lvar&.
                        Code:
                            LOCAL nIndex, nIter AS LONG
                            nIndex = -1
                        
                            ARRAY SCAN gnm_vertexCache(), = nHash, TO nIter
                        
                            IF nIter = 0 THEN
                               '// Vertex nHash doesn't exist in the cache.
                        You are testing for the presence of a matching value. In the C++ example, they walk a list.
                        It is a hash table, but it is just a list. If they reach .end, then the search is unsuccessful,
                        and they test for the iterator exceeding the boundaries of the list without success.
                        In PowerBASIC, the zero value returned by the ARRAY SCAN will indicate a search failure.
                        You aren't required to walk the list manually. That is all handled for you by ARRAY SCAN.
                        The world is strange and wonderful.*
                        I reserve the right to be horrifically wrong.
                        Please maintain a safe following distance.
                        *wonderful sold separately.

                        Comment


                        • #13
                          Kurt,

                          Thank you for your suggestion, it is already what i am doing, as you can see in the post i sent just before yours.

                          ...
                          Patrice Terrier
                          www.zapsolution.com
                          www.objreader.com
                          Addons: GDImage.DLL 32/64-bit (Graphic library), WinLIFT.DLL 32/64-bit (Skin Engine).

                          Comment


                          • #14
                            This is "aimed" at lurkers rather than at Patrice.

                            MCM's point about REDIM is a good one, as this little app demonstrates.

                            Code:
                            #compile exe
                            #dim all
                            '
                            %narray = 99999
                            '
                            function pbmain () as long
                                local i, n as long
                                local q as quad
                                local a() as long
                                local s as string
                                
                                tix q
                                for i = 1 to %narray
                                    n = ubound(a) + 1
                                    redim preserve a(0 to n)
                                next
                                tix end q
                                s = "lots of little redims" + str$(q)
                                '
                                tix q
                                redim preserve a(0 to %narray)
                                tix end q
                                s += $crlf + "one big redim" + str$(q)
                                ? s
                            end function

                            Comment


                            • #15
                              I think my code will have a slight edge on C++. Couple places you'll have to watch for performance.
                              1) Taking the hash to an index. Once you find a performant algorithm you like, probabbly worth looking at assembly.

                              2) If m_vertexBuffer( index ).dta = pVertex. I'm assuming PB will be pretty good at this, but if needed you might memcmp using varptr's instead or even drop this into assembly. Probabbly worth checking the tix values.

                              There's one other thing of note. Unless your plan is to only deal with a single ModelOBJ at a time (and so you can use globals), you need all those variables in the class bundled up in a memory structure somewhere and passed to each function. i.e. in c++ all of those values come along with *this, you're missing that parameter in your example functions.

                              Edit: One other thing, I haven't looked at the code closely, but assuming there's no actual use for the lists in the hash table (such as quickly accessing related vertices), you have the potential to blow the doors of the C++ implementation by creating a reallly efficient hash against pVertex directly. I'm not sure how often v0, v1, and v2 collide but if it's very often, hashing the vertex could give you a big boost in performance assuming you can keep the cost of hashing down.
                              Last edited by Larry Charlton; 14 Jun 2012, 05:13 AM.
                              LarryC
                              Website
                              Sometimes life's a dream, sometimes it's a scream

                              Comment


                              • #16
                                I made change to REDIM PRESERVE with a modulus 10000, but that didn't speed up much the code.

                                The HashFind / HashAdd i am using, is still the bottleneck compared to C++.

                                When i could get everything to work as i want, then i will think of using a class instead of flat API, but so far my main concern is to get the same speed than the original C++ code.

                                ...
                                Patrice Terrier
                                www.zapsolution.com
                                www.objreader.com
                                Addons: GDImage.DLL 32/64-bit (Graphic library), WinLIFT.DLL 32/64-bit (Skin Engine).

                                Comment


                                • #17
                                  Patrice, just had a quick peek at your code, couple of observations:

                                  You are doubling up the stack frame overhead in hashadd by calling hash_add from it - I'm thinking about extra code to be executed not about stack size.

                                  Also, your hashes are strings. Could you avoid the overhead of creating a string every time you want to find a key?

                                  Comment


                                  • #18
                                    I shall post later on, the C++ project i am translating to PowerBASIC, as well as the PB's source code including an OBJ model for the purpose of benchmark test.

                                    Note: I have converted the C++ code to work with VS2010, but i will post the final EXE with it.

                                    ...
                                    Patrice Terrier
                                    www.zapsolution.com
                                    www.objreader.com
                                    Addons: GDImage.DLL 32/64-bit (Graphic library), WinLIFT.DLL 32/64-bit (Skin Engine).

                                    Comment


                                    • #19
                                      Here is the full ModelOBJ project

                                      You can download the full ModelOBJ.zip here
                                      http://www.zapsolution.com/preview/ModelOBJ.zip
                                      (size 8,182 Kb)

                                      full VS2010 solution is in the "GLObjViewer" folder,
                                      the EXE name is GLObjViewer.exe, there are several OBJ models inside of the "\Content\Models" subfolder.

                                      The name of the PB project is Test.bas, and it is located into the "ModelOBJ" root folder. The "Test.bas" does use the "venusm.obj" model for the purpose of benchmarking the wole process of reading/loading a model.
                                      Test.bas is written in PB10, it is intended to use José Roca include and i am using "compile.bat" from prompt to compile the source code.

                                      When runing Test.exe it takes 20.4 seconds on my HP HDX 18 Core2 Duo P8400 @2.26Ghz with 64-bit OS.

                                      The C++ code loads venusm.obj in less than 4", and i would like to get close to the same time in PB, that's my challenge

                                      ...
                                      Last edited by Patrice Terrier; 14 Jun 2012, 01:04 PM.
                                      Patrice Terrier
                                      www.zapsolution.com
                                      www.objreader.com
                                      Addons: GDImage.DLL 32/64-bit (Graphic library), WinLIFT.DLL 32/64-bit (Skin Engine).

                                      Comment


                                      • #20
                                        There is a typo in the address. Must be http://www.zapsolution.com/preview/ModelOBJ.zip
                                        Forum: http://www.jose.it-berater.org/smfforum/index.php

                                        Comment

                                        Working...
                                        X