/* -*- Mode: C; tab-width: 2 -*- */
/* glcells --- Cells growing on your screen */

/*-
 * Cells growing on your screen
 *
 * Copyright (c) 2007 by Matthias Toussaint
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * 2007: Written by Matthias Toussaint
 * 0.1 Initial version
 * 0.2 Bugfixes (threading) and code cleanup by Jamie Zawinski
 *     Window scaling bug + performance bug in tick()
 */
 
#include <sys/time.h> /* gettimeofday */

#include "xlockmore.h"
#include <math.h>

/**********************************
  DEFINES
 **********************************/

#define INDEX_OFFSET 100000
#define NUM_CELL_SHAPES 10

#define release_glcells 0
#define glcells_handle_event xlockmore_no_events

#define DEF_DELAY     "20000"
#define DEF_MAXCELLS  "800"
#define DEF_RADIUS    "40"
#define DEF_SEEDS     "1"
#define DEF_QUALITY   "3"
#define DEF_KEEPOLD   "False"
#define DEF_MINFOOD   "5"
#define DEF_MAXFOOD   "20"
#define DEF_DIVIDEAGE "20"
#define DEF_MINDIST   "1.4"
#define DEF_PAUSE     "50"

#define DEFAULTS	"*delay:	30000            \n" \
			"*showFPS:      False            \n" \
			"*wireframe:    False            \n" \
			"*suppressRotationAnimation: True\n" \

#ifndef HAVE_JWZGLES /* glDrawElements unimplemented... */
# define USE_VERTEX_ARRAY
#endif

#define TEX_SIZE 64

/**********************************
  TYPEDEFS
 **********************************/
 
typedef struct    /* a 3-D vector */
{
  double x, y, z;   /* 3-D coordinates (we don't need w here) */
} Vector;

typedef struct    /* a triangle (indexes of vertexes in some list) */
{
  int i[3];         /* the three indexes for the triangle corners */
} Triangle;

typedef struct
{
  float *vertex;
  float *normal;
  unsigned *index;
  int num_index;
} VertexArray;

typedef struct    /* an 3-D object without normal vectors */
{ 
  Vector *vertex;       /* the vertexes */
  Triangle *triangle;   /* triangle list */
  int num_vertex;       /* number of vertexes */
  int num_triangle;     /* number of triangles */
} Object;

typedef struct    /* an 3-D object with smooth normal vectors */
{
  Vector *vertex;       /* the vertexes */
  Vector *normal;       /* the vertex normal vectors */
  Triangle *triangle;   /* triangle list */
  int num_vertex;       /* number of vertexes */
  int num_triangle;     /* number of triangles */
} ObjectSmooth;

typedef struct    /* Cell */
{
  double x, y;      /* position */
  double vx, vy;    /* movement vector */
  int age;          /* cells age */
  double min_dist;  /* minimum distance to other cells */
  int energy;       /* health */
  double rotation;  /* random rot, so they don't look all the same */
  double radius;    /* current size of cell */
  double growth;    /* current growth rate. might be <1.0 while dividing,
                       >1.0 when finished dividing and food is available
                       and 1.0 when grown up */
} Cell;

typedef struct    /* hacks state */
{
  GLXContext *glx_context;
  int width, height;    /* current size of viewport */
  double screen_scale;  /* we scale content with window size */
  int num_cells;        /* current number of cell in list */
  Cell *cell;           /* array of cells */
  int cell_polys;
  GLfloat color[4];     /* current cell color */
  double radius;        /* cell radius */
  int move_dist;        /* min distance from neighbours for forking */
  int max_cells;        /* maximum number of cells */
  int num_seeds;        /* number of initial seeds */
  int keep_old_cells;   /* draw dead cells? */
  int divide_age;       /* min age for division */
                        /* display lists for the cell stages */
  int cell_list[NUM_CELL_SHAPES];    
  int nucleus_list;
  int minfood;          /* minimum amount of food per area unit */
  int maxfood;          /* maximum amount of food per area unit */
  int pause;            /* pause at end (all cells dead) */
  int pause_counter;
  int wire;             /* draw wireframe? */
  Object *sphere;       /* the raw undisturbed sphere */
  double *disturbance;  /* disturbance values for the vertexes */
  int *food;            /* our petri dish (e.g. screen) */
  GLubyte *texture;     /* texture data for nucleus */
  GLuint texture_name;  /* texture name for binding */
} State;

/**********************************
  STATIC STUFF
 **********************************/
 
static State *sstate = NULL;

static XrmOptionDescRec opts[] = {
  { "-maxcells",   ".maxcells",   XrmoptionSepArg, 0 },
  { "-radius",     ".radius",     XrmoptionSepArg, 0 },
  { "-seeds",      ".seeds",      XrmoptionSepArg, 0 },
  { "-quality",    ".quality",    XrmoptionSepArg, 0 },
  { "-minfood",    ".minfood",    XrmoptionSepArg, 0 },
  { "-maxfood",    ".maxfood",    XrmoptionSepArg, 0 },
  { "-divideage",  ".divideage",  XrmoptionSepArg, 0 },
  { "-mindist",    ".mindist",    XrmoptionSepArg, 0 },
  { "-pause",      ".pause",      XrmoptionSepArg, 0 },
  { "-keepold",    ".keepold",    XrmoptionNoArg, "True" }
};

static int  s_maxcells;
static int  s_radius;
static int  s_seeds;
static int  s_quality;
static int  s_minfood;
static int  s_maxfood;
static int  s_divideage;
static int  s_pause;
static float s_min_dist;
static Bool s_keepold;

