world.cpp #24

  • //
  • guest/
  • sam_stafford/
  • scenesaver/
  • world.cpp
  • View
  • Commits
  • Open Download .zip Download (10 KB)
#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.