/* 
 Program : Coil v2.0  
 Purpose : Create coil objects for PoV Ray v1.0 raytracer
 Created : 7/25/92
 By      : Bill Kirby CIS [70711,2407]
 File    : COIL.C
 Compiler: Borland C++ v3.1
 Model   : Small
 Comments: Creates twisted coil object made with spheres.
*/ 
/*         
 Program : Coil v2.10b 
 Purpose : Create coil objects for PoV Ray v1.0 and Polyray v1.6 raytracers
           and also export to CTDS and WORM file formats.   
 Created : 9/14/93
 By      : Rob Bryerton CIS [73747,433]... based on original C code 
           by Bill Kirby
 File    : COIL.CPP
 Compiler: Microsoft C++ v8.00  
 Model   : Small
 Comments: Creates twisted coil object made with spheres or other shapes
*/ 
 
#include <fstream.h>
#include <math.h>    
#include <stdio.h>  // for getchar()
#include <stdlib.h>  
#include <string.h>
#include <graph.h>   // for graphics

ofstream outfile;   
const int BUFFSIZE = 256;
enum Format {POV,POLY,CTDS,WORM}; 
enum bool   {FALSE,TRUE};
enum Shape  {Sphere,Ellipsoid,Box,Cone,Y_Cylinder};
char buff[BUFFSIZE];
const double PI = 3.1415926535897932384626;

/*
#ifndef PI
#define PI 3.1415926535897932384626
#endif*/


void err_exit(char *message);
void usage(void);

class Base_Obj
{
   protected:                             
    double x_1,y_1,x_2,z_2,xpos,ypos,zpos,Rad1,rad2,radius,angle1,angle2,k;    
    double xpos_1,ypos_1;  // for graphics... remembers 1st graphics points
    long Ntwist,Ntube,counter,steps,shape_type;
    char com[3],union_name[80];
    bool extended, display;
    Format format;
    Shape shape;
    int v_mode;
   public:                             
    char filename[80];                      
    Base_Obj(){         //  no arg constructor          
        xpos=0.0; ypos=0.0; zpos=0.0; radius=0.25;strcpy(com,"//");
        extended=FALSE;display = FALSE; format=POV; counter=0;shape=Sphere;
        outfile.setf(ios::showpoint | ios::fixed);outfile.precision(6);  }       
    virtual void do_calc(void);
    virtual void get_inputs(void); 
    void init_display(void);
    void close_display(void);
    void draw_piece(void);
    void process_args(int argc, char* argv[]);  
    void process_option(char *s);   
    void set_precision(char *prec);     
    void show_title(void);
    virtual void write_end(void){};   
    virtual void write_header(void);
    virtual bool write_piece(void){if(display) draw_piece();  // draw current line
                                   return (counter < steps) ? TRUE:FALSE; }    
}; 

class POV_Obj:public Base_Obj
{   
   private:    
    double xmax,xmin,ymax,ymin,zmax,zmin;     
   public:
    POV_Obj(){
        xmax=0.0,xmin=0.0,ymax=0.0, // no arg constructor 
        ymin=0.0,zmax=0.0,zmin=0.0;}      
    void do_calc(void);    
    void get_inputs(void);      
    void write_header(void);    
    bool write_piece(void);
    void write_end(void);
};

class POLY_Obj:public Base_Obj
{   
   public: 
    POLY_Obj(){format=POLY;}
    void get_inputs(void);          
    void write_header(void);
    bool write_piece(void);
    void write_end(void);       
};               

class CTDS_Obj:public Base_Obj  // ********** full definition of class
{
   public:
    CTDS_Obj() {format=CTDS; strcpy(com,";"); strcpy(union_name,"-----");}
    bool write_piece(void) 
        {
        outfile << xpos << " " << ypos << " " << zpos << " " 
                << radius << endl;
        Base_Obj::write_piece();  
        return ((counter < steps) ? TRUE:FALSE);
        }                 
    void write_end(void){};
};              

class WORM_Obj:public Base_Obj  // ********** full definition of class
{
   public:
    WORM_Obj(){format=WORM;}
    void write_header(void)
        { outfile << steps << endl; }
    bool write_piece(void) 
        {
        outfile << xpos << ", " << ypos << ", " << zpos << ", " 
                << radius <<", " << (counter==1?0:1) << endl;  
       Base_Obj::write_piece(); 
        return ((counter < steps) ? TRUE:FALSE);}
    void write_end(void){};    
};              



