/*
  Convert DXF files from StrataStudio into Radiance files
  Written by Paul Bourke

  Comments
  16 Nov 93 Started, modified ArchiCad2rad.c
  17 Nov 93 Added light, materials, and geometry options
  08 Apr 03 Modified to handle different line terminination on OS-X
*/
#include "stdio.h"
#include "stdlib.h"
#include "math.h"

/* Definitions */
#define EPSILON 0.0001
#define TRUE  1
#define FALSE 0
#define ABS(x) ((x) < 0 ? -(x) : (x))
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define MAX(x,y) ((x) < (y) ? (y) : (x))
#define POLYMAX 4

/* Coordinate structure */
typedef struct {
  double x,y,z;
} XYZ;

/* Material handling stuff */
#define MATMAX 200       /* Maximum number of allable materials       */
int nmat = 0;            /* Current number of materials               */
typedef struct {
  char name[64];         /* As from StrataStudio file                 */
  double r,g,b;          /* As defined from StrataStudio              */
  char type[32];         /* Plastic unless infered from material name */
  int specular;          /* Specularity index +- 1                    */
  int roughness;         /* Roughness index +- 1                      */
} MAT;
MAT material[MATMAX];    /* List of materials                         */

/* Special materials types */
#define nmattypes 11
char mattypes[nmattypes][32];

/* List of names for metals */
#define nmetals 13
char metals[nmetals][32];

/* Special colours */
#define ncolours 19
typedef struct {
    char name[32];
    double r,g,b;
} COLOUR;
COLOUR colours[ncolours];

/* Specularity modifiers */
#define NORMAL  0
#define MATTE  -1
#define DULL   -1
#define FLAT   -1
#define SHINY   1
#define GLOSSY  1

/* Roughness modifiers */
#define ROUGH   1
#define SMOOTH -1

/* Scale factor for RGB to light values */
#define LSCALE 100

/* Prototypes */
int   ConvertMaterial(FILE *);
void  PrintColour(double,double,double);
int   ConvertModel(FILE *);
void  ReadToString(FILE *,char *);
void  ReadUntil(FILE *,int,int);
void  ReadLine(FILE *,char *);
void  AddMaterial(MAT);
void  FindBounds(FILE *);
void  PrintTitle(char *);
void  ErrorMsg(char *);
int   EqualVertices(XYZ,XYZ);
void  WritePolygon(char *,int,XYZ *);

/* Geometry bounds */
double xmin=1e32,xmax=-1e32;
double ymin=1e32,ymax=-1e32;
double zmin=1e32,zmax=-1e32;

