/*	
	The Clifford Attractor
	
	developed by Jonas Lindstrøm Jensen (mail@jonaslindstrom.dk).
	http://www.jonaslindstrom.dk
*/
  
#include <stdio.h>
#include <SDL/SDL.h>
#include <gmp.h>
#include <math.h>

#define WIDTH 800 // Width of screen
#define HEIGHT 600 // Height of screen
#define BPP 4 // Bytes per pixel
#define DEPTH 32
#define FULLSCREEN 0 // Fullscreen? (currently not doing anything, I think)

// Number of iterations, and how to scale the coordinate system to the screen
#define MAX_ITERATIONS 100000 // The number of points calculated in the sequence
#define SCALE 150 // How many pixels should correspond to distance 1 in the coordinate system
#define X_TRANSLATE 2.1 // Translation of the coordinate system
#define Y_TRANSLATE 1.6

// Parameters determing the colouring of the picture
#define TONE_RATIO 0.009 	// The lightning of each pixel is determined by the number of points hitting that pixel 
							//- this is the coefficient used to scale this number into the lightning, which is a number between 0 and 1
#define ANGLE_RATIO 0.7		// The hue is determined by the angle the last point hitting the pixel had - this is the coefficient used
							// to scale that angle into a number between 0 and 1
#define SPEED_RATIO 0.08	// The saturation is determined by the speed of the last pixel hitting the pixel - this is the coeffiecient
							// used to scale this speed into a number between 0 and 1

// The coefficients a and b of the iteration change over time in a circle.
#define RADIUS 0.13 // Radius of the circle the parameters (a,b) is moving around in
#define INCREMENT 0.01 // Speed of the change of (a,b)
#define A -1.3 // Center of the circle (a,b) is moving around in
#define B 1.4

short int tone[WIDTH][HEIGHT];	// Number of points in a pixel
float speed[WIDTH][HEIGHT];		// Speed of the iteration that hit this pixel
float angle[WIDTH][HEIGHT];		// Angle of the iteration that hit this pixel


void setpixel(SDL_Surface *screen, int x, int y, Uint8 r, Uint8 g, Uint8 b)
{
	Uint32 *pixmem32;
	Uint32 colour;  
 
	colour = SDL_MapRGB( screen->format, r, g, b );
	
	pixmem32 = (Uint32*) screen->pixels + WIDTH*y + x;
	*pixmem32 = colour;
}

// Draw pixel with the colour given by the HSL values (h,s,l) where each of the parameters are in the interval [0,1]
void setpixel_HSL(SDL_Surface *screen, int x, int y, float h, float s, float l) {
	Uint8 c[3];
	float tmp1, tmp2;
	float tmp3[3];
	int i;
	
	if(s == 0.0) {
		c[0] = l * 255;
		c[1] = l * 255;
		c[2] = l * 255;
	} else {
		if(l < 0.5) {
			tmp2 = l * (1.0 + s);
		} else {
			tmp2 = l + s - l*s;
		}
		
		tmp1 = 2.0*l - tmp2;
		
		
		tmp3[0] = h + 1.0/3.0;
		tmp3[1] = h;
		tmp3[2] = h - 1.0/3.0;
		if(tmp3[2] < 0) tmp3[2] += 1.0;
		
		for(i=0; i<3; i++) {
			if(6.0*tmp3[i] < 1) {
				c[i] = (int) 255*(tmp1 + (tmp2-tmp1)*6.0*tmp3[i]);
			} else if(2.0*tmp3[i] < 1) {
				c[i] = 255*tmp2;
			} else if(3.0*tmp3[i] < 2) {
				c[i] = 255*(tmp1+(tmp2-tmp1)*((2.0/3.0)-tmp3[i])*6.0);
			} else {
				c[i] = 255*tmp1;
			}
		}
	}

	setpixel(screen, x, y, c[0], c[1], c[2]);
}