//main
int main(int argc, char* argv[])
{   
    int choice = 1;
    
    Base_Obj* coil_ptr;    // pointer to class Base_Obj
    Base_Obj vec;          // object of   " "   "  "
    coil_ptr = &vec;
        
    if(argc > 1) coil_ptr->process_args (argc, argv); // check for validity
        
    coil_ptr->show_title();
                     
    cout<< "1\tPOV\n2\tPolyray\n3\tCTDS\n4\tWORM\n" << "Number for format? [1]: ";
    cin.getline(buff,BUFFSIZE); choice = atoi(buff); // output format   
    
    switch(choice){ // construct appropriate object based on above choice   
        case 2: {POLY_Obj vec2; coil_ptr = &vec2;} break;
        case 3: {CTDS_Obj vec3; coil_ptr = &vec3;} break; 
        case 4: {WORM_Obj vec4; coil_ptr = &vec4;} break;                                  
        default:{POV_Obj  vec1; coil_ptr = &vec1;}  }
                
    if(argc > 1) coil_ptr->process_args (argc, argv);    // set options      
    
    coil_ptr->get_inputs();         //   get parameters from user
    
    outfile.open(coil_ptr->filename,ios::out);      // try to open disk file
    if(! outfile)   err_exit("Opening file.");   // if disk access error,exit       
        
    coil_ptr->write_header();       //  success, make a coil  
    
    coil_ptr->init_display();     // set up display if desired
    cerr<< "\nCreating data file " << coil_ptr->filename << endl;
    
    do coil_ptr->do_calc();         //  calculate current vector        
    while(coil_ptr->write_piece()); //  write current line to disk
    
    coil_ptr->write_end();  //  write texture block (POV or Polyray only)
    outfile.close;          //  close disk file  
    
    coil_ptr->close_display();  // exit graphics mode if applicable         
    cout << "\nFile " << coil_ptr->filename <<" created.\n";       
    
    return(0);
}    



void err_exit(char *message)
{                    
    cerr << "\n\nERROR! \a" << message << "... Exiting \n"; 
    exit(1);
}       


void usage()
{
    cerr << "\n--------------------------------Coil v2.10--------------------------------------"    
        << "Usage: coil [options]\n"
        << "Options:  -d#   enables 2D (xy) display of coil... # is the graphics mode\n"
        << "                1=640x480  2=800x600  3=1024x768  (default # value is 1)\n\n"
        << "          -e    gives you an extended menu with a choice of objects \n"
        << "                (cone, cylinder, box, etc.) for POV-Ray or Polyray output.\n\n"
        << "          -p#   where # is the precision of the output file numbers.\n"
        << "                Range is 2 - 9 with a default precision of 6 decimal places.\n" 
        << "\nType COIL with no parameters for the default menus and options.\n"
        <<    "--------------------------------------------------------------------------------";
}       



// ************* Base and Derived Class function definitions**************


//   ************* Base Class Base_Obj function definitions **************

void Base_Obj::close_display()
{ 
    if(display){
      cerr << "\nPress return."; 
      getchar(); 
      _setvideomode(_DEFAULTMODE);}
}      

void Base_Obj::do_calc()
{ 
    angle1 = 2 * PI * Ntube * (double)counter / (double)steps;      
    x_1 = cos( angle1 );y_1 = sin( angle1 );        
    angle2 = (double)( Ntwist + 1.0/Ntube) * angle1;      
    x_2 = cos( angle2 );z_2 = sin( angle2);     
    xpos = k * ((Rad1 * x_1) + (rad2 * x_2 * x_1));
    ypos = k * ((Rad1 * y_1) + (rad2 * x_2 * y_1));
    zpos = k * rad2 * z_2; counter++;      
}   