/************************************************************************
    StrataSudio to Radiance converter
*/
int main(int argc,char **argv)
{
  int i;
  int showlights = FALSE,showmaterials = FALSE,showgeometry = FALSE;
  char fname[32];
  FILE *fdxf;

  /* Set up the materials types */
  strcpy(mattypes[ 0],"plastic");
  strcpy(mattypes[ 1],"light");
  strcpy(mattypes[ 2],"illum");
  strcpy(mattypes[ 3],"glow");
  strcpy(mattypes[ 4],"spotlight");
  strcpy(mattypes[ 5],"mirror");
  strcpy(mattypes[ 6],"metal");
  strcpy(mattypes[ 7],"trans");
  strcpy(mattypes[ 8],"dielectric");
  strcpy(mattypes[ 9],"interface");
  strcpy(mattypes[10],"glass");

  /* Set up metal keywords */
  strcpy(metals[ 0],"steel");
  strcpy(metals[ 1],"aluminium");
  strcpy(metals[ 2],"bronze");
  strcpy(metals[ 3],"copper");
  strcpy(metals[ 4],"lead");
  strcpy(metals[ 5],"brass");
  strcpy(metals[ 6],"iron");
  strcpy(metals[ 7],"gold");
  strcpy(metals[ 8],"platinum");
  strcpy(metals[ 9],"silver");
  strcpy(metals[10],"zinc");
  strcpy(metals[11],"titanium");
  strcpy(metals[12],"chrome");

  /* Set up the colours */
  strcpy(colours[0].name,"red");
  colours[0].r = 1.0; colours[0].g = 0.0; colours[0].b = 0.0;
  strcpy(colours[1].name,"green");
  colours[1].r = 0.0; colours[1].g = 1.0; colours[1].b = 0.0;
  strcpy(colours[2].name,"blue");
  colours[2].r = 0.0; colours[2].g = 0.0; colours[2].b = 1.0;
  strcpy(colours[3].name,"cyan");
  colours[3].r = 0.0; colours[3].g = 1.0; colours[3].b = 1.0;
  strcpy(colours[4].name,"magenta");
  colours[4].r = 1.0; colours[4].g = 0.0; colours[4].b = 1.0;
  strcpy(colours[5].name,"yellow");
  colours[5].r = 1.0; colours[5].g = 1.0; colours[5].b = 0.0;
  strcpy(colours[6].name,"grey");
  colours[6].r = 0.75; colours[6].g = 0.75; colours[6].b = 0.75;
  strcpy(colours[7].name,"black");
  colours[7].r = 0.0; colours[7].g = 0.0; colours[7].b = 0.0;
  strcpy(colours[8].name,"white");
  colours[8].r = 1.0; colours[8].g = 1.0; colours[8].b = 1.0;
  strcpy(colours[9].name,"brick");
  colours[9].r = 0.61; colours[9].g = 0.4; colours[9].b = 0.12;
  strcpy(colours[10].name,"pink");
  colours[10].r = 1.0; colours[10].g = 0.75; colours[10].b = 0.79;
  strcpy(colours[11].name,"maroon");
  colours[11].r = 0.69; colours[11].g = 0.19; colours[11].b = 0.37;
  strcpy(colours[12].name,"brown");
  colours[12].r = 0.5; colours[12].g = 0.16; colours[12].b = 0.16;
  strcpy(colours[13].name,"orange");
  colours[13].r = 1.0; colours[13].g = 0.5; colours[13].b = 0.0;
  strcpy(colours[14].name,"gold");
  colours[14].r = 1.0; colours[14].g = 0.84; colours[14].b = 0.0;
  strcpy(colours[15].name,"turquoise");
  colours[15].r = 0.25; colours[15].g = 0.88; colours[15].b = 0.82;
  strcpy(colours[16].name,"indigo");
  colours[16].r = 0.03; colours[16].g = 0.18; colours[16].b = 0.35;
  strcpy(colours[17].name,"purple");
  colours[17].r = 0.63; colours[17].g = 0.13; colours[17].b = 0.94;
  strcpy(colours[18].name,"voilet");
  colours[18].r = 0.56; colours[18].g = 0.37; colours[18].b = 0.6;

  /* Make sure there are enough parameters */
  if (argc < 2) {
    fprintf(stderr,"Call as: strata2rad [-l] [-m] [-g] <dxffilename>\n");
    return(1);
  } else {
    for (i=1;i<argc-1;i++) {
       if (strcasecmp(argv[i],"-l") == 0)
	  showlights = TRUE;
       if (strcasecmp(argv[i],"-m") == 0)
          showmaterials = TRUE;
       if (strcasecmp(argv[i],"-g") == 0)
          showgeometry = TRUE;
    }
    strcpy(fname,argv[argc-1]);
  }

  /* Attempt to open the StrataStudio DXF file */
  if ((fdxf = fopen(fname,"r")) == NULL) {
    ErrorMsg("Unable to open the DXF file");
    return(1);
  }

  /* Write the header */
  PrintTitle("Strata2rad output (From StrataStudio to Radiance converter)");

  /* Compute and write the model bounds */
  FindBounds(fdxf);
  rewind(fdxf);  

  if (showlights) {

    /* Create a default sky */
    PrintTitle("An example sky");
    printf("void glow dim\n0\n0\n4 0.35 0.4 0.55 0\n");
    printf("dim source sky\n0\n0\n4 0 0 1 360\n");

    /* Create a default sun */
    PrintTitle("An example sun");
    printf("void light solar\n0\n0\n3 800 800 800\n");
    printf("solar source sun\n0\n0\n4 0 0 1 5\n");

    /* Create a default secondary sun (moon?) */
    PrintTitle("An example moon");
    printf("void light lunar\n0\n0\n3 200 200 200\n");
    printf("lunar source moon\n0\n0\n4 1 1 1 5\n");

    /* Set up a light source, commented because we don't know where to put it */
    PrintTitle("An example light source");
    printf("#void illum lighting\n#0\n#0\n#3 1000 1000 1000\n");
    printf("#lighting sphere bulb\n#0\n#0\n#4 x y z 0.03\n");
  }

  if (showmaterials) {

    /* Extract the surface types */
    if (!ConvertMaterial(fdxf)) {
      ErrorMsg("Error parsing DXF materials");
      fclose(fdxf);
      return(1);
    }
    rewind(fdxf);
  }

  if (showgeometry) {

    /* Convert the geometry */
    if (!ConvertModel(fdxf)) {
      ErrorMsg("Error parsing model geometry");
      fclose(fdxf);
      return(1);
    }
  }

  /* Close the file and exit */
  fclose(fdxf);
  return(0);
}