static argtype vars[] = {
  {&s_maxcells,  "maxcells",  "Max Cells",      DEF_MAXCELLS, t_Int},
  {&s_radius,    "radius",    "Radius",         DEF_RADIUS,   t_Int},
  {&s_seeds,     "seeds",     "Seeds",          DEF_SEEDS,    t_Int},
  {&s_quality,   "quality",   "Quality",        DEF_QUALITY,  t_Int},
  {&s_minfood,   "minfood",   "Min Food",       DEF_MINFOOD,  t_Int},
  {&s_maxfood,   "maxfood",   "Max Food",       DEF_MAXFOOD,  t_Int},
  {&s_pause,   "pause",     "Pause at end",   DEF_PAUSE,  t_Int},
  {&s_divideage, "divideage", "Age for duplication (Ticks)",       DEF_DIVIDEAGE,  t_Int},
  {&s_min_dist,  "mindist",   "Minimum preferred distance to other cells",       DEF_MINDIST,  t_Float},
  {&s_keepold,   "keepold",   "Keep old cells", DEF_KEEPOLD,  t_Bool}
};

/**********************************
  PROTOTYPES
 **********************************/
 
/* render scene */
static int render( State *st );
/* create initial cells and fill petri dish with food */
static void create_cells( State * );
/* do one animation step */
static void tick( State *st );
/* draw a single cell */
static void draw_cell( State *st, int shape );
/* draw cells nucleus */
static void draw_nucleus( State *st );
/* return randum number in the interval min-max */
static int random_interval( int min, int max );
/* retunr random number in the interval 0-max */
static int random_max( int max );
/* create display list for given disturbance weighting factor */
static int create_list( State *st, double fac );
/* return length of vector */
static double vector_length( Vector * );
/* normalize vector */
static void vector_normalize( Vector * );
/* a += b */
static void vector_add( Vector *a, Vector *b );
/* a -= b */
static void vector_sub( Vector *a, Vector *b );
/* a *= fac */
static void vector_mul( Vector *a, double fac );
/* a.x = a.y = a.z = 0 */
static void vector_clear( Vector *a );
/* return crossproduct a*b in out */
static void vector_crossprod( Vector *a, Vector *b, Vector *out );
/* return 1 if vectors are equal (epsilon compare) otherwise 0 */
static int vector_compare( Vector *a, Vector *b );
/* compute normal vector of given triangle and return in out */
static void triangle_normal( Vector *a, Vector *b, Vector *c, Vector *out );
/* take an Object and create an ObjectSmooth out of it */
static ObjectSmooth *create_ObjectSmooth( Object * );
/* Subdivide the Object once (assuming it's supposed to be a shpere */
static Object *subdivide( Object *obj );
/* free an Object */
static void free_Object( Object * );
/* free an ObjectSmooth */
static void free_ObjectSmooth( ObjectSmooth * );
/* scale an Object. return pointer to the object */
/*static Object *scale_Object( Object *obj, double scale );*/
/* create a perfect sphere refining with divisions */
static Object *create_sphere( State *st, int divisions );
/* make a copy of the given Object */
static Object *clone_Object( Object * );
/* return 1 if cell is capable to divide */
static int can_divide( State *st, Cell *cell );
#ifdef USE_VERTEX_ARRAY
static VertexArray *array_from_ObjectSmooth( ObjectSmooth * );
#endif
static void create_nucleus_texture( State *st );

ENTRYPOINT ModeSpecOpt glcells_opts = { countof(opts), opts,                                                   countof(vars), vars, 
                                        NULL };
                                        

/**********************************
  INLINE FUNCTIONS
 **********************************/
/* create random numbers
*/
static inline int random_interval( int min, int max )
{
  int n = max - min;
  if (n == 0) n = 1;
  return min+(random()%n);
}

static inline int random_max( int max )
{
  return random()%max;
}

/* Vector stuff
*/

/* a += b */
static inline void vector_add( Vector *a, Vector *b )
{
  a->x += b->x;
  a->y += b->y;
  a->z += b->z;
}

/* a -= b */
static inline void vector_sub( Vector *a, Vector *b )
{
  a->x -= b->x;
  a->y -= b->y;
  a->z -= b->z;
}

/* a *= v */
static inline void vector_mul( Vector *a, double v )
{
  a->x *= v;
  a->y *= v;
  a->z *= v;
}

/* set to 0 */
static inline void vector_clear( Vector *vec )
{
  vec->x = vec->y = vec->z = 0;
}

/* return vector length */
static inline double vector_length( Vector *vec )
{
  return sqrt( vec->x*vec->x + vec->y*vec->y + vec->z*vec->z );
}

/* normalize vector */
static inline void vector_normalize( Vector *vec )
{
  double len = vector_length( vec );
  
  if (len != 0.0) {
    vector_mul( vec, 1.0 / len );
  }
}

/* crossproduct */
static inline void vector_crossprod( Vector *a, Vector *b, Vector *out )
{
  out->x = a->y*b->z - a->z*b->y;
  out->y = a->z*b->x - a->x*b->z;
  out->z = a->x*b->y - a->y*b->x;
}

/* epsilon compare of two vectors */
static inline int vector_compare( Vector *a, Vector *b )
{
  const double epsilon = 0.0000001;
  Vector delta = *a;
  
  vector_sub( &delta, b );
  if (fabs(delta.x) < epsilon && 
      fabs(delta.y) < epsilon &&
      fabs(delta.z) < epsilon) {
    return 1;
  }
  
  return 0;
}

/* check if given cell is capable of dividing 
   needs space, must be old enough, grown up and healthy
*/
static inline int can_divide( State *st, Cell *cell )
{
  if (cell->min_dist > st->move_dist &&
      cell->age >= st->divide_age &&
      cell->radius > 0.99 * st->radius &&
      cell->energy > 0) {
    return 1;
  }
  
  return 0;
}

/**********************************
  FUNCTIONS
 **********************************/

/* compute normal vector of given 
   triangle spanned by the points a, b, c 
*/
static void triangle_normal( Vector *a, Vector *b, Vector *c, Vector *out )
{
  Vector v1 = *a;
  Vector v2 = *a;
  
  vector_sub( &v1, b );
  vector_sub( &v2, c );
  vector_crossprod( &v1, &v2, out );
}

/* free */
static void free_Object( Object *obj )
{
  free( obj->vertex );
  free( obj->triangle );
  free( obj );
}

