#include <math.h>
#include <iostream.h>
#include <stdio.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

const DIMX=500;
const DIMY=500;

//--------------------------------------------------------------------------------------
//Targa file header and file writing routines...

typedef struct 
  { char id_len;
    char map_type;
    char img_type;
    int map_first;
    int map_len;
    char map_entry_size;
    int x;
    int y;
    int width;
    int height;
    char bpp;
    char misc;
  } targa_header;

const D_NOIDFIELD=0;
const D_NOCOLORMAP=0;
const D_IMGTYPE=2;
const D_NOTUSED=0;
const D_XSTART=0;
const D_YSTART=0;
const D_WIDTH=DIMX;
const D_HEIGHT=DIMY;
const D_BITSPERPIXEL=24;
const D_SCANFROMUPPERLEFTCORNER=0x20;

void initheader(targa_header *h)
  { h->id_len=D_NOIDFIELD;
    h->map_type=D_NOCOLORMAP;
    h->img_type=D_IMGTYPE;
    h->map_first=D_NOTUSED;
    h->map_len=D_NOTUSED;
    h->map_entry_size=D_NOTUSED;
    h->x=D_XSTART;
    h->y=D_YSTART;
    h->width=D_WIDTH;
    h->height=D_HEIGHT;
    h->bpp=D_BITSPERPIXEL;
    h->misc=D_SCANFROMUPPERLEFTCORNER;
  }

void writeheader(targa_header h,FILE *tga)
  { fputc(h.id_len,tga);
    fputc(h.map_type,tga);
    fputc(h.img_type,tga);
    fputc(h.map_first%256,tga);
    fputc(h.map_first/256,tga);
    fputc(h.map_len%256,tga);
    fputc(h.map_len/256,tga);
    fputc(h.map_entry_size,tga);
    fputc(h.x%256,tga);
    fputc(h.x/256,tga);
    fputc(h.y%256,tga);
    fputc(h.y/256,tga);
    fputc(h.width%256,tga);
    fputc(h.width/256,tga);
    fputc(h.height%256,tga);
    fputc(h.height/256,tga);
    fputc(h.bpp,tga);
    fputc(h.misc,tga);
  }

const char* FileName="TARGAOUT.tga";

void writetargafile()
  { FILE *tga;
    targa_header h;
    initheader(&h);
    tga=fopen(FileName,"wb");
    writeheader(h,tga);
    int x,y;
    for(y=0;y<D_HEIGHT;y++)
      for(x=0;x<D_WIDTH;x++)
        { fputc(2*y,tga);            //Blue
          fputc(255-(x+y),tga);      //Green
          fputc(x,tga);               //Red
        }
  }


//-------------------------------------------------------------------------------------
// "complex" Number Class  
// Pretty Self-Explanatory

class complex
  { private:
      double Real,Imaginary;
    public:
      complex() { Real=0; Imaginary=0; }
      complex(double r,double i) { Real=r; Imaginary=i; }
      void Equals(double r,double i) { Real=r; Imaginary=i; }
      double R() { return Real; }
      double I() { return Imaginary; }
      complex operator + (complex);
      complex operator - (complex);
      complex operator * (complex);
//    complex operator / (complex);
      void operator += (complex);
      void operator -= (complex);
      void operator *= (complex);
//    void operator /= (complex);
      void sqr();
  };

complex complex::operator + (complex a)
  { complex X(R()+a.R(),I()+a.I());
    return X; 
  }
  
complex complex::operator - (complex a)
  { complex X(R()-a.R(),I()-a.I());
    return X;
  }

complex complex::operator * (complex a)
  { complex X(R()*a.R()-(I()*a.I()),R()*a.I()+I()*a.R());
    return X;
  }

//complex complex::operator / (complex a)
//  { complex X(R()/a.R()-(I()/a.I()),R()/a.I()+I()/a.R());
//    return X;
//  }

void complex::operator += (complex a)
  { Real=R()+a.R();
    Imaginary=I()+a.I();
  }  

