#include <stdlib.h>
#include <stdio.h>
#include "framerate.h"

#include "inc/rat_physics.h"
#include "drawutil/draw.h"
#include "drawutil/ortho.h"
#include "drawutil/font.h"
#include "drawutil/extensions.h"

#if defined(RAT_PHYSICS_WINDOWS)
#	include "SDL\include\SDL.h"
#	include "SDL\include\SDL_opengl.h"
#	include <GL\wglext.h>
#elif defined(RAT_PHYSICS_LINUX)
#	include <SDL/SDL.h>
#	include <SDL/SDL_opengl.h>
#endif

// some macros that give us strings describing state
#define binary_state_str(a) ((a)?" on":"off")
#define control_mode_str(a) ((a)?"constraint construction mode":"rigid body construction mode")

// scene's real coordinate size
#define SCENE_WIDTH 1024.0f
#define SCENE_HEIGHT 768.0f

// number of constraint modes for cycling through constraint modes
#define NUM_CONSTRAINT_MODES	3

// testbed code
static void initialize_testbed();
static void destroy_testbed();
static void update_and_draw_testbed();

// function that helps with mouse picking
static int testbed_pointpick(vector2 point,rat_body **bodypicked,vector2 *local_picked);

// constraint builder mode names
const char *constraint_mode_strs[]=
{
	"Spring Constraint",
	"Bar Constraint",
	"Slider Constraint"/*,
	"Pivot Constraint"*/
};

unsigned int physics_accuracy=10;	// accuracy of the impulse solver

vector2 tmpvec={0,0};	// temporary vector
vector2 zero_vec={0,0};	// zeroed vector
vector2 grav_vec={0,128};	// gravity vector

// world bounding aabb for use in spatial indexing and leaving world events
aabb world_bounds={{0,0},{SCENE_WIDTH,SCENE_HEIGHT}};
rat_world *world;	// pointer to the world struct itself

int control_mode=0;		// control mode (build bodies / manipulate + build constraints)
int constraint_mode=0;	// what kind of constraint to build
int mouse_0_state=0;	// for keeping track of the mouse states
int mouse_1_state=0;
rat_real mouse_x=0,mouse_y=0;	// for keeping track of mouse position
rat_real new_spring_k=128.0;	// "stiffness" or spring constant for new springs

int draw_arbiters=0;	// draw arbiters (collision contact points)
int draw_bboxes=0;		// draw body bounding boxes
int draw_bodymgr=0;		// draw any drawable body manager aspects
int apply_gravity=0;	// whether or not to apply gravity field

int show_text=1;		// show the information text

// for click tracking
int mouse_0_curdown=0,mouse_0_click=0;
int mouse_1_curdown=0,mouse_1_click=0;

// for building constraints
int is_building_constraint=0;
rat_constraint_type constraint_build_type;
rat_body *constraint_build_body_a;
rat_body *constraint_build_body_b;
vector2 constraint_build_anch_a;
vector2 constraint_build_anch_b;
rat_real constraint_build_slider_length1;
rat_real constraint_build_slider_length2;

// for building polygons
int is_building_polygon=0;
int poly_build_num_verts=0;
vector2 poly_build_verts[64];

// for building circles
int is_building_circle=0;
vector2 circle_build_center;
rat_real circle_build_radius;

// for grabbing bodies
rat_body *grabbed_body;
rat_spring *grabbed_spring;
int has_grabbed=0;

int fullscreen=0;
unsigned int winwid=1024;
unsigned int winhgt=768;

rat_glyph_font *glyphfont;
rat_texture_font *outfont=NULL;

int reset_clock=0;
int pretty_graphics=1;

// callback for when a body leaves the world
static void leave_world_callback(rat_world *world,rat_body *body,void *userdata);
static float textcolor[]={0.0f,0.0f,0.0f,1.0f};

rat_real deltatime=1.0,deltasim=0.05;
clock_t oldclock,newclock;