static void free_ObjectSmooth( ObjectSmooth *obj )
{
  free( obj->vertex );
  free( obj->triangle );
  free( obj->normal );
  free( obj );
}

/* scale the given Object */
#if 0
static Object *scale_Object( Object *obj, double scale )
{
  int v;
  
  for (v=0; v<obj->num_vertex; ++v) {
    vector_mul( &obj->vertex[v], scale );
  }
  
  return obj;
}
#endif

/* create a copy of the given Object */
static Object *clone_Object( Object *obj )
{
  /* alloc */
  Object *ret = (Object *) malloc( sizeof( Object ) );
  
  ret->vertex = 
      (Vector *) malloc( obj->num_vertex*sizeof(Vector) );
  ret->triangle = 
      (Triangle *) malloc( obj->num_triangle*sizeof(Triangle) );
  ret->num_vertex = obj->num_vertex;
  ret->num_triangle = obj->num_triangle;
  /* copy */
  memcpy( ret->vertex, obj->vertex, 
          obj->num_vertex*sizeof(Vector) );
  memcpy( ret->triangle, obj->triangle,
          obj->num_triangle*sizeof(Triangle) );
  
  return ret;
}

#ifdef USE_VERTEX_ARRAY
static VertexArray *array_from_ObjectSmooth( ObjectSmooth *obj )
{
  int i, j;
  VertexArray *array = (VertexArray *) malloc( sizeof( VertexArray ) );
  
  array->vertex = (float *) malloc( 3*sizeof(float)*obj->num_vertex );
  array->normal = (float *) malloc( 3*sizeof(float)*obj->num_vertex );
  array->index = (unsigned *) malloc( 3*sizeof(unsigned)*obj->num_triangle );
  array->num_index = obj->num_triangle*3;
  
  for (i=0, j=0; i<obj->num_vertex; ++i) {
    array->vertex[j]   = obj->vertex[i].x;
    array->normal[j++] = obj->normal[i].x;
    array->vertex[j]   = obj->vertex[i].y;
    array->normal[j++] = obj->normal[i].y;
    array->vertex[j]   = obj->vertex[i].z;
    array->normal[j++] = obj->normal[i].z;
  }

  for (i=0, j=0; i<obj->num_triangle; ++i) {
    array->index[j++] = obj->triangle[i].i[0];
    array->index[j++] = obj->triangle[i].i[1];
    array->index[j++] = obj->triangle[i].i[2];
  }
  
  return array;
}
#endif /* USE_VERTEX_ARRAY */


/* create a smoothed version of the given Object
   by computing average normal vectors for the vertexes 
*/
static ObjectSmooth *create_ObjectSmooth( Object *obj )
{
  int t, v, i;
  Vector *t_normal = 
      (Vector *) malloc( obj->num_triangle*sizeof(Vector) );
  ObjectSmooth *ret = 
      (ObjectSmooth *) malloc( sizeof( ObjectSmooth ) );
  
  /* fill in vertexes and triangles */
  ret->num_vertex = obj->num_vertex;
  ret->num_triangle = obj->num_triangle;
  ret->vertex = 
      (Vector *) malloc( obj->num_vertex * sizeof( Vector ) );
  ret->normal = 
      (Vector *) malloc( obj->num_vertex * sizeof( Vector ) );
  ret->triangle = 
      (Triangle *) malloc( obj->num_triangle * sizeof( Triangle ) );
  
  for (v=0; v<obj->num_vertex; ++v) {
    ret->vertex[v] = obj->vertex[v];
  }
  
  for (t=0; t<obj->num_triangle; ++t) {
    ret->triangle[t] = obj->triangle[t];
  }
  
  /* create normals (triangles) */
  for (t=0; t<ret->num_triangle; ++t) {
    triangle_normal( &ret->vertex[ret->triangle[t].i[0]],
                     &ret->vertex[ret->triangle[t].i[1]],
                     &ret->vertex[ret->triangle[t].i[2]],
                     &t_normal[t] );
  }
  
  /* create normals (vertex) by averaging triangle 
     normals at vertex
  */
  for (v=0; v<ret->num_vertex; ++v) {
    vector_clear( &ret->normal[v] );
    for (t=0; t<ret->num_triangle; ++t) {
      for (i=0; i<3; ++i) {
        if (ret->triangle[t].i[i] == v) {
          vector_add( &ret->normal[v], &t_normal[t] );
        }
      }
    }
    /* as we have only a half sphere we force the
       normals at the bortder to be perpendicular to z.
       the simple algorithm above makes an error here.
    */
    if (fabs(ret->vertex[v].z) < 0.0001) {
      ret->normal[v].z = 0.0;
    }  
    
    vector_normalize( &ret->normal[v] );
  }
  
  free( t_normal );
  
  return ret;
}