void complex::operator -= (complex a)
  { Real=R()-a.R();
    Imaginary=I()-a.I();
  }

void complex::operator *= (complex a)
  { Real=R()*a.R()-I()*a.I();
    Imaginary=R()*a.I()+I()*a.R();
  }

void complex::sqr()
  { Real=R()*R()-I()*I();
    Imaginary=2*R()*I();
  }

//---------------------------------------------------------------------------------
//Constants and Fractal Manipulation and Calculation Procedures...

const XWINDOW=DIMX;               // Window Size (x)
const YWINDOW=DIMY;               // Window Size (y)

double XMin=-2,XMax=2;           // Cartesian Coordinate Plane Frame of Reference
double YMin=-2,YMax=2;           // Coordinates

const Mandelbrot=0;              // For "Mandelbrot Mode"
const Julia=1;                   // For "Julia Mode"

const NoTarga=0;
const Targa=1;

int Mode=Mandelbrot;             // Initially in Mandelbrot Mode...
complex JuliaConstant(-0.12375,0.56508); // The Inital Julia Constant
int FileMode=NoTarga;

const Circle=0;
const Square=1;

int BoundMode=0;

int Iterations=30;               // The number of times iterated 

const BoundLow=-10;
const BoundHigh=10;

int bounded(double x,double y)   // Boolean Function to determine Divergence 
  { if (BoundMode==Circle)
      return (sqrt(x*x+y*y)<=BoundHigh);
      else 
        return ((x<=BoundHigh)&&(x>=BoundLow)&&(y<=BoundHigh)&&(y>=BoundLow)); 
  }

int iterate(double x,double y)   // The Iterate Procedure.  Returns number of iterations before
  { complex c(x,y),d(0,0);       // "Divergence"
    if (Mode==Mandelbrot) d.Equals(x,y); 
      else d=JuliaConstant;
    int b=0;
    while ((b<Iterations)&&(bounded(c.R(),c.I())))
      { c=c*c+d; b++; }
    return b;
  }  

//--------------------------------------------------------------------------------
// GL stuff...

static void Init()
  { glClearColor(0.0,0.0,0.0,1.0);
    glShadeModel(GL_FLAT);
  }

static void Reshape(int w,int h)
  { glViewport(0,0,(GLint)w,(GLint)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0,XWINDOW,0,YWINDOW,-1.0,1.0);
    glMatrixMode(GL_MODELVIEW);
  }

void DrawingRoutine() //Draws the Fractal
  { double x,y,t;
    glBegin(GL_POINTS);
      for(y=0;y<YWINDOW;y+=1)
        { for(x=0;x<XWINDOW;x+=1)
            { t=(double)iterate(XMin+x*(XMax-XMin)/XWINDOW,YMin+y*(YMax-YMin)/YWINDOW);
              if (t!=0) 
                { if (t==Iterations) glColor3f(0.0,0.0,0.0);
                    else glColor3f(sqrt(t/(Iterations/2)),0.0,0.0);
                  glVertex3f(x+0.5,y+0.5,0.0);
                }
            } 
        }
   glEnd();
 }
  
void DrawingRoutineTargaFileStyle() //Draws the Fractal and Saves it as a TargaFile...
  { FILE *tga;
    targa_header h;
    initheader(&h);
    tga=fopen(FileName,"wb");
    writeheader(h,tga);
    double x,y,t;
    glBegin(GL_POINTS);
      for(y=0;y<YWINDOW;y+=1)
        { for(x=0;x<XWINDOW;x+=1)
            { t=(double)iterate(XMin+x*(XMax-XMin)/XWINDOW,YMin+y*(YMax-YMin)/YWINDOW);
              if (t!=0)
                { fputc(0,tga);
                  fputc(0,tga);
                  if (t==Iterations) 
                     { glColor3f(0.0,0.0,0.0); 
                       fputc(0,tga);
                     }
                   else 
                     { glColor3f(sqrt(t/(Iterations/2)),0.0,0.0); 
                       fputc((char)(sqrt(t/(Iterations/2))*256),tga);
                     }
                   glVertex3f(x+0.5,y+0.5,0.0);
                }
            }
        }
    glEnd();
    fclose(tga);
  }

