#include <GLUT/glut.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

enum fractal_type {KOCH, KOCH2, KOCH_8, SIERPINSKI, HILBERT, HARTMAN, PEANO, DRAGON,
		   DRAGON2, TREE, TREE2, TREE3} whichfrac=KOCH2;
int main_menu_id,frac_menu_id,level_menu_id, level=1,direction=0;
GLdouble x=-.9,y=0,tx=0,ty=0,sc=1;
void draw_nogard(int,float);

void line_forward(double howlong)
{
glBegin(GL_LINES);
glVertex2d(x,y);
x+=howlong*cos(M_PI/180.0*direction);
y+=howlong*sin(M_PI/180.0*direction);
glVertex2d(x,y);
glEnd();
}

void trunk(double howlong, double howwide)
{
glPushMatrix();
glTranslated(x,y,0);
glRotated(direction-90,0,0,1);
glBegin(GL_QUADS);
glVertex2d(-howwide/2,0);
glVertex2d(howwide/2,0);
glVertex2d(howwide/2,howlong);
glVertex2d(-howwide/2,howlong);
glEnd();
glPopMatrix();
x+=howlong*cos(M_PI/180.0*direction);
y+=howlong*sin(M_PI/180.0*direction);
}

void draw_tree1a(int loclevel, double width)
{
GLdouble keepx, keepy;
int keepd;

if(loclevel)
   {
   draw_tree1a(loclevel-1,width);
   draw_tree1a(loclevel-1,width);
   }
else
   {
   glColor3d(.3,.3,0);
   trunk(1.6/(pow(2.0,level+1)),width);
   }
}
#define TRUNKWID .001+.03/pow(2,level-loclevel)


void draw_tree1b(int loclevel)
{
GLdouble keepx, keepy;
int keepd;

if(loclevel)
   {
   draw_tree1a(loclevel-1,TRUNKWID);
   keepx=x; keepy=y; keepd=direction;
   direction += 60;   
   draw_tree1b(loclevel-1);
   x=keepx; y=keepy; direction = keepd;
   draw_tree1a(loclevel-1,TRUNKWID);
   draw_tree1a(loclevel-1,TRUNKWID);
   keepx=x; keepy=y; keepd=direction;
   direction -= 60;
   draw_tree1b(loclevel-1);
   x=keepx; y=keepy; direction = keepd;
   }
else
   {
   glColor3d(.3,1.0,.2);
   line_forward(1.6/(pow(2.0,level+1)));
   }
}

void draw_tree2b(int loclevel)
{
GLdouble keepx, keepy;
int keepd;

if(loclevel)
   {
   draw_tree1a(loclevel-1,TRUNKWID);

   keepx=x; keepy=y; keepd=direction;
   direction += 29;   
   draw_tree2b(loclevel-1);
   x=keepx; y=keepy; direction = keepd;

   draw_tree1a(loclevel-1,TRUNKWID);

   keepx=x; keepy=y; keepd=direction;
   direction -= 29;
   draw_tree2b(loclevel-1);
   x=keepx; y=keepy; direction = keepd;

   draw_tree2b(loclevel-1);   
   }
else
   {
   glColor3d(.3,1.0,.2);
   line_forward(1.6/(pow(2.0,level+1)));
   }
}

void draw_tree3b(int loclevel)
{
GLdouble keepx, keepy;
int keepd;

if(loclevel)
   {
   draw_tree1a(loclevel-1,TRUNKWID);

   keepx=x; keepy=y; keepd=direction;
   direction += 76;   
   draw_tree3b(loclevel-1);
   x=keepx; y=keepy; direction = keepd;

   keepx=x; keepy=y; keepd=direction;
   direction -= 84;   
   draw_tree3b(loclevel-1);
   x=keepx; y=keepy; direction = keepd;

   draw_tree1a(loclevel-1,TRUNKWID);

   draw_tree3b(loclevel-1);
   }
else
   {
   glColor3d(.3,1.0,.2);
   line_forward(1.6/(pow(2.0,level+1)));
   }
}

void draw_koch(int loclevel, float distance)
{
if(loclevel)
   {
   draw_koch(loclevel-1,distance/3);
   direction += 60;
   draw_koch(loclevel-1,distance/3);
   direction -= 120;
   draw_koch(loclevel-1,distance/3);
   direction += 60;
   draw_koch(loclevel-1,distance/3);
   }
else line_forward(distance);
}

void draw_koch2(int loclevel, float distance)
{
if(loclevel)
   {
   draw_koch2(loclevel-1,distance/3);
   direction += 60;
   draw_koch2(loclevel-1,distance/3);
   direction -= 120;
   draw_koch2(loclevel-1,distance/3);
   direction -= 120;
   draw_koch2(loclevel-1,distance/3);
   direction += 120;
   draw_koch2(loclevel-1,distance/3);
   direction += 120;
   draw_koch2(loclevel-1,distance/3);
   direction -= 60;
   draw_koch2(loclevel-1,distance/3);
   }
else line_forward(distance);
}