// draw everything and update testbed; pretty self explanitory
static void update_and_render_all()
{
	char buf[4096];
	memset(buf,0,4096);

	if (pretty_graphics)
	{
		glEnable(GL_LINE_SMOOTH);
		glEnable(GL_POINT_SMOOTH);
		glHint(GL_LINE_SMOOTH_HINT,GL_FASTEST);
		glHint(GL_POINT_SMOOTH_HINT,GL_FASTEST);
	}

	update_and_draw_testbed(); // update and draw testbed

	rat_set_text_color(textcolor);
	rat_texture_font_render_text(outfont,30,30,"Rat Physics TestBed Build 23");

	if (show_text)
	{
		char memalcstr[256];

		rat_mem_alloced_string(memalcstr);
		sprintf(buf,"memory usage  blocks_alloced %08d  mem_alloced %s",rat_blocks_alloced(),memalcstr);
		rat_texture_font_render_text(outfont,30,70,buf);

		sprintf(buf,"delta_time %01.3f * delta_sim %01.3f = sim_step %02.3f",deltatime,deltasim,deltasim*deltatime);
		rat_texture_font_render_text(outfont,30,90,buf);

		sprintf(buf,"mouse states  left<%s> right<%s>",
			binary_state_str(mouse_0_state),
			binary_state_str(mouse_1_state));
		rat_texture_font_render_text(outfont,30,110,buf);

		sprintf(buf,"mouse coords  %3.3f %3.3f",mouse_x,mouse_y);
		rat_texture_font_render_text(outfont,30,130,buf);

		switch (world->body_manager->type)
		{
		case rat_body_manager_type_array_table:
			rat_texture_font_render_text(outfont,30,170,"using brute force collision checking");
			break;
		case rat_body_manager_type_quad_tree:
			rat_texture_font_render_text(outfont,30,170,"using quad tree collision checking");
			break;
		default:
			rat_texture_font_render_text(outfont,30,170,"WTF is this?");
			break;
		};

		sprintf(buf,"physics accuracy      %03u     (up/down arrows to change)",physics_accuracy);
		rat_texture_font_render_text(outfont,30,190,buf);

		sprintf(buf,"new spring stiffness  %03.2f   (left/right arrows to change)",new_spring_k);
		rat_texture_font_render_text(outfont,30,210,buf);

		sprintf(buf,"draw arbiters        <%s>    (toggle with F1)",binary_state_str(draw_arbiters));
		rat_texture_font_render_text(outfont,30,230,buf);

		sprintf(buf,"draw bounding boxes  <%s>    (toggle with F2)",binary_state_str(draw_bboxes));
		rat_texture_font_render_text(outfont,30,250,buf);

		if (world->body_manager->type==rat_body_manager_type_quad_tree)
		{
			sprintf(buf,"draw quad tree       <%s>    (toggle with F3)",binary_state_str(draw_bodymgr));
			rat_texture_font_render_text(outfont,30,270,buf);
		}
		else
			rat_texture_font_render_text(outfont,30,270,"no drawable body manager aspects");

		sprintf(buf,"press 'G' to toggle gravity.             <%s>",binary_state_str(apply_gravity));
		rat_texture_font_render_text(outfont,30,310,buf);

		sprintf(buf,"press enter to toggle control mode:      <%s>",control_mode_str(control_mode));
		rat_texture_font_render_text(outfont,30,350,buf);

		sprintf(buf,"press shift to change constraints:       <%s>",constraint_mode_strs[constraint_mode]);
		rat_texture_font_render_text(outfont,30,370,buf);

		sprintf(buf,"solver islands  %u",world->numislands);
		rat_texture_font_render_text(outfont,30,410,buf);

		rat_texture_font_render_text(outfont,30,450,"press the space bar to hide info.");
	}
	else
		rat_texture_font_render_text(outfont,30,70,"press the space bar to show info.");
}

PFNWGLSWAPINTERVALEXTPROC pwglSwapIntervalEXT;

static void create_video()
{
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
	SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE,16);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,16);

	SDL_SetVideoMode(winwid,winhgt,16,SDL_OPENGL|(fullscreen?SDL_FULLSCREEN:0));

	SDL_WM_SetCaption("Rat Physics TestBed",NULL);
	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,SDL_DEFAULT_REPEAT_INTERVAL/2);

	if (outfont) rat_texture_font_destroy(outfont);
	assert(glyphfont);
	outfont=rat_texture_font_from_glyph_font(glyphfont);

#ifdef RAT_PHYSICS_WINDOWS
	if (!pwglSwapIntervalEXT) pwglSwapIntervalEXT=load_extension("wglSwapIntervalEXT");
	if (pwglSwapIntervalEXT)  pwglSwapIntervalEXT(0);
#endif
}

static void toggle_fullscreen()
{
	fullscreen=!fullscreen;
	SDL_QuitSubSystem(SDL_INIT_VIDEO);
	if (SDL_InitSubSystem(SDL_INIT_VIDEO)!=0)
	{
		printf("VIDEO SUBSYSTEM EPIC FAIL! ... \"%s\"\n",SDL_GetError());
		fflush(stdout);
		exit(1);
	}
	reset_clock=1;
}

