/*****************************************************************************
 *                      
 *  Name:       Path to POVRAY scene description file 
 *                      
 *  Filename:   PATH2POV.C
 *  System:     486 / 100 MHz
 *  Compiler:   Boland C++ Version 4.0
 *                      
 *  Date:       Sun Sep 1 10:33 1996
 *  
 *  Author:     Robert H. Morrison
 *              Brucknertsr. 12
 *              D-76744 W”rth
 *  
 *              GERMANY
 *  
 *              Tel/FAX:    +49 7271 2383
 *              Email:      mo@hitex.ti.ba-karlsruhe.de
 *              Compuserve: 101627,2205
 *  
 ****************************************************************************/

#include 
#include 
#include 
#include 

/*----------------------------------------------------------------------------
                        Typedefs, Structures & Variables 
----------------------------------------------------------------------------*/

#define PROGRAM_NAME    "Path2POV"
#define PROGRAM_DESC    PROGRAM_NAME ## " - Aimless PATH to POVray scene description file"
#define AUTHOR          "Robert H. Morrison"
#define COPYRIGHT       "Copyright (c) 1996 by " ## AUTHOR ## " - CODE placed in the PUBLIC DOMAIN"

#define VERSION_MAJOR   1
#define VERSION_MINOR   10

#define MAX_WIDTH       800
#define MAX_HEIGHT      600

#define MAX_PATH        (80*20*10)

#define SPECIAL_CH  '@'
#define CR          '\r'
#define LF          '\n'
#define EOS         '\0'

#define SPECIAL_STR "@"
#define CRLF        "\r\n"

typedef enum { FALSE, TRUE } Boolean;
typedef enum { X, Y, Z, Num_of_Axis } Axis;
typedef enum { Red, White, Blue, Yellow, Green, Cyan, Magenta, Num_of_Colors } Color;
typedef enum { North, South, East, West, Up, Down, Num_of_Dirs } Dir;

typedef enum
{
    NN, NS, NE, NW, NU, ND,
    SN, SS, SE, SW, SU, SD,
    EN, ES, EE, EW, EU, ED,
    WN, WS, WE, WW, WU, WD,
    UN, US, UE, UW, UU, UD,
    DN, DS, DE, DW, DU, DD,

    Num_of_Arrows

}   Arrow;

Arrow   curr_arrow;
Color   curr_color;

char *  color[ Num_of_Colors + 1 ] = { "Red", "White", "Blue", "Yellow", "Green", "Cyan", "Magenta", NULL };

int     origin_change[Num_of_Dirs][Num_of_Axis] =
{
    {  0,  0,  1 },     /*  North   */
    {  0,  0, -1 },     /*  South   */
    {  1,  0,  0 },     /*  East    */
    { -1,  0,  0 },     /*  West    */
    {  0,  1,  0 },     /*  Up      */
    {  0, -1,  0 },     /*  Down    */
};

typedef struct
{
    double  now;
    double  min;
    double  mid;
    double  max;
    double  len;

}   Pos;

typedef enum
{
    spc_Date_Time_Group,
    spc_Program_Header,
    spc_Copyright,
    spc_Program,
    spc_PathFile,
    spc_Path,
    spc_CompassObjects,
    spc_POV_INI_file,
    spc_Height,
    spc_Width,
    spc_FirstColor,

    Num_of_Specials

}   Special;

char * special_str[ Num_of_Specials + 1 ] =
{
    "@DateTimeGroup",
    "@ProgramHeader",
    "@Copyright",
    "@Program",
    "@PathFile",
    "@Path",
    "@CompassObjects",
    "@POV_INI_file",
    "@Height",
    "@Width",
    "@FirstColor",

    NULL
};

char * err_file_open_sary[] = { "\nError:  Unable to open the file \"@PathFile\".", NULL };

char *  INI_header_sary[] =
{
    ";/*----------------------------------------------------------------------------",
    ";  Automatically generated by the @Program program for use by POVRAY Ver 3.0",
    ";  @Copyright",
    ";",
    ";  Generated on: @DateTimeGroup",
    ";----------------------------------------------------------------------------*/\n",
    "Antialias = ON\n",

    "Clock = 0.8333\n",
    
    "Width  = @Width",
    "Height = @Height\n",

    "Input_File_Name = @POV_INI_file.pov\n",

    "[Animate]",
    "Output_File_Name = @POV_INI_file.tga\n",

    "Initial_Frame = 1",
    "Final_Frame   = 12",

    NULL
};