void draw_screen(SDL_Surface *screen, float a, float b, float c, float d) {
	int iterations=0;				// Counting the number of iterations done so far
	float t, an, s;					// The parameters determing the color of the current pixel
	float x_1=0, y_1=0, x_2, y_2;	// Used to calculate the sequence - starting in (0,0)
	short int max_tone = 0;			// The maximal number of times we've hit a certain pixel (currently not used)
	int x, y, i, j;					// Index variables

	// Clear the tone-array	
	for(x=0; x<WIDTH; x++)
		for(y=0; y<HEIGHT; y++) {
			tone[x][y] = 0;
			speed[x][y] = 0;
			angle[x][y] = 0;
		}

	// Make the screen white
	SDL_FillRect( SDL_GetVideoSurface(), NULL,  SDL_MapRGB( screen->format, 0, 0, 0 ));
	
	// Lock the surface...
	if(SDL_MUSTLOCK(screen)) {
		if(SDL_LockSurface(screen) < 0) return;
	}
		
	// Iterate the sequence, and see how many times each pixel is hit
	while(iterations < MAX_ITERATIONS) {
		iterations++;
		x_2 = sin(a * y_1) + c * cos(a * x_1);
		y_2 = sin(b * x_1) + d * cos(b * y_1);

		x = (int) (SCALE*(x_2 + X_TRANSLATE));
		y = (int) (SCALE*(y_2 + Y_TRANSLATE)); 
		
		tone[x][y]++;

		if(tone[x][y] > max_tone)
			max_tone = tone[x][y];
		
		speed[x][y] = (x_1-x_2)*(x_1-x_2) + (y_1-y_2)*(y_1-y_2);

		if(y_1 != y_2)
			angle[x][y] = atan((x_2-x_1) / (y_2-y_1));
		
		x_1 = x_2;
		y_1 = y_2;
	}
	
	// Run through all pixels and draw the tone on the screen
	for(x=0; x<WIDTH; x++) {
		for(y=0; y<HEIGHT; y++) {
			if(tone[x][y] > 0) {

				t = TONE_RATIO*tone[x][y];
				an = ANGLE_RATIO*angle[x][y]*angle[x][y];
				s = SPEED_RATIO*speed[x][y];
				
				//printf("%f ", t);
				
				setpixel_HSL(screen, x, y, an, s, t);
			}
		}
	}
	
	// Unlock the surface and flip
	if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);
		SDL_Flip(screen); 
}

int main(int argc, char* argv[])
{
	printf("The Clifford Attractor\n\nDrawing the sequence\n x_{n+1} = sin(a y_n) + c cos(a x_n)\n y_{n+1} = sin(b x_n) + d cos(b y_n)\n\n");
	// The keyboard map and a variable indicating if we should exit or not
	Uint8 *keyDown;
	int exit=0;
	float a=-1.2, b=1.6, c=1.0, d=0.7;
	float frame=0;

	// Pointer to the screen and event handler
	SDL_Surface *screen;
	SDL_Event event;

	if(SDL_Init(SDL_INIT_VIDEO) < 0 ) return 1;

	if(!(screen = SDL_SetVideoMode(WIDTH, HEIGHT, DEPTH, SDL_HWSURFACE))) {
		SDL_Quit();
		return 1;
	}

	while(!exit)
	{
		while(SDL_PollEvent(&event)) 
		{
			switch(event.type) 
			{
				// If a escape or the exit button is pressed exit
				case SDL_QUIT:
				exit = 1;
				break;
				
				case SDL_KEYDOWN:
				keyDown = SDL_GetKeyState(NULL);
				if(keyDown[SDLK_ESCAPE] == 1) exit = 1;

				// Used to change parameters manually
				if(keyDown[SDLK_UP] == 1) a+=0.01;
				if(keyDown[SDLK_DOWN] == 1) a-=0.01;

				if(keyDown[SDLK_LEFT] == 1) b+=0.01;
				if(keyDown[SDLK_RIGHT] == 1) b-=0.01;
								
				draw_screen(screen, a, b, c, d);
				break;
			}
		}
		
		// Move the parameters a and b round in a circle
		frame+=INCREMENT;
		a = B + RADIUS*sin(frame);
		b = A + RADIUS*cos(frame);
		printf("a=%f b=%f c=%f d=%f\r", a, b, c, d);
		fflush(stdout);
		draw_screen(screen, a, b, c, d);
	}
   
	
    
	SDL_Quit();
	return 0;
}