/************************************************************************
  Read the material types
  These are indicated by layer control = 8
  Material types are infered from name, otherwise plastic is used
*/
int ConvertMaterial(FILE *fptr)
{
  int i;
  char s[100];
  static MAT m;

  /* Scan through the file looking for the LAYERS control */
  while (fscanf(fptr,"%s",s) == 1) {

    /* Identify material comment lines */
    if (strcmp(s,"8") == 0) {
       ReadLine(fptr,m.name);            /* Get a legal material name        */
       strcpy(m.type,"plastic");         /* Plastic is default material type */
       m.r = 0.5; m.g = 0.5; m.b = 0.5;  /* Grey is the default colour       */
       strcpy(m.type,"plastic");         /* Default material type            */
       m.specular = NORMAL;              /* Default specularity index        */
       m.roughness = NORMAL;             /* Default roughness index          */
       for (i=0;i<nmattypes;i++) {       /* Check for material keywords      */
         if (strstr(m.name,mattypes[i]) != NULL) {
	        strcpy(m.type,mattypes[i]);
	        break;
	      }
       }
       for (i=0;i<nmetals;i++) {         /* Check for various metal types    */
	      if (strstr(m.name,metals[i]) != NULL) {
	        strcpy(m.type,"metal");
	        break;
	      }
       }
       for (i=0;i<ncolours;i++) {        /* Check for various colours        */
         if (strstr(m.name,colours[i].name) != NULL) {
           m.r = colours[i].r;
	        m.g = colours[i].g;
	        m.b = colours[i].b;
           break;
         }
       }

       /* Extract any specular modifiers */
       if (strstr(m.name,"dull") != NULL)
	      m.specular = DULL;
       if (strstr(m.name,"matte") != NULL)
         m.specular = MATTE;
       if (strstr(m.name,"shiny") != NULL)
         m.specular = SHINY;
       if (strstr(m.name,"glossy") != NULL)
         m.specular = GLOSSY;

       /* Extract and roughness modifiers */
       if (strstr(m.name,"rough") != NULL)
         m.roughness = ROUGH;
       if (strstr(m.name,"smooth") != NULL)
         m.roughness = SMOOTH;

       AddMaterial(m);
    }
  }

  /* 
    Output the material definitiions to the Radiance file 
    We can't guess at what the material should be so set up a default plastic
  */
  PrintTitle("");
  printf("# Material definitions (%d of them)\n",nmat);
  for (i=0;i<nmat;i++) {
    printf("void %s %s\n",material[i].type,material[i].name);
    if (strcmp(material[i].type,"plastic") == 0) {
       printf("0\n0\n5 ");
       PrintColour(material[i].r,material[i].g,material[i].b);
       printf("\t%g %g\n",
	      0.05 + material[i].specular * 0.05,
	      0.05 + material[i].roughness * 0.05);
    } else if (strcmp(material[i].type,"glow") == 0) {
       printf("0\n0\n4 ");
       PrintColour(material[i].r,material[i].g,material[i].b);
       printf("\t0\n");
    } else if (strcmp(material[i].type,"metal") == 0) {
       printf("0\n0\n5 ");
       PrintColour(material[i].r,material[i].g,material[i].b);
       printf("\t%g %g\n",
	      0.9  + material[i].specular * 0.1,
	      0.05 + material[i].roughness * 0.05);
    } else if (strcmp(material[i].type,"dielectric") == 0) {
       printf("0\n0\n5 ");
       PrintColour(material[i].r,material[i].g,material[i].b);
       printf("\t1.5 0\n");
    } else if (strcmp(material[i].type,"spotlight") == 0) {
       printf("0\n0\n7 ");
       PrintColour(LSCALE *material[i].r,
		   LSCALE *material[i].g,
		   LSCALE *material[i].b);
       printf("\t60 0 0 -1\n");
    } else if (strcmp(material[i].type,"light") == 0) {
       printf("0\n0\n3 ");
       PrintColour(LSCALE *material[i].r,
                   LSCALE *material[i].g,
                   LSCALE *material[i].b);
    } else if (strcmp(material[i].type,"illum") == 0) {
       printf("0\n0\n3 ");
       PrintColour(LSCALE *material[i].r,
                   LSCALE *material[i].g,
                   LSCALE * material[i].b);
    } else if (strcmp(material[i].type,"trans") == 0) {
       printf("0\n0\n7 ");
       PrintColour(material[i].r,material[i].g,material[i].b);
       printf("\t%g %g 0.5 0\n",
	      0.05 + material[i].specular  * 0.05,
	      0.05 + material[i].roughness * 0.05);
    } else if (strcmp(material[i].type,"interface") == 0) {
       printf("0\n0\n8 ");
       PrintColour(material[i].r,material[i].g,material[i].b);
       printf("\t1.5\n");
       PrintColour(material[i].r,material[i].g,material[i].b);
       printf("\t1.5\n");
    } else {
       printf("0\n0\n3 ");
       PrintColour(material[i].r,material[i].g,material[i].b);
    }
  }

  return(TRUE);
}

