// Copyright (C) Thorsten Thormaehlen, Marburg, 2014, All rights reserved
// Contact: www.thormae.de

// This software is written for educational (non-commercial) purpose. 
// There is no warranty or other guarantee of fitness for this software, 
// it is provided solely "as is". 

// This tool converts a Wavefront OBJ file into an array of float values, which is
// then stored as a binary file or a plain text file.
// Such an array-based representation of a 3D mesh is useful because the data
// can be directly placed (without parsing) in a Vertex Buffer Object (VBO).
// Vertex buffers are, for example, used for direct rendering by OpenGL 
// via the "glDrawArrays" function or by DirectX via the "DrawPrimitive" function.

// The first number in the plain text file (or first 4 bytes interpreted 
// as "unsigned int" in the binary file) informs about the total number of 
// floats to follow in the file. This is useful for allocating the correct amount of 
// memory while reading.

// The output format of the float array can be customized in the 
// parameter section of this file.

#include <string>
#include <iostream> 
#include <iomanip>
#include <fstream>
#include <sstream>
#include <vector>
#include <cmath>
using namespace std;

enum VertexDataTypes { 
  POS_3f,      // position x,y,z (3 floats)
  NORMAL_3f,   // normal (3 floats)
  TEXCOORD_2f, // texture coordinates (2 floats)
  DIFFUSE_3f,  // diffuse color (3 floats)
  SPECULAR_3f, // specular color (3 floats)
  AMBIENT_3f,  // ambient color (3 floats)
  SPEC_EXP_1f, // specular exponent (1 float)
  ALPHA_1f,    // alpha value (1 float)
  MAT_ID_1f,   // internal material id (1 float)
  FILL1_1f,    // 1.0 (1 float)
  FILL0_1f     // 0.0 (1 float)
};

//////////////////////////////////////////////////////////////////////
// Start parameter section (choose your customized output format here)
//////////////////////////////////////////////////////////////////////

int includedPerVertexData[] = {POS_3f, NORMAL_3f, TEXCOORD_2f, DIFFUSE_3f, SPECULAR_3f, AMBIENT_3f, SPEC_EXP_1f, ALPHA_1f, MAT_ID_1f};

// Here you can select which per-vertex data should be included as well as their order, e.g.,
// if the per-vertex data in the output VBO should look like this:
//
//  struct Vertex {
//    float position[3];
//    float texCoord[2];
//    float normal[3];
//  };
//
// you could select: 
// int includedPerVertexData[] = {POS_3f, TEXCOORD_2f, NORMAL_3f};
// 
// The FILL1 and FILL0 elements can be used to fill up with 0 or 1 entries, e.g.,
// if the per-vertex data in the output VBO should look like this:
//
//  struct Vertex {
//    float rgba[4];
//    float position[4];
//  };
//
// you could select:
// int includedPerVertexData[] = {DIFFUSE_3f, ALPHA_1f, POS_3f, FILL1_1f};
//
// One last example, which is a very typical setting:
//
//  struct Vertex {
//    float position[3];
//    float color[4];
//    float texCoord[2];
//    float normal[3];
//  };
//  
//  you could select:
//  int includedPerVertexData[] = {POS_3f, DIFFUSE_3f, ALPHA_1f, TEXCOORD_2f, NORMAL_3f};
//

bool writeBinary = false;
// false = write to plain text file, true = write to binary file
// A binary file will be faster to read but is less portable
// becaue the target machine might have a different byte order (endianness).

std::string separator("\n");
// if the float array is written in plain text format, you can choose a 
// separator here, such as " " or "," or ";" or "\t" or "\n"

bool normalize = true;
float boundingSize = 1.0f;
// false = position data is used as it is read from the obj file
// true =  position data is moved and scaled such that
//         the center of the mesh is at the origin and
//         the compete mesh is within the bounds 
//         [-boundingSize/2; boundingSize/2] in x, y, and z-direction

