/***************************************************************************
 *   Copyright (C) 2007 by A.J. Tavakoli                                   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <cstdio>
#include "common.h"
#include "TriangleMesh.h"


TriangleMesh::TriangleMesh():
     numTris(0),
     numVerts(0),
     indices(NULL),
     indexedVerts(NULL),
     verts(NULL) {
}


TriangleMesh::~TriangleMesh() {
  clear();
}


void TriangleMesh::clear() {
 if ( indexedVerts != NULL ) {
    delete [] indexedVerts;
    indexedVerts = NULL;
  }

  if ( indices != NULL ) {
    delete [] indices;
    indices = NULL;
  }

  if ( verts != NULL ) {
    delete [] verts;
    verts = NULL;
  }

  numTris  = 0;
  numVerts = 0;
}


bool TriangleMesh::load(const char *filename, bool buildUnindexed) {
  // clear any data currently in triangle mesh
  clear();

  if ( NULL == filename )
    return false;

  FILE *fin = fopen(filename, "r");

  if ( NULL == fin )
    return false;

  // read in number of triangles
  int n = 0;
  if ( fscanf(fin, "TRIANGLE_MESH NUM_TRIANGLES %d\n", &n) != 1 ) {
    fclose(fin);
    return false;
  }

  if ( n < 0 ) {
    fclose(fin);
    return false;
  }

  // store number of triangles
  numTris = n;

  // attempt to allocate storage for triangles
  indices = new unsigned int[n*3];

  if ( NULL == indices ) {
    fclose(fin);
    return false;
  }

  // zero out triangles
  for ( int i=0; i < n*3; i++ )
    indices[i] = 0;

  // read in number of vertices
  n = 0;
  if ( fscanf(fin, "NUM_VERTICES %d\n", &n) != 1 ) {
    clear();
    fclose(fin);
    return false;
  }

  if ( n < 0 ) {
    clear();
    fclose(fin);
    return false;
  }

  numVerts = n;

  // allocate storage for indexed vertices
  indexedVerts = new Video::Vertex[n];
    
  if ( NULL == indexedVerts ) {
    clear();
    fclose(fin);
    return false;
  }

  // zero out vertices
  for ( int i=0; i < numVerts; i++ ) {
    indexedVerts[i].pos.set(0.0f, 0.0f, 0.0f);
    indexedVerts[i].faceNormal.set(0.0f, 0.0f, 0.0f);
    indexedVerts[i].faceTangent.set(0.0f, 0.0f, 0.0f);
    indexedVerts[i].tc[0] = indexedVerts[i].tc[1] = 0.0f;
  }

  // read in HAS_TEXCOORDS value
  int hasTexCoords = 0;
  if ( fscanf(fin, "HAS_TEXCOORDS %d", &hasTexCoords) != 1 ) {
    clear();
    fclose(fin);
    return false;
  }

  if ( hasTexCoords != 0 && hasTexCoords != 1 ) {
    clear();
    fclose(fin);
    return false;
  }

  // read in triangles
  for ( int i=0; i < numTris*3; i+=3 ) {
    if ( fscanf(fin, "%u %u %u", &indices[i], &indices[i+1], &indices[i+2]) != 3 ) {
      clear();
      fclose(fin);
      return false;
    }
  }

  // read in vertices
  for ( int i=0; i < numVerts; i++ ) {
    Video::Vertex &v = indexedVerts[i];
    if ( fscanf(fin, "%f %f %f", &v.pos[0], &v.pos[1], &v.pos[2]) != 3 ) {
      clear();
      fclose(fin);
      return false;
    }
  }

  // read in texture coords
  for ( int i=0; i < numVerts; i++ ) {
    Video::Vertex &v = indexedVerts[i];
    if ( fscanf(fin, "%f %f", &v.tc[0], &v.tc[1]) != 2 ) {
      clear();
      fclose(fin);
      return false;
    }
  }

  fclose(fin);

  // if the buildUnindexed variable is set to true
  // then allocate storage for unindexed vertices
  // and copy vertex data into unindexed vertex list
  if ( true == buildUnindexed ) {
    // allocate storage for unindexed vertices
    verts = new Video::Vertex[numTris*3];

    if ( NULL == verts ) {
      clear();
      return false;
    }

    // copy vertices into unindexed vertex list
    n = numTris*3;
    for ( int i=0; i < n; i++ )
      verts[i] = indexedVerts[ indices[i] ];
  }

  buildNormals();

  return true;
}


// computes vertex normal and face normal and tangent
// of each triangle in mesh
void TriangleMesh::buildNormals() {
  ///// build vertex normals /////

  // make sure there is mesh data
  if ( NULL == indexedVerts || NULL == indices || numTris <= 0 )
    return;

  // allocate temporary storage
  int *contribCount = new int[numVerts];

  if ( NULL == contribCount )
    return;

  for ( int i=0; i < numVerts; i++ ) {
    contribCount[i] = 0;
    indexedVerts[i].faceNormal.set(0.0f, 0.0f, 0.0f);
  }

  for ( int i=0; i < numTris; i++ ) {
    int v0 = indices[i*3  ],
        v1 = indices[i*3+1],
        v2 = indices[i*3+2];

    // compute face normal of this triangle
    Vector3 e0 = indexedVerts[v1].pos - indexedVerts[v0].pos,
            e1 = indexedVerts[v2].pos - indexedVerts[v0].pos;

    Vector3 n = Vector3::cross(e0, e1);

    contribCount[v0]++;
    contribCount[v1]++;
    contribCount[v2]++;
    indexedVerts[v0].vertNormal += n;
    indexedVerts[v1].vertNormal += n;
    indexedVerts[v2].vertNormal += n;
  }

  // normalize each normal
  for ( int i=0; i < numVerts; i++ ) {
    if ( contribCount[i] > 0 ) {
      float fCount = float(contribCount[i]);
      indexedVerts[i].vertNormal /= fCount;
      indexedVerts[i].vertNormal.normalize();
    }
  }

  // deallocate temporary storage
  delete [] contribCount;

  ///// build face tangents and normals for unindexed vertices /////
  if ( verts != NULL ) {
    for ( int i=0; i < numTris; i++ ) {
      int v0 = i*3;
      Video::computeTangentAndNormal(verts[v0  ], verts[v0+1], verts[v0+2]);
    }
  }

  ///// build face tangent and normal for unindexed vertices /////
  for ( int i=0; i < numTris; i++ ) {
    int v0 = indices[i*3  ],
        v1 = indices[i*3+1],
        v2 = indices[i*3+2];
    Video::computeTangentAndNormal(indexedVerts[v0],
                                   indexedVerts[v1],
                                   indexedVerts[v2]);
  }
}
