// jch Sep 2003 making Seasons. This grows day lilies.
// $Header: /Users/jch/zStuff/cvsstuff/cvsroot/trivo/daylily.cpp,v 1.8 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 PHASE_ONE  30
#define START_SLOW 60
#define START_FLOWERS 80
#define END_FLOWERS 160

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

static int gFrame = 0;
static int gLastFrame = 250;
static int gSaveAnim = 0;
static int gFlipY = 1;
static int gVerbose = 0;
static int gNumPlants = 17;
static int gMaxBranches = 200;
static int gOffsetFrame = 519;
static double gSpread = 2.0;
static double gScale = 0.4;
static double gBlossomScale = 0.08;
static double gSprout = 0.25;
static double gUppy = 0.2;
static float gRGColor = 0.77;
static float gGColor = 1.0;
static double gLeafWidth = 0.04; // scaled by gScale if changed.
static double gFineness = 0.1;
static double currentCenter[2] = {-4.05,1.1};
static double currentZoom = 0.16;
static double deltaCenter[2] = {0.06,-0.00333};
static double deltaZoom = .04;
static double gBounds[4];

#define MY_ASSERT(x) {if (!(x)) {printf(" died\n"); exit(1);}}

// 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;
}

#define PIE 3.141593
int getArcVertices(const double center[2], const double angles[2], double radius, int maxVertex, double dAngle, 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 (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));
      // printf("gav %d, %g, %g, %g\n", i, angle, vertices[i][0], vertices[i][1]);
      //radius += dRadius;
      angle += deltaAngle;
    }
  return steps;
}


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

class dlFlower {
public:
  dlFlower() { m_point[0] = m_point[1] = 0; m_tick = 0; m_angle = 0; m_phase = 0; }
  dlFlower(const dlFlower &other) {
    m_point[0] = other.m_point[0];
    m_point[1] = other.m_point[1];
    m_tick = other.m_tick;
    m_angle = other.m_angle;
    m_phase = other.m_phase;
  }

  dlFlower(double x, double y) {
    m_point[0] = x;
    m_point[1] = y;
    m_tick = 0;
    m_angle = PIE*0.2+ranDub()*.8;
    m_phase = 0;
  }

  void draw() const {
    dlFlower *pMode = (dlFlower *)this;
    pMode->m_tick++;
    if (m_phase == 0) {
      if (m_tick > 12 || ranDub() > 0.8)
	{
	  pMode->m_phase = 1;
	  pMode->m_tick = 1;
	}
    }
    if (m_phase == 0)
      {
	/*
	glColor3d(0,1,0);
	glBegin(GL_POLYGON);
	glVertex2dv(m_point);
	glVertex2d(m_point[0]+0.25*cos(m_angle), m_point[1]+0.25*sin(m_angle));
	glVertex2d(m_point[0]+0.15*cos(m_angle), m_point[1]+0.1*sin(m_angle));
	glEnd();
	*/
      }
    else if (m_tick < 5)
      {
	glBegin(GL_POLYGON);
	glVertex2dv(m_point);
	glVertex2d(m_point[0]+0.1*cos(m_angle), m_point[1]+0.15*sin(m_angle));
	glVertex2d(m_point[0]+0.3*cos(m_angle), m_point[1]+0.4*sin(m_angle));
	glVertex2d(m_point[0]+0.7*cos(m_angle), m_point[1]+0.3*sin(m_angle));
	glVertex2d(m_point[0]+0.15*cos(m_angle), m_point[1]+0.1*sin(m_angle));
	glEnd();
      }
    else
      pMode->m_phase = 0;
  }

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

  double m_point[2];
  double m_angle;
  int m_tick;
  int m_phase;
};

class dlLeaf {
public:
  dlLeaf() 
  { 
    m_point[0] = m_point[1] = 0;  m_center[0] = 0; m_center[1] = 0;
    m_angles[0] = 0; m_angles[1] = 0; m_radius = 1; m_left = 0;
    m_leafColor[0] = 0; m_leafColor[1] = 0; m_leafColor[2] = 0; 
  }

