// jch Sep 2003 making Seasons. This grows and zooms forsythia.
// $Header: /Users/jch/zStuff/cvsstuff/cvsroot/trivo/forsythia.cpp,v 1.13 2005/12/23 20:12:26 jch Exp $
// Copyright 2003 YON - Jan C. Hardenbergh. Permission to copy is
// granted as long as this copyright and this URL are maintained:
// http://www.jch.com/NURBS/
//
#include <GL/glut.h>
#include <stdlib.h>
#include <image.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <list>

#define STOP_GROWTH 50
#define START_FLOWERS 90
#define START_GREEN 120

const double kOuterLimitX = 6;
const double kOuterLimitY = 4;
const double kSquishCenter = 0.45;

static int gFrame = 0;
static int gLastFrame = 244;
static int gSaveAnim = 0;
static int gFlipY = 1;
static int gVerbose = 0;
static int gNumShoots = 13;
static int gMaxBranches = 200;
static int gOffsetFrame = 275;
static double gSpread = 0.1;
static double gScale = 0.04;
static double gBlossomScale = 0.08;
static double gSprout = 0.25;
static double gUppy = 0.2;
static float gRGColor = 1.0;
static float gGColor = 1.0;
static double currentCenter[2] = {0.45,0};
static double currentZoom = 4;
static double deltaCenter[2] = {0.0025,0.015};
static double deltaZoom = -.03;

class fNode {
public:
  fNode() { m_point[0] = m_point[1] = 0; }
  fNode(const fNode &other) {
    m_point[0] = other.m_point[0];
    m_point[1] = other.m_point[1];
  }

  fNode(double x, double y) {
    m_point[0] = x;
    m_point[1] = y;
  }

  const fNode& operator= (const fNode& right) {return *this;}

  double m_point[2];
};

// random near one
double ranDub()
{
  double chance = ((double)rand())/((double)RAND_MAX);
  return chance;
}

// random near one
double rnOne()
{
  double ret = 0.5 - ranDub();
  return 1.0 + (ret*ret*ret);
}

bool oneInTen(double tenth)
{
  if (ranDub() < tenth)
    return true;
  
  return false;
}

/********************************************************************
 *
 */

class fBranch {
public:
  fBranch() { 
    // m_nodes.clear();
    m_growthVector[0] = m_growthVector[1] = 0;
    m_blossomColor[0] = m_blossomColor[1] = 1; m_blossomColor[2] = 0;
    m_lastBranchLeft = true;
    m_lowest = 0.8;
  }

  fBranch(const fBranch &other) {
    // m_nodes.clear();
    list<fNode>::const_iterator iter;
    for (iter = other.m_nodes.begin(); iter != other.m_nodes.end(); ++iter)
    {
      m_nodes.push_back(*iter);
    }
    m_growthVector[0] = other.m_growthVector[0]; 
    m_growthVector[1] = other.m_growthVector[1]; 
    m_blossomColor[0] = other.m_blossomColor[0]; 
    m_blossomColor[1] = other.m_blossomColor[1]; 
    m_blossomColor[2] = other.m_blossomColor[2]; 
    m_lastBranchLeft = other.m_lastBranchLeft; 
    m_lowest = other.m_lowest; 
  }

  fBranch(const fNode &initNode, const double growthVec[2]) { 
    // m_nodes.clear();
    m_nodes.push_back(initNode);
    m_growthVector[0] = growthVec[0];
    m_growthVector[1] = growthVec[1];

    float red = rnOne();
    float green = rnOne();
    if (red > 1) red = 1;
    if (green > 1) green = 1;
    m_blossomColor[0] = red;
    m_blossomColor[1] = green;
    m_blossomColor[2] = 0;
    m_lastBranchLeft = true;
    m_lowest = 0.1 + 0.08*ranDub();
  }


  const list<fNode> &getNodes() const {return m_nodes;}
  list<fNode> *getNodesPointer() {return &m_nodes;}

  list<fNode> m_nodes;
  double m_growthVector[2];  
  float m_blossomColor[3];
  bool m_lastBranchLeft;
  double m_lowest;
};

list<fBranch> gBranches;

