#include <stdlib.h> #include <GL/glut.h> #include <math.h> #include <stdio.h> #include <time.h> #include "animal.h" #include "globals.h" #include "util.h" #include "world.h" #include "pixel.h" World::World( int imagec, char** imagev, int w, int h ) { srand( time( 0 ) ); pop = chase = NULL; pop_c = 0; fit_cycle = image_cycle = 0; image_v = imagev; image_c = imagec; image_w = image_h = 0; image_i = 0; //find a valid image to base image allocation on for ( ; image_i < image_c ; image_i++ ) { image_w = image_width( image_v[image_i] ); image_h = image_height( image_v[image_i] ); if ( image_w && image_h ) break; } if ( !image_w || !image_h ) { //no valid images found, so null out image_v image_w = 640; image_h = 480; image_v[0] = 0; image_i = 0; image_c = 0; } //Size screen and make image_w/image_h match world. Resize( w, h ); image_w = world_r - world_l + 1; image_h = world_t - world_b + 1; //Resize again to center the world over the image. Resize( w, h ); image_d = image_w * image_h * 3.0; //allocate pixels and initialize front buffer to grey. image_p = new Pixel*[image_w]; for ( int i = 0 ; i < image_w ; i++ ) { image_p[i] = new Pixel[image_h]; for ( int j = 0 ; j < image_h ; j++ ) { image_p[i][j].f_r = 1.0; image_p[i][j].f_g = 1.0; image_p[i][j].f_b = 1.0; } } //load back buffer from first image image_d = load_pixels( image_p, image_v[image_i], image_w, image_h ); //push back to front, schedule image rotation Promote(); CycleImage(); settings.display_buffer = 'f'; } void World::Display() { glClearColor( 0, 0, 0, 0 ); glClear( GL_COLOR_BUFFER_BIT ); Step(); switch( settings.camera ) { case 'w': //World view projection matrix. glMatrixMode( GL_PROJECTION ); glLoadIdentity(); glViewport( 0, 0, screen_w, screen_h ); glOrtho( world_l, world_r, world_b, world_t, -0.1, 0.1 ); glMatrixMode( GL_MODELVIEW ); RenderImage(); if ( ( A_O > 0.0 ) && pop ) { for ( Animal* a = pop ; a ; a = a->lo ) { a->Render(); } } break; case 'c': RenderChase(); } } void World::FitnessCheck() { if ( pop_c < 2 ) return; Animal* a = pop; //do incremental sort to gauge relative fitness for ( a = pop ; a ; a = a->lo ) { a->Sort( this ); } float df = 1.0 / ( pop_c - 1 ); float fit = 1.0; for ( a = pop ; a ; a = a->lo ) { a->fitness_relative = fit; fit -= df; } fit_cycle++; if ( fit_cycle < W_F ) return; for ( a = pop ; a ; a = a->lo ) { a->fitness = 0.0; } //Delete the lowball, replace him with a clone of the highball. Procreate( pop->dna ); fit_cycle = 0; } void World::ImageCheck() { image_cycle++; if ( image_cycle < W_I ) return; image_cycle = 0; image_i++; if ( image_c < 2 ) { image_i = 0; image_d = load_random( image_p, image_w, image_h ); return; } if ( settings.shuffle && image_c > 2 ) { if ( settings.alternate && image_i > 1 ) { image_i = 0; } else { image_i += RandInt( image_c - 2 ); if ( image_i >= image_c ) image_i -= image_c; } } if ( image_i >= image_c || image_i < 0 ) image_i = 0; image_d = load_pixels( image_p, image_v[image_i], image_w, image_h ); } void World::RenderChase() { if ( !chase ) chase = pop; if ( !chase ) return; chase->tagged = true; glClearColor( 0, 0, 0, 0 ); glClear( GL_COLOR_BUFFER_BIT ); glMatrixMode( GL_PROJECTION ); glLoadIdentity(); glViewport( 0, 0, screen_w, screen_h ); glMatrixMode( GL_MODELVIEW ); glPushMatrix(); glLoadIdentity(); glTranslatef( 0.0, 0.5, 0.0 ); float rr = max( A_V, chase->size * 2 ); glScalef( 0.5 / rr, 0.5 / rr, 1 ); glTranslatef( -chase->x, -chase->y, 0 ); RenderVisible( chase ); glPopMatrix(); chase->RenderFitness(); chase->brain.Render(); } void World::RenderImage() { Pixel p; glPointSize( ceil( max( screen_w/(world_r - world_l), screen_h/(world_t - world_b) ) ) ); glDisable( GL_BLEND ); glBegin( GL_POINTS ); for ( int x = 0 ; x < image_w ; x++ ) { for ( int y = 0 ; y < image_h ; y++ ) { p = image_p[x][y]; switch ( settings.display_buffer ) { case 'b': glColor3f( p.b_r, p.b_g, p.b_b ); break; case 'd': glColor3f( p.d_r, p.d_g, p.d_b ); break; default: case 'f': glColor3f( p.f_r, p.f_g, p.f_b ); break; } glVertex2f( x, y ); } } glEnd(); glEnable( GL_BLEND ); } void World::RenderVisible( Animal* a ) { Pixel p; glPointSize( 1 ); float rr = max( A_V, a->size * 2 ); int x1 = max( 0, a->x - rr ); int x2 = a->x + rr; int y1 = max( 0, a->y - rr ); int y2 = a->y + rr; int avs = rr * rr; float c = 0; glBegin( GL_POINTS ); for ( int x = x1 ; x <= x2 && x < image_w ; x++ ) { for ( int y = y1 ; y <= y2 && y < image_h ; y++ ) { if ( (x-a->x)*(x-a->x) + (y-a->y)*(y-a->y) > avs ) continue; p = image_p[x][y]; switch ( 'd' ) //settings.display_buffer { case 'b': glColor3f( p.b_r, p.b_g, p.b_b ); break; case 'd': c = p.d_r * a->r + p.d_g * a->g + p.d_b * a->b; c *= 3.0; glColor3f( c * a->r, c * a->g, c * a->b ); break; default: case 'f': glColor3f( p.f_r, p.f_g, p.f_b ); break; } glVertex2f( x, y ); } } glEnd(); for ( Animal* b = pop ; b ; b = b->lo ) { if ( (b->x-a->x)*(b->x-a->x) + (b->y-a->y)*(b->y-a->y) <= avs ) b->Render(); } } void World::Resize( int w, int h ) { screen_w = w; screen_h = h; //do these calculations with floats since width/height ratios tend //to fall within 1 and 2, even though final product ends up being pixels. float sw = float( screen_w ); float sh = float( screen_h ); float iw = float( image_w ); float ih = float( image_h ); //determine world bounds based on image/screen dimensions if ( iw / ih < sw / sh ) { //image is less wide than the screen, so pad world_w float ww = ih * sw / sh; float dw = ( ww - iw ) / 2.0; world_l = int( -dw ); world_r = int( iw + dw - 1.0 ); world_b = 0; world_t = image_h - 1; } else if ( iw / ih > sw / sh ) { //image is less tall than the screen, so pad world_h float wh = iw * sh / sw; float dh = ( wh - ih ) / 2.0; world_b = int( -dh ); world_t = int( ih + dh - 1.0 ); world_l = 0; world_r = image_w - 1; } else { //same dimensions, so one-to-one correspondence world_l = 0; world_r = image_w - 1; world_b = 0; world_t = image_h - 1; } } void World::ScanDiffs() { float d_r, d_g, d_b; //highest diffs found d_r = d_g = d_b = 0.0; float d_t = 0.0; //diff count r_x = r_y = g_x = g_y = b_x = b_y = -1.0; //null value Pixel p; for ( int x = 0 ; x < image_w ; x++ ) { for ( int y = 0 ; y < image_h ; y++ ) { p = image_p[x][y]; if ( p.d_r > d_r ) { d_r = p.d_r; r_x = x; r_y = y; } if ( p.d_g > d_g ) { d_g = p.d_g; g_x = x; g_y = y; } if ( p.d_b > d_b ) { d_b = p.d_b; b_x = x; b_y = y; } d_t += p.d_r + p.d_g + p.d_b; } } if ( image_d == 0.0 ) { image_d = 1.0; //prevent divide by zero, cycle image } //cycle the image if insufficient diffs left if ( 1.0 - d_t / image_d >= W_C ) CycleImage(); } void World::InsertAnimal( DNA* dna ) { if ( pop_c >= W_P ) return; //don't add past designated limit if ( !pop ) { pop = new Animal( dna ); } else { pop->hi = new Animal( dna ); pop->hi->lo = pop; pop = pop->hi; } pop->x = RandInt( world_r - world_l ) + world_l; pop->y = RandInt( world_t - world_b ) + world_b; pop->velocity_x = RandFloat() * 2.0 - 1.0; pop->velocity_y = RandFloat() * 2.0 - 1.0; pop_c++; } void World::Procreate( DNA* d ) { Animal* low = pop; while ( low->lo ) low = low->lo; if ( low == chase ) { chase = pop; pop->tagged = true; } float x = low->x; float y = low->y; float vx = low->velocity_x; float vy = low->velocity_y; low->hi->lo = NULL; delete low; pop_c--; DNA* dna = new DNA( pop->dna ); for ( int m = 0 ; m < D_M ; m++ ) dna->Mutate(); InsertAnimal( dna ); pop->x = x; pop->y = y; pop->velocity_x = vx; pop->velocity_y = vy; //Now put the new creature on the bottom. low = pop; while ( low->lo ) low = low->lo; low->lo = pop; pop->hi = low; pop->lo->hi = NULL; low = pop; pop = pop->lo; low->lo = NULL; } void World::Load() { char* c = LoadGenes(); DNA* dna; while ( c && *c && *c != EOF ) { dna = new DNA(); c = dna->Load( c ); InsertAnimal( dna ); } while ( pop_c < W_P ) { dna = new DNA(); if ( settings.gmo ) dna->CreatePainter( 'x' ); else dna->Randomize(); InsertAnimal( dna ); } } void World::Save() { if ( !W_S ) return; FILE* f = fopen( "SceneGenes.tmp", "w" ); if ( !f ) return; Animal* a; for ( a = pop ; a ; a = a->lo ) a->dna->Dump( f ); int e = fclose( f ); if ( e ) return; unlink( "SceneGenes.txt" ); rename( "SceneGenes.tmp", "SceneGenes.txt" ); } void World::ScreenShot( char* file ) { save_pixels( image_p, file, image_w, image_h ); } void World::Step() { ScanDiffs(); FitnessCheck(); ImageCheck(); if ( !pop ) return; for ( Animal* a = pop ; a ; a = a->lo ) { a->Step( this ); } } void World::CycleCameraMode() { if ( settings.camera == 'c' ) settings.camera = 'w'; else settings.camera = 'c'; } void World::CycleChaseCam() { if ( !chase ) return; chase->tagged = false; chase = chase->lo; if ( !chase ) chase = pop; } void World::CycleDisplayBuffer() { if ( settings.display_buffer == 'f' ) settings.display_buffer = 'd'; else if ( settings.display_buffer == 'd' ) settings.display_buffer = 'b'; else settings.display_buffer = 'f'; } void World::CycleDrawCreatures() { settings.opacity_f += 0.1; if ( settings.opacity_f > 1.0 ) settings.opacity_f = 0.0; } void World::CycleImage() { image_cycle = W_I; } void World::Promote() { for ( int x = 0 ; x < image_w ; x++ ) { for ( int y = 0 ; y < image_h ; y++ ) { image_p[x][y].f_r = image_p[x][y].b_r; image_p[x][y].f_g = image_p[x][y].b_g; image_p[x][y].f_b = image_p[x][y].b_b; compute_diff( & image_p[x][y] ); } } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#24 | 5818 | Sam Stafford | Configurable painter opacity. | ||
#23 | 5691 | Sam Stafford |
Rebuilt Scenesaver with Qt 3.3.6. Added a new flag to the config file (not in the GUI since it's pretty esoteric) that uses the background image on every other rotation in shuffle mode. |
||
#22 | 5454 | Sam Stafford | Fixed a crash bug related to very short scene lists. | ||
#21 | 5098 | Sam Stafford |
Rollback the multithreaded image loading functionality. It didn't really boost performance like I'd hoped it would - the CPU would still swamp for a second while decoding the image, and there was no way around that. |
||
#20 | 4791 | Sam Stafford |
Fix random color mode (the default if no valid image files are found). It was severely broken by the change that introduced threads. |
||
#19 | 4751 | Sam Stafford |
Change chase-cam view to display everything in terms of what the chased creature can eat. |
||
#18 | 4707 | Sam Stafford |
Make image buffer match screen dimensions rather than first image dimensions. The first image is still used to determine the resolution. (If the first image matches the screen's dimensions, like it should, this change does nothing.) |
||
#17 | 4642 | Sam Stafford |
Load images in a seperate thread to cut down on framerate lag. Also moved to Qt 3.3.3. |
||
#16 | 4525 | Sam Stafford |
Make second image in shuffle mode (first "back" image) random, instead of loading the first two images in sequence. The first image remains non-random because it determines the image dimensions for the rest of the run, which you probably want to be the same each time, even if your image collection is mixed-size. Also upped optimization settings in the project file, though no real performance difference either way is apparent from casual inspection. Considered putting in a fps monitor, but it doesn't seem worth the effort. |
||
#15 | 4465 | Sam Stafford |
Genetically engineered organisms - there's now an option to seed the initial population with these relentlessly efficient creatures rather than random mutants. Kinda neat-looking, but not as organic-looking as pure evolved creatures. |
||
#14 | 4462 | Sam Stafford | Image shuffle option. | ||
#13 | 4458 | Sam Stafford |
Allow screenshot to capture the current buffer rather than always defaulting to the front one. (This lets people take cool "diff" screenshots.) |
||
#12 | 4457 | Sam Stafford |
After the first round of user feedback: 1) Added option to autoscale images if they don't match in size (on by default) 2) Fixed crash bug if you tried to cycle the chasecam when it had never been initialized. |
||
#11 | 4453 | Sam Stafford |
Added screenshot feature, moved SceneSaver files to home directory rather than system directory, added code to handle invalid or missing images (loading a random color instead of crashing). I think this thing's good to go. |
||
#10 | 4452 | Sam Stafford | Fix a couple of small bugs. | ||
#9 | 4451 | Sam Stafford | All significant variables are now user-tweakable. | ||
#8 | 4449 | Sam Stafford | Speed lines, toggle creatures on and off. | ||
#7 | 4448 | Sam Stafford | Turn this thing into a Windows screensaver. | ||
#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 | 4429 | Sam Stafford |
A bit of work in progress that currently works as a crude image diff tool. |