char *  header_sary[] =
{
    "/*-----------------------------------------------------------------------------",
    "  Automatically generated by the @Program program for use by POVRAY Ver 3.0",
    "  @Copyright\n",
    "  Path being converted:",
    "  @Path\n",
    "  Generated on: @DateTimeGroup",
    "-----------------------------------------------------------------------------*/\n",

    "#include \"colors.inc\"",
    "#include \"chars.inc\"",
    "#include \"glass.inc\"",
    "#include \"textures.inc\"",
    "#include \"shapes.inc\"",
    "#include \"stones.inc\"",
    "#include \"shapes2.inc\"\n",

    "/*-----------------------------------------------------------------------",
    "   DEFINE OBJECTS: Straight_Arrow, Curved_Arrow, Entrance and Compass",
    "-----------------------------------------------------------------------*/\n",

    "#declare Straight_Arrow =",
    "union",
    "{",
    "    cylinder { <0, -0.5, 0>,  <0, 0.5, 0>, 0.06 }",
    "}\n",

    "#declare Curved_Arrow =",
    "union",
    "{",
    "    cylinder { <0, -0.5, 0>,    <0, -0.11, 0>,   0.06 }",
    "    cylinder { <0,  0,   0.11>, <0,  0,    0.5>, 0.06 }",
    "    torus { 0.11, 0.06 clipped_by {plane{x,0}} clipped_by{plane{z,0}} rotate <0, 0, -90> translate <0, -0.11, 0.11> }",
    "}\n",

    "#declare Entrance =",
    "difference",
    "{",
    "    box { <-0.5, -0.5, 0.5>, <0.5, 0.5, -0.5> }",
    "    box { <-0.6, -0.4, 0.4>, <0.6, 0.4, -0.4> }",
    "    box { <-0.4, -0.6, 0.4>, <0.4, 0.6, -0.4> }",
    "    box { <-0.4, -0.4, 0.6>, <0.4, 0.4, -0.6> }",
    "}\n",

    "#declare Compass =",
    "union",
    "{",
    "    sphere{ <-2, 0, 0>, 0.5 texture{ Silver3 }}",
    "    @CompassObjects",
    "}\n",

    "/*-----------------------",
    "   The entire BUILDING",
    "------------------------*/\n",

    "#declare Building =",
    "union",
    "{",
    "    /*-------------------------------------------------------",
    "        Put a FRAME around the BUILDING ENTRY/EXIT square",
    "    -------------------------------------------------------*/\n",

    "    object { Entrance texture { Gold_Nugget } finish{ ambient rgb <0.6, 0.6, 0.6> }}\n",

    "    /*-------------------------------------------------",
    "        Here come the Arrows, here come the Arrows,",
    "        the Arrows are here, HERE ARE THE ARROWS !",
    "    -------------------------------------------------*/\n",

    NULL
};

char *  building_sary[] =
{
    "#declare Start = 0",
    "#declare End   = 360",
    "#declare Angle = Start+(End-Start)*clock\n",

    "object { Building  rotate y*200 rotate -x*10 }",

    NULL
};

char *  compass_sary[] =
{
    "",
    "    /*--------------------------------------------------",
    "       And a 'nice' COMPASS rose to show us the way",
    "    --------------------------------------------------*/\n",

    NULL
};
    
char *  translate_sary[] =
{
    "",
    "    /*----------------------------------------------------",
    "       And now TRANSLATE the WHOLE BUILDING to <0,0,0>",
    "    ----------------------------------------------------*/\n",

    NULL
};
    
char *  lights_sary[] =
{
    "",
    "/*--------------",
    "    LIGHTS...",
    "---------------*/\n",

    NULL
};

char *  camera_sary[] =
{
    "",
    "/*------------",
    "    CAMERA",
    "------------*/\n",

    NULL
};

char *  usage_sary[] =
{
    "@ProgramHeader",
    "@Copyright\n",
    "Usage: @Program PATH_file [POV_INI_file]\n",
    "       where;  PATH_file:     contains the PATH in the BUILDING.",
    "                              any characters other than 'n','s',",
    "                              'e','w','u' and 'd' are ignored.\n",
    "               POV_INI_file:  is an optional parameter that specifies",
    "                              the NAME that will be used for the .POV",
    "                              and .INI files that will be created.",
    "                                  If omitted then PATH_file will be",
    "                              used for NAME (under DOS this means",
    "                              that you may NOT use an extension for",
    "                              the PATH_file if POV_INI_file is not",
    "                              specified).",

    NULL
};

