/*
 *
 * Name
 *	offconv
 *
 * Description
 *	Convert off (Object File Format) files from ASCII to binary
 *	and vice versa.
 *	Optionally, ``pack'' the geometry to improve file size and
 *	rendering speed.
 *      Optionally adds per polygon normals.
 *	Optionally decrements vertex numbers so they begin at 0.
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 *	Packing option added by Allen Akin, also of WSE
 *
 * Diagnostics
 *	Returns -1 if it blows up for any reason
 *	
 * History
 *	Randi J. Rost	14-Nov-1986	created
 *	Allen Akin	19-Feb-1987	packing option added
 *      James Painter   10-Aug-1988     Per polygon normals added
 *	Randi J. Rost	 8-Aug-1989	vertex number adjustment option added
 *
 */

#include <stdio.h>
#include <math.h>
#include "off.h"

extern double sqrt();
static char *ProgramName;
static int toascii = 1, addnormals = 0, pack = 0, adjust = 0;
char *infilename = NULL, *outfilename;

Usage()
{
  fprintf(stderr, "usage: %s [-a][-b][p-][-n] inobj outobj\n", ProgramName );
}


ParseCommandLine(argc, argv)
     int argc;
     char *argv[];
{
  int ArgsParsed = 0;
  char *sw;

  ProgramName = argv[ArgsParsed++];

  while ( ArgsParsed < argc ) {
    if (argv[ArgsParsed][0] == '-')
      {
	sw = argv[ArgsParsed++] + 1;
	for( ; *sw != '\0'; sw++) {
	  switch (*sw)
	    {
	    case 'a':  toascii = 1;	break;
	    case 'b':  toascii = 0;	break;
	    case 'n':  addnormals = 1;  break;
	    case 'p':  pack = 1;	break;
	    case '1':  adjust = 1;	break;  /* undocumented option */
	    default:
	      fprintf(stderr, "Unknown option '%s'\n\n", *sw );
	      Usage();
	      exit(-1);
	    }
	}
      } else {
	if (infilename != NULL) {
	  if (outfilename != NULL) {
	    Usage(); exit(-1);
	  }
	  outfilename = argv[ArgsParsed++];
	}  else
	  infilename = argv[ArgsParsed++];
      }
  }
  if (infilename == NULL || outfilename == NULL) {
    Usage(); exit(-1);
  }
}


main(argc, argv)
    int		argc;
    char	*argv[];

    {
    OFFObjDesc	obj;
    char	inbase[80], outbase[80];
    char	indir[80], outdir[80];
    char	inext[80], outext[80];
    char	newname[80];
    int		i;
    int		type;
    char	dir[80], base[80], ext[80];
    OFFProperty	*pProp;


    ParseCommandLine( argc, argv );

    if (-1 == OFFReadObj(&obj, infilename ) )
      exit(-1);

    basename(infilename, indir, inbase, inext);
    basename(outfilename, outdir, outbase, outext);
    if (addnormals)
      AddNormals( outbase, &obj );
    if (pack)
	OFFPackObj(&obj);
    if (adjust)
	AdjustVertexNums(&obj);

    pProp = obj.FirstProp;
    while (pProp != NULL)
	{
	if (pProp->PropFileName != NULL)
	    {
	    basename(pProp->PropFileName, dir, base, ext);
	    if (toascii)
		{
		strcpy(newname, outbase);
		strcat(newname, ".");

	    /*  The following kludge will not work if there are valid  */
	    /*  ASCII file extensions that begin with 'b'.  Right now  */
	    /*  we assume that if the extension begins with 'b', the   */
	    /*  file is in binary.                                     */

		if (ext[0] == 'b')
		    strcat(newname, &(ext[1]));
		else
		    strcat(newname, ext);
		}
	    else
		{
		strcpy(newname, outbase);
		strcat(newname, ".b");
		strcat(newname, ext);
		}
	    strcpy(pProp->PropFileName, newname);
	    }
	pProp = pProp->NextProp;
	}

    
    if (toascii)
	type = OFF_ASCII; 
    else
	type = OFF_BINARY; 
    OFFWriteObj(&obj, outfilename, outdir, type);
    }


basename(fullname, dir, base, ext)
    char	*fullname;
    char	*dir;
    char	*base;
    char	*ext;

    {
    int		i, j;

    i = strlen(fullname);
    while ((i >= 0) && (fullname[i] != '.')) i--;
    if (i < 0)
	{ i = 0; ext[0] = '\0'; }
    else
	strcpy(ext, &(fullname[i + 1]));
       
    j = i;
    while ((j >= 0) && (fullname[j] != '/')) j--;
    if (j < 0)
	{ dir[0] = '\0'; }
    else
	{
	strncpy(dir, fullname, j);
	dir[j] = '\0';
	}

    if ((j <= 0) && (i == 0))
	strcpy(base, fullname);
    else
	{
	strncpy(base, &(fullname[j + 1]), i - j - 1);
	base[i - j - 1] = '\0';
	}
    }


/* Some types needed for the normal generation.  We should probably
**  stick these in a header file.
*/
typedef float float32_t;
typedef short int16_t;
typedef long  int32_t;
typedef struct { float32_t x, y, z } Point;
typedef struct { float32_t dx, dy, dz } Normal;
typedef struct { int32_t count; Normal n[1] } NormalProperty;


