/* -*- 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 /* gettimeofday */ #include "xlockmore.h" #include /********************************** 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" \ #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) #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; vnum_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; inum_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; inum_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; vnum_vertex; ++v) { ret->vertex[v] = obj->vertex[v]; } for (t=0; tnum_triangle; ++t) { ret->triangle[t] = obj->triangle[t]; } /* create normals (triangles) */ for (t=0; tnum_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; vnum_vertex; ++v) { vector_clear( &ret->normal[v] ); for (t=0; tnum_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; tnum_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; tnum_triangle; ++t) { ret->triangle[t] = tmp->triangle[t]; } ret->num_triangle = tmp->num_triangle; /* copy unique vertexes and correct triangle list */ for (v=0; vnum_vertex; ++v) { /* create list of vertexes that are the same */ index_cnt = 0; for (i=0; inum_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; inum_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; tnum_triangle; ++t) { for (i=0; itriangle[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; tnum_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; vnum_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; bnum_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; bnum_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; bnum_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; tnum_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; icell_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; vnum_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 ); #else glBegin( GL_TRIANGLES ); for (t=0; tnum_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; ytexture[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; ifood[i] = random_interval( st->minfood, st->maxfood ); } /* create the requested seed-cells */ st->num_cells = st->num_seeds; for (i=0; inum_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; bnum_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; bnum_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; bnum_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; jnum_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 (distcell[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; bnum_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)]; # ifdef HAVE_MOBILE int rot = current_device_rotation(); # endif 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 ); # ifdef HAVE_MOBILE glRotatef (rot, 0, 0, 1); # endif 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; icell_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; isphere->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) { glXMakeCurrent( MI_DISPLAY(mi), MI_WINDOW(mi), *(st->glx_context) ); /* nuke everything before exit */ if (st->sphere) free_Object( st->sphere ); if (st->food) free( st->food ); for (i=0; icell_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 ); } } XSCREENSAVER_MODULE( "GLCells", glcells )