#define PIE 3.141593
static double dAngle = 0.1;
int getArcVertices(double center[2], double angles[2], double radii[2], int maxVertex, GLfloat vertices[][2])
{
  double angle;
  double radius;
  double dRadius;
  int steps, i;
  double startAngle;
  double endAngle;
  double deltaAngle;

  startAngle = angles[0];
  endAngle = angles[1];

  // while (startAngle > 2*PIE) startAngle -= 2*PIE;
  // while (startAngle > 2*PIE) startAngle -= 2*PIE;
  while (endAngle < startAngle) endAngle += 2*PIE;

  steps = (int)(fabs(endAngle - startAngle)/dAngle);
  if (steps >= maxVertex)
    return -1;

  deltaAngle = (endAngle - startAngle)/(double)(steps);
  dRadius = (radii[1] - radii[0])/(double)(steps);

  radius = radii[0];
  angle = startAngle;
	
  for(i = 0; i <= steps; i++)
    {
      vertices[i][0] = (GLfloat)(center[0] + radius*cos(angle));
      vertices[i][1] = (GLfloat)(center[1] + radius*sin(angle));
      radius += dRadius;
      angle += deltaAngle;
    }
  return steps;
}

#define MAX_VERTS 200
void drawHalfCircleSquashed(double center[2], double radius, double scale)
{
  GLfloat vertices[MAX_VERTS][2];
  int steps, i;
  double radii[2] = {radius, radius};
  double angles[2] = {PIE, 2*PIE};

  steps = getArcVertices(center, angles, radii, MAX_VERTS, vertices);

  glBegin(GL_TRIANGLE_FAN);
  glVertex2d(center[0],center[1]*scale);
  for(i = 0; i <= steps; i++)
    {
      glVertex2f(vertices[i][0],vertices[i][1]*scale);
    }
  glEnd();
}

void setZoom(double center[2], double scale)
{
  int vp[4];
  double width, height;

  glGetIntegerv(GL_VIEWPORT, vp);

  width = (double)vp[2];
  height = (double)vp[3];
  if (width > height)
    {
      width = width/height;
      height = 1;
    }
  else
    {
      height = height/width;
      width = 1;
    }

  if (scale < 0.01)
    scale = 0.01;

  height *= scale;
  width *= scale;

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glOrtho(center[0]-width, center[0]+width, 
	  center[1]-gFlipY*height, center[1]+gFlipY*height, -1, 1);

  glMatrixMode (GL_MODELVIEW);
}

double Normalize2D(double vec[2])
{
  double len = sqrt(vec[0]*vec[0]+vec[1]*vec[1]);
  vec[0] /= len;
  vec[1] /= len;
  return len;
}

void init(void) 
{
  glClearColor (1.0, 1.0, 1.0, 0.0);
  glShadeModel (GL_FLAT);
  // init lists
  double left = -gNumShoots/2*gSpread+0.04+0.45;
  double startVec[2] = { -0.707, 0.707 };
  int i;

  for (i = 0; i < gNumShoots; i++)
    {
      gBranches.push_back(fBranch(fNode(left, 0), startVec));

      left += gSpread;
      startVec[0] = left;

      if (left < 0.45)
	startVec[1] += gSpread;
      else
	startVec[1] -= gSpread;

      Normalize2D(startVec);
    }
}

void doTick(void)
{
  static double color = 1.0;

  //  glClearColor (color, color, color, 0.0);
  //color -= 0.04;
  //if (color < 0)
  //  color = 1;
  glutPostRedisplay();
}

/*
  self.frame += 1
  self.SaveTo("frame%s.jpg"%(string.zfill("%d"%(self.frame),4)))
*/
void saveFrame(int frameFile)
{
  static int gScratchSize;
  static void *gScratch;
  GLint vp[4];
  Image image;
  char filename[80];
  int i;

  glGetIntegerv(GL_VIEWPORT, vp);
  if (gScratchSize == 0)
    {
      gScratchSize = vp[2]*vp[3]*4;
      gScratch = (void *)malloc(gScratchSize);
      if (!gScratch)
	return;
    }
  
  if (vp[2]*vp[3]*4 > gScratchSize)
    return;

  glReadBuffer(GL_BACK);
  glReadPixels(0,0, vp[2], vp[3], GL_RGB, GL_UNSIGNED_BYTE, gScratch);

  image.width = vp[2];
  image.height = vp[3];
  image.isGrey = 0;
  image.cmapSize = 0;
  image.data = gScratch;

  sprintf(filename, "frame%4d.jpg", frameFile);
  for (i = 0; i < strlen(filename); i++) 
    if (filename[i] == ' ') filename[i] = '0';

  WriteJPEG(filename, 100, &image);
}