/* subdivide the triangles of the object once
   The order of this algorithm is probably something like O(n^42) :)
   but I can't think of something smarter at the moment 
*/
static Object *subdivide( Object *obj )
{
  /* create for worst case (which I dont't know) */
  int start, t, i, v;
  int index_list[1000];
  int index_cnt, index_found;
  Object *tmp = (Object *)malloc( sizeof(Object) );
  Object *ret = (Object *)malloc( sizeof(Object) );
  Object *c_ret;
  
  tmp->vertex = 
      (Vector *)malloc( 100*obj->num_vertex*sizeof( Vector ) );
  tmp->triangle = 
      (Triangle *)malloc( 4*obj->num_triangle*sizeof( Triangle ) );
  tmp->num_vertex = 0;
  tmp->num_triangle = 0;
  ret->vertex = 
      (Vector *)malloc( 100*obj->num_vertex*sizeof( Vector ) );
  ret->triangle = 
      (Triangle *)malloc( 4*obj->num_triangle*sizeof( Triangle ) );
  ret->num_vertex = 0;
  ret->num_triangle = 0;
#ifdef PRINT_STAT
  fprintf( stderr, "in v=%d t=%d\n", 
           obj->num_vertex, obj->num_triangle );
#endif
  /* for each triangle create 3 new vertexes and the 4 
     corresponding triangles 
  */
  for (t=0; t<obj->num_triangle; ++t) {
    /* copy the three original vertexes */
    for (i=0; i<3; ++i) {
      tmp->vertex[tmp->num_vertex++] =
        obj->vertex[obj->triangle[t].i[i]];
    }
    
    /* create 3 new */
    tmp->vertex[tmp->num_vertex] = 
        obj->vertex[obj->triangle[t].i[0]];
    vector_add( &tmp->vertex[tmp->num_vertex],
                 &obj->vertex[obj->triangle[t].i[1]] );
    vector_mul( &tmp->vertex[tmp->num_vertex++], 0.5 ); 
    
    tmp->vertex[tmp->num_vertex] = 
        obj->vertex[obj->triangle[t].i[1]];
    vector_add( &tmp->vertex[tmp->num_vertex],
                 &obj->vertex[obj->triangle[t].i[2]] );
    vector_mul( &tmp->vertex[tmp->num_vertex++], 0.5 ); 
    
    tmp->vertex[tmp->num_vertex] = 
        obj->vertex[obj->triangle[t].i[2]];
    vector_add( &tmp->vertex[tmp->num_vertex],
                 &obj->vertex[obj->triangle[t].i[0]] );
    vector_mul( &tmp->vertex[tmp->num_vertex++], 0.5 ); 

    /* create triangles */
    start = tmp->num_vertex-6;
    
    tmp->triangle[tmp->num_triangle].i[0] = start;
    tmp->triangle[tmp->num_triangle].i[1] = start+3;
    tmp->triangle[tmp->num_triangle++].i[2] = start+5;
      
    tmp->triangle[tmp->num_triangle].i[0] = start+3;
    tmp->triangle[tmp->num_triangle].i[1] = start+1;
    tmp->triangle[tmp->num_triangle++].i[2] = start+4;
      
    tmp->triangle[tmp->num_triangle].i[0] = start+5;
    tmp->triangle[tmp->num_triangle].i[1] = start+4;
    tmp->triangle[tmp->num_triangle++].i[2] = start+2;
      
    tmp->triangle[tmp->num_triangle].i[0] = start+3;
    tmp->triangle[tmp->num_triangle].i[1] = start+4;
    tmp->triangle[tmp->num_triangle++].i[2] = start+5;
  }
  
  /* compress object eliminating double vertexes 
     (welcome to the not so smart section)
  */
  /* copy original triangle list */
  for (t=0; t<tmp->num_triangle; ++t) {
    ret->triangle[t] = tmp->triangle[t];
  }
  ret->num_triangle = tmp->num_triangle;
  
  /* copy unique vertexes and correct triangle list */
  for (v=0; v<tmp->num_vertex; ++v) {
    /* create list of vertexes that are the same */
    index_cnt = 0;
    for (i=0; i<tmp->num_vertex; ++i) {
      /* check if i and v are the same
         first in the list is the smallest index
      */
      if (vector_compare( &tmp->vertex[v], &tmp->vertex[i] )) {
        index_list[index_cnt++] = i;
      }
    }
    
    /* check if vertex unknown so far */
    index_found = 0;
    for (i=0; i<ret->num_vertex; ++i) {
      if (vector_compare( &ret->vertex[i],
          &tmp->vertex[index_list[0]] )) {
        index_found = 1;
        break;
      }
    }
    
    if (!index_found) {
      ret->vertex[ret->num_vertex] = tmp->vertex[index_list[0]];
      
      /* correct triangles 
         (we add an offset to the index, so we can tell them apart)
      */
      for (t=0; t<ret->num_triangle; ++t) {
        for (i=0; i<index_cnt; ++i) {
          if (ret->triangle[t].i[0] == index_list[i]) {
            ret->triangle[t].i[0] = ret->num_vertex+INDEX_OFFSET;
          }
          if (ret->triangle[t].i[1] == index_list[i]) {
            ret->triangle[t].i[1] = ret->num_vertex+INDEX_OFFSET;
          }
          if (ret->triangle[t].i[2] == index_list[i]) {
            ret->triangle[t].i[2] = ret->num_vertex+INDEX_OFFSET;
          }
        }
      }
      ret->num_vertex++;
    }
  }
  
  free_Object( tmp );
  
  /* correct index offset */
  for (t=0; t<ret->num_triangle; ++t) {
    ret->triangle[t].i[0] -= INDEX_OFFSET;
    ret->triangle[t].i[1] -= INDEX_OFFSET;
    ret->triangle[t].i[2] -= INDEX_OFFSET;
  }
  
  /* normalize vertexes */
  for (v=0; v<ret->num_vertex; ++v) {
    vector_normalize( &ret->vertex[v] );
  }
#ifdef PRINT_STAT
  fprintf( stderr, "out v=%d t=%d\n", 
           ret->num_vertex, ret->num_triangle );
#endif
  /* shrink the arrays by cloning */
  c_ret = clone_Object( ret );
  free_Object( ret );
  
  return c_ret;
}

