/*
  Convert RIB files from ArchiCAD into Radiance files
  Written by Paul Bourke

  Comments
  24 Jan 93 Started
  25 Jan 93 Added nicer header and light source handling
  26 Jan 93 Successfully rendered two models from ArchiCAD, look perfect
  26 Jan 93 Increased the number of points in loops, polygons, vertices
  06 Feb 93 Changed FindBounds to look for coordinate list after "P"
  06 Feb 93 Added -b option for bounds only display
  06 Feb 93 Did a bit of a cleanup
  07 Feb 93 Implemented loops in PointsGeneralPolygon (got a headache!)
  07 Feb 93 Added WritePolygon, and some other small functions
  07 Feb 93 Added geometry only options
  11 Feb 93 Removed duplicate edges generated by ArchiCAD
  02 Aug 93 Infer material types from material name, else plastic
  02 Aug 93 Remove options -b and -g
  02 Aug 93 Added material specularity and roughness indexes
  03 Aug 93 Write documentation
  16 Nov 93 Added lights, materials, and geometry options
  24 Jun 94 Redesign for version 3.5 of ArchiCAD
  15 Apr 96 Update for version 4.55 of Archicad for Instances
  01 Sep 97 Changes for DOS/Windows style EOL by Peter Apian-Bennewitz
*/
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "string.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 LISTMAX 1000
#define POLYMAX 500
#define VERTMAX 500

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

/* Material handling stuff */
#define MATMAX 100       /* Maximum number of allowable materials     */
int nmat = 0;            /* Current number of materials               */
typedef struct {
  char name[64];         /* As from ArchiCAD file                     */
  double r,g,b;          /* As defined from ArchiCAD                  */
  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];

/* 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 */
short ConvertSpecial(FILE *);
short ConvertMaterial(FILE *);
void  PrintColour(double,double,double);
short ConvertModel(FILE *);
void  ReadString(FILE *,char *);
void  ReadToString(FILE *,char *);
void  ReadUntil(FILE *,int);
void  AddMaterial(MAT);
void  FindBounds(FILE *);
short ReadList(FILE *,double *);
void  AddPolygons(XYZ *,int *,XYZ *,int);
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;