char *  compass_fmt   = "    object{ Compass translate <%g, %g, %g> }\n";
char *  letter_fmt    = "    object{ %s translate <0, -2.5, 0> scale <0.2, 0.2, 0.2> translate <%g, %g, %g> pigment { color %s } finish{ ambient rgb <0.4, 0.4, 0.4> }}\n";
char *  object_fmt    = "    object{ %s rotate %s translate <%g, %g, %g> pigment { color %s } finish{ ambient rgb <0.4, 0.4, 0.4> }}\n";

char *  translate_fmt = "    translate <%g, %g, %g>\n}\n\n";

char *  light_fmt     = "light_source { <%g, %g, %g> color White }\n";
char *  camera_fmt    = "camera{ orthographic up <0, %g, 0> right <%g, 0, 0> location <0, 0, %g> look_at <0, 0, 0> }\n";

char    last_path_ltr = 'e';

char    path_str[ MAX_PATH + 1 ];
char    pgm_name[255];
char    fname[255];

char    dir_str[ Num_of_Dirs + 1 ] = "nsewud";

char    straight_arrow[] = "Straight_Arrow";
char    curved_arrow  [] = "Curved_Arrow";

char *  arrow_object;
char *  POV_INI_name;
char *  path_ptr;
char *  path_fname;

double  up;
double  right;

int     ch;

int     height;
int     width;

FILE *  fp_PATH;
FILE *  fp_POV;
FILE *  fp_INI;

Pos     origin[ Num_of_Axis ];

/*  -----------------------------------------------------------------------
 *
 *  Name:           calculate_midpoints
 *
 *  Description:    We must calculate the MID POINT of the PATH which just
 *                  happens to be the midpoint for the X, Y, and Z axis.
 *
 *  Parameters:     NONE
 *
 *  Return Value:   X, Y, and Z MID POINTS (in the origin array)
 *
 *--------------------------------------------------------------------------*/

void calculate_midpoints( void )
{
    Axis    axis;
    
    for ( axis = 0; axis < Num_of_Axis; ++axis )
    {
        origin[axis].mid  = 0.0;
        origin[axis].len  = abs( origin[axis].max - origin[axis].min );

        origin[axis].now  = origin[axis].min + origin[axis].len / 2;

        origin[axis].min += origin[axis].now;
        origin[axis].max += origin[axis].now;
    }
}

/*  -----------------------------------------------------------------------
 *
 *  Name:           get_direction
 *
 *  Description:    Obtain number to represent the direction letter.
 *
 *  Parameters:     in  char    dir_ch      direction letter
 *
 *  Return Value:   Dir value that corresponds to the dir_ch
 *
 *--------------------------------------------------------------------------*/

Dir get_direction( char dir_ch )
{
    Dir     dir;

    for ( dir = 0; dir < Num_of_Dirs; ++dir )
        if ( dir_str[dir] == dir_ch )
            break;
    
    return dir;
}

/*  -----------------------------------------------------------------------
 *
 *  Name:           calculate_arrow_type
 *
 *  Description:    Obtain number to represent the arrow type based on the
 *                  last and current direction letters.
 *
 *  Parameters:     in  char    last_ch     LAST direction letter
 *                  in  char    curr_ch     CURR direction letter
 *
 *  Return Value:   Arrow value that is correct based on last_ch and curr_ch
 *
 *--------------------------------------------------------------------------*/

Arrow calculate_arrow_type( char last_ch, char curr_ch )
{
    return ( get_direction( last_ch ) * Num_of_Dirs + get_direction( curr_ch ));
}

/*  -----------------------------------------------------------------------
 *
 *  Name:           get_rotation_string
 *
 *  Description:    Given the ARROW DIRECTION "NN, NE, ...", returns a pointer
 *                  to the rotation string that is required.
 *
 *  Parameters:     in  Arrow   dir     Direction for Arrow
 *
 *  Return Value:   Pointer to the rotation string to be used.
 *
 *--------------------------------------------------------------------------*/