static int render( State *st )
{
#ifdef PRINT_STAT
  struct timeval tv1, tv2;
  int usec;
#endif
  GLfloat LightAmbient[]= { 0.1f, 0.1f, 0.1f, 1.0f };
  GLfloat LightPosition[]= { -20.0f, -10.0f, -100.0f, 0.0f };
  int b;
  int num_paint = 0;
  
  if (0 == st->food) return 0;
#ifdef PRINT_STAT
  gettimeofday( &tv1, NULL );
#endif
  /* life goes on... */
  tick( st );
#ifdef PRINT_STAT
  gettimeofday( &tv2, NULL );
  usec = (tv2.tv_sec-tv1.tv_sec)*1000000+(tv2.tv_usec-tv1.tv_usec);
  fprintf( stderr, "tick %d\n", usec );
  gettimeofday( &tv1, NULL );
#endif

  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
  glLightfv( GL_LIGHT0, GL_AMBIENT, LightAmbient );
  glLightfv( GL_LIGHT0, GL_DIFFUSE, st->color );
  glLightfv( GL_LIGHT0, GL_POSITION, LightPosition );
  
  /* prepare lighting vs. wireframe */
  if (!st->wire) {
    glEnable( GL_LIGHT0 );
    glEnable( GL_LIGHTING );
    glEnable( GL_NORMALIZE );
    glPolygonMode( GL_FRONT, GL_FILL );
  } else {
# ifndef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
    glPolygonMode( GL_FRONT, GL_LINE );
# endif
  }
  
# if 0
  if (st->wire) {
    glDisable(GL_DEPTH_TEST);
    glColor3f (1, 1, 1);
    glBegin(GL_LINE_LOOP);
    glVertex3f(0, 0, 0); glVertex3f(st->width, 0, 0);
    glVertex3f(st->width, st->height, 0); glVertex3f(0, st->height, 0);
    glVertex3f(0, 0, 0); glVertex3f(st->width/4, 0, 0);
    glVertex3f(st->width/4, st->height/4, 0); glVertex3f(0, st->height/4, 0);
    glEnd();
  }
# endif

  /* draw the dead cells if choosen */
  if (st->keep_old_cells) {
    for (b=0; b<st->num_cells; ++b) {
      if (st->cell[b].energy <= 0) {
        num_paint++;
        glPushMatrix();
        glTranslatef( st->cell[b].x, st->cell[b].y, 0.0 );
        glRotatef( st->cell[b].rotation, 0.0, 0.0, 1.0 );
        glScalef( st->cell[b].radius, st->cell[b].radius, st->cell[b].radius );
        draw_cell( st, 9 );
        glPopMatrix();
      }
    }
  }
  
  /* draw the living cells */
  for (b=0; b<st->num_cells; ++b) {
     if (st->cell[b].energy >0) { 
      double fac = (double)st->cell[b].energy / 50.0; 
      int shape;
      if (fac < 0.0) fac = 0.0;
      if (fac > 1.0) fac = 1.0;
      
      shape = (int)(9.0*fac);
      num_paint++;
      /*glColor3f( fac, fac, fac );*/
      
# if 0
      if (st->wire) {
        glBegin(GL_LINES);
        glVertex3f(0, 0, 0);
        glVertex3f(st->cell[b].x, st->cell[b].y, 0);
        glEnd();
      }
# endif

      glPushMatrix();
      glTranslatef( st->cell[b].x, st->cell[b].y, 0.0 );
      glRotatef( st->cell[b].rotation, 0.0, 0.0, 1.0 );
      glScalef( st->cell[b].radius, st->cell[b].radius, st->cell[b].radius );
      draw_cell( st, 9-shape );
      glPopMatrix();
    }
  }
  
  /* draw cell nuclei */
  if (!st->wire)
  {
    glDisable( GL_LIGHT0 );
    glDisable( GL_LIGHTING );
    
    glEnable( GL_BLEND );
    glDisable( GL_DEPTH_TEST );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );    
    glEnable( GL_TEXTURE_2D );
    glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
    glBindTexture( GL_TEXTURE_2D, st->texture_name );
    
    for (b=0; b<st->num_cells; ++b) {
      if (st->cell[b].energy>0 || st->keep_old_cells) {
        glPushMatrix();
        glTranslatef( st->cell[b].x, st->cell[b].y, 0.0 );
        glScalef( st->cell[b].radius, st->cell[b].radius, st->cell[b].radius );
        draw_nucleus( st );
        glPopMatrix();
      }
    }
    
    glDisable( GL_TEXTURE_2D );
    glDisable( GL_BLEND );
  }
  
#ifdef PRINT_STAT
  gettimeofday( &tv2, NULL );
  usec = (tv2.tv_sec-tv1.tv_sec)*1000000+(tv2.tv_usec-tv1.tv_usec);
  fprintf( stderr, "OpenGL %d\n", usec );
#endif
  return num_paint * st->cell_polys;
}

/* this creates the initial subdivided half-dodecaedron */
static Object *create_sphere( State *st, int divisions )
{
  int num_vertex = 9;
  int num_triangle = 10;  
  int i, v, t;
  double a, aStep = (double)M_PI / 3.0;
  double e;
  int vi[30] = { 0, 7, 1, 1, 7, 2, 2, 8, 3, 3, 8, 4, 4, 6, 5,
                 5, 6, 0, 0, 6, 7, 2, 7, 8, 4, 8, 6, 6, 8, 7 };
  Object *obj = (Object *)malloc( sizeof( Object ) );
  
  obj->vertex = (Vector *)malloc( num_vertex*sizeof( Vector ) );
  obj->triangle = 
      (Triangle *)malloc( num_triangle*sizeof( Triangle ) );
  obj->num_vertex = num_vertex;
  obj->num_triangle = num_triangle;
                
  /* create vertexes for dodecaedron */
  a = 0.0;
  for (v=0; v<6; ++v) {
    obj->vertex[v].x = sin( a );
    obj->vertex[v].y = -cos( a );
    obj->vertex[v].z = 0.0;
  
    a += aStep;
  }

  a = -60.0/180.0*(double)M_PI;
  e = 58.2825/180.0 * (double)M_PI;
  for (;v<9; ++v) {
    obj->vertex[v].x = sin( a )*cos( e );
    obj->vertex[v].y = -cos( a )*cos( e );
    obj->vertex[v].z = -sin( e );
  
    a += 2.0*aStep;
  }

  /* create triangles */
  for (t=0; t<obj->num_triangle; ++t) {
    obj->triangle[t].i[0] = vi[3*t];
    obj->triangle[t].i[1] = vi[3*t+1];
    obj->triangle[t].i[2] = vi[3*t+2];
  }

  /* subdivide as specified */
  for (i=0; i<divisions; ++i) {
    Object *newObj = subdivide( obj );
    free_Object( obj );
    obj = newObj;
  }
  
  st->cell_polys = obj->num_triangle;
  
  return obj;
}