void checkZoomActionForFrame(int frame)
{
  if (frame == STOP_GROWTH)
    {
      deltaCenter[0] = 0;
      deltaCenter[1] = 0;
      deltaZoom = -0.04;
    }
  else if (frame == START_FLOWERS)
    {
      deltaZoom = 0;
    }
  else if (frame == START_GREEN+30)
    {
      deltaCenter[0] = 0.0;
      deltaCenter[1] = 0.005;
      deltaZoom = -0.016;
    }
  else if (frame == START_GREEN+70)
    {
      deltaCenter[0] = 0.01;
      deltaCenter[1] = 0;
      deltaZoom = -0.0032;
    }
  /*
  else if (frame == 120) // was 96
    {
      deltaCenter[0] = 0.0;
      deltaZoom = .055; // was 7
    }
  */
  /*
  else if (frame == 14)
    {
      double cen2[2] = {0.45,0.0};
      setZoom(cen2, 0.90);
    }
  else if (frame == 16)
    {
      double cen2[2] = {0.45,0.0};
      setZoom(cen2, 1.0);
    }
  else if (frame == 20)
    {
      double cen2[2] = {0.45,0.0};
      setZoom(cen2, 1.30);
    }
  */
  currentCenter[0] += deltaCenter[0];
  currentCenter[1] += deltaCenter[1];
  currentZoom += deltaZoom;
  setZoom(currentCenter, currentZoom);
}

double Distance(double center[2], double endPoint[2])
{
  double distance, dx, dy;

  dx = center[0] - endPoint[0];
  dy = center[1] - endPoint[1];
  
  distance = sqrt(dx*dx+dy*dy);

  return distance;
}

void drawBlossom(const double point[2])
{
  glBegin(GL_TRIANGLE_STRIP);
      glVertex2dv(point);
      glVertex2d(point[0]-gBlossomScale*rnOne()*0.3,point[1]+gBlossomScale*rnOne()*0.25);
      glVertex2d(point[0]+gBlossomScale*(0.1+0.25*ranDub()),
		 point[1]+gBlossomScale*(0.08+0.2*ranDub()));
      glVertex2d(point[0]+gBlossomScale*rnOne()*0.1,point[1]+gBlossomScale*rnOne()*0.4);
  glEnd();
}

void drawGroundAndSky(int frame)
{
  float deltaGreen = 0.0015;

  double green = gGColor + deltaGreen*2;
  glColor4f(gGColor,green,gGColor,1.0);
  glBegin(GL_TRIANGLE_STRIP);
  glVertex2d(-kOuterLimitX,-kOuterLimitY);
  glVertex2d(-kOuterLimitX,0);
  glVertex2d(kOuterLimitX,-kOuterLimitY);
  glVertex2d(kOuterLimitX,0);
  glEnd();

  if (frame == START_GREEN)
    deltaGreen = 0.01;
  gGColor -= deltaGreen;

  float deltaRG = 0.01;
  if (frame < STOP_GROWTH)
    deltaRG = 0.007;
  else if (frame > START_FLOWERS+16)
    deltaRG = 0;

  glShadeModel(GL_SMOOTH);
  glBegin(GL_TRIANGLE_STRIP);
  glColor4f(gRGColor-deltaRG,gRGColor-deltaRG,1.0,1.0);
  glVertex2d(-kOuterLimitX,kOuterLimitY);
  glColor4f(1.0,1.0,1.0,1.0);
  glVertex2d(-kOuterLimitX,0);
  glColor4f(gRGColor-deltaRG,gRGColor-deltaRG,1.0,1.0);
  glVertex2d(kOuterLimitX,kOuterLimitY);
  glColor4f(1.0,1.0,1.0,1.0);
  glVertex2d(kOuterLimitX,0);
  glEnd();
  glShadeModel(GL_FLAT);
  gRGColor -= deltaRG;
}