  dlLeaf(const dlLeaf &other) {
    m_point[0] = other.m_point[0];
    m_point[1] = other.m_point[1];
    m_center[0] = other.m_center[0]; 
    m_center[1] = other.m_center[1];
    m_angles[0] = other.m_angles[0]; 
    m_angles[1] = other.m_angles[1]; 
    m_leafColor[0] = other.m_leafColor[0]; 
    m_leafColor[1] = other.m_leafColor[1]; 
    m_leafColor[2] = other.m_leafColor[2]; 
    m_radius = other.m_radius;
    m_left = other.m_left;
  }

  dlLeaf(double x, double y, int left, double radius ) {
    m_point[0] = x;
    m_point[1] = y;
    m_radius = radius;
    m_left = left;
    // arc length = radius * ( angle2 - angle1 )
    // so, we like angle2 = PIE*4/9 when r = 2. which means we like 
    // length = PIE*8/9, so, angle = PIE*8/9 / radius
    double angle = 0.44 * PIE / radius * rnOne();
    if (angle > PIE) angle = PIE*0.9;
    m_center[1] = m_point[1];
    m_center[0] = m_point[0] + radius * ((left)?-1:1);
    m_angles[0] = (left)?(0):(PIE-angle);
    m_angles[1] = (left)?(angle):(PIE);

    m_leafColor[0] = 0.1 + 0.5*ranDub(); 
    m_leafColor[1] = 1; 
    m_leafColor[2] = m_leafColor[0];

  }

  void getEnd(double point[2]) {
    point[0] = m_radius * cos(m_angles[0]) + m_center[0];
    point[1] = m_radius * sin(m_angles[0]) + m_center[1];
  }

  void draw() const {
    draw(gLeafWidth);
  }

  void draw(double width) const {
    GLfloat vertices[200][2];
    GLfloat vertice2[200][2];
    int i;

    int r1 = getArcVertices(m_center, m_angles, m_radius+width, 200, gFineness, vertices);
    MY_ASSERT(r1> 0);
    int r2 = getArcVertices(m_center, m_angles, m_radius-width, 200, gFineness, vertice2);
    MY_ASSERT(r2 > 0);
    if (r1 != r2)
      {
	printf("r1, r2 diff %d, %d\n", r1, r2);
	r1 = (r1<r2)?r1:r2;
      }

    double angle = (m_left)?(m_angles[1]+width):(m_angles[0]-width);
    double px = cos(angle)*m_radius+m_center[0];
    double py = sin(angle)*m_radius+m_center[1];

    glColor3dv(m_leafColor);

    glBegin(GL_TRIANGLE_STRIP); //LINE_STRIP); //POLYGON);

    if (!m_left)
      glVertex2d(px,py);
    else
      glVertex2dv(m_point);

    for(i = 0; i <= r1; i++)
      {
	glVertex2fv(vertices[i]);
	glVertex2fv(vertice2[i]);
      }

    if (m_left)
	glVertex2d(px,py);
    else
      glVertex2dv(m_point);

    glEnd();
    
  }

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

  double m_point[2];
  double m_center[2];
  double m_angles[2];
  double m_leafColor[3];
  double m_radius;
  int m_left;
};


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

class dlPlant {
public:
  dlPlant() { 
    m_stalk = 0;
    m_position[0] = m_position[1] = 0;
    m_blossomColor[0] = m_blossomColor[1] = 1; m_blossomColor[2] = 0;
  }

  dlPlant(const dlPlant &other) {
    // m_nodes.clear();
    list<dlFlower>::const_iterator iter;
    for (iter = other.m_flowers.begin(); iter != other.m_flowers.end(); ++iter)
    {
      m_flowers.push_back(*iter);
    }

    list<dlLeaf>::const_iterator iterleaf;
    for (iterleaf = other.m_leaves.begin(); iterleaf != other.m_leaves.end(); ++iterleaf)
    {
      m_leaves.push_back(*iterleaf);
    }

    m_stalk = new dlLeaf(*other.m_stalk);

    m_position[0] = other.m_position[0]; 
    m_position[1] = other.m_position[1]; 
    m_blossomColor[0] = other.m_blossomColor[0]; 
    m_blossomColor[1] = other.m_blossomColor[1]; 
    m_blossomColor[2] = other.m_blossomColor[2]; 
  }