static int create_list( State *st, double fac )
{
  int v;
  Object *obj = clone_Object( st->sphere );
  ObjectSmooth *smooth;
#ifdef USE_VERTEX_ARRAY
  VertexArray *vertex_array;
#else
  int t, i;
#endif
  int list = glGenLists(1);  
  
  /* apply wrinckle factor */
  for (v=0; v<obj->num_vertex; ++v) {
    vector_mul( &obj->vertex[v], 1.0+fac*st->disturbance[v] );
  }
  
  /* compute normals */
  smooth = create_ObjectSmooth( obj );
  free_Object( obj );
  
  /* Create display list */
  glNewList( list, GL_COMPILE );
#ifdef USE_VERTEX_ARRAY
  vertex_array = array_from_ObjectSmooth( smooth );
  glEnableClientState( GL_VERTEX_ARRAY );
  glEnableClientState( GL_NORMAL_ARRAY );
  glVertexPointer( 3, GL_FLOAT, 0, vertex_array->vertex );
  glNormalPointer( GL_FLOAT, 0, vertex_array->normal );
  glDrawElements( GL_TRIANGLES, vertex_array->num_index, 
                  GL_UNSIGNED_INT, vertex_array->index );
  free (vertex_array->vertex);
  free (vertex_array->normal);
  free (vertex_array->index);
  free( vertex_array );
#else
  glBegin( GL_TRIANGLES );
  
  for (t=0; t<smooth->num_triangle; ++t) {
    for (i=0; i<3; ++i) {
      glNormal3f( smooth->normal[smooth->triangle[t].i[i]].x, 
                  smooth->normal[smooth->triangle[t].i[i]].y, 
                  smooth->normal[smooth->triangle[t].i[i]].z );
      glVertex3f( smooth->vertex[smooth->triangle[t].i[i]].x, 
                  smooth->vertex[smooth->triangle[t].i[i]].y, 
                  smooth->vertex[smooth->triangle[t].i[i]].z );
    }
  }    
  
  glEnd();
#endif
  glEndList();
  
  free_ObjectSmooth( smooth );
  
  return list;
}

static void draw_cell( State *st, int shape )
{
# ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
  if (st->wire) {
    glDisable(GL_DEPTH_TEST);
    glColor3f (1, 1, 1);
    glPushMatrix();
    glScalef (0.33, 0.33, 1);
    glBegin (GL_LINE_LOOP);
    glVertex3f (-1, -1, 0); glVertex3f (-1,  1, 0);
    glVertex3f ( 1,  1, 0); glVertex3f ( 1, -1, 0);
    glEnd();
    if (shape == 9) {
      glBegin (GL_LINES);
      glVertex3f (-1, -1, 0); glVertex3f (1,  1, 0);
      glVertex3f (-1,  1, 0); glVertex3f (1, -1, 0);
      glEnd();
    }
    glPopMatrix();
    return;
  }
# endif

  if (-1 == st->cell_list[shape]) {
    st->cell_list[shape] = create_list( st, (double)shape/10.0 );
  }
  
  glCallList( st->cell_list[shape] );
}

static void create_nucleus_texture( State *st )
{
  int x, y;
  int w2 = TEX_SIZE/2;
  float s = w2*w2/4.0;
  
  st->texture = (GLubyte *) malloc( 4*TEX_SIZE*TEX_SIZE );
  
  for (y=0; y<TEX_SIZE; ++y) {
    for (x=0; x<TEX_SIZE; ++x) {
      float r2 = ((x-w2)*(x-w2)+(y-w2)*(y-w2));
      float v = 120.0 * expf( -(r2) / s );
      st->texture[4*(x+y*TEX_SIZE)]   = (GLubyte)0;
      st->texture[4*(x+y*TEX_SIZE)+1] = (GLubyte)0;
      st->texture[4*(x+y*TEX_SIZE)+2] = (GLubyte)0;
      st->texture[4*(x+y*TEX_SIZE)+3] = (GLubyte)v;
    }
  }
  
  glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
  glGenTextures( 1, &st->texture_name );
  glBindTexture( GL_TEXTURE_2D, st->texture_name );
  
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, TEX_SIZE, TEX_SIZE, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, st->texture );
}

static void draw_nucleus( State *st )
{
  if (-1 == st->nucleus_list) {
    float z = -1.2f;
    float r=1.0/2.0f;
    st->nucleus_list = glGenLists( 1 );
    glNewList( st->nucleus_list, GL_COMPILE );
    glBegin( GL_QUADS );
    glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -r, -r, z );
    glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -r, r, z );
    glTexCoord2f( 1.0f, 1.0f ); glVertex3f( r, r, z );
    glTexCoord2f( 1.0f, 0.0f ); glVertex3f( r, -r, z );    
    glEnd();
    glEndList();
  }
  
  glCallList( st->nucleus_list );
}

static void create_cells( State *st )
{
  int border = (int)(200.0 * st->screen_scale);
  int i, foodcnt;
  int w = st->width-2*border;
  int h = st->height-2*border;
  
  st->color[0] = 0.5 + random_max( 1000 ) * 0.0005;
  st->color[1] = 0.5 + random_max( 1000 ) * 0.0005;
  st->color[2] = 0.5 + random_max( 1000 ) * 0.0005;
  st->color[3] = 1.0f;
  
  /* allocate if startup */
  if (!st->cell) {
    st->cell = (Cell *) malloc( st->max_cells * sizeof(Cell));
  }
  
  /* fill the screen with random food for our little critters */
  foodcnt = (st->width*st->height)/16;  
  for (i=0; i<foodcnt; ++i) {
    st->food[i] = random_interval( st->minfood, st->maxfood );
  }
    
  /* create the requested seed-cells */
  st->num_cells = st->num_seeds;

  for (i=0; i<st->num_cells; ++i) {
    st->cell[i].x        = border + random_max( w );
    st->cell[i].y        = border + random_max( h );
    st->cell[i].vx       = 0.0;
    st->cell[i].vy       = 0.0;
    st->cell[i].age      = random_max( 0x0f );
    st->cell[i].min_dist = 500.0;
    st->cell[i].energy   = random_interval( 5, 5+0x3f );
    st->cell[i].rotation = ((double)random()/(double)RAND_MAX)*360.0;
    st->cell[i].radius   = st->radius;
    st->cell[i].growth   = 1.0;
  }
}