char * get_rotation_string( Arrow dir )
{
    char *  ptr = "0";
    
    switch( dir )
    {
        /*--------------------------------------------------
            The following use the Straight_Arrow (Up-Up)
        --------------------------------------------------*/

        case NN:    ptr = "x*90";               break;
        case SS:    ptr = "-x*90";              break;
        case EE:    ptr = "-z*90";              break;
        case WW:    ptr = "z*90";               break;
        case DD:    ptr = "z*180";              break;

        /*---------------------------------------------------
            The following use the Curved_Arrow (Up-North)
        ---------------------------------------------------*/

        case US:    ptr = "y*180";              break;
        case UE:    ptr = "y*90";               break;
        case UW:    ptr = "-y*90";              break;

        case NE:    ptr = "<90,  0,  90>";      break;
        case NW:    ptr = "<90,  0, -90>";      break;
        case NU:    ptr = "<90,  0, 180>";      break;
        case ND:    ptr = "x*90";               break;

        case DN:    ptr = "z*180";              break;
        case DS:    ptr = "<0, 180, 180>";      break;
        case DE:    ptr = "<0, -90, 180>";      break;
        case DW:    ptr = "<0,  90, 180>";      break;

        case SE:    ptr = "< 90, 180,   0>";    break;
        case SW:    ptr = "<-90,   0,  90>";    break;
        case SU:    ptr = "-x*90";              break;
        case SD:    ptr = "<-90,   0, 180>";    break;

        case EN:    ptr = "-z*90";              break;
        case ES:    ptr = "<180,  0,  90>";     break;
        case EU:    ptr = "<-90, -90,  0>";     break;
        case ED:    ptr = "< 90,  90,  0>";     break;

        case WN:    ptr = "z*90";               break;
        case WS:    ptr = "< 0, 180, 90>";      break;
        case WU:    ptr = "< 0,  90, 90>";      break;
        case WD:    ptr = "< 0, -90, 90>";      break;
    }
    
    return ( ptr );
}

/*-  ----------------------------------------------------------------------
 *
 *  Name:           special_cmd
 *
 *  Description:    Takes care of processing any 'special' commands.
 *
 *  Parameters:     in  char * line
 *                  in  char * buf
 *                  in  FILE * stream
 *
 *  Return Value:   TRUE if a special command has been processed,
 *                  otherwise FALSE.
 *
 *--------------------------------------------------------------------------*/