void Base_Obj::draw_piece()
{ 
    struct _videoconfig vc;
    _getvideoconfig( &vc );
    _setcolor (12); /* brt red */ 
    double rads = Rad1+rad2;      
    int vidx = int((xpos*(vc.numxpixels*0.33)/k)/rads+(0.5*vc.numxpixels));
    int vidy = int((ypos*(vc.numxpixels*0.33)/k)/rads+(0.5*vc.numypixels));    
    if(counter==1){ _moveto (vidx, vidy); xpos_1=vidx; ypos_1=vidy; }              
    if(counter<=steps){ _lineto (vidx,vidy); _setcolor (10); /* brt grn */
       int elip_x1 = (vidx-(radius*(0.28*vc.numypixels/k)));
       int elip_y1 = (vidy-(radius*(0.28*vc.numypixels/k)));
       int elip_x2 = (vidx+(radius*(0.28*vc.numypixels/k))); 
       int elip_y2 = (vidy+(radius*(0.28*vc.numypixels/k)));
        //_ellipse( _GBORDER, vidx-(radius*(0.28*vc.numxpixels)),vidy-(radius*(0.28*vc.numxpixels)),vidx+(radius*(0.28*vc.numxpixels)),vidy+(radius*(0.28*vc.numxpixels))); }
        _ellipse( _GBORDER, elip_x1, elip_y1, elip_x2, elip_y2); }
    if(counter==steps){ _setcolor (12);  _lineto (xpos_1,ypos_1);      }
}      
    


void Base_Obj::get_inputs() 
{           
    cout << "Ouput filename? [coil.inc]: ";
    cin.getline(buff,BUFFSIZE);     strcpy(filename,buff); 
    
    if(format < CTDS){
      cout << "Union name? [coil]: ";
      cin.getline(buff,BUFFSIZE);     strcpy(union_name,buff);}                  
            
    cout << "Number of objects? [100]: ";
    cin.getline(buff,BUFFSIZE);     steps = atoi(buff);
            
    cout << "Number of objects in cross-section? [2]: ";
    cin.getline(buff,BUFFSIZE);     Ntube = atoi(buff);

    cout << "Number of twists per revolution? [2]: ";
    cin.getline(buff,BUFFSIZE);     Ntwist = atoi(buff);

    cout << "Major radius? [1.0]: ";
    cin.getline(buff,BUFFSIZE);     Rad1 = atof(buff);

    cout << "Minor radius? [0.25]: ";
    cin.getline(buff,BUFFSIZE);     rad2 = atof(buff);

    cout << "Object 'radius'? [0.25]: ";
    cin.getline(buff,BUFFSIZE);     radius = atof(buff);

    cout << "Overall scale value (k)? [1.0]: ";
    cin.getline(buff,BUFFSIZE);     k = atof(buff);

    // Set up default values      
    if(filename[0]=='\0') strcpy(filename,"coil.inc");
    if(union_name[0]=='\0') strcpy(union_name,"coil");
    if(steps==0) steps = 100;   if(Ntube==0) Ntube = 2;
    if(Ntwist==0) Ntwist = 2;   if(Rad1==0.0) Rad1 = 1.0;
    if(rad2==0.0) rad2 = 0.25;  if(radius==0.0) radius = 0.25;
    if(k==0.0) k = 1.0;         if(shape_type==0) shape_type = 1;
        switch(shape_type){
            case 2 :    shape = Ellipsoid;  break;
            case 3 :    shape = Box;        break;
            case 4 :    shape = Cone;       break;
            case 5 :    shape = Y_Cylinder; break;
            default:    shape = Sphere; 
        }
}

void Base_Obj::init_display() 
{  if(display){
    switch(v_mode){
        case 3  : if(!_setvideomode(_XRES16COLOR)) cerr << "\nCouldn't set graphics mode... continuing\n";
                break;  /* 1024x768x16 */
        case 2  : if(!_setvideomode(_SRES16COLOR)) cerr << "\nCouldn't set graphics mode... continuing\n";
                break;  /*  800x600x16 */
        default : if(!_setvideomode(_VRES16COLOR)) cerr << "\nCouldn't set graphics mode... continuing\n";
     }                  /*  640x480x16 */
   }
}

void Base_Obj::process_args (int argc, char* argv[])
{
    for (int i = 1; i < argc; i++)
    {
        if (argv[i][0] == '-' || argv[i][0] == '/')
            process_option (&argv[i][1]);
        else
        {usage();   err_exit("Invalid option");}
    }
}     