//////////////////////////////////////////////////////////////////////
// End parameter section
//////////////////////////////////////////////////////////////////////

#define OBJ2VBO_ERROR(msg) { cout << "ERROR: "  << msg << " (in Line: "<< __LINE__ << ")" << endl; }  
#define OBJ2VBO_WARN(msg) { cout << "WARNING: " << msg << " (in Line: "<< __LINE__ << ")" << endl; }  
#define OBJ2VBO_DEBUG(msg) {/*cout << "DEBUG: "   << msg << " (in Line: "<< __LINE__ << ")" << endl;*/} 
#define OBJ2VBO_INFO(msg) { cout << "INFO: "    << msg << " (in Line: "<< __LINE__ << ")" << endl;}

class Material
{
public :
  float diffuseColor[3];   // diffuse color in range [0.0, 1.0]
  float specularColor[3];  // specular color in range [0.0, 1.0]
  float ambientColor[3];   // ambient color in range [0.0, 1.0]
  float specularExponent;  // specular exponent in range [0.0, 1000.0]
  float alpha;             // alpa value: 0.0 = fully transparent, 1.0 = fully opaque 
  float illuMod;           // obj illumination model

  // constructor 
  Material() {
    diffuseColor[0] = 0.5f;
    diffuseColor[1] = 0.5f;
    diffuseColor[2] = 0.5f;
    specularColor[0] = 1.0f;
    specularColor[1] = 1.0f;
    specularColor[2] = 1.0f;
    ambientColor[0] = 0.1f;
    ambientColor[1] = 0.1f;
    ambientColor[2] = 0.1f;
    specularExponent = 10.0f;
    alpha = 1.0f;
    illuMod = 2.0f;
  }
};

// some local helper functions
bool fileExist(const std::string &filename) 
{
  OBJ2VBO_DEBUG ("Probing file: " << filename);
  ifstream myfile(filename.c_str());
  if (!myfile.is_open()) {    
    return false;
  }
  myfile.close();
  return true;
}

std::string findFile(const std::string &filename, const std::string &baseFile) 
{
  std::string delimiters ("\\/"); // backslash or slash
  string::size_type posFile = filename.find_last_of(delimiters);
  string::size_type posBase = baseFile.find_last_of(delimiters);
  std::string basePath ("");
  if(posBase != string::npos) {
    basePath = baseFile.substr(0, posBase+1);
  }

  if(posFile != string::npos) {
    string::size_type posCol = filename.find_last_of(':');
    if(posCol != string::npos) {
      // if the file name seems to be an absolute path, use the filename string directly
      if(fileExist(filename)) return filename;
    }
  }

  // assuming a relative path
  if(fileExist(basePath + filename)) return basePath + filename;

  // trying to find the mat file in the same directory as the obj file
  std::string inDirName = filename;
  if(posFile != string::npos) {
    inDirName = filename.substr(posFile+1, filename.size()-posFile);
  }
  if(fileExist(basePath + inDirName)) return basePath + inDirName;

  return filename;
}

void normalizePositions(std::vector <float>&pos, float targetBound) 
{
  if(pos.size() < 3) return;

  float min[3], max[3], center[3];

  min[0] = min[1] = min[2] = 1e16f;
  max[0] = max[1] = max[2] = -1e16f;

  // get min, max
  for(unsigned i=0; i < pos.size(); i+=3) {
    for(unsigned j=0; j < 3; j++) {
      float val = pos[i+j];
      if(val < min[j]) min[j] = val;
      if(val > max[j]) max[j] = val;
    }
  }

  // get center
  for(unsigned j=0; j < 3; j++) {
    center[j] = (max[j] + min[j]) / 2.0f;
  }

  // get largest axis
  float largestDiff = 0.0f;
  for(unsigned j=0; j < 3; j++) {
    float diff = fabs(max[j] - min[j]);
    if(diff > largestDiff) largestDiff = diff;
  }

  // normalize
  for(unsigned i=0; i < pos.size(); i+=3) {
    for(unsigned j=0; j < 3; j++) {
      pos[i+j] -= center[j];
      if(largestDiff > 1e-16) {
        pos[i+j] *=  targetBound / largestDiff;
      }
    }
  }
  OBJ2VBO_INFO("Mesh's center moved from (" << center[0] << ","  << center[1] << "," << center[2] << ") to (0,0,0)");
  if(largestDiff > 1e-16) {
    OBJ2VBO_INFO("Mesh's maximum bounding length scaled from " << largestDiff << " to " << targetBound);
  }
}