void drawFlowers(const fBranch &shoot, int every)
{
  int one = 0;

  if (shoot.getNodes().size() < 2)
    return;

  glColor3fv(shoot.m_blossomColor);

  list<fNode>::const_iterator iter;
  for (iter = shoot.getNodes().begin(); iter != shoot.getNodes().end(); ++iter)
    {
      if (iter->m_point[1] > shoot.m_lowest && ((every == 1) || ((++one)%every == 0)))
	drawBlossom(iter->m_point);
    }
}

void drawGreen(const fBranch &shoot, int every)
{
  int one = 13;

  if (shoot.getNodes().size() < 2)
    return;

  if ((shoot.m_blossomColor[0] > 0.2) && (every == 1))
    {
      float *t = (float *)shoot.m_blossomColor;
      t[0] *= 0.8;
      t[2] *= 0.8;
    }
  glColor3fv(shoot.m_blossomColor);
  float greener[3] = {shoot.m_blossomColor[0]*0.37, shoot.m_blossomColor[1], shoot.m_blossomColor[2]*0.28};

  list<fNode>::const_iterator iter;
  for (iter = shoot.getNodes().begin(); iter != shoot.getNodes().end(); ++iter)
    {
      if (iter->m_point[1] > shoot.m_lowest)
	{
	  if ((every > 1) && (((one++)%every) == 0))
	    glColor3fv(greener);
	  else
	    glColor3fv(shoot.m_blossomColor);
	  drawBlossom(iter->m_point);
	}
    }
}

void drawShoot(const fBranch &shoot)
{
  int count = shoot.getNodes().size();

  if (count < 2)
    return;

  GLfloat vertices[(count+1)*2][2];// = new GLfloat[count*2];

  glColor4f(0.0,0.0,0.0,1.0);

  glBegin(GL_LINE_STRIP);
  list<fNode>::const_iterator iter;
  for (iter = shoot.getNodes().begin(); iter != shoot.getNodes().end(); ++iter)
    {
      glVertex2dv(iter->m_point);
    }
  glEnd();
  list<fNode>::const_iterator iter2 = 0;
  list<fNode>::const_iterator iter3 = 0;
  iter = shoot.getNodes().begin();
}

void draw(int doFlowers)
{
  glLineWidth(4.0);
  glColor4f(0.0,0.0,0.0,1.0);

  glBegin(GL_LINES);
  glVertex2d(-kOuterLimitX,0);
  glVertex2d(kOuterLimitX,0);
  glEnd();

  glLineWidth(2.0);
  
  list<fBranch>::const_iterator iter;
  for (iter = gBranches.begin(); iter != gBranches.end(); ++iter)
    {
      drawShoot(*iter);
    }

  if (doFlowers == 1)
    {
      static int every = 16;
      srand(1969);
      for (iter = gBranches.begin(); iter != gBranches.end(); ++iter)
	{
	  drawFlowers(*iter, every);
	}
      every = (every > 1)?(every>>1):1;
    }
  else if (doFlowers == 2)
    {
      static int every = 16;
      srand(1969);
      for (iter = gBranches.begin(); iter != gBranches.end(); ++iter)
	{
	  drawGreen(*iter, every);
	}
      if (gFrame % 3 == 0)
	every = (every > 1)?(every>>1):1;
    }
}

void grow(int frame)
{
  if (frame & 0x1)
    return;

  list<fBranch>::iterator iter;
  for (iter = gBranches.begin(); iter != gBranches.end(); ++iter)
    {
      const fNode &last = iter->getNodes().back();
      double scale = gScale * rnOne();
      double nextVec[2];
      nextVec[0] = iter->m_growthVector[0];
      nextVec[1] = iter->m_growthVector[1] + gUppy * rnOne();
      Normalize2D(nextVec);
      iter->getNodesPointer()->push_back(fNode(
				  last.m_point[0]+scale*iter->m_growthVector[0], 
				  last.m_point[1]+scale*iter->m_growthVector[1]));
      iter->m_growthVector[0] = nextVec[0];
      iter->m_growthVector[1] = nextVec[1];

      if ((iter->getNodes().size() > 3) && oneInTen(gSprout) && gBranches.size() < gMaxBranches)
	{
	  double newVec[2] = {rnOne(), rnOne()};
	  if (iter->m_lastBranchLeft)
	    newVec[0] = -newVec[0];
	  iter->m_lastBranchLeft = !iter->m_lastBranchLeft;
	  Normalize2D(newVec);
	  gBranches.push_back(fBranch(fNode(last.m_point[0], last.m_point[1]),
				      newVec));
	}
    }
}

