#include #include #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 #elif defined(RAT_PHYSICS_LINUX) # include # include #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)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_length12) { // 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; ihull), 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); }