/* all this is rather expensive :( */
static void tick( State *st )
{
  int new_num_cells, num_cells=0;
  int b, j;
  int x, y, w4=st->width/4, h4=st->height/4, offset;
  double min_dist;
  int min_index;
  int num_living = 0;
  const double check_dist = 0.75*st->move_dist;
  const double grow_dist = 0.75*st->radius;
  const double adult_radius = st->radius;
  
  /* find number of cells capable of division 
     and count living cells
  */
  for (b=0; b<st->num_cells; ++b) {
    if (st->cell[b].energy > 0) num_living++;
    if (can_divide( st, &st->cell[b] )) num_cells++;
  }
  new_num_cells = st->num_cells + num_cells;
  
  /* end of simulation ? */
  if (0 == num_living || new_num_cells >= st->max_cells) {
    if (st->pause_counter > 0) st->pause_counter--;
    if (st->pause_counter > 0) return;
    create_cells( st );
    st->pause_counter = st->pause;
  } else if (num_cells) { /* any fertile candidates ? */
    for (b=0, j=st->num_cells; b<st->num_cells; ++b) {
      if (can_divide( st, &st->cell[b] )) {
        st->cell[b].vx      = random_interval( -50, 50 ) * 0.01;
        st->cell[b].vy      = random_interval( -50, 50 ) * 0.01;
        st->cell[b].age     = random_max( 0x0f );
        /* half energy for both plus some bonus for forking */
        st->cell[b].energy  = 
            st->cell[b].energy/2 + random_max( 0x0f );
        /* forking makes me shrink */
        st->cell[b].growth  = 0.995;
        
        /* this one initially goes into the oposite direction */
        st->cell[j].vx       = -st->cell[b].vx;
        st->cell[j].vy       = -st->cell[b].vy;
        /* same center */
        st->cell[j].x        = st->cell[b].x;
        st->cell[j].y        = st->cell[b].y;
        st->cell[j].age      = random_max( 0x0f );
        st->cell[j].energy   = (st->cell[b].energy);
        st->cell[j].rotation =
            ((double)random()/(double)RAND_MAX)*360.0;
        st->cell[j].growth   = st->cell[b].growth;
        st->cell[j].radius   = st->cell[b].radius;
        ++j;
      } else {
        st->cell[b].vx = 0.0;
        st->cell[b].vy = 0.0;
      }
    }
    
    st->num_cells = new_num_cells;
  }

  /* for each find a direction to escape */
  if (st->num_cells > 1) {
    for (b=0; b<st->num_cells; ++b) {
      if (st->cell[b].energy > 0) {
        double vx;
        double vy;
        double len;
        
        /* grow or shrink */
        st->cell[b].radius *= st->cell[b].growth;
        /* find closest neighbour */
        min_dist = 100000.0;
        min_index = 0;
        for (j=0; j<st->num_cells; ++j) {
          if (j!=b) {
            const double dx = st->cell[b].x - st->cell[j].x;
            const double dy = st->cell[b].y - st->cell[j].y;
            
            if (fabs(dx) < check_dist || fabs(dy) < check_dist) {
              const double dist = dx*dx+dy*dy;
              /*const double dist = sqrt( dx*dx+dy*dy );*/
              if (dist<min_dist) {
                min_dist = dist;
                min_index = j;
              }
            }
          }
        }
        /* escape step is away from closest normalized with distance */
        vx = st->cell[b].x - st->cell[min_index].x;
        vy = st->cell[b].y - st->cell[min_index].y;
        len = sqrt( vx*vx + vy*vy );
        if (len > 0.0001) {
          st->cell[b].vx = vx/len;
          st->cell[b].vy = vy/len;
        }
        st->cell[b].min_dist = len;
        /* if not adult (radius too small) */
        if (st->cell[b].radius < adult_radius) {
          /* if too small 60% stop shrinking */
          if (st->cell[b].radius < adult_radius * 0.6) {
            st->cell[b].growth = 1.0;
          }
          /* at safe distance we start growing again */
          if (len > grow_dist) {
            if (st->cell[b].energy > 30) {
              st->cell[b].growth = 1.005;
            } 
          }
        } else {  /* else keep size */
          st->cell[b].growth = 1.0;
        }
      }
    }
  } else {
    st->cell[0].min_dist = 2*st->move_dist;
  }
    
  /* now move em, snack and burn energy */
  for (b=0; b<st->num_cells; ++b) {
    /* if still alive */
    if (st->cell[b].energy > 0) {
      /* agility depends on amount of energy */
      double fac = (double)st->cell[b].energy / 50.0;
      if (fac < 0.0) fac = 0.0;
      if (fac > 1.0) fac = 1.0;
      
      st->cell[b].x += fac*(2.0 - 
          (4.0*(double)random() / (double)RAND_MAX) + 
          st->cell[b].vx);
      st->cell[b].y += fac*(2.0 - 
          (4.0*(double)random() / (double)RAND_MAX) + 
          st->cell[b].vy);
      
      /* get older and burn energy */
      if (st->cell[b].energy > 0) {
        st->cell[b].age++;
        st->cell[b].energy--;
      }
      
      /* have a snack */
      x = ((int)st->cell[b].x)/4;
      if (x<0) x=0;
      if (x>=w4) x = w4-1;
      y = ((int)st->cell[b].y)/4;
      if (y<0) y=0;
      if (y>=h4) y = h4-1;
    
      offset = x+y*w4;
    
      /* don't eat if already satisfied */
      if (st->cell[b].energy < 100 &&
          st->food[offset] > 0) {
        st->food[offset]--;
        st->cell[b].energy++;
        /* if you are hungry, eat more */
        if (st->cell[b].energy < 50 && 
            st->food[offset] > 0) {
          st->food[offset]--;
          st->cell[b].energy++;
        }
      }
    }
  }
}