  dlPlant(const double position[2]) { 
    m_position[0] = position[0];
    m_position[1] = position[1];

    float red = rnOne();
    float green = 0.53 * rnOne();
    float blue = 0.24 * rnOne();
    if (red > 1) red = 1;
    // #f48f42
    m_blossomColor[0] = red;
    m_blossomColor[1] = green;
    m_blossomColor[2] = blue;

    m_stalk = new dlLeaf(m_position[0], m_position[1], false, 8*gScale);
    m_stalk->m_leafColor[0] = 0;
    m_stalk->m_leafColor[2] = 0;

    double pos[2];
    m_stalk->getEnd(pos);

    m_flowers.push_back(dlFlower(pos[0], pos[1]));
    m_flowers.push_back(dlFlower(pos[0], pos[1]-pos[1]/20*rnOne()));
    m_flowers.push_back(dlFlower(pos[0]-pos[1]/20*rnOne(), pos[1]-pos[1]/15*rnOne()));

    int leaves = 6;
    double sx = position[0] - 2*gScale/(leaves-1);
    for (int i = 0; i < leaves; i++)
    {
      double radius = (leaves-1)/2 + 2*rnOne() - 0.8*rnOne()*abs(i-((leaves-1)/2));
      m_leaves.push_back(dlLeaf(sx, position[1], i<(leaves/2), radius*gScale));
      sx += gScale*rnOne()/leaves;
    }

  }

  void draw(int stage) const
  {
    m_stalk->draw(gLeafWidth*0.2);

    list<dlLeaf>::const_iterator iter;
    for (iter = getLeaves().begin(); iter != getLeaves().end(); ++iter)
    {
      iter->draw();
    }
    
    if (stage != 1)
      return;

    list<dlFlower>::const_iterator fiter;
    for (fiter = getFlowers().begin(); fiter != getFlowers().end(); ++fiter)
    {
      glColor3fv( m_blossomColor);
      fiter->draw();
    }
  }

  const list<dlFlower> &getFlowers() const {return m_flowers;}

  const list<dlLeaf> &getLeaves() const {return m_leaves;}

  list<dlFlower> m_flowers;
  list<dlLeaf> m_leaves;
  double m_position[2];  
  float m_blossomColor[3];
  float m_leafColor[3];
  dlLeaf *m_stalk;
};