bool loadMaterialLib(const std::string &filename, std::vector <Material> &materials, std::vector <std::string> &matNames)
{

  if(matNames.size() != materials.size()) {
    matNames.resize(materials.size());
  }

  OBJ2VBO_INFO("Loading material lib: " << filename);
  std::string line;
  unsigned lineNumber = 0;
  int currentMaterial = -1;

  ifstream myfile(filename.c_str());
  if (!myfile.is_open()) {
    OBJ2VBO_ERROR ("Can not open file: " << filename);
    return false;
  }

  while ( myfile.good() )
  {
    std::getline (myfile,line);
    ++lineNumber;
    OBJ2VBO_DEBUG (line);

    std::istringstream ss(line);
    ss.unsetf(std::ios_base::skipws);

    // skip whitespace at beginning of line
    ss >> std::ws;
    if(!ss.eof()) {
      char firstChar;
      ss >> firstChar;
      switch(firstChar) {
      case '#' : 
        {
          OBJ2VBO_DEBUG ("Found comment:");
        }
        break;
      case 'n' : 
        {
          // newmtl
          std::string newmtl;
          std::string materialName;
          ss >> newmtl >> std::ws >> materialName;
          if(newmtl.compare("ewmtl") == 0) {
            OBJ2VBO_DEBUG ("newmtl");
            Material mat;
            matNames.push_back(materialName);
            materials.push_back(mat);
            currentMaterial = materials.size() - 1;
          }
        }
        break;
      case 'm' : 
        {
          // map
          std::string map;
          std::string mapFileName;
          ss >> map >> std::ws >> mapFileName;
          OBJ2VBO_DEBUG ("Map is not used: " << mapFileName);
        }
        break;
      case 'i' : 
        {
          // illumination model
          std::string illum;
          int illumNo;
          ss >> illum >> std::ws >> illumNo;
          if (!ss.fail()) {
              if(currentMaterial >=0 && currentMaterial < int(materials.size())) {
                OBJ2VBO_DEBUG ("illumination model");
                materials[currentMaterial].illuMod = float(illumNo);
              }
            }
        } // end 'i'
        break;
      case 'N' :
        {
          // Ns (specular coefficient)
          char nextChar, ws;
          ss >> nextChar;
          if(nextChar == 's') {
            float specular;
            ss >> ws >> std::ws >> specular;
            if (!ss.fail() && isspace(ws)) {
              if(currentMaterial >=0 && currentMaterial < int(materials.size())) {
                OBJ2VBO_DEBUG ("specular coefficient");
                materials[currentMaterial].specularExponent = specular;
              }
            }
          }
        }// end 'N' 
      break;
      case 'T' :
        {
          // Tr (transparency)
          char nextChar, ws;
          ss >> nextChar;
          if(nextChar == 'r') {
            float tr;
            ss >> ws >> std::ws >> tr;
            if (!ss.fail() && isspace(ws)) {
              if(currentMaterial >=0 && currentMaterial < int(materials.size())) {
                OBJ2VBO_DEBUG ("transparency");
                materials[currentMaterial].alpha = 1-tr;
              }
            }
          }
        }// end 'T' 
      break;
      case 'd' :
        {
          // d (dissolved = transparency)
          float alpha;
          char ws;
          ss >> ws >> std::ws >> alpha;
          if (ss && isspace(ws)) {
            if(currentMaterial >=0 && currentMaterial < int(materials.size())) {
              OBJ2VBO_DEBUG ("dissolved");
              materials[currentMaterial].alpha = alpha;
            }
          }
        }// end 'd' 
      break;
      case 'K': // Ka, Kd, or Ks
        {
          char nextChar, ws_r, ws_g, ws_b;
          ss >> nextChar;
          switch(nextChar) 
          {
          case 'a': 
            {
              // ambient color
              OBJ2VBO_DEBUG ("Ambient color");
              float rgb[3];
              ss >> ws_r >> std::ws >> rgb[0] >> ws_g >> std::ws >> rgb[1] >> ws_b >> std::ws >> rgb[2];
              if (!ss.fail() && isspace(ws_r) &&  isspace(ws_g) && isspace(ws_b)) {
                if(currentMaterial >=0 && currentMaterial < int(materials.size())) {
                   materials[currentMaterial].ambientColor[0] = rgb[0];
                   materials[currentMaterial].ambientColor[1] = rgb[1];
                   materials[currentMaterial].ambientColor[2] = rgb[2];
                }
              }else{
                OBJ2VBO_ERROR ("Parsing error");
              }
            }
            break;
          case 'd' :                       
            {
              // diffuse color
              OBJ2VBO_DEBUG ("Diffuse color");
              float rgb[3];
              ss >> ws_r >> std::ws >> rgb[0] >> ws_g >> std::ws >> rgb[1] >> ws_b >> std::ws >> rgb[2];
              if (!ss.fail() && isspace(ws_r) &&  isspace(ws_g) && isspace(ws_b)) {
                if(currentMaterial >=0 && currentMaterial < int(materials.size())) {
                   materials[currentMaterial].diffuseColor[0] = rgb[0];
                   materials[currentMaterial].diffuseColor[1] = rgb[1];
                   materials[currentMaterial].diffuseColor[2] = rgb[2];
                }
              }else{
                OBJ2VBO_ERROR ("Parsing error");
              }
            }
            break;
          case 's' :                       
            {
              // diffuse color
              OBJ2VBO_DEBUG ("Specular color");
              float rgb[3];
              ss >> ws_r >> std::ws >> rgb[0] >> ws_g >> std::ws >> rgb[1] >> ws_b >> std::ws >> rgb[2];
              if (!ss.fail() && isspace(ws_r) &&  isspace(ws_g) && isspace(ws_b)) {
                if(currentMaterial >=0 && currentMaterial < int(materials.size())) {
                   materials[currentMaterial].specularColor[0] = rgb[0];
                   materials[currentMaterial].specularColor[1] = rgb[1];
                   materials[currentMaterial].specularColor[2] = rgb[2];
                }
              }else{
                OBJ2VBO_ERROR ("Parsing error");
              }
            }
            break;
          default:
            OBJ2VBO_DEBUG ("Not supported");
            break;
          }
        } // end 'K'
        break;
      }
    }
  }
  myfile.close();

  return true;
}