void draw_koch8(int loclevel, float distance)
{
if(loclevel)
   {
   draw_koch8(loclevel-1,distance/4);
   direction += 90;
   draw_koch8(loclevel-1,distance/4);
   direction -= 90;
   draw_koch8(loclevel-1,distance/4);
   direction -= 90;
   draw_koch8(loclevel-1,distance/4);
   draw_koch8(loclevel-1,distance/4);
   direction += 90;
   draw_koch8(loclevel-1,distance/4);
   direction += 90;
   draw_koch8(loclevel-1,distance/4);
   direction -= 90;
   draw_koch8(loclevel-1,distance/4);
   }
else line_forward(distance);
}

void draw_sierpinski(int loclevel, float u1, float u2, float v1, float v2, int orient)
{
float midu=(u1+u2)/2,midv=(v1+v2)/2;
if(loclevel) 
   {
   draw_sierpinski(loclevel-1,u1,midu,v1,midv,orient);    
   draw_sierpinski(loclevel-1,v1,v2,midu,(u1+midu)/2,1-orient);  
   draw_sierpinski(loclevel-1,v2,v1,midu,(u2+midu)/2,1-orient);
   draw_sierpinski(loclevel-1,midu,u2,v1,midv,orient);
   }
else if (orient) glVertex2f(midu,midv);
     else glVertex2f(midv,midu);
}

void draw_hilbert(int loclevel, float u1, float u2, float v1, float v2, int orient)
{
float midu=(u1+u2)/2,midv=(v1+v2)/2;

if (loclevel) 
   {
   draw_hilbert(loclevel-1,v1,midv,u1,midu,1-orient); 
   draw_hilbert(loclevel-1,midu,u2,v1,midv,orient);
   draw_hilbert(loclevel-1,midu,u2,midv,v2,orient);
   draw_hilbert(loclevel-1,v2,midv,midu,u1,1-orient);
   }
else if (orient) glVertex2f(midu,midv);
     else glVertex2f(midv,midu);
}

void draw_dragon2(int loclevel, float x1, float y1, float x2, float y2)
{
float xup = x1+((x2-x1)-(y2-y1))/2;
float yup = y1+((x2-x1)+(y2-y1))/2;

if (loclevel)
   {
   draw_dragon2(loclevel-1,x1,y1,xup,yup);
   draw_dragon2(loclevel-1,x2,y2,xup,yup);
   }
else
   {
   glBegin(GL_LINES);
   glVertex2f(x1,y1);
   glVertex2f(x2,y2);
   glEnd();
   }
}

void draw_hartman(int loclevel, float u1, float u2, float v1, float v2, int orient)
{
float midu1=(2*u1+u2)/3,midu2=(u1+2*u2)/3;
float midu=(u1+u2)/2,midv=(v1+v2)/2;

if (loclevel) 
   {
   draw_hartman(loclevel-1,v1,v2,u1,midu1,1-orient); 
   draw_hartman(loclevel-1,v2,v1,midu1,midu2,1-orient);
   draw_hartman(loclevel-1,v1,v2,midu2,u2,1-orient);
   }
else if (orient) glVertex2f(midu,midv);
     else glVertex2f(midv,midu);
}

void draw_peano(int loclevel, float distance)
{
if(loclevel)
   {
   draw_peano(loclevel-1,distance/3);
   direction -= 90;
   draw_peano(loclevel-1,distance/3);
   direction += 90;
   draw_peano(loclevel-1,distance/3);
   direction += 90;
   draw_peano(loclevel-1,distance/3);
   direction += 90;
   draw_peano(loclevel-1,distance/3);
   direction -= 90;
   draw_peano(loclevel-1,distance/3);
   direction -= 90;
   draw_peano(loclevel-1,distance/3);
   direction -= 90;
   draw_peano(loclevel-1,distance/3);
   direction += 90;
   draw_peano(loclevel-1,distance/3);
   }
else
   line_forward(distance);
}

void draw_dragon(int loclevel, float distance)
{
if(loclevel)
   {
   draw_dragon(loclevel-1,distance/M_SQRT2);
   direction += 90;
   draw_nogard(loclevel-1,distance/M_SQRT2);
   }
else line_forward(distance);
}

void draw_nogard(int loclevel, float distance)
{
if(loclevel)
   {
   draw_dragon(loclevel-1,distance/M_SQRT2);
   direction -= 90;
   draw_nogard(loclevel-1,distance/M_SQRT2);
   }
else line_forward(distance);
}