Boolean special_cmd( char * line, char * buf, FILE * stream )
{
    Boolean     rc;
    char        sbuf[255];
    char *      old_pos;
    char *      pos;
    int         count;
    int         len;
    Special     mode;

    rc   = FALSE;
    *buf = EOS;

    old_pos = line;
    pos = strchr( line, SPECIAL_CH );

    if ( pos == NULL )
        return rc;

    do
    {
        for ( mode = 0; mode < Num_of_Specials; mode++ )
        {
            len = strlen(special_str[mode]);

            if ( ! strncmp( pos, special_str[mode], len ))
            {
                count = pos - old_pos;

                if ( count )
                {
                    strncpy( sbuf, old_pos, count ) ;
                    sbuf[count] = EOS;
                    strcat( buf, sbuf );
                }

                switch ( mode )
                {
                    case spc_Date_Time_Group:
                    {
                        time_t      t;
                        struct tm * area;

                        t = time(NULL);
                        area = localtime(&t);
                        strcat( buf, asctime( area ));
                        buf[strlen(buf)-1] = EOS;
                        break;
                    }

                    case spc_Program_Header:
                        count = strlen(COPYRIGHT) - 13;
                        sprintf( strchr( buf, EOS ), "%-*s Version %d.%02d", count, PROGRAM_DESC, VERSION_MAJOR, VERSION_MINOR );
                        break;

                    case spc_Copyright:
                        strcat( buf, COPYRIGHT );
                        break;

                    case spc_Program:
                        strcat( buf, PROGRAM_NAME );
                        break;

                    case spc_PathFile:
                        strcat( buf, path_fname );
                        break;

                    case spc_Path:
                    {
                        char *  curr;
                        int     clen;
                        int     olen;
                        int     step;
                        
                        /*---------------------------------------------------------
                            At this point count represents the number of spaces
                            that should be output before each line containing
                            a MAXIMUM of (79 - count) PATH letters.
                        ---------------------------------------------------------*/

                        step = 79 - ( count << 1 );
                        
                        for ( curr = path_str, clen = strlen( path_str ); clen; clen -= step, curr += step )
                        {
                            if ( clen < step )
                                step = clen;

                            olen = strlen( buf );
                            strncpy( strchr( buf, EOS ), curr, step );
                            buf[ olen + step ] = EOS;

                            if ( clen > step )
                            {
                                fputs( buf, ( stream ? stream : stdout ));
                                fputc( LF,  ( stream ? stream : stdout ));
                                memset( buf, ' ', count );
                                buf[count] = EOS;
                            }
                        }
                        break;
                    }

                    case spc_CompassObjects:
                    {
                        char *  obj_letter;
                        double  dx, dy, dz;
                        Dir     dir;

                        for ( dir = 0; dir < Num_of_Dirs; )
                        {
                            dx = (double)(origin_change[dir][X]);
                            dy = (double) origin_change[dir][Y] * 2;
                            dz = (double) origin_change[dir][Z] * 2;
                            
                            switch ( dir )
                            {
                                case East:  dx += 1.5;  break;
                                case West:  dx -= 1.5;  break;
                                
                                case Up:    dy += 0.5;  break;
                                case Down:  dy -= 0.5;  break;
                                    
                                default:
                                    break;
                            }

                            switch ( dir )
                            {
                                case North: obj_letter = "char_N";  break;
                                case South: obj_letter = "char_S";  break;
                                case East:  obj_letter = "char_E";  break;
                                case West:  obj_letter = "char_W";  break;
                                case Up:    obj_letter = "char_U";  break;
                                case Down:  obj_letter = "char_D";  break;
                                
                                default:    obj_letter = NULL;      break;
                            }

                            if ( obj_letter != NULL )
                            {
                                sprintf( buf, letter_fmt, obj_letter, dx - 2, dy, dz, color[dir>>1] );
                                fprintf(( fp_POV ? fp_POV : stdout ), buf );
                            }

                            curr_arrow = calculate_arrow_type( dir_str[dir], dir_str[dir] );

                            sprintf( buf, object_fmt, straight_arrow,
                                get_rotation_string( curr_arrow ),
                                (double)(origin_change[dir][X] - 2),
                                (double) origin_change[dir][Y],
                                (double) origin_change[dir][Z],
                                color[dir>>1]
                            );

                            if ( ++dir < Num_of_Dirs )
                                fprintf(( fp_POV ? fp_POV : stdout ), buf );
                            else
                                buf[strlen(buf)-1] = EOS;
                        }
                        break;
                    }

                    case spc_POV_INI_file:
                        strcat( buf, POV_INI_name );
                        break;

                    case spc_Height:
                        sprintf( strchr( buf, EOS ), "%d", height );
                        break;

                    case spc_Width:
                        sprintf( strchr( buf, EOS ), "%d", width );
                        break;

                    case spc_FirstColor:
                        strcat( buf, color[0] );
                        break;
                }

                rc = TRUE;
                break;
            }
        }

        if ( ! ( mode < Num_of_Specials ))
        {
            strcat( buf, SPECIAL_STR );
            len = 1;
        }

        old_pos = pos + len;
        pos = strchr( old_pos, SPECIAL_CH );
    }
    while ( pos != NULL );

    if ( *old_pos )
        strcat( buf, old_pos );

    return rc;
}

/*-  ----------------------------------------------------------------------
 *
 *  Name:           sary_print             
 *
 *  Description:    Prints out the array of strings line for line until
 *                  a NUL pointer is found.
 *
 *  Parameters:     in  char ** line    Pointer to array of strings
 *                  in  FILE *  stream  File stream to output to
 *
 *  Return Value:   NONE
 *
 *--------------------------------------------------------------------------*/

void sary_print( char ** line, FILE * stream )
{
    char    buf[255];

    for ( ; *line; line++ )
    {
        if ( ! special_cmd( *line, buf, stream ))
            fputs( *line, ( stream ? stream : stdout ));
        else
            fputs( buf, ( stream ? stream : stdout ));

        fputc( LF, ( stream ? stream : stdout ));
    }
}

/*  -----------------------------------------------------------------------
 *
 *  Name:           update_origin
 *
 *  Description:    Given the last direction we must adjust the origin
 *                  array for X, Y, and Z axis, all values - MIN, MAX,
 *                  and NOW.
 *
 *  Parameters:     in  Dir     dir     Direction for Arrow
 *
 *  Return Value:   NONE
 *
 *--------------------------------------------------------------------------*/