int main(int argc, char *argv[])
{

  if(argc < 2) {
    cout << "Usage: ObjtoVbo input.obj [output.vbo]" << endl;	
    return 1;
  }

  std::string filename(argv[1]);
  std::string output("default.vbo");

  if(argc >= 3) {
    output = std::string(argv[2]);
  }

  OBJ2VBO_INFO("Loading OBJ file: " << filename);

  std::vector<float> positionData;
  std::vector<float> texCoordData;
  std::vector<float> normalData;

  // The loader only supports triangles. Quads are 
  // converted into two triangles.
  std::vector<int> triangles; 

  std::vector <Material> materials;
  std::vector<int> materialIDs;
  std::vector <std::string> matNames;

  Material defaultMat;
  materials.push_back(defaultMat);
  matNames.push_back("ObjToVboDefaultMat");
  int currentMaterial = 0;

  std::string line;
  unsigned lineNumber = 0;

  ifstream myfile(filename.c_str());
  if (!myfile.is_open()) {
    OBJ2VBO_ERROR ("Can not open file: " << filename);
    return 1;
  }
  while ( myfile.good() )
  {
    std::getline (myfile,line);
    ++lineNumber;
    OBJ2VBO_DEBUG (line);

    std::istringstream ss(line);
    ss.unsetf(std::ios_base::skipws);

    // skip whitespace at beginning of line
    ss >> std::ws;
    if(!ss.eof()) {
      char firstChar;
      ss >> firstChar;
      switch(firstChar) {
      case '#' : 
        {
          OBJ2VBO_DEBUG ("Found comment");
        }
        break;
      case 'v' : 
        {
          // vertex
          char nextChar, ws_tx, ws_xy, ws_yz;
          ss >> nextChar;
          if(isspace(nextChar)) {
            // Polygonal Vertex
            OBJ2VBO_DEBUG ("Polygonal Vertex");
            float xyz[3];
            ss >> std::ws >> xyz[0] >> ws_xy >> std::ws >> xyz[1] >> ws_yz >> std::ws >> xyz[2];
            bool test = isspace(ws_xy);
            if (!ss.fail() && isspace(ws_xy) && isspace(ws_yz)) {
              positionData.push_back(xyz[0]);
              positionData.push_back(xyz[1]);
              positionData.push_back(xyz[2]);
            }else{
              OBJ2VBO_ERROR ("Parsing error");
            }
          }else{
            switch(nextChar) 
            {
            case 't' : 
              {
                // Texture Vertex
                OBJ2VBO_DEBUG ("Texture Vertex");
                float xyz[3];
                ss >> ws_tx >> std::ws >> xyz[0] >> ws_xy >> std::ws >> xyz[1];
                if (!ss.fail() && isspace(ws_tx) &&  isspace(ws_xy)) {
                  texCoordData.push_back(xyz[0]);
                  texCoordData.push_back(xyz[1]);
                  texCoordData.push_back(0.0);
                }else{
                  OBJ2VBO_ERROR ("Parsing error");
                }
              }
              break;
            case 'n' :                       
              {
                //Normal
                OBJ2VBO_DEBUG ("Normal");
                float xyz[3];
                ss >> ws_tx >> std::ws >> xyz[0] >> ws_xy >> std::ws >> xyz[1] >> ws_yz >> std::ws >> xyz[2];
                if (ss && isspace(ws_tx) &&  isspace(ws_xy) && isspace(ws_yz)) {
                  normalData.push_back(xyz[0]);
                  normalData.push_back(xyz[1]);
                  normalData.push_back(xyz[2]);
                }else{
                  OBJ2VBO_ERROR ("Parsing error");
                }
              }
              break;
            default:
              OBJ2VBO_DEBUG ("Not supported");
              break;
            }
          }
        } // end 'v'
        break;
      case 'g' : 
        {
          // group
          OBJ2VBO_DEBUG ("Group");
        }
        break;
      case 'f' : 
        {
          // face
          OBJ2VBO_DEBUG ("Face");
          char slash, ws;
          int v[4][3];
          for(unsigned k=0; k < 4; k++) {
            v[k][0] = -1;
            v[k][1] = -1;
            v[k][2] = -1;
            ss >> ws >> std::ws >> v[k][0];
            if(!ss.fail() && isspace(ws)) {
              bool firstSlashFound = false;
              if(ss.peek() == '/') {
                ss >> slash;
                if(!ss.fail()) firstSlashFound = true;
              }
              if(firstSlashFound) {
                if(ss.peek() == '/') {
                  // format is f v//vn
                  ss >> slash;
                  ss >> v[k][2];
                } else {
                  // format is f v/vt/vn or v/vt
                  ss >> v[k][1];
                  if(!ss.fail()) {
                    if(ss.peek() == '/') {
                      //format is f v/vt/vn 
                      ss >> slash >> v[k][2];
                    }
                  }
                }
              }
            }
          } // end for
          if(v[0][0] != -1 && v[1][0] != -1 && v[2][0] != -1) {
            if(v[3][0] == -1) { 
              // triangle
              for(unsigned k=0; k < 3; k++) {
                for(unsigned j=0; j < 3; j++) {
                  triangles.push_back(v[k][j]);
                }
              }
              materialIDs.push_back(currentMaterial);
            } else {
              // quad
              // The loader only supports triangles. Quads are 
              // converted into two triangles.
              for(unsigned k=0; k < 3; k++) {
                for(unsigned j=0; j < 3; j++) {
                  triangles.push_back(v[k][j]);
                }
              }
              materialIDs.push_back(currentMaterial);
              for(unsigned k=0; k < 3; k++) {
                for(unsigned j=0; j < 3; j++) {
                  triangles.push_back(v[(k+2)%4][j]);
                }
              }
              materialIDs.push_back(currentMaterial);
            }
          }
        } // end 'f'
        break;
      case 'm' : 
        {

          // material library file ("mtllib")
          OBJ2VBO_DEBUG ("mtllib");
          std::string mtllib;
          std::string mtlfile;
          ss >> mtllib >> std::ws >> mtlfile;
          if(mtllib.compare("tllib") == 0) {
            std::string newFileName = findFile(mtlfile, filename);
            loadMaterialLib(newFileName, materials, matNames);
          }
        }
        break; 
      case 'u' : 
        {
          // usemtl
          std::string usemtl;
          std::string mtlName;
          ss >> usemtl >> std::ws >> mtlName;
          if(usemtl.compare("semtl") == 0) {
            OBJ2VBO_DEBUG ("usemtl");
            bool foundMat = false;
            if(matNames.size() != materials.size()) {
              OBJ2VBO_ERROR ("matNames has wrong size");
            } else {
              for(int m = matNames.size()-1; m >=0 && !foundMat; m--) {
                if(mtlName.compare(matNames[m]) == 0) {
                  currentMaterial = m;
                  foundMat = true;
                }
              }
            }
            if(!foundMat) {
              OBJ2VBO_WARN ("could not find material \"" << mtlName << "\" using default material instead" );
              currentMaterial = 0;
            }
          }
        }
        break; 
      }
    }
  }
  myfile.close();

  if(triangles.size() % 9 != 0) {
    OBJ2VBO_ERROR ("Parsing error");
    return 1;
  }

  unsigned noTriangles = triangles.size() / 9;
  if(noTriangles != materialIDs.size()) {
    OBJ2VBO_ERROR ("materialIDs has wrong size");
  };
  OBJ2VBO_INFO("Number of triangles = " << noTriangles);
  OBJ2VBO_INFO("Number of vertices = " << positionData.size() / 3);
  OBJ2VBO_INFO("Number of normals = " << normalData.size() / 3);
  OBJ2VBO_INFO("Number of texture coordinates = " << texCoordData.size() / 3);
  OBJ2VBO_INFO("Number of materials = " << materials.size()-1);
   
  if(normalize) {
    normalizePositions(positionData, boundingSize);
  }

  std::vector <float> vbo;
  int noElement = sizeof(includedPerVertexData)/sizeof(int);

  for(unsigned t=0; t < noTriangles; t++) {
    for(unsigned k=0; k < 3; k++) {
      int mId = materialIDs[t];
      Material &m = materials[mId];
      for(int i=0; i < noElement; i++) {
        switch(includedPerVertexData[i]) {
        case POS_3f :  // position x,y,z (3 floats)
          {
            int v = triangles[t*9 + k*3] - 1; // -1 because they start with 1 not 0 in the file
            if(v >=0 && (v*3) < (int)positionData.size()) {
              vbo.push_back(positionData[v*3 + 0]);
              vbo.push_back(positionData[v*3 + 1]);
              vbo.push_back(positionData[v*3 + 2]);
            }else{
              if(v+1 != -1) {
                OBJ2VBO_ERROR ("Parsing error");
              }
              vbo.push_back(0.0f);
              vbo.push_back(0.0f);
              vbo.push_back(0.0f);
            }
          }
          break;
        case NORMAL_3f :  // normal (3 floats)
          {
            int n = triangles[t*9 + k*3 + 2] - 1; // -1 because they start with 1 not 0 in the file
            if(n >=0 && (n*3) < (int)normalData.size()) {
              vbo.push_back(normalData[n*3 + 0]);
              vbo.push_back(normalData[n*3 + 1]);
              vbo.push_back(normalData[n*3 + 2]);
            }else{
              if(n+1 != -1) {
                OBJ2VBO_ERROR ("Parsing error");
              }
              vbo.push_back(0.0f);
              vbo.push_back(0.0f);
              vbo.push_back(0.0f);
            }
          }
          break;
        case TEXCOORD_2f : // texture coordinates (2 floats)
          {
            int vt = triangles[t*9 + k*3 + 1] - 1; // -1 because they start with 1 not 0 in the file;
            if(vt >=0 && (vt*3) < (int)texCoordData.size()) {
              vbo.push_back(texCoordData[vt*3 + 0]);
              vbo.push_back(texCoordData[vt*3 + 1]);
            }else{
              if(vt+1 != -1) {
                OBJ2VBO_ERROR ("Parsing error");
              }
              vbo.push_back(0.0f);
              vbo.push_back(0.0f);
            }
          }
          break;
        case DIFFUSE_3f :  // diffuse color (3 floats)
          vbo.push_back(m.diffuseColor[0]);
          vbo.push_back(m.diffuseColor[1]);
          vbo.push_back(m.diffuseColor[2]);
          break;
        case SPECULAR_3f :  // specular color (3 floats)
          vbo.push_back(m.specularColor[0]);
          vbo.push_back(m.specularColor[1]);
          vbo.push_back(m.specularColor[2]);
          break;
        case AMBIENT_3f :  // ambient color (3 floats)
          vbo.push_back(m.ambientColor[0]);
          vbo.push_back(m.ambientColor[1]);
          vbo.push_back(m.ambientColor[2]);
          break;
        case SPEC_EXP_1f : // specular exponent (1 float)
          vbo.push_back(m.specularExponent);
          break;
        case  ALPHA_1f :  // alpha value (1 float)
          vbo.push_back(m.alpha);
          break;
        case MAT_ID_1f :  // internal material id (1 float)
          vbo.push_back(float(mId));
          break;
        case FILL1_1f :   // 1.0 (1 float)
          vbo.push_back(1.0f);
          break;
        case FILL0_1f :   // 0.0 (1 float)
          vbo.push_back(0.0f);
          break;
        default:
          {
            OBJ2VBO_ERROR ("Unknown per-vertex data type");
          }
          break;
        }
      }
    }
  }

  if(writeBinary) {
    ofstream outfile(output.c_str(), ofstream::out | ofstream::binary);
    if (!outfile.is_open()) {
        OBJ2VBO_ERROR ("could not open output file \"" << output << "\"");
        return 1;
    }
    unsigned int numFloats = vbo.size();
    int byteCount =  numFloats * sizeof(float);

    outfile.write((char*)(&numFloats), sizeof(unsigned int));
    outfile.write( (char*)(&vbo[0]), byteCount);
    OBJ2VBO_INFO (numFloats << " float values written to \"" << output << "\" in binary format (4 + " <<  byteCount << "  bytes)." );
    outfile.close();
  } else {
    ofstream outfile(output.c_str());
    if (!outfile.is_open()) {
      OBJ2VBO_ERROR ("could not open output file \"" << output << "\"");
      return 1;
    }
    unsigned int numFloats = vbo.size();
    outfile << numFloats;
    for(unsigned i=0; i < vbo.size(); i++) {
      outfile << separator;
      outfile << vbo[i];
    }
    OBJ2VBO_INFO (numFloats << " float values written to \"" << output << "\" as plain text." );
    outfile.close();
  }

  return 0;
}