void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
switch(whichfrac) {
   case KOCH:       glColor3d(1,1,0); x=-.9; y=direction=0; draw_koch(level,1.8); break;
   case KOCH2:      glColor3d(1,1,0); x=-.9; y=direction=0; draw_koch2(level,1.8); break;
   case KOCH_8:     glColor3d(1,1,0); x=-.9; y=direction=0; draw_koch8(level,1.8); break;
   case SIERPINSKI: glColor3d(1,1,0); glBegin(GL_LINE_STRIP); draw_sierpinski(level,-.9,.9,0,.9,1); glEnd(); break;
   case HILBERT:    glBegin(GL_LINE_STRIP);glColor3d(1,1,0); draw_hilbert(level,-.9,.9,.9,-.9,0); glEnd(); break;
   case HARTMAN:    glBegin(GL_LINE_STRIP);glColor3d(1,1,0); draw_hartman(level,-.9,.9,.9,-.9,0); glEnd(); break;
   case PEANO:      glColor3d(1,1,0); x=-.9; y=direction=0; draw_peano(level,1.8); break;
   case DRAGON:     glColor3d(1,1,0); x=y=direction=0; draw_dragon(level,.9); break;
   case DRAGON2:    glColor3d(1,0,1); draw_dragon2(level,-.5,0,.5,0); break;
   case TREE:       x=0; direction=90; y=-.9; draw_tree1b(level);     break;
   case TREE2:      x=0; direction=90; y=-.9; draw_tree2b(level);     break;
   case TREE3:      x=0; direction=90; y=-.9; draw_tree3b(level);     break;
   }
}

void init (void) 
{
   glClearColor(0.0, 0.0, 0.0, 0.0);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(-1,1,-1,1);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void adjust_level(int newlevel)
{
level=newlevel;
glutPostRedisplay();
}

void keyboard(unsigned char dakey, int x, int y)
{
#define MOVESIZE .01
if( dakey >= '0' && dakey <= '9') adjust_level(dakey-'0');
else switch(dakey) {
   case 27: exit(0);
   case '=':
      glTranslated(-tx,-ty,0);
      glScalef(2,2,1);
      sc *= 2;
      glTranslated(tx,ty,0);    
      glutPostRedisplay();
      break;  
   case '-':
      glTranslated(-tx,-ty,0);
      glScalef(.5,.5,1);
      sc /= 2;
      glTranslated(tx,ty,0);    
      glutPostRedisplay();
      break;
   case 'l' :
      glTranslated(MOVESIZE/sc,0,0);
      tx += MOVESIZE/sc;
      glutPostRedisplay();
      break;
   case 'j' :
      glTranslated(-MOVESIZE/sc,0,0);
      tx -= MOVESIZE/sc;
      glutPostRedisplay();
      break;
   case 'i' :
      glTranslated(0,MOVESIZE/sc,0);
      ty += MOVESIZE/sc;
      glutPostRedisplay();
      break;
   case 'k' :
      glTranslated(0,-MOVESIZE/sc,0);
      ty -= MOVESIZE/sc;
      glutPostRedisplay();
      break;
   }
#undef MOVESIZE
}

void handle_level_menu(int value)
{
adjust_level(value);
}

void handle_main_menu(int value)
{
if(value==1) exit(0);
}

void handle_frac_menu(int value)
{
whichfrac = (enum fractal_type) value;
glutPostRedisplay();
}

int main(int argc, char** argv)
{
int ii;
char lstring[5];

   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (800, 800); 
   glutInitWindowPosition (100, 100);
   glutCreateWindow ("Fractal curves");
   init();
   glutDisplayFunc(display);
   glutKeyboardFunc(keyboard);
   frac_menu_id = glutCreateMenu(handle_frac_menu);
   glutAddMenuEntry("Koch snowflake",KOCH);
   glutAddMenuEntry("Koch snowflake 2",KOCH2);
   glutAddMenuEntry("Koch 8 segment",KOCH_8);
   glutAddMenuEntry("Sierpinski",SIERPINSKI);
   glutAddMenuEntry("Hilbert",HILBERT);
   glutAddMenuEntry("Hartman",HARTMAN);
   glutAddMenuEntry("Peano",PEANO);
   glutAddMenuEntry("Dragon",DRAGON);
   glutAddMenuEntry("Dragon 2 ",DRAGON2);
   glutAddMenuEntry("Tree 1",TREE);
   glutAddMenuEntry("Tree 2",TREE2);
   glutAddMenuEntry("Tree 3",TREE3);
   level_menu_id = glutCreateMenu(handle_level_menu);
   for(ii=0;ii<20;ii++)
      {
      sprintf(lstring,"%d",ii);
      glutAddMenuEntry(lstring,ii);
      }
   main_menu_id = glutCreateMenu(handle_main_menu);
   glutAddSubMenu("Fractal", frac_menu_id);
   glutAddSubMenu("Level", level_menu_id);
   glutAddMenuEntry("Quit",1);
   glutAttachMenu(GLUT_RIGHT_BUTTON);
   glutMainLoop();
   return 0;   /* ANSI C requires main to return int. */
}