/************************************************************************
  Print out a colour
*/
void PrintColour(double r,double g,double b)
{
  printf("\t%g %g %g\n",r,g,b);
}

/************************************************************************
  Add a material to the list of unique materials
*/
void AddMaterial(MAT m)
{
  int i;

  /* 
    Does the material already exist
    The name and colour components must match
    If they do then ignore the colour
  */
  for (i=0;i<nmat;i++) {
    if (strcasecmp(material[i].name,m.name) == 0) {
      if (ABS(material[i].r - m.r) < 0.001 &&
          ABS(material[i].g - m.g) < 0.001 &&
          ABS(material[i].b - m.b) < 0.001)
         return;
    }
  }

  /* Add the material to the database */
  material[nmat] = m;
  nmat++;
}

/************************************************************************
  Convert the geometry of the model into Radiance format
  At the moment the only primitive used is
     3DFACE
*/
int ConvertModel(FILE *fptr)
{
  int i,j,k;
  int ignored;
  int nxyz;
  XYZ xyz[POLYMAX];
  char s[100],mat[32];

  PrintTitle("The Geometry");

  /*
    Read the whole file looking at each string in turn
  */
  while (fscanf(fptr,"%s",s) == 1) {

    /* 
      Handle 3DFACE
    */
    if (strcasecmp(s,"3DFACE") == 0) {

       /* Read the material */
       fscanf(fptr,"%d",&ignored);
       ReadLine(fptr,mat);

       /* Read the vertices and add any loops together */
       for (j=0;j<4;j++) {
	      fscanf(fptr,"%d",&ignored);
	      fscanf(fptr,"%lf",&xyz[j].x);
         fscanf(fptr,"%d",&ignored);
         fscanf(fptr,"%lf",&xyz[j].z);
         fscanf(fptr,"%d",&ignored);
         fscanf(fptr,"%lf",&xyz[j].y);
	      xyz[j].y *= -1;
       }
       nxyz = 4;
       if (EqualVertices(xyz[2],xyz[3]))
          nxyz--;

       /* Output the polygon in Radiance format */
       WritePolygon(mat,nxyz,xyz);
    }
  }
  
  /* Thats all */
  PrintTitle("The end");
  return(TRUE);
}