list<dlPlant> gPlants;

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;

  gBounds[0] = center[0]-width;
  gBounds[1] = center[0]+width; 
  gBounds[2] = center[1]-height;
  gBounds[3] = center[1]+height;
  int yMax = 3;
  int yMin = 2;
  if (gFlipY<0) { yMin = 3; yMax = 2; }
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glOrtho(gBounds[0], gBounds[1], gBounds[yMin], gBounds[yMax], -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);

  int i;
  double start[2] = {-gSpread*1.5,0};
  for (i = 0; i < gNumPlants; i++)
    {
      gPlants.push_back(dlPlant(start));
      start[0] += (2*rnOne()*gSpread/(gNumPlants-1) + 4*ranDub()/gNumPlants);
    }
}

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 == PHASE_ONE)
    {
      printf("final frame %d, zoom %g, center %g, %g\n", gFrame+gOffsetFrame, 
	     currentZoom, currentCenter[0], currentCenter[1]);
      deltaCenter[0] = 0.07;
      deltaCenter[1] = 0.0000523;
      deltaZoom = 0.037333;
    }
  if (frame == START_SLOW)
    {
      printf("final frame %d, zoom %g, center %g, %g\n", gFrame+gOffsetFrame, 
	     currentZoom, currentCenter[0], currentCenter[1]);
      deltaCenter[0] = 0.04;
      deltaCenter[1] = 0;
      deltaZoom = 0.024;
    }
  else if (frame == START_FLOWERS)
    {
      printf("final frame %d, zoom %g, center %g, %g\n", gFrame+gOffsetFrame, 
	     currentZoom, currentCenter[0], currentCenter[1]);
      deltaCenter[0] = 0;
      deltaZoom = 0;
    }
  else if (frame == END_FLOWERS)
    {
      deltaCenter[0] = 0.1;
      deltaCenter[1] = 0.02;
      deltaZoom = 0.02;
    }
  /* - midnight
static double currentCenter[2] = {-3.6,1.2};
static double currentZoom = 0.16;
static double deltaCenter[2] = {0.1,-0.005};
static double deltaZoom = .04;

start slow frame 560, zoom 1.72, center 0.3, 1.005
start flowers frame 600, zoom 2.92, center 0.7, 1.005
end_flowers frame 680, zoom 2.92, center 0.7, 1.005
final frame 770, zoom 4.74, center 9.8, 2.825
  */
  /*
  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)
{

  glShadeModel(GL_SMOOTH);
  glBegin(GL_TRIANGLE_STRIP);
  glColor4f(0,1,0,1.0);
  glVertex2d(gBounds[0],gBounds[2]);
  glColor4f(0.8,1,0.75,1.0);
  glVertex2d(gBounds[0],0);
  glColor4f(0,1,0,1.0);
  glVertex2d(gBounds[1],gBounds[2]);
  glColor4f(0.8,1,0.75,1.0);
  glVertex2d(gBounds[1],0);
  glEnd();

  //  gGColor -= deltaGreen;

  float deltaRG = 0.01;

  glBegin(GL_TRIANGLE_STRIP);
  glColor4f(gRGColor-deltaRG,gRGColor-deltaRG,1.0,1.0);
  glVertex2d(gBounds[0],gBounds[3]);
  glColor4f(1.0,1.0,1.0,1.0);
  glVertex2d(gBounds[0],0);
  glColor4f(gRGColor-deltaRG,gRGColor-deltaRG,1.0,1.0);
  glVertex2d(gBounds[1],gBounds[3]);
  glColor4f(1.0,1.0,1.0,1.0);
  glVertex2d(gBounds[1],0);
  glEnd();
  glShadeModel(GL_FLAT);
  gRGColor -= deltaRG;
}

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

  glBegin(GL_LINES);
  glVertex2d(gBounds[0],0);
  glVertex2d(gBounds[1],0);
  glEnd();

  glLineWidth(2.0);
  
  list<dlPlant>::const_iterator iter;
  for (iter = gPlants.begin(); iter != gPlants.end(); ++iter)
    {
      iter->draw(stage);
    }
}

void grow(int frame)
{
  list<dlPlant>::iterator iter;
  for (iter = gPlants.begin(); iter != gPlants.end(); ++iter)
    {
      //      const fNode &last = iter->getNodes().back();
      // double scale = gScale * rnOne();
    }
}

void display(void)
{
  int c;

  gFrame++;

  checkZoomActionForFrame(gFrame);

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

  drawGroundAndSky(gFrame);

  int stage = (gFrame < START_FLOWERS)?0:((gFrame < END_FLOWERS)?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,0};
  // Prevent a divide by zero
  if(h == 0)
    h = 1;

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

  setZoom(center, 1.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("-plants",argv[i]))
	{
	  i++; if (i < argc)
	    gNumPlants = atoi(argv[i]);
	}
      else if (!strcmp("-scale",argv[i]))
	{
	  double ogs = gScale;
	  i++; if (i < argc)
	    gScale = atof(argv[i]);
	  gLeafWidth *= (gScale/ogs);
	}
      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("-start3",argv[i]))
	{
	  gFrame = 20;
	}
      else if (!strcmp("-big",argv[i]))
	{
	  winSizeX = 1024;
	  winSizeY = 838;
	}
      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("-plants <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;
}