void Base_Obj::process_option (char *s)
{
    switch (toupper(s[0]))
    {
    case 'E': extended = TRUE; break;
    case 'P': set_precision(&s[1]);  break;
    case 'D': display = TRUE; v_mode = atoi(&s[1]); break;      
    case '?': usage(); exit(0);
    case 'H': usage(); exit(0);
    default : usage();  err_exit("Invalid option");     
    }
}


void Base_Obj::set_precision(char *prec)
{  
    outfile.precision((atoi(prec) > 1 ? atoi(prec) : 6 ));  
}


void Base_Obj::show_title()
{   
    cout << "Coil v2.10\n"    
        << "Creates a data file of a twisted coil object for the PoV-Ray v1.0" << endl
        << "and Polyray v1.6 raytracers, Connect The Dots Smoother (CTDS), and WORM.\n"    
        << "- v2.0 C source by W. D. Kirby 7/25/92\n"   
        << "- v2.1 C++ update by R. Bryerton  9/14/93\n\n"; 
}                 

 

void Base_Obj::write_header()
{  
    outfile 
    <<com<<" File: "<<filename<<"  Union Name: "<<union_name<< endl
    <<com<<" This data file created by COIL.EXE v2.10 for the PoV Ray v1.0 and\n"
    <<com<<" Polyray v1.6 raytracers, Connect The Dots Smoother (CTDS), and WORM.\n\n"  
    <<com<<"  Twists="<<Ntwist<<"  Cross Section="<<Ntube<<"  Objects="<<steps<<"  Scale=" << k << "\n\n";
}


// *************************** Derived Class POV_Obj function definitions
void POV_Obj::get_inputs()
{   
    if(extended){
    cout << "\n1\tSphere\n2\tEllipsoid\n3\tBox\n4\tCone\n5\tY_Cylinder\n";  
    cout << "Shape type? [1]: ";
    cin.getline(buff,BUFFSIZE);     shape_type = atoi(buff); }
        
    Base_Obj::get_inputs();     //  call base class function
}

void POV_Obj::do_calc() 
{                      
    Base_Obj::do_calc();            //call base class function 1st
    xmax = __max(xmax,xpos);xmin = __min(xmin,xpos); // calculate bounds  
    ymax = __max(ymax,ypos);ymin = __min(ymin,ypos); //  (POV only)
    zmax = __max(zmax,zpos);zmin = __min(zmin,zpos);
}       

void POV_Obj::write_header()
{
    Base_Obj::write_header();   //  call base class function first
    
    if(shape > Sphere){ 
    outfile << "//   You MUST #declare shapes.inc in either this file or your .POV file...\n\n"
            << "#declare object_scale = <" << 0.5*radius << " "<< 0.5*radius << " "<< 0.5*radius << ">"<< endl
            << "#declare object_rotate = <0 0 0>" << endl
            << "//   Change object_scale and object_rotate to modify EVERY object\n\n";
    }
    
    outfile << "declare "<<union_name<<" = object {\n union {\n";
                
}   


bool POV_Obj::write_piece() 
{
    switch(shape){
    case Ellipsoid  : outfile << "      quadric { Ellipsoid scale object_scale rotate object_rotate translate< "<<xpos<<" "<<ypos<<" "<<zpos<<" > } \n";    break;
    case Box        : outfile << "      box { UnitBox scale object_scale rotate object_rotate translate< "<<xpos<<" "<<ypos<<" "<<zpos<<" > } \n";  break;
    case Cone       : outfile << "      intersection { Cone_Y scale object_scale rotate object_rotate translate< "<<xpos<<" "<<ypos<<" "<<zpos<<" > } \n";  break;
    case Y_Cylinder : outfile << "      intersection { Disk_Y scale object_scale rotate object_rotate translate< "<<xpos<<" "<<ypos<<" "<<zpos<<" > } \n";  break;
    default         : outfile << "      sphere { < "<<xpos<<" "<<ypos<<" "<<zpos<<" > "<<radius<<" }\n";}
    Base_Obj::write_piece(); return (counter < steps) ? TRUE:FALSE; 
}