/************************************************************************
  Read until a particular string is encountered
  Ignore the case
*/
void ReadToString(FILE *fptr,char *s)
{
  char ss[100];

  while (fscanf(fptr,"%s",ss) == 1) {
    if (strcasecmp(ss,s) == 0)
      return;
  }
}

/************************************************************************
  Read until a particular character is encountered
*/
void ReadUntil(FILE *fptr,int c1,int c2)
{
  int cc;

  while ((cc = fgetc(fptr)) != EOF && cc != c1 && cc != c2)
    ;
}

/************************************************************************
  Read a line of text, filtering so as to make a legal material
*/
void ReadLine(FILE *fptr,char *s)
{
  int i=0,c;

  ReadUntil(fptr,'\n','\r');
  while ((c = fgetc(fptr)) != EOF && c != '\n' && c != '\r')
    if ((c >= 'a' && c <= 'z') ||
        (c >= 'A' && c <= 'Z') ||
        (c >= '0' && c <= '9') ||
        c == '_') {
      if (c >= 'A' && c <= 'Z')
        c -= ('A' - 'a');
      s[i++] = c;
    }
  s[i] = '\0';
}

/************************************************************************
  Find the model bounds
  This assumes that point lists are preceded by a "P"
*/
void FindBounds(FILE *fptr)
{
  int i,n;
  double x,y,z;
  char s[100];

  /* Read 10,20,and 30 to 30,31,and 32 group codes */
  while (fscanf(fptr,"%s",s) == 1) {
     if (strcmp(s,"10") == 0 || 
	      strcmp(s,"11") == 0 || 
	      strcmp(s,"12") == 0 || 
	      strcmp(s,"13") == 0) {
	    fscanf(fptr,"%lf",&x);
	    xmin = MIN(xmin,x);
	    xmax = MAX(xmax,x);
     }
     if (strcmp(s,"20") == 0 ||
         strcmp(s,"21") == 0 ||
         strcmp(s,"22") == 0 ||
         strcmp(s,"23") == 0) {
        fscanf(fptr,"%lf",&z);
        zmin = MIN(zmin,z);
        zmax = MAX(zmax,z);
     }
     if (strcmp(s,"30") == 0 ||
         strcmp(s,"31") == 0 ||
         strcmp(s,"32") == 0 ||
         strcmp(s,"33") == 0) {
        fscanf(fptr,"%lf",&y);
	     y *= -1;
        ymin = MIN(ymin,y);
        ymax = MAX(ymax,y);
     }
  }

  PrintTitle("The model bounds");
  printf("#  %g < x < %g\n",xmin,xmax);
  printf("#  %g < y < %g\n",ymin,ymax);
  printf("#  %g < z < %g\n",zmin,zmax);
}

/************************************************************************
  Print a title block for the pretty Radiance file we create
*/
void PrintTitle(char *s)
{
  printf("# --------------------------------------------------------------\n");
  if (strlen(s) > 0)
     printf("# %s\n",s);
}

/************************************************************************
  Display an error message
*/
void ErrorMsg(char *s)
{
  fprintf(stderr,"strata2rad - %s\n",s);
}

/************************************************************************
  Return TRUE if two vertices are "equal" else FALSE
*/
int EqualVertices(XYZ v1,XYZ v2)
{
  if (ABS(v1.x - v2.x) > EPSILON)
    return(FALSE);
  if (ABS(v1.y - v2.y) > EPSILON)
    return(FALSE);
  if (ABS(v1.z - v2.z) > EPSILON)
    return(FALSE);
  return(TRUE);
}

/************************************************************************
  Write a polygon to standard output
*/
void WritePolygon(char *mat,int n,XYZ *p)
{
  int i;
  static long count = 0;

  if (n <= 2)
    return;

  printf("%s polygon p%d\n",mat,count++);
  printf("0\n0\n%d",3 * n);
  for (i=0;i<n;i++)
    printf("\t%g %g %g\n",p[i].x,p[i].y,p[i].z);  
}