AddNormals( basename, obj )
char *basename;
OFFObjDesc *obj;
{
  OFFProperty *gprop = NULL, *pnprop = NULL, *op;
  int nv, np, ne;
  NormalProperty *normals;
  Normal *normal;
  Point *vertices;
  int16_t *edges;
  int16_t *nedges;
  int clockwise = 1;
  int i, start, end, j, vi, vj, p, n;
  char *ptr;
  double len;

  /*  Find the "geometery" property and the vertex_order property*/
  for( op = obj->FirstProp; op; op = op->NextProp) {
    if (strcmp( op->PropName, "geometry") == 0)
      gprop = op;
    else if (strcmp( op->PropName, "vertex_order") == 0) {
      if (strcmp( *((char **) op->PropData), "clockwise")) 
	clockwise = 1;
      else if (strcmp( *((char **) op->PropData), "counter-clockwise"))
	clockwise = 0;
      else {
	fprintf( stderr, "Invalid vertex order: %s\n", *((char **) op->PropData));
	exit(-1);
      }
    }
  }

  /* Remove any existing polygon normals property */
  OFFRemoveProperty( obj, "polygon_normals" );

  /* Get the geometry data */
  ptr = gprop->PropData;
  
  /* extract the counts */
  nv = *( (int32_t *) ptr); ptr += sizeof(int32_t);
  np = *( (int32_t *) ptr); ptr += sizeof(int32_t);
  ne = *( (int32_t *) ptr); ptr += sizeof(int32_t);


  /* Get the vertex data */
  vertices = (Point *) ptr;
  ptr += 3*nv*sizeof(float32_t);

  /* Get the edge-per-polygon counts */
  nedges = (int16_t *) ptr;
  ptr += np * sizeof(int16_t);

  /* Get the edge data */
  edges = (int16_t *) ptr;
  ptr += ne *sizeof(int16_t);

  /* Allocate memory for the normals */
  normals = (NormalProperty *) malloc( np * sizeof(Normal) + sizeof(int16_t) );
  if (normals == NULL) {
    perror( "Out of memory!\n" );
    exit(-1);
  }
  normals->count = np;

  /* Add a new polygon_normals property */
  pnprop = OFFAddProperty( obj );
  if (pnprop == NULL)  {
    perror( "Out of memory!\n" );
    exit(-1);
  }

  /* Set up the property */
  strcpy( pnprop->PropName, "polygon_normals" );
  pnprop->PropType = OFF_GENERIC_DATA;
  strcpy( pnprop->PropFileName, basename );
  strcat( pnprop->PropFileName, ".pnorm" );
  pnprop->PropCount = np;
  strcpy( pnprop->DataFormat, "fff" );
  pnprop->PropData = (char *) normals;

  /* Loop through all the polygons computing normals as we go */
  i=0;
  for(p=0; p<np; p++) {
    n = nedges[p];
    start = i;
    end = i + n;
    normal = normals->n +p;
    normal->dx = normal->dy = normal->dz = 0.0;
    for( j=i+1; i < end; i++, j++) {
      if (j == end) j = start;
      vi = edges[ i ] -1;
      vj = edges[ j ] -1;

      /* Martin Newell's method:  Newman & Sproull page 499
      ** More robust than the 3 point method.  
      */
      normal->dx += (vertices[vi].y - vertices[vj].y)*
	                  (vertices[vi].z + vertices[vj].z);
      normal->dy += (vertices[vi].z - vertices[vj].z)*
	                  (vertices[vi].x + vertices[vj].x);
      normal->dz += (vertices[vi].x - vertices[vj].x)*
	                  (vertices[vi].y + vertices[vj].y);
    }
    len = sqrt( normal->dx * normal->dx +
 	        normal->dy * normal->dy +
	        normal->dz * normal->dz );
    if (len > 0) {		/* Normalize length */
      normal->dx /= len;
      normal->dy /= len;
      normal->dz /= len;
    }
    if (!clockwise) {		/* Flip the normal */
      normal->dx = -normal->dx;
      normal->dy = -normal->dy;
      normal->dz = -normal->dz;
    }
  }
}


/* Undocumented option */

AdjustVertexNums(obj)
    OFFObjDesc *obj;
    {
    OFFProperty *gprop = NULL, *op;
    int		nv, np, ne;
    int		i;
    char	*ptr;
    int16_t	*edges;

/*  Find the "geometery" property and the vertex_order property  */
    for (op = obj->FirstProp; op; op = op->NextProp)
	{
	if (strcmp(op->PropName, "geometry") == 0)
	    gprop = op;
	}

/*  Get the geometry data */
    ptr = gprop->PropData;
  
/*  extract the counts */
    nv = *((int32_t *) ptr); ptr += sizeof(int32_t);
    np = *((int32_t *) ptr); ptr += sizeof(int32_t);
    ne = *((int32_t *) ptr); ptr += sizeof(int32_t);

/*  Get the edge data */
    ptr += 3*nv*sizeof(float32_t);
    ptr += np * sizeof(int16_t);
    edges = (int16_t *) ptr;

/*  Decrement each vertex number by 1  */
    for (i = 0; i < ne; i++)
	{
	(*edges)--;
	edges++;
	}
    }