void display(void)
{
  int c;

  gFrame++;

  checkZoomActionForFrame(gFrame);

  // Clear the window with current clearing color
  glClear(GL_COLOR_BUFFER_BIT);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  if (gFrame < STOP_GROWTH)
    grow(gFrame);

  drawGroundAndSky(gFrame);

  if (gFrame < STOP_GROWTH*2)
    {
      double scale = 1 - 0.7*((double)gFrame/(double)(STOP_GROWTH-4));
      if (scale < 0) scale = 0;
      double squishCenter[2] = {kSquishCenter, 0};
      glColor4f(0,0,0,1.0);     
      drawHalfCircleSquashed(squishCenter, 0.45, scale);
    }

  int stage = (gFrame < START_FLOWERS)?0:((gFrame < START_GREEN)?1:2);
  draw(stage);

  // Flush drawing commands
  glFlush();
  if (gSaveAnim) 
    {
      saveFrame(gFrame+gOffsetFrame);
    }
  if (gFrame >= gLastFrame)
    {
      saveFrame(9999);
      printf("final frame %d, zoom %g, center %g, %g\n", gFrame+gOffsetFrame, 
	     currentZoom, currentCenter[0], currentCenter[1]);
      exit(0);
    }

  glutSwapBuffers();
}

void reshape (int w, int h)
{
  double center[2] = {0.45,0};
  // Prevent a divide by zero
  if(h == 0)
    h = 1;

  glViewport (0, 0, (GLsizei) w, (GLsizei) h); 

  setZoom(center, 4.0);
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case 27:
         exit(0);
         break;
   }
}

int main(int argc, char** argv)
{
  int i;
  char *texfile = 0;
  int winSizeX = 704;
  int winSizeY = 576;

  glutInit(&argc, argv);
  
  for (i = 1; i < argc; i++)
    {
      if (argv[i][0] != '-') /* ignore args with leading - */
	{
	    texfile = argv[i];
	}
      else if (!strcmp("-save",argv[i]))
	{
	  gSaveAnim = 1;
	}
      else if (!strcmp("-flip",argv[i]))
	{
	  gFlipY = -1;
	}
      else if (!strcmp("-last",argv[i]))
	{
	  i++; if (i < argc)
	    gLastFrame = atoi(argv[i]);
	  else
	    gLastFrame = 3;
	}
      else if (!strcmp("-shoots",argv[i]))
	{
	  i++; if (i < argc)
	    gNumShoots = atoi(argv[i]);
	}
      else if (!strcmp("-scale",argv[i]))
	{
	  i++; if (i < argc)
	    gScale = atof(argv[i]);
	}
      else if (!strcmp("-sprout",argv[i]))
	{
	  i++; if (i < argc)
	    gSprout = atof(argv[i]);
	}
      else if (!strcmp("-branch",argv[i]))
	{
	  i++; if (i < argc)
	    gMaxBranches = atoi(argv[i]);
	}
      else if (!strcmp("-big",argv[i]))
	{
	  winSizeX = 1024;
	  winSizeY = 838;
	}
      else if (!strcmp("-start3",argv[i]))
	{
	  gFrame = 20;
	}
      else if (!strcmp("-v",argv[i]))
	{
	  gVerbose = 0;
	}
      else
	{
	  printf("YOU GOOFED UP! - %s\n", argv[i]);
	  printf("forsythia - groweth with these arguments:\n");
	  printf("-save , -flip , -last <frameNum> , -scale <nodeLen> , -start3, -v\n");
	  printf("-shoots <n> how many should we start with\n");
	  printf("-sprout <.f> likely hood of having a branch sprout at a node\n");
	  printf("-branch <n> how many branches should we have total\n");
	  exit(0);
	}
    }

   glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize (winSizeX, winSizeY); 
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display); 
   glutIdleFunc(doTick); 
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return 0;
}