void update_origin( Dir dir )
{
    Axis    axis;
    
    for ( axis = 0; axis < Num_of_Axis; ++axis )
    {
        origin[axis].now += (double) origin_change[dir][axis];

        if ( origin[axis].now < origin[axis].min )
            origin[axis].min = origin[axis].now;

        if ( origin[axis].now > origin[axis].max )
            origin[axis].max = origin[axis].now;    }
}

/*  -----------------------------------------------------------------------
 *
 *  Name:           write_INI_file
 *
 *  Description:    Calculates and prints data to the POVRAY INI file.
 *
 *  Parameters:     NONE
 *
 *  Return Value:   NONE
 *
 *--------------------------------------------------------------------------*/

void write_INI_file( void )
{
    if ( right > 1.3333 * up )
    {
        width  = MAX_WIDTH;
        height = (int)((double) width * up / right );
    }
    else
    {
        height = MAX_HEIGHT;
        width  = (int)((double) height * right / up );
    }
    
    sary_print( INI_header_sary, fp_INI );
}

/*  -----------------------------------------------------------------------
 *
 *  Name:           file_open_error
 *
 *  Description:    Outputs USAGE INFO and an appropriate ERROR MESSAGE
 *                  indicating what file we could not open.
 *
 *  Parameters:     in  char *  file_name   Name of the file to open
 *
 *  Return Value:   EXIT_FAILURE
 *
 *--------------------------------------------------------------------------*/

file_open_error( char * file_name )
{
    path_fname = file_name;
    sary_print( usage_sary, stderr );
    sary_print( err_file_open_sary, stderr );
            
    return EXIT_FAILURE;
}