ENTRYPOINT void 
reshape_glcells( ModeInfo *mi, int width, int height )
{
  State *st  = &sstate[MI_SCREEN(mi)];
  int rot = current_device_rotation();
  st->height = height;
  st->width  = width;
# ifdef HAVE_MOBILE
  st->screen_scale = (double)(width < height ? width : height) / 1600.0;
# else
  st->screen_scale = (double)width / 1600.0;
# endif

  st->radius = s_radius;
  if (st->radius < 5) st->radius = 5;
  if (st->radius > 200) st->radius = 200;
  st->radius *= st->screen_scale;
       
  st->move_dist = s_min_dist;
  if (st->move_dist < 1.0) st->move_dist = 1.0;
  if (st->move_dist > 3.0) st->move_dist = 3.0;
  st->move_dist *= st->radius;

  glViewport (0, 0, (GLint) width, (GLint) height);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho( 0, width, height, 0, 200, 0 );
  glRotatef (rot, 0, 0, 1);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  if (st->food) free( st->food );
  st->food = (int *)malloc( ((width*height)/16)*sizeof(int) );
  /* create_cells( st );*/

# ifdef HAVE_MOBILE
  glTranslatef (st->width/2, st->height/2, 0);
  if (rot == 90 || rot == -90 || rot == 270 || rot == -270)
    st->width = height, st->height = width;
  glRotatef (rot, 0, 0, 1);
  if (st->wire) glScalef(0.8, 0.8, 1);
  glTranslatef (-st->width/2, -st->height/2, 0);
# endif
}

ENTRYPOINT void 
init_glcells( ModeInfo *mi )
{
  int i, divisions;
  State *st=0;
  
  MI_INIT(mi, sstate);
  st = &sstate[MI_SCREEN(mi)];
  
  st->glx_context = init_GL(mi);
  st->cell = 0;
  st->num_cells = 0;
  st->wire = MI_IS_WIREFRAME(mi);
  
  /* get settings */
  st->max_cells = s_maxcells;;
  if (st->max_cells < 50) st->max_cells = 50;
  if (st->max_cells > 10000) st->max_cells = 10000;
  
  st->pause = s_pause;
  if (st->pause < 0) st->pause = 0;
  if (st->pause > 400) st->pause = 400;
  st->pause_counter = st->pause;
  
  st->radius = s_radius;
  if (st->radius < 5) st->radius = 5;
  if (st->radius > 200) st->radius = 200;
  
  divisions = s_quality;
  if (divisions < 0) divisions = 0;
  if (divisions > 5) divisions = 5;
  
  st->num_seeds = s_seeds;
  if (st->num_seeds < 1) st->num_seeds = 1;
  if (st->num_seeds > 16) st->num_seeds = 16;
  
  st->minfood = s_minfood;
  if (st->minfood < 0) st->minfood = 0;
  if (st->minfood > 1000) st->minfood = 1000;
  
  st->maxfood = s_maxfood;
  if (st->maxfood < 0) st->maxfood = 0;
  if (st->maxfood > 1000) st->maxfood = 1000;
  
  if (st->maxfood < st->minfood) st->maxfood = st->minfood+1;
  
  st->keep_old_cells = s_keepold;
  
  st->divide_age = s_divideage;
  if (st->divide_age < 1) st->divide_age = 1;
  if (st->divide_age > 1000) st->divide_age = 1000;
     
  st->move_dist = s_min_dist;
  if (st->move_dist < 1.0) st->move_dist = 1.0;
  if (st->move_dist > 3.0) st->move_dist = 3.0;
  st->move_dist *= st->radius;
  
  for (i=0; i<NUM_CELL_SHAPES; ++i) st->cell_list[i] = -1;
  st->nucleus_list = -1;
  st->food = 0;
  
  st->sphere = create_sphere( st, divisions );
  st->disturbance = 
      (double *) malloc( st->sphere->num_vertex*sizeof(double) );
  for (i=0; i<st->sphere->num_vertex; ++i) {
    st->disturbance[i] = 
        0.05-((double)random()/(double)RAND_MAX*0.1);
  }
  
  create_nucleus_texture( st );

  reshape_glcells (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
}

ENTRYPOINT void
draw_glcells( ModeInfo *mi )
{
  State *st = &sstate[MI_SCREEN(mi)];
  Display *dpy = MI_DISPLAY(mi);
  Window window = MI_WINDOW(mi);
  
  if (!st->glx_context) return;

  glXMakeCurrent( MI_DISPLAY(mi), MI_WINDOW(mi), *st->glx_context);
  
  mi->polygon_count = render( st );

  if (mi->fps_p) do_fps (mi);
  
  glFinish();
  glXSwapBuffers( dpy, window );
}

ENTRYPOINT void 
free_glcells( ModeInfo *mi )
{
  int i;
  State *st  = &sstate[MI_SCREEN(mi)];

  if (!st->glx_context) return;
  glXMakeCurrent( MI_DISPLAY(mi), MI_WINDOW(mi), *st->glx_context);

  if (st->sphere) free_Object( st->sphere );
  if (st->food)   free( st->food );
  for (i=0; i<NUM_CELL_SHAPES; ++i) {
    if (st->cell_list[i] != -1) {
      glDeleteLists( st->cell_list[i], 1 );
    }
  }
  if (st->cell) free( st->cell );
  free( st->disturbance );
  glDeleteTextures( 1, &st->texture_name );
  free( st->texture );
  if (glIsList(st->nucleus_list)) glDeleteLists(st->nucleus_list, 1);
}

XSCREENSAVER_MODULE( "GLCells", glcells )