/************************************************************************
    ArchiCAD to Radiance converter
*/
int main(argc,argv)
int argc;
char **argv;
{
  int i;
  int showlights = FALSE,showmaterials = FALSE,showgeometry = FALSE;
  char fname[32];
  FILE *frib;
  
  int objectnum;
  int useinstance = FALSE,copyon;
  char s[255];
  FILE *ftmp,*fexp;

  /* 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");

  /* Make sure there are enough parameters */
  if (argc < 2) {
    fprintf(stderr,"Call as: archicad2rad [-l] [-m] [-g] <ribfilename>\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]);
  }

  PrintTitle("Archicad2rad output (From ArchiCAD to Radiance converter)");

  /* Attempt to open the ArchiCAD rib file */
  if ((frib = fopen(fname,"r")) == NULL) {
     ErrorMsg("Unable to open the RIB file");
     return(1);
  }
  
  /* Extract any special things and write the radiance header */
  if (!ConvertSpecial(frib)) {
     ErrorMsg("Error reading some RIB parameters");
     fclose(frib);
     return(1);
  }
  rewind(frib);

  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 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");
  }

  /* Find and model bounds and write in header */
  FindBounds(frib);
  rewind(frib);

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

   if (showgeometry) {
      /* *********************************************************************/
      /* This is the new version 4.5 stuff                                   */
      /* Expand out the instances                                            */
      rewind(frib);
      while (fscanf(frib,"%s",s) == 1) {
         if (strcasecmp(s,"ObjectBegin") == 0) {      /* This is an instance */
            useinstance = TRUE;
            fscanf(frib,"%d",&objectnum);          /* Read the object number */
            sprintf(s,"ArchiCAD_Object_%d",objectnum);
            ftmp = fopen(s,"w");                   /* Open the instance file */
            while (fscanf(frib,"%s",s) == 1) {          /* Copy the instance */
               if (strcasecmp(s,"ObjectEnd") == 0)
                  break;
               fprintf(ftmp,"%s ",s);
           }
           fclose(ftmp);                           /* Closing instance file */
        } 
     }
     rewind(frib);
     copyon = !useinstance;
     fexp = fopen("ArchiCAD_tmp","w");         /* This is the expanded file */
     while (fscanf(frib,"%s",s) == 1) {
        if (useinstance) {                  /* Skip all until "lightsource" */
           if (strcasecmp(s,"LightSource") == 0)
              copyon = TRUE;
        }
        if (strcasecmp(s,"ObjectInstance") == 0) {
           if (copyon) {
              fscanf(frib,"%d",&objectnum);
              sprintf(s,"ArchiCAD_Object_%d",objectnum);
              ftmp = fopen(s,"r");
              while (fscanf(ftmp,"%s",s) == 1)
                 fprintf(fexp,"%s ",s);
              fprintf(fexp,"\n");
              fclose(ftmp);
           }
        } else {
           fprintf(fexp,"%s ",s);
           while ((i = fgetc(frib)) != '\n' && i != EOF && i != '\r')
              fputc(i,fexp);
           fprintf(fexp,"\n");
        }
     }
     fclose(fexp);
     fexp = fopen("ArchiCAD_tmp","r");
     /* End of the 4.5 stuff                                  */
     /* *******************************************************/

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

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

/************************************************************************
  Read special RIB parameters that we could convert over to radiance
*/
short ConvertSpecial(fptr)
FILE *fptr;
{
  int dh=512,dv=512;
  double ambient=0.1;
  XYZ lto,lfrom;
  char s[100],name[32],version[32];

  while (fscanf(fptr,"%s",s) == 1) {
     if (strcasecmp(s,"##Creator") == 0) {
        fscanf(fptr,"%s %s\n",name,version);
        printf("# Creator: %s %s\n",name,version);
     }
     if (strcasecmp(s,"Format") == 0) {
        fscanf(fptr,"%d %d",&dh,&dv);
        printf("# Image size: -x %d -y %d\n",dh,dv);
     }
     if (strcasecmp(s,"\"ambientlight\"") == 0) {
        ReadToString(fptr,"\"intensity\"");
        ReadUntil(fptr,'[');
        fscanf(fptr,"%lf",&ambient);
        printf("# Ambient light: -av %g %g %g\n",ambient,ambient,ambient);
      }
      if (strcasecmp(s,"\"distantlight\"") == 0) {
         ReadToString(fptr,"\"from\"");
         ReadUntil(fptr,'[');
         fscanf(fptr,"%lf %lf %lf",&lfrom.x,&lfrom.y,&lfrom.z);
         printf("# Distant light from %g %g %g\n",lfrom.x,lfrom.y,lfrom.z);
      }
      if (strcasecmp(s,"\"distantlight\"") == 0) {
         ReadToString(fptr,"\"to\"");
         ReadUntil(fptr,'[');
         fscanf(fptr,"%lf %lf %lf",&lto.x,&lto.y,&lto.z);
         printf("#                 to %g %g %g\n",lto.x,lto.y,lto.z);
      }
  }

  return(TRUE);
}

/************************************************************************
  Read the material types
  These are indicated by RIB comments #MATERIAL "materialname"
  All the materials need to be accumulated to remove duplicates
  Material types are infered from name, otherwise plastic is used
  Note: it is possible to have the same material name but with
        different colours. This should not happen for user defined
        materials but it may happen if the default materials are
        used from ArchiCAD.
*/
short ConvertMaterial(fptr)
FILE *fptr;
{
  int i;
  char s[100];
  static MAT m;
  static int gotmaterial=FALSE;

  /* 
    Scan through the file looking for the MATERIAL comment keyword 
    Assume that the next colour keyword applies to that material
  */
  while (fscanf(fptr,"%s",s) == 1) {

    /* Identify material comment lines */
    if (strcasecmp(s,"#MATERIAL") == 0) {
       ReadString(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;
          }
       }

       /* 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;

       gotmaterial = TRUE;
    }

    /* Identify colour specifications, assume it is for the current material */
    if (strcasecmp(s,"Color") == 0) {
       ReadUntil(fptr,'[');
       fscanf(fptr,"%lf %lf %lf",&m.r,&m.g,&m.b);
       if (gotmaterial) {
          AddMaterial(m);
          gotmaterial = FALSE;
       }
    }
  }

  /* 
    Output the material definitions 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(r,g,b)
double r,g,b;
{
  printf("\t%g %g %g\n",r,g,b);
}

/************************************************************************
  Add a material to the list of unique materials
*/
void AddMaterial(m)
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 Renderman primitives created by ArchiCAD
  and handled here are: 
     GeneralPolygon
     PointsGeneralPolygons
*/
short ConvertModel(fptr)
FILE *fptr;
{
  int i,j,k;

  int nloop,nplength,npoly;
  double loops[LISTMAX],plength[LISTMAX],polygons[LISTMAX];
  int nvert;
  XYZ vertices[LISTMAX];

  int nplenptr=0,pptr=0;

  int nxyz;
  XYZ txyz[POLYMAX],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) {

    /* This is the current material */
    if (strcasecmp(s,"#MATERIAL") == 0)
       ReadString(fptr,mat);

    /* 
      Handle GeneralPolygon
      This consists of a loop list followed by a list of coordinates,
      followed optionally by a list of normals (which are ignored)
    */
    if (strcasecmp(s,"GeneralPolygon") == 0) {

       /* Read the loop list */
       if ((nloop = ReadList(fptr,loops)) <= 0) {
          ErrorMsg("Error reading loops of generalpolygon");
          return(FALSE);
       }

       /* Go to the start of the vertex list */
       ReadUntil(fptr,'[');

       /* Read the vertices and add any loops together */
       nxyz = 0;
       for (j=0;j<nloop;j++) {
          for (i=0;i<loops[j];i++) {
             if (fscanf(fptr,"%lf %lf %lf",
                &(txyz[i].x),
                &(txyz[i].y),
                &(txyz[i].z)) != 3) {
                ErrorMsg("Unexpected end to vertex list of a generalpolygon");
                return(FALSE);
             }
          }
          AddPolygons(xyz,&nxyz,txyz,(int)loops[j]);
       }

       /* Output the polygon in Radiance format */
       WritePolygon(mat,nxyz,xyz);
    }

    /*
      Handle the pointsgeneralpolygon
      Loops are not implemented because I don't know what they are!
    */
    if (strcasecmp(s,"PointsGeneralPolygons") == 0) {

       /* Read the loop list */
       if ((nloop = ReadList(fptr,loops)) <= 0) {
          ErrorMsg("Error reading loops of PointsGeneralPolygon");
          return(FALSE);
       }

       /* Read the polygon length list */
       if ((nplength = ReadList(fptr,plength)) <= 0) {
          ErrorMsg("Error reading polygon length list of PointsGeneralPolygon");
          return(FALSE);
       }

       /* Read the polygon point list */
       if ((npoly = ReadList(fptr,polygons)) <= 0) {
          ErrorMsg("Error reading polygon point list of PointsGeneralPolygon");
          return(FALSE);
       }
       
       /* Find the biggest polygon index */
       nvert = 0;
       for (i=0;i<npoly;i++) {
          if (polygons[i] > nvert)
             nvert = polygons[i];
       }
       nvert++;

       /* Go to the start of the vertex list */
       ReadUntil(fptr,'[');
 
       /* Read the vertices */
       for (i=0;i<nvert;i++) {
          if (fscanf(fptr,"%lf %lf %lf",
             &(vertices[i].x),
             &(vertices[i].y),
             &(vertices[i].z)) != 3) {
             ErrorMsg("Error reading vertices of PointsGeneralPolygon");
             return(FALSE);
          }
       }

       /* Output the polygons in Radiance format */
       nplenptr = 0;               /* Pointer into polygon length list       */
       pptr = 0;                   /* Pointer into polygon vertex index list */
       for (i=0;i<nloop;i++) {                           /* A polygon        */
          nxyz = 0;                                       /* Number of points */
          for (j=0;j<loops[i];j++) {                      /* Number of pieces */
   
             for (k=pptr;k<pptr+plength[nplenptr];k++) {
                txyz[k-pptr] = vertices[(int)polygons[k]];
             }
             AddPolygons(xyz,&nxyz,txyz,(int)plength[nplenptr]);

             pptr += plength[nplenptr];
             nplenptr++;
          }

          /* 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(fptr,s)
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(fptr,c)
FILE *fptr;
int c;
{
  int cc;

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

/************************************************************************
  Read a string, string = something between double quotes
  Filter out all but characters (letters and digits) and
  convert to lower case. This ensures legal and non duplicate
  Radiance material names.
*/
void ReadString(fptr,s)
FILE *fptr;
char *s;
{
  int i=0,c;

  ReadUntil(fptr,'"');
  while ((c = fgetc(fptr)) != EOF && c != '"')
    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(fptr)
FILE *fptr;
{
  int i,n;
  double xyz[LISTMAX];
  char s[100];

  /*
    Read until we get the string "P" (including quotes)
    Read a list and compute the range
  */
  while (fscanf(fptr,"%s",s) == 1) {
     if (strcasecmp(s,"\"P\"") == 0) {
        n = ReadList(fptr,xyz);
        for (i=0;i<n;i+=3) {
           xmin = MIN(xmin,xyz[i]);
           xmax = MAX(xmax,xyz[i]);
           ymin = MIN(ymin,xyz[i+1]);
           ymax = MAX(ymax,xyz[i+1]);
           zmin = MIN(zmin,xyz[i+2]);
           zmax = MAX(zmax,xyz[i+2]);
        }
     }
  }

  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);
}

/************************************************************************
  Read a RenderMan list of numbers in [] brackets
  Return the list and the number of items
*/
short ReadList(fptr,list)
FILE *fptr;
double *list;
{
  int n = 0,i;
  char c;
  char s[32];
  
  /* Read up to the first bracket */
  ReadUntil(fptr,'[');
  
  /* Accumulate the number into s */
  i = 0;
  s[i] = '\0';

  /* 
    Parse the list 
    The items in the list are separated by spaces or carriage returns
  */
  for (;;) {
     c = fgetc(fptr);
     switch (c) {
     case ' ':
     case '\n':
     case '\r':
        if (strlen(s) > 0) {
           s[i] = '\0';
           list[n] = atof(s);
           n++;
           if (n >= LISTMAX-1) {
               fprintf(fptr,"archica2rad - Argument list size exceeded\n");
               return(n);
           }
           i = 0;
           s[i] = '\0';
        }
        break;
     case ']':
     case EOF:
        if (strlen(s) > 0) {
           s[i] = '\0';
           list[n] = atof(s);
           n++;
           if (n >= LISTMAX-1) {
              fprintf(fptr,"archica2rad - Argument list size exceeded\n");
              return(n);
           }
        }
        return(n);
     default:
        s[i] = c;
        i++;
        break;
     }
  }
}

/************************************************************************ 
  Add polygon p2 to polygon p1 
  This is to handle the "holes" in the Rendermans loop structure
  for its general polygons. This is done by finding the closest
  vertex on polygon p1 to vertex 0 on poygon p2. A new edge is then
  created from this closest vertex of p1 to vertex 0 of polygon p2,
  around polygon p2 and then back to the closest point of polygon
  p1, and then complete polygon p1.
  Well it is a bit messy but it works.
*/
void AddPolygons(p1,np1,p2,np2)
XYZ *p1,*p2;
int *np1,np2;
{
  int i,closest,ntp;
  double dx,dy,dz,dl,dlmin;
  XYZ tp[POLYMAX];

  /* 
    Special case where the first polygon is empty 
    Polygon p1 just becomes p2
  */
  if (*np1 <= 0) {
     for (i=0;i<np2;i++) {
        p1[i] = p2[i];
        (*np1)++;
     }
     return;
  }

  /* 
    Find the closest vertex in polygon 2 to vertex 0 of polygon 1 
  */
  dlmin = 1e32;
  closest = 0;
  for (i=0;i<*np1;i++) {
     dx = p1[i].x - p2[0].x;
     dy = p1[i].y - p2[0].y;
     dz = p1[i].z - p2[0].z;
     dl = dx * dx + dy * dy + dz * dz;
     if (dl < dlmin) {
        dlmin = dl;
        closest = i;
     }
  }

  /* 
    Create a temporary polygon that contains
    - points up to the closest point of p1
    - the points of p2 with the start point added to the end
    - the points from the closest to the end of p1
  */
  ntp = 0;
  for (i=0;i<=closest;i++) {
     tp[ntp] = p1[i];
     ntp++;
  }
  for (i=0;i<np2;i++) {
     tp[ntp] = p2[i];
     ntp++;
  }
  tp[ntp] = p2[0];
  ntp++;
  for (i=closest;i<*np1;i++) {
     tp[ntp] = p1[i];
     ntp++;
  }

  /* 
     Copy this temporary polygon into polygon p1, the result!
  */
  *np1 = 0;
  for (i=0;i<ntp;i++) {
     p1[i] = tp[i];
     (*np1)++;
  }
}

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

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

/************************************************************************
  Return TRUE if two vertices are "equal" else FALSE
*/
int EqualVertices(v1,v2)
XYZ v1,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(mat,n,p)
char *mat;
int n;
XYZ *p;
{
  int i;
  static long count = 0;

  if (n <= 2)
     return;

  if (strlen(mat) <= 0)
     return;

  printf("%s polygon p%ld\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);  
}