void ClearAndDraw()     //Clears the screen and draws the new fractal
  { glFlush();
    glClear(GL_COLOR_BUFFER_BIT);
    if (FileMode==Targa) DrawingRoutineTargaFileStyle();
      else DrawingRoutine();
  }

int FirstTime=1;

static void DoNothing() 
  { if (FirstTime) { ClearAndDraw(); FirstTime=0; }     
  }

void KeyboardInput(unsigned char key,int x,int y)
  { switch (key)
      { case 27: //ESC                        Quits
          exit(0);
          break;
        case 'm':                          // m Changes between Mandelbrot and Julia Modes
           { Mode=(!Mode); 
             ClearAndDraw();
           }
          break;
        case 't':                          // t Toggles Targa file saving mode.
           { FileMode=(!FileMode); 
             ClearAndDraw(); 
           }
          break;
        case 's':
           { XMin=-2;XMax=2;YMin=-2;YMax=2; // s resets the Cartesian Perspective
             ClearAndDraw();
           } 
          break; 
        case 'b':                           // b changes Bound modes
           { BoundMode=(!BoundMode);
             ClearAndDraw();
           }
           break;
        case '=':                         // = (or +) raised the Iterations by 5
           { Iterations+=5; 
             ClearAndDraw(); 
           }
          break;
        case '-':                         // - lowers the Iterations by 5 
           { Iterations-=5; 
             ClearAndDraw();
           }
        default:
          break;
      }
  }

void iteratestringdraw(double x,double y)
  { int t=iterate(x,y);    
    complex c(x,y),d(0,0);       // "Divergence"
    if (Mode==Mandelbrot) d.Equals(x,y);
      else d=JuliaConstant;
    glColor3f(1.0,1.0,1.0);
    glBegin(GL_LINE_STRIP);
    for(int g=0;g<t;g++)
      { glVertex3f(c.R(),c.I(),0.0);
        c=c*c+d;  
      }
    glEnd();
  }

double XCorner=0,YCorner=0;

void MouseInput(int button,int state,int x,int y)
  { switch (button)                                   // Middle button creates new Julia fractal with 
      { case (GLUT_MIDDLE_BUTTON):                    // constant at cursor
          { if (state==GLUT_DOWN)
              { Mode=Julia;
                JuliaConstant.Equals(XMin+x*(XMax-XMin)/XWINDOW,YMin+y*(YMax-YMin)/YWINDOW);
                ClearAndDraw();
              }
          }    
          break;
        case (GLUT_LEFT_BUTTON):                     // Zooms (click, drag box, click)
          { if (state==GLUT_DOWN)
              { XCorner=XMin+x*(XMax-XMin)/XWINDOW;
                YCorner=YMin+y*(YMax-YMin)/YWINDOW;
              }
              else { double TempX=XMin+x*(XMax-XMin)/XWINDOW,
                            TempY=YMin+y*(YMax-YMin)/YWINDOW;
                     XMin=XCorner;YMin=YCorner;
                     XMax=TempX;YMax=TempY;
                     ClearAndDraw();
                   } 
       
          }
            break;
        case (GLUT_RIGHT_BUTTON): 
          { if (state==GLUT_DOWN)
              { iteratestringdraw(XMin+x*(XMax-XMin)/XWINDOW,YMin+y*(YMax-YMin)/YWINDOW);}
          } break;  
         default:
          break;
      }
  }

int main(int argc,char **argv)  // And Procedure Main
  { glutInit(&argc,argv);
    glutInitDisplayMode(GLUT_RGBA);
    glutInitWindowSize(XWINDOW,YWINDOW);
    glutInitWindowPosition(0,0);
    glutCreateWindow(argv[0]);
    Init();
    glutReshapeFunc(Reshape);
    glutDisplayFunc(DoNothing);
    glutKeyboardFunc(KeyboardInput);
    glutMouseFunc(MouseInput);
    glutMainLoop();
    return 0;
  }