/*-  ----------------------------------------------------------------------
 *
 *  Name:           main                  
 *  
 *  Description:    This program reads a POTM AIMLESS path string
 *                  that describes a valid path through a building (see
 *                  potm problem Summer 1996) and writes a POVRAY scene
 *                  description file that is to be ray traced by POVRAY
 *                  Version 3.0 (or higher).
 *
 *  Parameters:     in  int     argc    Number of arguments
 *                  in  char *  argv[]  Array of pointers to the arguments
 *
 *  Return Value:   EXIT_SUCCESS
 *
 *--------------------------------------------------------------------------*/

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

    tzset();                /*  Seamed like a good idea at the time         */
    origin[X].min = -3.0;   /*  Include COMPASS rose as part of dimensions  */

    /*-----------------------------------------------------------------
        Save the PROGRAM NAME to a global char array (just in case)
    -----------------------------------------------------------------*/
    
    strncpy( pgm_name, argv[0], sizeof( pgm_name ));
    pgm_name[ sizeof( pgm_name ) - 1 ] = EOS;

    /*-----------------------------------------------------------------------
        If NO ARGUMENTS on the command line then print out the usage INFO
    -----------------------------------------------------------------------*/
    
    if ( argc < 2 || argc > 3 )
    {
        sary_print( usage_sary, stderr );
        return EXIT_FAILURE;
    }

    POV_INI_name = argv[argc-1];

    /*-------------------------------------------------------------
        Read the PATH that we are to display into 'path_str'...
    -------------------------------------------------------------*/
    
    fp_PATH = fopen( argv[1], "r" );

    if ( ! fp_PATH )
        return file_open_error( argv[1] );
    
    for ( path_ptr = path_str; ( ch = fgetc( fp_PATH ? fp_PATH : stdin )) > 0; )
        if ( strchr( dir_str, ch ))
            *(path_ptr++) = (char) ch;

    *path_ptr = EOS;
    
    /*-----------------------------------
        Open the POV file for writing
    -----------------------------------*/

    sprintf( fname, "%s.pov", POV_INI_name );
    fp_POV = fopen( fname, "w" );

    if ( ! fp_POV )
        return file_open_error( fname );
    
    /*-----------------------------------
        Open the INI file for writing
    -----------------------------------*/

    sprintf( fname, "%s.ini", POV_INI_name );
    fp_INI = fopen( fname, "w" );

    if ( ! fp_INI )
        return file_open_error( fname );
    
    /*--------------------------------------------
        Print the POVRAY header information...
    --------------------------------------------*/
    
    sary_print( header_sary, fp_POV );

    /*---------------------------------------------------------------------------
        LOOP through the PATH creating one Arrow of the appropriate POSITION,
        ORIENTATION, and COLOR for each of the letters in the PATH string.
    ---------------------------------------------------------------------------*/

    for ( curr_color = -1, path_ptr = path_str; *path_ptr; ++path_ptr )
    {
        curr_arrow = calculate_arrow_type( last_path_ltr, *path_ptr );

        ++curr_color;
        curr_color %= Num_of_Colors;

        switch ( curr_arrow )
        {
            case NN:    case SS:
            case EE:    case WW:
            case UU:    case DD:
                arrow_object = straight_arrow;
                break;

            case NE:    case NW:    case NU:    case ND:
            case SE:    case SW:    case SU:    case SD:
            case EN:    case ES:    case EU:    case ED:
            case WN:    case WS:    case WU:    case WD:
            case UN:    case US:    case UE:    case UW:
            case DN:    case DS:    case DE:    case DW:
                arrow_object = curved_arrow;
                break;

            default:
                fprintf( stderr, "Error: INVALID Arrow = \"%c%c\"\n", last_path_ltr, *path_ptr );
                arrow_object = NULL;
                break;
        }

        if ( arrow_object )
        {
                curr_color=origin[Z].now;
                curr_color%=Num_of_Colors;

            fprintf(( fp_POV ? fp_POV : stdout ),   object_fmt,
                arrow_object,
                get_rotation_string( curr_arrow ),
                origin[X].now,
                origin[Y].now,
                origin[Z].now,
                color[curr_color]
            );
        }

        last_path_ltr = *path_ptr;
        update_origin( get_direction( last_path_ltr ));
    }

    /*--------------------------------------------------
        And a 'nice' COMPASS rose to show us the way
    --------------------------------------------------*/
    
    sary_print( compass_sary, fp_POV );
    fprintf(( fp_POV ? fp_POV : stdout ), compass_fmt, -2.0, origin[Y].max / 2, 0.0 );

    /*----------------------------------------------------------------
        Calculate the EXACT MIDPOINT of the BUILDING (X, Y, and Z)
    ----------------------------------------------------------------*/
    
    calculate_midpoints();

    /*-------------------------------------------------------------------------------
        Finish defining BUILDING object by translating the MIDPOINT to the ORIGIN
    -------------------------------------------------------------------------------*/
    
    sary_print( translate_sary, fp_POV );
    fprintf(( fp_POV ? fp_POV : stdout ), translate_fmt, -origin[X].now, -origin[Y].now, -origin[Z].now );
    
    /*--------------------------------------------------
        Define ANIMATION paramaters and the BUILDING
    --------------------------------------------------*/
    
    sary_print( building_sary, fp_POV );

    /*------------
        LIGHTS
    ------------*/
    
    sary_print( lights_sary, fp_POV );

    fprintf(( fp_POV ? fp_POV : stdout ),   light_fmt,
        origin[X].min,
        origin[Y].max + 1.0,
        origin[Z].min
    );

    fprintf(( fp_POV ? fp_POV : stdout ),   light_fmt,
        origin[X].min,
        origin[Y].max + 1.0,
        origin[Z].max
    );

    fprintf(( fp_POV ? fp_POV : stdout ),   light_fmt,
        origin[X].max,
        origin[Y].max + 1.0,
        origin[Z].min
    );

    fprintf(( fp_POV ? fp_POV : stdout ),   light_fmt,
        origin[X].max,
        origin[Y].max + 1.0,
        origin[Z].max
    );

    fprintf(( fp_POV ? fp_POV : stdout ), "\n" );
    fprintf(( fp_POV ? fp_POV : stdout ), light_fmt, 0.0, 0.0, -(double)((int)(origin[X].len + 0.5)));

    /*------------
        CAMERA
    ------------*/
    
    up    = (double)((int)(origin[Y].len * 2.0 + 0.5));
    right = (double)((int)(origin[X].len + 4.5));
    
    sary_print( camera_sary, fp_POV );
    fprintf(( fp_POV ? fp_POV : stdout ), camera_fmt, up, right, -(double)((int)(origin[X].len + 0.5)));

    /*----------------------------
        Now write the INI file
    ----------------------------*/

    write_INI_file();
    
    /*--------------------------
        Clean up our ROOM...
    --------------------------*/

    if ( fp_PATH )
        fclose( fp_PATH );

    if ( fp_POV )
        fclose( fp_POV );

    if ( fp_INI )
        fclose( fp_INI );

    return EXIT_SUCCESS;    /* and ACTION! */
}