int running=1;
int delete_down=0;
int esc_down=0;
int space_down=0;
int alt_down=0;
int enter_down=0;
int shift_down=0;
int g_down=0;
int p_down=0;
int f1_down=0;
int f2_down=0;
int f3_down=0;

void handle_events()
{
	SDL_Event event;

	while (SDL_PollEvent(&event))
	{
		switch (event.type)
		{
		case SDL_QUIT:
			running=0;
			break;
		case SDL_MOUSEMOTION:
			mouse_x=(rat_real)event.motion.x/(rat_real)winwid;
			mouse_y=(rat_real)event.motion.y/(rat_real)winhgt;
			break;
		case SDL_MOUSEBUTTONDOWN:
			switch (event.button.button)
			{
			case SDL_BUTTON_LEFT:
				mouse_0_state=1;
				break;
			case SDL_BUTTON_RIGHT:
				mouse_1_state=1;
				break;
			default:
				break;
			}
			break;
		case SDL_MOUSEBUTTONUP:
			switch (event.button.button)
			{
			case SDL_BUTTON_LEFT:
				mouse_0_state=0;
				break;
			case SDL_BUTTON_RIGHT:
				mouse_1_state=0;
				break;
			default:
				break;
			}
			break;
		case SDL_KEYDOWN:
			switch (event.key.keysym.sym)
			{
			case SDLK_LALT:
			case SDLK_RALT:
				alt_down=1;
				break;
			case SDLK_DELETE:
				delete_down=1;
				break;
			case SDLK_ESCAPE:
				esc_down=1;
				break;
			case SDLK_SPACE:
				space_down=1;
				break;
			case SDLK_RETURN:
				enter_down=1;
				break;
			case SDLK_g:
				g_down=1;
				break;
			case SDLK_p:
				p_down=1;
				break;
			case SDLK_F1:
				f1_down=1;
				break;
			case SDLK_F2:
				f2_down=1;
				break;
			case SDLK_F3:
				f3_down=1;
				break;
			case SDLK_UP:
				physics_accuracy=(++physics_accuracy)>128?128:physics_accuracy;
				rat_world_set_solver_iterations(world,physics_accuracy);
				break;
			case SDLK_DOWN:
				physics_accuracy=(--physics_accuracy)<1?1:physics_accuracy;
				rat_world_set_solver_iterations(world,physics_accuracy);
				break;
			case SDLK_RIGHT:
				new_spring_k+=0.05;
				break;
			case SDLK_LEFT:
				new_spring_k=(new_spring_k-=0.05)<0.05?0.05:new_spring_k;
				break;
			default:
				break;
			}
			break;
		case SDL_KEYUP:
			switch (event.key.keysym.sym)
			{
			case SDLK_LALT:
			case SDLK_RALT:
				alt_down=0;
				if (enter_down)
				{
					enter_down=0;
					toggle_fullscreen();
					create_video();
				}
				break;
			case SDLK_DELETE:
				delete_down=0;
				break;
			case SDLK_ESCAPE:
				esc_down=0;
				running=0;
				break;
			case SDLK_SPACE:
				space_down=0;
				show_text=!show_text;
				break;
			case SDLK_RETURN:
				enter_down=0;
				if (alt_down)
				{
					alt_down=0;
					toggle_fullscreen();
					create_video();
				}
				else
					control_mode=!control_mode;
				break;
			case SDLK_RSHIFT:
				shift_down=0;
				constraint_mode=(++constraint_mode)<NUM_CONSTRAINT_MODES?constraint_mode:0;
				is_building_constraint=0;
				break;
			case SDLK_g:
				g_down=0;
				apply_gravity=!apply_gravity;
				rat_world_set_gravity(world,apply_gravity?grav_vec:zero_vec);
				rat_world_awaken_all(world);
				break;
			case SDLK_p:
				p_down=0;
				pretty_graphics=!pretty_graphics;
				break;
			case SDLK_F1:
				f1_down=0;
				draw_arbiters=!draw_arbiters;
				break;
			case SDLK_F2:
				f2_down=0;
				draw_bboxes=!draw_bboxes;
				break;
			case SDLK_F3:
				f3_down=0;
				draw_bodymgr=!draw_bodymgr;
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
	}
}

#ifdef WIN32SUBSYS
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
#else
int main(int argc,char *argv[])
{
#endif
	clock_t new_clock,old_clock;
	rat_frame_counter *framectr;
	vector2 cell={150.0,150.0};

	printf("at startup...\n");
	printf("num blocks alloced: %d\n",rat_blocks_alloced());
	printf("num bytes alloced: %d\n\n",rat_mem_alloced());

	atexit(SDL_Quit);
	if (SDL_Init(SDL_INIT_VIDEO)!=0)
	{
		printf("VIDEO SUBSYSTEM EPIC FAIL! ... \"%s\"\n",SDL_GetError());
		fflush(stdout);
		return 1;
	}

	rat_start_font_system();
	glyphfont=rat_glyph_font_load("small.ttf",14);

	create_video();

	rat_ortho_set_mode(rat_ortho_res_vres);
	rat_ortho_set_res(winwid,winhgt);
	rat_ortho_set_virtual_res(SCENE_WIDTH,SCENE_HEIGHT);

#ifdef DEBUGMODE
	rat_set_alloc_fn(malloc);
	rat_set_realloc_fn(realloc);
	rat_set_dealloc_fn(free);
#endif

	if (!rat_physics_is_init())
	{
		if (!rat_physics_init())
		{
			printf("Failed to initialize Rat Physics!\n");
			fflush(stdout);
			return 1;
		}
	}

	world=rat_world_create(world_bounds,rat_integrator_euler,physics_accuracy,rat_body_manager_type_quad_tree,&cell);

	rat_world_set_gravity(world,tmpvec);
	rat_world_set_damping(world,0.9);

	rat_world_set_body_left_world_callback(world,&leave_world_callback,NULL);

	initialize_testbed();
	rat_pretty_draw=1;
	running=1;
	framectr=rat_frame_counter_create(60.0,0.25);
	while (running)
	{
		glClearColor(0.8f,0.8f,0.8f,1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		rat_ortho_push();
		{
			update_and_render_all();		// update ui and render all

			if (reset_clock)
			{
				oldclock=newclock=clock();
				reset_clock=0;
			}
			else
				new_clock=clock();

			// update the world using a smooth and corrective timestepping scheme
			newclock=clock();
			deltatime=(deltatime*9.0+framectr->delta_time)/10.0;
			deltatime=deltatime>2.0?2.0:deltatime;
			rat_world_update(world,deltasim*deltatime,deltatime<=1.0?1:0);

			SDL_GL_SwapBuffers();
			rat_frame_counter_update(framectr,1);
		}
		rat_ortho_pop();
		handle_events();
	}
	rat_frame_counter_destroy(framectr);

	rat_glyph_font_destroy(glyphfont);
	rat_texture_font_destroy(outfont);
	rat_stop_font_system();

	destroy_testbed();
	rat_world_free_entities(world);
	rat_world_destroy(world);

	rat_physics_cleanup();

	SDL_Quit();

	printf("\nat shutdown...\n");
	printf("num blocks alloced: %d\n",rat_blocks_alloced());
	printf("num bytes alloced: %d\n\n",rat_mem_alloced());

	printf("Do you see any memory leaks?  |:B\n");

	return 0;
}

static void initialize_testbed()
{
	vector2 wallverts[4];

	rat_hull **wallhulls=(rat_hull **)rat_alloc(4*sizeof(rat_hull *));
	rat_body **walls=(rat_body **)rat_alloc(4*sizeof(rat_body *));

	vector2_set(wallverts,-20,-SCENE_HEIGHT/2.0);
	vector2_set(wallverts+1,-20,SCENE_HEIGHT/2.0);
	vector2_set(wallverts+2,20,SCENE_HEIGHT/2.0);
	vector2_set(wallverts+3,20,-SCENE_HEIGHT/2.0);

	vector2_set(&tmpvec,0,SCENE_HEIGHT/2.0);
	wallhulls[0]=rat_hull_polygon_create(tmpvec,4,wallverts);
	vector2_set(&tmpvec,SCENE_WIDTH,SCENE_HEIGHT/2.0);
	wallhulls[1]=rat_hull_polygon_create(tmpvec,4,wallverts);

	vector2_set(wallverts,-SCENE_WIDTH/2.0,-20);
	vector2_set(wallverts+1,-SCENE_WIDTH/2.0,20);
	vector2_set(wallverts+2,SCENE_WIDTH/2.0,20);
	vector2_set(wallverts+3,SCENE_WIDTH/2.0,-20);

	vector2_set(&tmpvec,SCENE_WIDTH/2.0,0);
	wallhulls[2]=rat_hull_polygon_create(tmpvec,4,wallverts);
	vector2_set(&tmpvec,SCENE_WIDTH/2.0,SCENE_HEIGHT);
	wallhulls[3]=rat_hull_polygon_create(tmpvec,4,wallverts);

	walls[0]=rat_body_create(wallhulls[0],1.0,1.0,0.0,0.0);
	walls[1]=rat_body_create(wallhulls[1],1.0,1.0,0.0,0.0);
	walls[2]=rat_body_create(wallhulls[2],1.0,1.0,0.0,0.0);
	walls[3]=rat_body_create(wallhulls[3],1.0,1.0,0.0,0.0);

	rat_world_add_body(world,walls[0]);
	rat_world_add_body(world,walls[1]);
	rat_world_add_body(world,walls[2]);
	rat_world_add_body(world,walls[3]);

	rat_dealloc((void *)wallhulls);
	rat_dealloc((void *)walls);
}

static void destroy_testbed()
{
	rat_world_free_entities(world);
}

static void update_and_draw_testbed()
{
	register unsigned int i;
	register vector2 world_cursor;

	world_cursor.x=mouse_x*SCENE_WIDTH;
	world_cursor.y=mouse_y*SCENE_HEIGHT;

	// now we draw the world
	rat_draw_world(world,draw_bodymgr,draw_arbiters,draw_bboxes);

	// check left mouse button for click
	mouse_0_click=0;
	if (mouse_0_state)
		mouse_0_curdown=1;
	else
	{
		if (mouse_0_curdown)
		{
			mouse_0_click=1;
			mouse_0_curdown=0;
		}
	}

	// check right mouse button for click
	mouse_1_click=0;
	if (mouse_1_state)
		mouse_1_curdown=1;
	else
	{
		if (mouse_1_curdown)
		{
			mouse_1_click=1;
			mouse_1_curdown=0;
		}
	}

	if (delete_down)
	{
		if (mouse_0_click) // delete the picked objects
		{
			vector2 global_anchor;
			vector2 local_anchor;
			rat_body *picked_body;

			if (testbed_pointpick(world_cursor,&picked_body,&local_anchor))
				rat_world_remove_and_destroy_body(world,picked_body);
		}
	}
	else
	{
		if (control_mode) // constraint construction / manipulation
		{
			if (mouse_0_state)
			{
				if (has_grabbed)
					rat_spring_update_global_anchor(grabbed_spring,world_cursor);
				else
				{
					vector2 global_anchor;
					vector2 local_anchor;
					rat_body *picked_body;

					if (testbed_pointpick(world_cursor,&picked_body,&local_anchor))
					{
						// we grab this puppy
						has_grabbed=1;
						grabbed_body=picked_body;
						grabbed_spring=rat_spring_create(grabbed_body,NULL,
							local_anchor,world_cursor,new_spring_k,0,new_spring_k*0.5);

						rat_world_add_spring(world,grabbed_spring);
					}
				}
			}
			else
			{
				if (has_grabbed)
				{
					rat_world_remove_and_destroy_spring(world,grabbed_spring);
					has_grabbed=0;
				}
			}

			if (mouse_1_click)
			{
				if (!is_building_constraint)
				{
					// start building a constraint
					is_building_constraint=1;
					constraint_build_type=(rat_constraint_type)constraint_mode;
				}

				// which constraint type are we building
				switch (constraint_build_type)
				{
				case 0:		// this is a spring
				case 1:		// this is a bar constraint
					switch (is_building_constraint)
					{
					case 1: // set first anchor
						is_building_constraint=2;

						// it's a point-pick, ma!
						if (!testbed_pointpick(world_cursor,
							&constraint_build_body_a,&constraint_build_anch_a))
						{
							is_building_constraint=0;
							break;
						}
						break;
					case 2: // set second anchor and build
						is_building_constraint=0;

						// it's a point-pick, ma!
						if (!testbed_pointpick(world_cursor,&constraint_build_body_b,
							&constraint_build_anch_b)) break;

						if (constraint_build_type) // we're making a bar constraint
						{
							// get our world transforms for the distance function
							vector2 world_anch_a=vector2_local_to_world(
								constraint_build_anch_a,
								rat_hull_get_pos(constraint_build_body_a->hull),
								rat_hull_get_angle(constraint_build_body_a->hull));
							vector2 world_anch_b=vector2_local_to_world(
								constraint_build_anch_b,
								rat_hull_get_pos(constraint_build_body_b->hull),
								rat_hull_get_angle(constraint_build_body_b->hull));

							// make a new bar constraint from the anchor points
							rat_world_add_constraint(world,
								rat_constraint_bar_create(
									constraint_build_body_a,constraint_build_body_b,
									constraint_build_anch_a,constraint_build_anch_b));
						}
						else // we're making a spring constraint
						{
							// get our world transforms for the distance function
							vector2 world_anch_a=vector2_local_to_world(
								constraint_build_anch_a,
								rat_hull_get_pos(constraint_build_body_a->hull),
								rat_hull_get_angle(constraint_build_body_a->hull));
							vector2 world_anch_b=vector2_local_to_world(
								constraint_build_anch_b,
								rat_hull_get_pos(constraint_build_body_b->hull),
								rat_hull_get_angle(constraint_build_body_b->hull));

							// make a new spring from the anchor points
							rat_world_add_spring(world,
								rat_spring_create(constraint_build_body_a,
									constraint_build_body_b,constraint_build_anch_a,
									constraint_build_anch_b,new_spring_k,
									vector2_distance(world_anch_a,world_anch_b),0.2));
						}
						break;
					default:
						break;
					};
					break;
				case 2:	// this is a slider constraint
					switch (is_building_constraint)
					{
					case 1: // set first anchor
						is_building_constraint=2;

						// it's a point-pick, ma!
						if (!testbed_pointpick(world_cursor,
							&constraint_build_body_a,&constraint_build_anch_a))
						{
							is_building_constraint=0;
							break;
						}
						break;
					case 2: // set second anchor
						is_building_constraint=3;

						// it's a point-pick, ma!
						if (!testbed_pointpick(world_cursor,
							&constraint_build_body_b,&constraint_build_anch_b))
						{
							is_building_constraint=0;
							break;
						}
						break;
					case 3: // set first length limit
						is_building_constraint=4;
						// we need this extra scope in ANSI C for the memory used only here
						{
							// get our world transforms for the distance function
							vector2 world_anch_a=vector2_local_to_world(
								constraint_build_anch_a,
								rat_hull_get_pos(constraint_build_body_a->hull),
								rat_hull_get_angle(constraint_build_body_a->hull));
							vector2 world_anch_b=vector2_local_to_world(
								constraint_build_anch_b,
								rat_hull_get_pos(constraint_build_body_b->hull),
								rat_hull_get_angle(constraint_build_body_b->hull));

							// project the cursor on to the constraint line to
							// see what the first length limit should be
							vector2 projected;
							line2 world_anch_line;
							line2_set(&world_anch_line,world_anch_a,world_anch_b);
							projected=line2_vector2project(world_anch_line,world_cursor);
							constraint_build_slider_length1=vector2_distance(projected,world_anch_a);
						}
						break;
					case 4: // set other length limit and build
						is_building_constraint=0;
						// we need this extra scope in ANSI C for the memory used only here
						{
							// the minimum and maximum lengths
							rat_real min_length,max_length;

							// get our world transforms for the distance function
							vector2 world_anch_a=vector2_local_to_world(
								constraint_build_anch_a,
								rat_hull_get_pos(constraint_build_body_a->hull),
								rat_hull_get_angle(constraint_build_body_a->hull));
							vector2 world_anch_b=vector2_local_to_world(
								constraint_build_anch_b,
								rat_hull_get_pos(constraint_build_body_b->hull),
								rat_hull_get_angle(constraint_build_body_b->hull));

							// project the cursor on to the constraint line to
							// see what the other length limit should be
							vector2 projected;
							line2 world_anch_line;
							line2_set(&world_anch_line,world_anch_a,world_anch_b);
							projected=line2_vector2project(world_anch_line,world_cursor);
							constraint_build_slider_length2=vector2_distance(projected,world_anch_a);

							// now that we have both values, we can
							// set the minimum and maximum lengths
							if (constraint_build_slider_length1<constraint_build_slider_length2)
							{
								min_length=constraint_build_slider_length1;
								max_length=constraint_build_slider_length2;
							}
							else
							{
								min_length=constraint_build_slider_length2;
								max_length=constraint_build_slider_length1;
							}

							// now that we have those, we can create the constraint
							rat_world_add_constraint(world,
								rat_constraint_slider_create(
									constraint_build_body_a,constraint_build_body_b,
									constraint_build_anch_a,constraint_build_anch_b,
									min_length,max_length));
						}
						break;
					};
					break;
				default:
					break;
				};
			}
		}
		else // rigid body construction
		{
			if (mouse_0_click) // on left click
			{
				if (is_building_polygon) // finish building a polygon
				{
					// attributes for the polygon
					rat_real moi,area;
					vector2 neg_centroid;		// world to local translate
					vector2 local_verts[64];	// local vertices

					if (poly_build_num_verts>2)
					{
						// do the world to local translate
						neg_centroid=vector2_negative(vector2_average(poly_build_verts,poly_build_num_verts));
						translate_points2((rat_real *)&neg_centroid,
							(rat_real *)poly_build_verts,(rat_real *)local_verts,poly_build_num_verts);

						// calculate area and moment of inertia
						area=rat_area_polygon(local_verts,poly_build_num_verts);
						moi=rat_moi_polygon(area,local_verts,poly_build_num_verts);

						rat_world_add_body(world,
								rat_body_create(
									rat_hull_polygon_create(vector2_negative(neg_centroid),
										poly_build_num_verts,local_verts),
									0.8,1.0,area<=0?0.0001:area,moi));
					}
					is_building_polygon=poly_build_num_verts=0;
				}
				else // go through building a circle
				{
					rat_real moi,area; // attributes for circle

					// get started
					if (!is_building_circle)
						is_building_circle=1;

					// let's do it!
					switch (is_building_circle)
					{
					case 1: // set point
						is_building_circle=2;
						circle_build_center=world_cursor;
						break;
					case 2: // set radius
						is_building_circle=0;

						// calculate radius
						circle_build_radius=vector2_distance(circle_build_center,world_cursor);

						// calculate area and moment of inertia
						area=rat_area_circle(circle_build_radius);
						moi=rat_moi_circle(area,circle_build_radius);

						rat_world_add_body(world,
							rat_body_create(
								rat_hull_circle_create(circle_build_center,circle_build_radius),
								0.8,1.0,area<=0?0.0001:area,moi));
						break;
					default:
						break;
					};
				}
			}

			if (mouse_1_click) // on right click
			{
				if (is_building_circle) // cancel building circle
					is_building_circle=0;
				else	// we'll build a polygon
				{
					is_building_polygon=1;

					poly_build_verts[poly_build_num_verts]=world_cursor;
					poly_build_num_verts++;
				}
			}
		}
	}

	// draw circle construction
	if (is_building_circle)
	{
		circle_build_radius=vector2_distance(circle_build_center,world_cursor);
		rat_set_color(0,0,0,0.4);
		rat_line_width(2.5);
		rat_draw_circle(circle_build_center.x,
			circle_build_center.y,circle_build_radius,0.0625f,0);
	}

	// draw polygon construction
	if (is_building_polygon)
	{
		rat_set_color(0,0,0,0.4);
		rat_line_width(2.5);
		rat_point_size(3.5);

		if (poly_build_num_verts>2)
		{
			for (i=0; i<poly_build_num_verts; i++)
			{
				vector2 vert_a=poly_build_verts[i];
				vector2 vert_b=poly_build_verts[(i+1)%poly_build_num_verts];
				rat_draw_point(vert_a.x,vert_a.y);
				rat_draw_line(vert_a.x,vert_a.y,vert_b.x,vert_b.y);
			}
		}
		else
		{
			for (i=0; i<poly_build_num_verts; i++)
				rat_draw_point(poly_build_verts[i].x,poly_build_verts[i].y);
		}
	}

	// draw constraint construction
	if (is_building_constraint)
	{
		// get our world transform to display the line
		vector2 world_anch_a=vector2_local_to_world(
			constraint_build_anch_a,
			rat_hull_get_pos(constraint_build_body_a->hull),
			rat_hull_get_angle(constraint_build_body_a->hull));

		rat_set_color(0,0,0,0.4);
		rat_line_width(2.5);
		rat_point_size(3.5);

		switch (constraint_build_type)
		{
		case 0:
		case 1:
			rat_draw_point(world_anch_a.x,world_anch_a.y);
			rat_draw_point(world_cursor.x,world_cursor.y);
			rat_draw_line(
				world_anch_a.x,world_anch_a.y,
				world_cursor.x,world_cursor.y);
			break;
		case 2:
			if (is_building_constraint<3)
			{
				rat_draw_point(world_anch_a.x,world_anch_a.y);
				rat_draw_point(world_cursor.x,world_cursor.y);
				rat_draw_line(
					world_anch_a.x,world_anch_a.y,
					world_cursor.x,world_cursor.y);
			}
			else
			{
				// get our world transform to display the line
				vector2 world_anch_b=vector2_local_to_world(
					constraint_build_anch_b,
					rat_hull_get_pos(constraint_build_body_b->hull),
					rat_hull_get_angle(constraint_build_body_b->hull));

				// get the normal so we can offset some parallel lines
				vector2 constraint_normal=vector2_perp_left(vector2_sub(world_anch_b,world_anch_a));
				vector2_normalize(&constraint_normal);

				// draw the constraint line
				rat_draw_point(world_anch_a.x,world_anch_a.y);
				rat_draw_point(world_cursor.x,world_cursor.y);
				rat_draw_line(
					world_anch_a.x,world_anch_a.y,
					world_anch_b.x,world_anch_b.y);

				if (is_building_constraint<4) // we're still putting in the first length boundary
				{
					// calculate the offset
					vector2 draw_offset=vector2_multf(constraint_normal,8.0);

					// line to be offset
					line2 offset_projection;

					// project the cursor on to the constraint line to
					// see where the first limit will be if the user clicks
					vector2 projected;
					line2 world_anch_line;
					line2_set(&world_anch_line,world_anch_a,world_anch_b);
					projected=line2_vector2project(world_anch_line,world_cursor);

					// offset that line so we can draw it parallel to the main line
					line2_set(&offset_projection,world_anch_a,projected);
					vector2_plus(&(offset_projection.a),draw_offset);
					vector2_plus(&(offset_projection.b),draw_offset);

					rat_draw_line(
						offset_projection.a.x,offset_projection.a.y,
						offset_projection.b.x,offset_projection.b.y);

					rat_set_color(0,0,0,0.6);
					rat_line_width(1.0);

					rat_draw_line(world_cursor.x,world_cursor.y,
						offset_projection.b.x,offset_projection.b.y);
				}
				else // we're putting in the second length boundary
				{
					// calculate the offsets
					vector2 draw_offset_a=vector2_multf(constraint_normal,8.0);
					vector2 draw_offset_b=vector2_multf(constraint_normal,16.0);

					// lines to be offset
					line2 offset_projection_a;
					line2 offset_projection_b;

					// project the cursor on to the constraint line to
					// see where the first limit will be if the user clicks
					vector2 projected_a,projected_b;
					line2 world_anch_line;
					line2_set(&world_anch_line,world_anch_a,world_anch_b);
					projected_a=vector2_add(vector2_multf(vector2_getnormalized(
						vector2_sub(world_anch_b,world_anch_a)),
						constraint_build_slider_length1),world_anch_a);
					projected_b=line2_vector2project(world_anch_line,world_cursor);

					// offset that lines so we can draw them parallel to the main line
					line2_set(&offset_projection_a,world_anch_a,projected_a);
					vector2_plus(&(offset_projection_a.a),draw_offset_a);
					vector2_plus(&(offset_projection_a.b),draw_offset_a);

					line2_set(&offset_projection_b,world_anch_a,projected_b);
					vector2_plus(&(offset_projection_b.a),draw_offset_b);
					vector2_plus(&(offset_projection_b.b),draw_offset_b);

					rat_draw_line(
						offset_projection_a.a.x,offset_projection_a.a.y,
						offset_projection_a.b.x,offset_projection_a.b.y);

					rat_draw_line(
						offset_projection_b.a.x,offset_projection_b.a.y,
						offset_projection_b.b.x,offset_projection_b.b.y);

					rat_set_color(0,0,0,0.6);
					rat_line_width(1.0);

					rat_draw_line(world_cursor.x,world_cursor.y,
						offset_projection_b.b.x,offset_projection_b.b.y);
				}
			}
			break;
		default:
			break;
		}
	}

	// lastly, draw the crosshairs
	rat_set_color(0,0,0,0.9);
	rat_line_width(1.0);

	rat_draw_line(world_cursor.x-10,world_cursor.y,world_cursor.x+10,world_cursor.y);
	rat_draw_line(world_cursor.x,world_cursor.y-10,world_cursor.x,world_cursor.y+10);
}

static int testbed_pointpick(vector2 point,rat_body **bodypicked,vector2 *local_picked)
{
	rat_body *thisbody;
	rat_iterator *iter=rat_body_manager_all_bodies_iterator(world->body_manager);
	for (; rat_iterator_finished(iter); rat_iterator_next(iter))
	{
		thisbody=(rat_body *)rat_iterator_value(iter);
		if (rat_hull_point_query(thisbody->hull,point))
		{
			rat_iterator_destroy(iter);
			*bodypicked=thisbody;
			*local_picked=vector2_world_to_local(
				point,rat_hull_get_pos(thisbody->hull),
				rat_hull_get_angle(thisbody->hull));

			return 1;
		}
	}
	rat_iterator_destroy(iter);
	return 0;
}

void leave_world_callback(rat_world *world,rat_body *body,void *userdata)
{
	// safe way to destroy and remove body in world.
	// since this callback is called in the middle of
	// a procedure that iterates through the bodies,
	// we must use the safe functions for manipulating
	// bodies inside a world callback.
	if (has_grabbed&&(body==grabbed_body))
	{
		//rat_world_remove_and_destroy_spring(world,grabbed_spring);
		has_grabbed=0;
	}
	rat_world_remove_and_destroy_body(world,body);
}
