// Genesaver: copyright 2003 Sam Stafford. #define _USE_MATH_DEFINES #include <math.h> #include <GL/glut.h> #include "globals.h" #include "animal.h" #include "world.h" #include "pixel.h" Animal::Animal( DNA* d ) :brain( d, muscles ), Thing() { hi = lo = NULL; meter_hue = 0.0; tagged = false; fitness = fitness_relative = fitness_d_food = fitness_d_flock = 0.0; dna = d; //initialize muscles for ( int m = 0 ; m < 4 ; m++ ) muscles[m] = 0.0; //establish creature color from genome short i; char cs[10]; r = g = b = 0.0; for ( i = 0 ; i < 4 ; i++ ) cs[i] = dna->hchr.colr[i]; for ( i = 4 ; i < 10 ; i++ ) cs[i] = dna->ichr[i - 4].colr; for ( i = 0 ; i < 10 ; i++ ) { switch( abs( cs[i] % 3 ) ) { case 0: r ++; break; case 1: g ++; break; case 2: b ++; break; } } float sum = r + g + b; if ( !sum ) sum = 1; r /= sum; g /= sum; b /= sum; //establish creature size from genome float ts = 0.5; float fd = 0.0; for ( i = 0 ; i < 4 ; i++ ) fd += float( dna->hchr.size[i] / 255.0 ); for ( i = 0 ; i < 6 ; i++ ) fd += float( dna->ichr[i].size / 255.0 ); fd /= 10; ts += fd; if ( ts > 1 ) ts = 1; if ( ts < 0.1 ) ts = 0.1; size = ts * A_R; //digestion per pixel based on creature size dpp = A_D / ( M_PI * size * size ); } Animal::~Animal(void) { delete dna; } void Animal::Render() { glPushMatrix(); glTranslatef( x, y, 0 ); float ax, ay; float da = M_PI / 13.0; glBegin( GL_TRIANGLES ); for ( float a = 0.0 ; a < 2 * M_PI ; a += da ) { ax = size * cos( a ); ay = size * sin( a ); glColor4f( r, g, b, A_O ); glVertex2f( ax, ay ); glVertex2f( size * cos( a + da ), size * sin( a + da ) ); glColor4f( fitness_relative, fitness_relative, fitness_relative, A_O ); glVertex2f( 0, 0 ); } glEnd(); if ( tagged ) { glBegin( GL_LINES ); glColor4f( 1, 1, 1, A_O ); glVertex2f( -size*1.5*cos(angle) + size*0.5, -size*1.5*sin(angle) ); glColor4f( 1, 1, 1, 0 ); glVertex2f( -size*2.0*speed*cos(angle)-size*1.5*cos(angle) + size*0.5, -size*2.0*speed*sin(angle)-size*1.5*sin(angle) ); glColor4f( 1, 1, 1, A_O ); glVertex2f( -size*1.5*cos(angle) - size*0.5, -size*1.5*sin(angle) ); glColor4f( 1, 1, 1, 0 ); glVertex2f( -size*2.0*speed*cos(angle)-size*1.5*cos(angle) - size*0.5, -size*2.0*speed*sin(angle)-size*1.5*sin(angle) ); glColor4f( 1, 1, 1, A_O ); glVertex2f( -size*1.5*cos(angle), -size*1.5*sin(angle) + size*0.5 ); glColor4f( 1, 1, 1, 0 ); glVertex2f( -size*2.0*speed*cos(angle)-size*1.5*cos(angle), -size*2.0*speed*sin(angle)-size*1.5*sin(angle) + size*0.5 ); glColor4f( 1, 1, 1, A_O ); glVertex2f( -size*1.5*cos(angle), -size*1.5*sin(angle) - size*0.5 ); glColor4f( 1, 1, 1, 0 ); glVertex2f( -size*2.0*speed*cos(angle)-size*1.5*cos(angle), -size*2.0*speed*sin(angle)-size*1.5*sin(angle) - size*0.5 ); glEnd(); } glPopMatrix(); } void Animal::RenderFitness() { float f1, f2; f1 = 2 * fitness_relative; if ( f1 > 1.0 ) { f2 = f1 - 1.0; f1 = 1.0; } else f2 = 0.0; meter_hue += 0.001; if ( meter_hue > 1.0 ) meter_hue = 0.0; float c1, c2; c1 = meter_hue; c2 = meter_hue - f2; if ( c2 < 0.0 ) c2 += 1.0; glBegin( GL_POLYGON ); glColor3f( 1.0 - f1, f1, 0 ); glVertex2f( -0.76, 0.5 - f1 / 2.1 ); glVertex2f( -0.74, 0.5 - f1 / 2.1 ); glColor3f( c1, 0, 1.0 - c1 ); glVertex2f( -0.74, 0.5 + f1 / 2.1 ); glVertex2f( -0.76, 0.5 + f1 / 2.1 ); glEnd(); if ( f2 == 0.0 ) return; glBegin( GL_POLYGON ); glColor3f( 1.0 - f2, 0, f2 ); glVertex2f( 0.76, 0.5 - f2 / 2.1 ); glVertex2f( 0.74, 0.5 - f2 / 2.1 ); glColor3f( c2, 1.0 - c2, 0 ); glVertex2f( 0.74, 0.5 + f2 / 2.1 ); glVertex2f( 0.76, 0.5 + f2 / 2.1 ); glEnd(); } void Animal::CheckFitness() { fitness += fitness_d_food * ( fitness_d_flock + 1.0 ); brain.input[12].axon = fitness_d_food; brain.input[13].axon = fitness_d_flock; fitness_d_food = 0.0; fitness_d_flock = 0.0; } void Animal::Sort( World* w ) { Animal* a; //check fitness of neighbors, promote or demote as appropriate if ( ( a = hi ) && hi->fitness < fitness ) //king me! { if ( lo ) lo->hi = a; if ( a->hi ) a->hi->lo = this; hi = a->hi; a->hi = this; a->lo = lo; lo = a; } else if ( ( a = lo ) && lo->fitness > fitness ) //d'oh! { if ( hi ) hi->lo = a; if ( a->lo )a->lo->hi = this; lo = a->lo; a->lo = this; a->hi = hi; hi = a; } if ( !hi ) w->pop = this; if ( hi && !hi->hi ) w->pop = hi; } void Animal::Eat( Pixel**p, int w, int h ) { int ix = floor( x ); int iy = floor( y ); int ars = size * size; int ar = ceil( size ); float fp; for ( int px = ix - ar ; px < ix + ar ; px++ ) { if ( px < 0 || px > w - 1 ) continue; for ( int py = iy - ar ; py < iy + ar ; py ++ ) { if ( py < 0 || py > h - 1 ) continue; if ( (px-ix)*(px-ix) + (py-iy)*(py-iy) > ars ) continue; //eat red fp = min( p[px][py].d_r, r * dpp ); fitness_d_food += fp; consume_diff( & p[px][py], fp, 'r' ); //eat green fp = min( p[px][py].d_g, g * dpp ); fitness_d_food += fp; consume_diff( & p[px][py], fp, 'g' ); //eat blue fp = min( p[px][py].d_b, b * dpp ); fitness_d_food += fp; consume_diff( & p[px][py], fp, 'b' ); } } } void Animal::Smudge( World* world ) //Smudge "ahead" based on current velocity and position. This //should get called in Step() just before position is updated. { Pixel** p = world->image_p; int w = world->image_w; int h = world->image_h; float sp = settings.smudge / 100.0; float st = 1.0 - sp; float sd = settings.smudgedist / 100.0; //Much of this is cribbed from Eat(). int ix = floor( x ); int iy = floor( y ); int ars = size * size; int ar = ceil( size ); int lx, ly, px, py; //Copy the current vicinity to use as the source of the //smudging. Pixel* local = new Pixel[ 4 * ar * ar ]; for ( px = ix - ar ; px < ix + ar ; px++ ) { if ( px < 0 || px > w - 1 ) continue; for ( py = iy - ar ; py < iy + ar ; py ++ ) { if ( py < 0 || py > h - 1 ) continue; lx = px - ix + ar; ly = py - iy + ar; local[ lx * 2 * ar + ly ] = p[px][py]; } } //Now calculate smudge vectors and smudge the image with //the contents of the local array. float fx, fy; int tx, ty; Pixel lp; for ( fx = x - ar ; fx < x + ar ; fx++ ) { px = floor( fx ); tx = floor( fx + velocity_x * sd ); if ( px < 0 || px > w - 1 || tx < 0 || tx > w - 1 ) continue; for ( fy = y - ar ; fy < y + ar ; fy++ ) { //p is the current location, t is the target of the smudge py = floor( fy ); ty = floor( fy + velocity_y * sd ); if ( px == tx && py == ty ) continue; if ( py < 0 || py > h - 1 || ty < 0 || ty > h - 1 ) continue; if ( (px-ix)*(px-ix) + (py-iy)*(py-iy) > ars ) continue; //Smudge p (taken from local array) onto live t lx = px - ix + ar; ly = py - iy + ar; lp = local[ lx * 2 * ar + ly ]; //Front buffer p[tx][ty].f_r = p[tx][ty].f_r * st + lp.f_r * sp; p[tx][ty].f_g = p[tx][ty].f_g * st + lp.f_g * sp; p[tx][ty].f_b = p[tx][ty].f_b * st + lp.f_b * sp; //Back buffer (to prevent smudge from creating food) p[tx][ty].b_r = p[tx][ty].b_r * st + lp.b_r * sp; p[tx][ty].b_g = p[tx][ty].b_g * st + lp.b_g * sp; p[tx][ty].b_b = p[tx][ty].b_b * st + lp.b_b * sp; //Diff buffer (inferred) compute_diff( & p[tx][ty] ); } } //Clean up! delete [ ] local; } void Animal::LookNear( Pixel** p, int w, int h ) { //This is the linear vector to the "nearest food". float nx = 0.0; float ny = 0.0; //We'll calculate it by summing unit vectors multiplied //by "food potential" towards all nearby pixels. int ix = floor( x ); int iy = floor( y ); int avs = A_V * A_V; int dx, dy; float fp, u, v, m, dxs, dys; //Scan a square with side = A_V * 2. for ( int px = ix - A_V ; px < ix + A_V ; px++ ) { if ( px < 0 || px > w - 1 ) continue; for ( int py = iy - A_V ; py < iy + A_V ; py ++ ) { if ( py < 0 || py > h - 1 ) continue; dx = px - ix; dy = py - iy; dxs = dx * dx; dys = dy * dy; if ( dx == 0 && dy == 0 ) continue; if ( dxs + dys > avs ) continue; //unit vector toward this pixel m = sqrt( dxs + dys ); u = dx / m; v = dy / m; //red food potential fp = min( p[px][py].d_r, r * dpp ); nx += u * fp; ny += v * fp; //green food potential fp = min( p[px][py].d_g, g * dpp ); nx += u * fp; ny += v * fp; //blue food potential fp = min( p[px][py].d_b, b * dpp ); nx += u * fp; ny += v * fp; } } //Normalize nearest-food vector to unit length. m = sqrt( nx * nx + ny * ny ); if ( m ) { nx /= m; ny /= m; } //Feed to brain at neurons 0 and 1. brain.input[0].axon = nx; brain.input[1].axon = ny; } void Animal::LookAnimals( World* w ) { float an2; float dist2 = 1000000000000000; Animal* nearest = NULL; for ( Animal* a = w->pop; a ; a = a->lo ) { if ( a == this ) continue; an2 = (a->x-x)*(a->x-x)+(a->y-y)*(a->y-y); if ( an2 < dist2 ) { dist2 = an2; nearest = a; } } if ( !nearest ) return; float nx = nearest->x - x; float ny = nearest->y - y; float nd = sqrt( nx*nx + ny*ny ); if ( !nd ) { //nearest animal is right on top //no flocking award, no neural input fitness_d_flock = 0.0; return; } //convert nx/ny to unit vector for neural input nx /= nd; ny /= nd; brain.input[8].axon = nx; brain.input[9].axon = ny; //calculate flocking award if ( nd <= A_R * 4 ) { fitness_d_flock = 1.0; } else { fitness_d_flock = 1.0 / ( nd / A_R * 2 ); } } void Animal::LookFar( float c_x, float c_y, float* vx, float* vy ) { //negative values of c_x or c_y indicate no diffs anywhere float u = c_x - x; float v = c_y - y; float m = sqrt( u*u + v*v ); if ( c_x < 0 || c_y < 0 || m == 0 ) { //nothing to report *vx = 0.0; *vy = 0.0; return; } u /= m; v /= m; *vx = u; *vy = v; } void Animal::Step( World* w ) { //Eat nearby pixels. Eat( w->image_p, w->image_w, w->image_h ); brain.Clear(); //Nearby pixels update inputs 0 and 1. LookNear( w->image_p, w->image_w, w->image_h ); //Global maximums update inputs 2 thru 7. LookFar( w->r_x, w->r_y, & brain.input[2].axon, & brain.input[3].axon ); LookFar( w->g_x, w->g_y, & brain.input[4].axon, & brain.input[5].axon ); LookFar( w->b_x, w->b_y, & brain.input[6].axon, & brain.input[7].axon ); //Nearest creature updates inputs 8 and 9. LookAnimals( w ); //Velocity vector (scaled, not unit) in 10 and 11. brain.input[10].axon = velocity_x / ( size * A_S ); brain.input[11].axon = velocity_y / ( size * A_S ); //Fitness calculations update inputs 12 and 13. //This depends on fitness data gathered from Eat() and LookAnimals(). CheckFitness(); //Process inputs. brain.Think(); //adjust velocity based on muscle values. //muscles[0,1] = x and y components float accel_x = muscles[0]; float accel_y = muscles[1]; //muscles[2] = parallel to velocity float v_m = sqrt( velocity_x * velocity_x + velocity_y * velocity_y ); float unit_v_x = 0; float unit_v_y = 0; if ( v_m ) { unit_v_x = velocity_x / v_m; unit_v_y = velocity_y / v_m; } //if muscles are pushing for reversing direction, clip them if ( muscles[2] < -v_m ) muscles[2] = -v_m; accel_x += muscles[2] * unit_v_x; accel_y += muscles[2] * unit_v_y; //muscles[3] = perpendicular to velocity accel_x += muscles[3] * unit_v_y * -1; accel_y += muscles[3] * unit_v_x; //clip acceleration to a unit vector float a_m = sqrt( accel_x * accel_x + accel_y * accel_y ); if ( a_m > 1.0 ) { accel_x /= a_m; accel_y /= a_m; } //scale by max accel - we'll say max speed / 10. accel_x *= size * A_S / 10; accel_y *= size * A_S / 10; //modify velocity by acceleration velocity_x += accel_x; velocity_y += accel_y; //clip velocity to terminal value v_m = sqrt( velocity_x * velocity_x + velocity_y * velocity_y ); speed = v_m / ( size * A_S ); if ( v_m > size * A_S ) { v_m /= ( size * A_S ); velocity_x /= v_m; velocity_y /= v_m; speed /= v_m; } angle = Angle( velocity_x, velocity_y ); if ( settings.smudge ) Smudge( w ); //modify position by velocity x += velocity_x; y += velocity_y; //bounce off walls if ( x < w->world_l ) { x += ( w->world_l - x ) * 2; velocity_x *= -0.95; } else if ( x > w->world_r ) { x -= ( x - w->world_r ) * 2; velocity_x *= -0.95; } if ( y < w->world_b ) { y += ( w->world_b - y ) * 2; velocity_y *= -0.95; } else if ( y > w->world_t ) { y -= ( y - w->world_t ) * 2; velocity_y *= -0.95; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#15 | 5818 | Sam Stafford | Configurable painter opacity. | ||
#14 | 5096 | Sam Stafford |
Make build work with VS 2003. (No functional change.) |
||
#13 | 4577 | Sam Stafford | Add new "smudge distance" tunable. | ||
#12 | 4572 | Sam Stafford |
New "smudge" feature. REALLY COOL. Off by default (pctsmudge=0) since it's a CPU hog. |
||
#11 | 4452 | Sam Stafford | Fix a couple of small bugs. | ||
#10 | 4451 | Sam Stafford | All significant variables are now user-tweakable. | ||
#9 | 4449 | Sam Stafford | Speed lines, toggle creatures on and off. | ||
#8 | 4448 | Sam Stafford | Turn this thing into a Windows screensaver. | ||
#7 | 4447 | Sam Stafford | Cleanup size-heredity code. | ||
#6 | 4446 | Sam Stafford |
Finished neural inputs, made size hereditary, auto-rotation of images once a certain amount of diffs have been consumed, saving genomes at finish. |
||
#5 | 4441 | Sam Stafford | Ported chase-cam view. | ||
#4 | 4440 | Sam Stafford | Bug fixes, new features, the usual. | ||
#3 | 4439 | Sam Stafford | Hooked the brain up to its muscles, gave the world physics. | ||
#2 | 4433 | Sam Stafford |
More work on this little project. The AI is still nonexistent. |
||
#1 | 4430 | Sam Stafford |
Start importing alife/AI code from Genesaver. Much tweaking will need to be done. |
||
//guest/sam_stafford/genesaver/src/Animal.cpp | |||||
#2 | 3348 | Sam Stafford | Decrease length of tail-color-flashes to 5 timesteps. | ||
#1 | 3052 | Sam Stafford |
Add Genesaver to the Public Depot. It's not in any way Perforce-related, but it does share a bit of code with Jamgraph, and it feels strange to have an open-source project that's not in the PD. |