void POV_Obj::write_end() 
{                           // calculate bounding box
    xmax = 1.01 * (xmax + radius); xmin = 1.01 * (xmin - radius);
    ymax = 1.01 * (ymax + radius); ymin = 1.01 * (ymin - radius);
    zmax = 1.01 * (zmax + radius); zmin = 1.01 * (zmin - radius);     
    outfile << " }\n\n" 
            << "bounded_by {\n"
            << "  box { < " << xmin<<" "<<ymin<<" "<<zmin << "> < "
                            << xmax<<" "<<ymax<<" "<<zmax << "> }\n }\n"
            << "  texture {\n"
            << "    ambient 0.3\n"
            << "    diffuse 0.7\n"
            << "    phong 1.0\n"
            << "    phong_size 20.0\n"
            << "    color red 1.0 green 0.0 blue 0.0\n"   
            << "  }\n\n}\n\n";  
  
}  
                     
// *************************** Derived Class POLY_Obj function definitions
void POLY_Obj::get_inputs()
{   
    if(extended){
    cout << "\n1\tSphere\n2\tEllipsoid\n3\tBox\n4\tCone\n5\tY_Cylinder\n\n";    
    cout << "Shape type? [1]: ";
    cin.getline(buff,BUFFSIZE);     shape_type = atoi(buff);}
        
    Base_Obj::get_inputs(); //  call base class function
}           

void POLY_Obj::write_header()
{
    Base_Obj::write_header();   //  call base class function first
    
    if(shape > Sphere) outfile  << "define object_scale <" << 0.5*radius << ", " << 0.5*radius << ", " << 0.5*radius << ">\n"
                                << "define object_rotate <0, 0, 0>\n"
                                <<"//   Change object_scale and object_rotate to modify EVERY object\n\n";
                                
    switch(shape){     // cap the cone and cylinder to be consistent w/POV
    case Ellipsoid : outfile << "define Ellipsoid object{ sphere<0,0,0>,1 scale object_scale rotate object_rotate}\n"; break;   
    case Box       : outfile << "define UnitBox object{ box<-1,-1,-1>,<1,1,1> scale object_scale rotate object_rotate}\n";break;
    case Cone      : outfile << "define Y_Cone  object{\n  object{cone<0,-1,0>,1,<0,1,0>,0  scale object_scale rotate object_rotate}+\n"
                             << "  object {disc <0, -1, 0>, <0, 1, 0>, 1 scale object_scale rotate object_rotate}\n}\n";break;
    case Y_Cylinder: outfile << "define Y_Cylinder object{\n  object{cylinder<0,-1,0>,<0,1,0>,1 scale object_scale rotate object_rotate}+\n"
                             << "  object {disc <0, 1, 0>, <0, 1, 0>, 1 scale object_scale rotate object_rotate}+\n"
                             << "  object {disc <0, -1, 0>, <0, 1, 0>, 1 scale object_scale rotate object_rotate}\n}\n";break;
    default :  break;   }
    outfile << "\ndefine "<<union_name<<"\n  object {\n";
}   

bool POLY_Obj::write_piece() 
{    
    switch(shape){
    case Ellipsoid : outfile << "     Ellipsoid{ translate<" << xpos << ", " << ypos << ", " << zpos << ">} "; break;
    case Box       : outfile << "     UnitBox{ translate<" << xpos << ", " << ypos << ", " << zpos << ">} "; break;
    case Cone      : outfile << "     Y_Cone{ translate<" << xpos << ", " << ypos << ", " << zpos << ">} "; break;
    case Y_Cylinder: outfile << "     Y_Cylinder{ translate<" << xpos << ", " << ypos << ", " << zpos << ">} "; break;
    default        : outfile << "     object { sphere < "<<xpos<<", "<<ypos<<", "<<zpos<<" >, "<<radius<<" } ";}

    if (counter < steps)  // for all obects EXCEPT the last 
        outfile << "+\n";
    else                 // for last object only (no "+")
        outfile << endl;   
                
    Base_Obj::write_piece(); return (counter < steps) ? TRUE:FALSE;
}

void POLY_Obj::write_end()
{
    outfile << "  texture {\n"
            << "    surface {\n"
            << "      ambient red, 0.275\n"      
            << "      diffuse red, 0.8\n"        
            << "      specular white, 1.0\n"
            << "      microfacet Phong 7\n     }\n"
            << "  }\n\n}\n\n";                            
        
} 

        