public void loadBuffers() {
    SurfCell curSurf; // the current surface being worked with
    PolyCell curPoly; // the current polygon in the surface
    VertListCell curVertLC;

    curSurf = surfHead;
    int vertCount;
    while (curSurf != null) {
      int vertsPerPrim;
      vertCount = 0;
      curPoly = curSurf.polyHead;
      // vertCount += curPoly.numVerts;
      vertsPerPrim = curPoly.numVerts;
      while (curPoly != null) {
        vertCount += curPoly.numVerts;
        if (curPoly.numVerts != vertsPerPrim) {
          System.out.printf("Surface %s: Unequal number of vertices ", curSurf.name);
          System.out.printf(
              "\n    First prim had %d Cur Prim has %d\n", curPoly.numVerts, vertsPerPrim);
          return;
        }
        curPoly = curPoly.next;
      }
      curSurf.numVerts = vertCount;
      float vertices[] = new float[vertCount * 3];
      int vInd = 0;
      float normals[] = new float[vertCount * 3];
      int nInd = 0;
      curPoly = curSurf.polyHead;
      while (curPoly != null) {
        curVertLC = curPoly.vert;
        while (curVertLC != null) {
          // for(int i = 0; i < curPoly.numVerts; i++);{
          VertCell curVert = vertArray.get(curVertLC.vert);
          vertices[vInd++] = (float) curVert.worldPos.x;
          vertices[vInd++] = (float) curVert.worldPos.y;
          vertices[vInd++] = (float) curVert.worldPos.z;
          normals[nInd++] = (float) vertNormArray[curVertLC.vert].x;
          normals[nInd++] = (float) vertNormArray[curVertLC.vert].y;
          normals[nInd++] = (float) vertNormArray[curVertLC.vert].z;
          curVertLC = curVertLC.next;
        }
        curPoly = curPoly.next;
      }
      // now put vertices and normals into VertexArray or Buffer
      curSurf.vertexBuffer = Buffers.newDirectFloatBuffer(vertices.length);
      curSurf.vertexBuffer.put(vertices);
      curSurf.vertexBuffer.rewind();

      curSurf.normalBuffer = Buffers.newDirectFloatBuffer(normals.length);
      curSurf.normalBuffer.put(normals);
      curSurf.normalBuffer.rewind();

      curSurf = curSurf.next;
    }
  }
  private void addPolyToSurf(SurfCell curSurf, String line, boolean inSmooth) {
    int curIndex = 0;
    StringTokenizer tokens;
    StringTokenizer vertTokens;
    PolyCell curPoly;
    PolyListCell curVertPoly;
    VertListCell curVert;

    tokens = new StringTokenizer(line);
    line = tokens.nextToken(); // "eat" up the f at the beginning of the line

    if (curSurf.polyHead == null) // This is the first polygon in the surface
    {
      curSurf.polyHead = new PolyCell();
      curPoly = curSurf.polyHead;
    } else // Other polygons are already in the surface
    {
      curPoly = curSurf.polyHead; // Begin at the first polygon
      while (curPoly.next != null) // Move to the next polygon as long as it exists
      curPoly = curPoly.next;

      curPoly.next = new PolyCell(); // Create a new polygon at the end of the list
      curPoly = curPoly.next;
    }
    curPoly.numVerts = 0;

    // At this point, we are dealing with an entirely new polygon!
    curPoly.parentSurf = curSurf;
    while (tokens
        .hasMoreTokens()) // Loop for constructing an entire polygon...one vertex at a time!
    {
      line = tokens.nextToken();
      vertTokens = new StringTokenizer(line, "/"); // A tokenizer separated by '/'

      curIndex = Integer.parseInt(vertTokens.nextToken()); // Grab the first number (a vertex index)
      if (curPoly.vert == null) // This is our first vertex in the polygon
      {
        curPoly.vert = new VertListCell();
        curVert = curPoly.vert;
      } else // Other vertices have already been added
      {
        curVert = curPoly.vert;
        while (curVert.next != null) curVert = curVert.next;

        curVert.next = new VertListCell();
        curVert = curVert.next;
      }
      // NOTE: copying of vertices should not be necessary since each surface is drawn by itself and
      // * each surface will either be drawn smoothed or flat and either way the correct normal will
      // be
      // * set and used for each polygon - if a surface is flat no vertex normals will be calculated
      // - could
      // * still be a problem if indeed vertices are reused in smoothed surfaces but that is
      // apparently not true??
      //////// All wrong - vertex normals are not recalculated at draw time so any vertex can NOT be
      // in
      ////////// multiple surfaces

      ////// CHECK for vertex copy here
      // if inSmooth then go ahead and setup the vertex references without regard to
      //   what is in vertexUsedArray
      // if !inSmooth then this is a one poly surface and if the vertex has already been
      //    used (as indicated in vertexUsedArray then a new copy of the vertex must be made

      if (!inSmooth) { // we are in an unsmoothed surface [often a one poly surface] -check to see
        // if vertex(curIndex-1) has
        // already been used in another surface
        if (curIndex <= vertUsedArray.size() && vertUsedArray.get(curIndex - 1) != null) {
          // make a copy of the vertex that curIndex-1 indicates
          vertArray.add(numVerts, new VertCell(vertArray.get(curIndex - 1)));
          // vertNormArray.add(numNorms++, new Double3D(vertNormArray.get(curIndex-1)));
          vertUsedArray.add(numVerts++, curSurf);
          curIndex = numVerts;
        }
      } else if (vertUsedArray.get(curIndex - 1) != null
          && vertUsedArray.get(curIndex - 1) != curSurf) {
        // we are in a smoothing group
        // but this vertex has already been used in another surface
        // so find out if there is already a copy in this surface
        int copyIndex = findCopyInSurf(curSurf, vertArray.get(curIndex - 1).worldPos);
        if (copyIndex == -1) {
          // make a copy of the vertex that curIndex-1 indicates
          vertArray.add(numVerts, new VertCell(vertArray.get(curIndex - 1)));
          // vertNormArray.add(numNorms++, new Double3D(vertNormArray.get(curIndex-1)));
          vertUsedArray.add(numVerts++, curSurf);
          curIndex = numVerts;
        } else {
          curIndex = copyIndex + 1;
        }
      }

      curVert.vert = curIndex - 1; // Assign the vertex index
      // curPoly.numVerts++;
      vertUsedArray.set(curIndex - 1, curSurf);

      /*
       * If we are not in a smoothing group at the moment do not build the
       * linked list specifying polygon membership for the vertex - when normals
       * are calculated any vertex with no list will be assumed to be part of  a
       * polygon which is to be flat shaded and the polygon normal will be used
       * for the vertex normals of each vertex.
       */
      if (inSmooth) {
        if (vertArray.get(curIndex - 1).polys == null) {
          vertArray.get(curIndex - 1).polys = new PolyListCell();
          curVertPoly = vertArray.get(curIndex - 1).polys;
        } else {
          curVertPoly = vertArray.get(curIndex - 1).polys;
          while (curVertPoly.next != null) curVertPoly = curVertPoly.next;
          curVertPoly.next = new PolyListCell();
          curVertPoly = curVertPoly.next;
        }
        curVertPoly.poly = curPoly;
      } else vertArray.get(curIndex - 1).polys = null;

      curIndex = line.indexOf('/'); // Find the first '/' if it exists
      if (curIndex > -1) {
        if (line.charAt(curIndex + 1) != '/') {
          curIndex = Integer.parseInt(vertTokens.nextToken()); // Grab the texture vertex index
          curVert.tex = curIndex - 1;
        } // end check for second /
        //////////////////////////////////////////////////////////////////////////////////////////////

      } // end check for ANY /'s
    } // end while
  } // end method addPolyToSurf
  private void readSurfaces(String filepath) {
    // System.out.println("\nBuilding surfaces...");
    // This method is the last to be called out of all of the methods in this class.  Once all
    // of the vertex information has been read in, as well as the materials, they are arranged
    // into the surfaces and polygons that form that object itself...See the included file
    // CustomOBJSpecs.txt for more details

    int curMat = 0; // this is a reference to the current material to be used
    // int curIndex = 0;			//This is a temporary variable for reading in vertices of polygons
    SurfCell curSurf; // the current surface being worked with
    // PolyCell curPoly;			//the current polygon in the surface
    String matName; // This is the material name from the obj file.  It will be used to find the
    // material in the list
    String line; // the current line being analyzed
    StringTokenizer token; // For tokenizing the string to grab the different components of the line
    // StringTokenizer vertTokens;	//For tokenizing the vertex entries in a polygon (surface) line
    FileReader fr; // Just a file reading stream for the file
    BufferedReader objFile; // this wraps around the file reader so we can do things like readLine()
    boolean inSmooth = false; // a flag for whether or not a given face is in a surface

    try {
      fr = new FileReader(filepath);
      objFile = new BufferedReader(fr);
      line = objFile.readLine();
      curSurf = surfHead;
      while (line != null) {
        if (line.length() > 0) {
          switch (line.charAt(0)) {
            case '#':
            case '!':
            case '$':
            case '\n':
            case 'v': // These are all comments or vertex info...skip them :-)
              break;
            case 'u':
              token = new StringTokenizer(line);
              line = token.nextToken(); // "eat" up the usemtl keyword
              matName = token.nextToken(); // actually grab the material name
              boolean found = false;
              int i = 0;
              while (!found && i < numMats)
              // for(int i = 0; i < numMats; i++)
              {
                // simply compare the stored material name to the name retrieved from the OBJ file.
                // if they match, set curMat to whatever index that material is at
                if (materials[i].materialName.toUpperCase().compareTo(matName.toUpperCase()) == 0) {
                  curMat = i; // Set curMat to the current material index
                  found = true;
                }
                i++;
              }
              if (!found) {
                System.out.printf(
                    "Group %s material %s not found - using default\n", curSurf.name, matName);
                curSurf.material = 0;
              } else curSurf.material = curMat;
              break;
            case 's':
              token = new StringTokenizer(line);
              line = token.nextToken(); // "eat" up the s at the beginning of the line
              // If there are more tokens on the line (specifically, the word "off")
              // then we will read them in.  The only one that we really care about is if it's
              // an "off" but it must be read if it's there.
              if (token.hasMoreTokens()) line = token.nextToken();
              // if smooth groups are turned off and no new smooth group is specified...
              if (line.toUpperCase().compareTo("OFF") == 0) {
                inSmooth = false;
              } else // We are simply starting a new smooth group
              {
                inSmooth = true;
                // curSurf.smooth = inSmooth;
              } // end else
              break;
            case 'f':
              if (curSurf != null) {
                addPolyToSurf(curSurf, line, inSmooth);
              } else // no active surface - create a "default" surface and add this poly to it
              {
                System.out.printf("ReadSurfaces: No active surface available\n");
                System.out.printf("Creating a default surface\n");
                if (inSmooth) surfHead = new SurfCell("default");
                else surfHead = new SurfCell("default");
                curSurf = surfHead;
                addPolyToSurf(curSurf, line, inSmooth);
              }
              curSurf.numPoly++; // Surface level count of the polygons
              numPolys++; // PMesh level count of the polygons
              break;

            case 'g': // Starts a new surface - if inSmooth is true set the smooth flag in
              // that surface
              token = new StringTokenizer(line);
              line = token.nextToken(); // "eat" up the g at the beginning of the line
              // If there are more tokens on the line (specifically a group name)
              // then we will read it in.
              if (token.hasMoreTokens()) line = token.nextToken();
              if (line == null) line = new String("Group" + numSurf);
              if (surfHead == null) // Create first surface
              {
                surfHead = new SurfCell(line);
                curSurf = surfHead;
              } else // Advance to next surface
              {
                curSurf.next = new SurfCell(line);
                curSurf = curSurf.next;
              }
              // Assign beginning variables
              numSurf++; // PMesh level count of surfaces							break;
            default:
              break;
          } // end switch
        } // end if(line.length() > 0)
        line = objFile.readLine(); // grab the next line for reading
      } // end while(line != null)
      objFile.close();
      fr.close();
    } // end try
    catch (IOException exception) {
      System.out.println("Error while reading surface data from: " + filepath);
    } // end catch
    System.out.printf("\n %d vertices  %d surfaces\n", numVerts, numSurf);
    // System.out.println("\n" + numSurf + " surfaces found");
  } // end method readSurfaces