00001 00005 /* Copyright © 2009, 2010 James Legg. 00006 This program is free software: you can redistribute it and/or modify 00007 it under the terms of the GNU General Public License as published by 00008 the Free Software Foundation, either version 3 of the License, or 00009 (at your option) any later version. 00010 */ 00011 #include <config.h> 00012 #include "GameScene.h" 00013 #include "../Graphics/Window.h" 00014 #include <Debug.h> 00015 #include "InputHandler.h" 00016 #include "InputDeviceAI.h" 00017 #include "ResourceHandler.h" 00018 #include "../UI/BasicFonts.h" 00019 #include "../MainLoop.h" 00020 #include "Audio.h" 00021 00022 #include <libtrack/Texture.h> 00023 #include <libtrack/StartingPosition.h> 00024 00025 #include <GL/glu.h> 00026 #include <cmath> 00027 #include <fstream> 00028 #include <limits> 00029 00030 #include <glibmm/miscutils.h> 00031 00032 #ifdef HAVE_FTGL_2_1_2 00033 typedef FTGLTextureFont FTTextureFont; 00034 #endif 00035 00036 namespace Engine 00037 { 00038 00040 typedef ResourceHandler<Track::Texture, std::string, std::string> TextureStore; 00041 00042 GameScene::GameScene(std::vector<std::pair<InputHandler::iterator, unsigned int> > input_devices, 00043 const Track::Track & track) 00044 : m_input_devices(input_devices) 00045 , sky(track.get_theme().get_skybox()) 00046 , track(track) 00047 , world(track) 00048 , save_replay(true) 00049 , fps(0) 00050 , countdown_timer(-3000) 00051 , paused(false) 00052 , show_debug(false) 00053 #ifndef NDEBUG 00054 // prepare ai_mesh's display list so we can call it recusively. 00055 , ai_mesh(track.get_ai_mesh()) 00056 , m_total_frames(0) 00057 , m_total_time(0) 00058 #endif 00059 { 00060 // make sure textures randomly accessed by the hud are cached before the 00061 // game starts, to prevent mini freezes while the textures are loaded. 00062 // These remain in memory after the GameScene ends, but are reused next 00063 // time without loading them again. 00064 TextureStore & tex_store = TextureStore::get_instance(); 00065 #define tex_load(bind_name, file_name, variable)\ 00066 tex_store.check_load(bind_name, file_name);\ 00067 variable = tex_store.get(bind_name);\ 00068 variable->make_cache() 00069 tex_load("speedometer.hud.ui", "data/ui/hud/speedometer.png", m_speedometer_texture); 00070 tex_load("best_lap.hud.ui", "data/ui/hud/best_lap.png", m_best_lap_texture); 00071 tex_load("border.finish.hud.ui", "data/ui/hud/finish_border.png", m_finish_border_texture); 00072 tex_load("text.finish.hud.ui", "data/ui/hud/finished_text.png", m_finish_text_texture); 00073 tex_load("disqualified.hud.ui", "data/ui/hud/disqualified_text.png", m_disqualified_text_texture); 00074 tex_load("reverse.hud.ui", "data/ui/hud/reverse.png", m_reverse_texture); 00075 tex_load("countdown.hud.ui", "data/ui/hud/countdown.png", m_countdown_texture); 00076 #undef tex_load 00077 00084 // first line contains the track's filename 00085 m_replay_header << track.get_filename() << std::endl; 00086 // The number of cars. 00087 m_replay_header << input_devices.size() << " "; 00088 // We add information about each car as we set it up 00089 00090 // Put cars in the starting positions. 00091 const Track::Path & path = track.get_path(); 00092 const Track::PathEdge & edge = path.get_edge(path.get_starting_edge()); 00093 start_point = path.find_start_position(); 00094 path.find_start_plane(start_plane_normal, start_plane_distance); 00095 00096 const std::size_t number_of_cars = input_devices.size(); 00097 cars.reserve(number_of_cars); 00099 car_cameras.reserve(number_of_cars); 00100 for(unsigned int i = 0; i< number_of_cars; i++) 00101 { 00102 // Find the starting position for this car. 00103 btTransform initial_transform = 00104 // guess transformation when starting position cannot be found. 00105 edge.get_transform(float(i) / float(number_of_cars)); 00106 // find the starting position track attachment for this car's rank. 00107 for (std::vector<boost::shared_ptr<Track::TrackAttachment> >::const_iterator it = edge.get_attachments().begin(); 00108 it != edge.get_attachments().end(); 00109 it++) 00110 { 00111 const Track::StartingPosition * pos = dynamic_cast<const Track::StartingPosition *>(&(**it)); 00112 if (pos) 00113 { 00114 // cars go in reverse order, so human players go last. 00115 if (pos->get_rank() == number_of_cars - i - 1) 00116 { 00117 initial_transform = pos->get_global_transform(); 00118 } 00119 } 00120 } 00121 // raise the car above the track slightly. 00122 initial_transform.setOrigin(initial_transform(btVector3(0.0, 0.0, 0.2))); 00123 cars.push_back(new GameObjects::Car(world, 00124 initial_transform, 00125 *(input_devices[i].first), 00126 input_devices[i].second, 00127 start_plane_normal, 00128 start_plane_distance, 00129 start_point)); 00130 if (!dynamic_cast<InputDeviceAI*>(*(input_devices[i].first))) 00131 { 00132 // Not a computer player (though it could be a replay of one). 00136 car_cameras.push_back(new CarCamera(*cars[i], world)); 00137 car_skip.push_back(false); 00138 m_humans.push_back(i); 00139 } 00140 // The pointer to the input device will be used to identify input 00141 // reports during the game. Store it as a long int instead of hex. 00142 m_replay_header << (unsigned long int)&*(input_devices[i].first) << " " 00143 // Record which car model was used. 00144 << input_devices[i].second << " "; 00145 00146 } 00147 btDefaultMotionState motion_state(btTransform(btQuaternion(0, 0, 0, 1), 00148 btVector3(0, 0, 0))); 00149 track_shape = track.get_collision_shape(); 00150 btRigidBody::btRigidBodyConstructionInfo 00151 rigid_body_CI(btScalar(0), 00152 &motion_state, 00153 &(*track_shape), 00154 btVector3(0, 0, 0)); 00155 rigid_body_CI.m_restitution = 1.0; 00156 track_body = new btRigidBody(rigid_body_CI); 00157 world.get_dynamics_world().addRigidBody(track_body); 00158 00159 // now do the floor shape in the floor world. 00160 floor_shape = track.get_floor_shape(); 00161 rigid_body_CI.m_collisionShape = &(*floor_shape); 00162 floor_body = new btRigidBody(rigid_body_CI); 00163 world.get_floor_world().addCollisionObject(floor_body); 00164 00165 // stage specific light and fog. 00166 track.get_lighting().initalise(); 00167 00168 00169 #ifndef NDEBUG 00170 // Create a display list to show the debug version of the track. 00171 // This shows the navigation mesh, textured by lap position, with the 00172 // connections between faces drawn over the top. 00173 00174 // Set up texture 00175 GLubyte t[16] = {0, 15, 31, 47, 63, 79, 95, 111, 127, 143, 159, 175, 191, 207, 223, 239}; 00176 glGenTextures(1, &m_debug_texture_name); 00177 glBindTexture(GL_TEXTURE_1D, m_debug_texture_name); 00178 gluBuild1DMipmaps(GL_TEXTURE_1D, GL_LUMINANCE4, 16, GL_LUMINANCE, GL_UNSIGNED_BYTE, t); 00179 00180 ai_mesh.make_cache(); 00181 m_debug_list_name = glGenLists(1); 00182 glNewList(m_debug_list_name, GL_COMPILE); 00183 glPushAttrib(GL_CURRENT_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT | GL_TRANSFORM_BIT); 00184 // navigation mesh 00185 glEnable(GL_TEXTURE_1D); 00186 glDisable(GL_TEXTURE_2D); 00187 glBindTexture(GL_TEXTURE_1D, m_debug_texture_name); 00188 glMatrixMode(GL_TEXTURE); 00189 glScalef(0.2, 1.0, 1.0); 00190 ai_mesh.draw(); 00191 glLoadIdentity(); 00192 glDisable(GL_TEXTURE_1D); 00193 00194 // connection lines 00195 const Track::MeshFaces::Graph & graph = track.get_ai_graph(); 00196 glDisable(GL_DEPTH_TEST); 00197 glBegin(GL_LINES); 00198 typedef boost::graph_traits<Track::MeshFaces::Graph>::edge_iterator EdgeIterator; 00199 std::pair<EdgeIterator, EdgeIterator> edge_range; 00200 for (edge_range = boost::edges(graph); 00201 edge_range.first != edge_range.second; 00202 edge_range.first++) 00203 { 00204 const btVector3 & source = graph[boost::source(*(edge_range.first), graph)].face_centre; 00205 const btVector3 & target = graph[boost::target(*(edge_range.first), graph)].face_centre; 00206 glColor3f(1.0, graph[boost::source(*(edge_range.first), graph)].fv1.texture_coord_u / 1000.0, 0); 00207 glVertex3f(source.x(), source.y(), source.z()); 00208 glVertex3f(target.x(), target.y(), target.z()); 00209 } 00210 glEnd(); 00211 glPopAttrib(); 00212 glEndList(); 00213 #endif 00214 } 00215 00216 GameScene::~GameScene() 00217 { 00218 // record the replay if requested. 00219 if (save_replay) 00220 { 00221 try 00222 { 00223 std::ofstream out; 00224 out.exceptions(std::ofstream::badbit | std::ofstream::failbit); 00225 out.open((Glib::get_user_config_dir() + "/racer_last_replay").c_str()); 00226 // Write metadata identifing the scene and cars 00227 out << m_replay_header.str(); 00228 // Write the input device events that occured during the game. 00229 world.write_replay_events(out); 00230 } catch (std::ofstream::failure e) 00231 { 00232 DEBUG_MESSAGE("Error while writing replay file."); 00233 } 00234 } 00235 00236 world.get_dynamics_world().removeRigidBody(track_body); 00237 delete track_body; 00238 world.get_floor_world().removeCollisionObject(floor_body); 00239 delete floor_body; 00240 // delete the cars' cameras 00241 for (std::vector<CarCamera *>::iterator i = car_cameras.begin(); 00242 i != car_cameras.end(); i++) 00243 { 00244 delete *i; 00245 } 00246 // delete the cars 00247 for (std::vector<GameObjects::Car *>::iterator i = cars.begin(); 00248 i != cars.end(); i++) 00249 { 00250 delete *i; 00251 } 00252 00253 #ifndef NDEBUG 00254 glDeleteLists(m_debug_list_name, 1); 00255 glDeleteTextures(1, &m_debug_texture_name); 00256 DEBUG_MESSAGE("Framerate: " << (float(m_total_frames) / float(m_total_time) * 1000.0)); 00257 #endif 00258 } 00259 00260 void GameScene::take_input(InputReport & report) 00261 { 00262 if (paused) 00263 { 00264 m_pause_menu.take_input(report); 00265 return; 00266 } 00267 switch (report.get_report_type()) 00268 { 00269 case InputReport::RT_MENU_BACK: 00270 #ifndef NDEBUG 00271 /* In debug builds, toggle debugging overlay when pressing escape 00272 * or backspace instead of pausing. 00273 */ 00274 show_debug = !show_debug; 00275 break; 00276 #endif 00277 case InputReport::RT_MENU_SELECT: 00278 { 00279 std::size_t car = -1; 00280 for (unsigned int i = 0; i < m_input_devices.size(); i++) 00281 { 00282 if (m_input_devices[i].first == report.get_input_device()) 00283 { 00284 car = i; 00285 break; 00286 } 00287 } 00288 if (car != std::size_t(-1)) 00289 { 00290 if (cars[car]->get_disqualified() || cars[car]->get_finished()) 00291 { 00292 car_skip[car] = true; 00293 } else { 00294 paused = true; 00295 Audio::get_instance().pause(); 00296 } 00297 } else { 00298 // unused controller. 00299 paused = true; 00300 Audio::get_instance().pause(); 00301 } 00302 } 00303 break; 00304 case InputReport::RT_DISCONNECT: 00305 // automatically pause the game if connection to a device is lost. 00306 paused = true; 00307 case InputReport::RT_BATTERY_LOW: 00311 break; 00312 default: 00313 // all car actions are passed to the car. 00314 break; 00315 } 00316 } 00317 00318 void GameScene::update_logic(unsigned int milliseconds_elapsed) 00319 { 00320 #ifndef NDEBUG 00321 m_total_frames++; 00322 m_total_time += milliseconds_elapsed; 00323 #endif 00324 fps = 1000.0 / float(milliseconds_elapsed); 00325 if (paused) 00326 { 00327 if (m_pause_menu.want_to_quit()) 00328 { 00329 // escape or backspace hit while pause menu showing. 00330 unpause(); 00331 } else { 00332 switch (m_pause_menu.get_response()) 00333 { 00334 case UI::PauseMenu::R_NONE: 00335 // wait for user to respond to pause menu. 00336 break; 00337 case UI::PauseMenu::R_CONTINUE: 00338 // continue the game. 00339 unpause(); 00340 break; 00341 case UI::PauseMenu::R_QUIT: 00342 // quit selected, then confirm pressed (space or enter). 00343 main_loop->pop_scene(); 00344 break; 00345 } 00346 } 00347 return; 00348 } 00349 if (track.get_theme().get_use_sky_particles()) 00350 { 00351 m_sky_particles.update(milliseconds_elapsed); 00352 } 00353 if (countdown_timer <=500) 00354 { 00355 countdown_timer += milliseconds_elapsed; 00356 // don't do physics simulation until race starts. 00357 if (countdown_timer < 0) return; 00358 } 00359 world.update(milliseconds_elapsed); 00360 00361 // if we are done with non computer controlled cars, quit the scene. 00362 bool quit = true; 00363 for (unsigned int i = 0; i < m_humans.size(); i++) 00364 { 00365 int car_index = m_humans[i]; 00366 bool this_car = cars[car_index]->get_disqualified() || 00367 cars[car_index]->get_finished(); 00368 if (this_car) 00369 { 00370 // done after a few seconds or if manually skipped. 00371 if ( (world.get_tick_number() - cars[car_index]->get_finish_time() < 300) 00372 && !car_skip[car_index]) 00373 { 00374 quit = false; 00375 } 00376 } 00377 else 00378 { 00379 quit = false; 00380 } 00381 } 00382 if (quit) 00383 { 00384 main_loop->pop_scene(); 00385 } 00386 } 00387 00388 void GameScene::draw() 00389 { 00390 // Find the correct ranking of cars to display. 00391 rank_cars(); 00392 00393 std::size_t camera_count = car_cameras.size(); 00397 // find reasonable way to split the viewports. 00398 // We pick the layout with the aspect ratio closest to 4:3. Given multiple 00399 // choices, we use the layout with the most screen utilisation (least area 00400 // spent on unused viewports). 00401 unsigned int height = Graphics::Window::get_instance().get_height(); 00402 unsigned int width = Graphics::Window::get_instance().get_width(); 00403 // try each possible number of columns 00404 unsigned int best_rows(1); 00405 unsigned int best_columns(1); 00406 float best_utilisation(0.0); 00407 float best_aspect(0.0); 00408 float best_aspect_wrongness(height * width); 00409 for (unsigned int rows = 1; rows <= camera_count; rows++) 00410 { 00411 unsigned int columns = (camera_count + rows - 1) / rows; 00412 float aspect = (float(width) / float(columns)) 00413 / (float(height) / float(rows)); 00414 float utilisation = float(camera_count) / float(rows * columns); 00415 float aspect_wrongness = 2.35 - aspect; 00416 if (aspect_wrongness < 0.0) 00417 { 00418 aspect_wrongness = -aspect_wrongness; 00419 } 00420 bool better = false; 00421 if (best_aspect_wrongness > aspect_wrongness) 00422 { 00423 better = true; 00424 } 00425 else if ( best_aspect_wrongness == aspect_wrongness 00426 && utilisation > best_utilisation) 00427 { 00428 // The aspect ratio is just as good, but this has less screen 00429 // space used for unused viewports. 00430 better = true; 00431 } 00432 if (better) 00433 { 00434 best_rows = rows; 00435 best_columns = columns; 00436 best_utilisation = utilisation; 00437 best_aspect = aspect; 00438 best_aspect_wrongness = aspect_wrongness; 00439 } 00440 } 00441 // now render each viewport with the choosen layout. 00442 glEnable(GL_SCISSOR_TEST); 00443 glEnable(GL_DEPTH_TEST); 00444 glEnable(GL_TEXTURE_2D); 00445 glEnable(GL_CULL_FACE); 00446 unsigned int row = 0; 00447 unsigned int column = 0; 00448 unsigned int row_size = height / best_rows; 00449 unsigned int column_size = width / best_columns; 00450 unsigned int player = 0; 00451 assert(best_aspect > 0); 00452 for (std::vector<CarCamera *>::iterator it = car_cameras.begin(); 00453 it != car_cameras.end(); it++) 00454 { 00455 glViewport(column * column_size, row * row_size, 00456 column_size, row_size); 00457 glScissor(column * column_size, row * row_size, 00458 column_size, row_size); 00464 draw_for_player(player, best_aspect); 00465 column++; 00466 player++; 00467 if (column == best_columns) 00468 { 00469 row++; 00470 column = 0; 00471 } 00472 } 00473 // blank the unused windows. 00474 00475 while(player < best_rows * best_columns) 00476 { 00477 glViewport(column * column_size, row * row_size, 00478 column_size, row_size); 00479 glScissor(column * column_size, row * row_size, 00480 column_size, row_size); 00481 glClear(GL_COLOR_BUFFER_BIT); 00482 player++; 00483 column++; 00484 if (column == best_columns) 00485 { 00486 row++; 00487 column = 0; 00488 } 00489 } 00490 glDisable(GL_SCISSOR_TEST); 00491 00492 // Indicate when paused 00493 if (paused) 00494 { 00495 draw_paused_screen(); 00496 } 00497 00498 // show the fps counter in adebug build. 00499 #ifndef NDEBUG 00500 char msg[10] = "f/s: 000"; 00501 // take the average frames per second over the last 60 frames. 00502 const int number_of_readings = 60; 00503 static int readings[number_of_readings] = {0.0}; 00504 static int which_reading = 0; 00505 static int total = 0; 00506 total += int(fps); 00507 total -= readings[which_reading]; 00508 readings[which_reading] = int(fps); 00509 which_reading++; 00510 if (which_reading == number_of_readings) 00511 { 00512 which_reading = 0; 00513 } 00514 int fpsi = total > 999 * number_of_readings ? 999 : total / number_of_readings; 00515 msg[5] = fpsi / 100 + '0'; 00516 msg[6] = (fpsi / 10) % 10 + '0'; 00517 msg[7] = fpsi % 10 + '0'; 00518 Graphics::Window::get_instance().set_ortho_projection(); 00519 glLoadIdentity(); 00520 glTranslatef(Graphics::Window::get_instance().get_width() - 150, 15, 0); 00521 FTTextureFont & font = UI::BasicFonts::get_instance().small_font; 00522 glDisable(GL_DEPTH_TEST); 00523 glColor4ub(0, 0, 0, 127); 00524 glEnable(GL_BLEND); 00525 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 00526 glDisable(GL_TEXTURE_2D); 00527 glBegin(GL_QUADS); 00528 glVertex2f(-5, -5); 00529 glVertex2f(50, -5); 00530 glVertex2f(50, 12); 00531 glVertex2f(-5, 12); 00532 glEnd(); 00533 glEnable(GL_TEXTURE_2D); 00534 if (fpsi > 60) 00535 { 00536 glColor3ub(0, 255, 0); // green, good refresh rate 00537 } 00538 else if (fpsi > 45) 00539 { 00540 glColor3ub(255, 255, 0); // yellow, mediocre refresh rate. 00541 } 00542 else if (fpsi > 30) 00543 { 00544 glColor3ub(255, 127, 0); // orange, poor refresh rate 00545 } 00546 else 00547 { 00548 glColor3ub(255, 0, 0); // red, very bad refresh rate. 00549 } 00550 font.Render(msg); 00551 glColor3ub(255, 255, 255); 00552 glEnable(GL_DEPTH_TEST); 00553 glDisable(GL_BLEND); 00554 glLoadIdentity(); 00555 #endif 00556 } 00557 00558 void GameScene::draw_paused_screen() 00559 { 00560 glViewport(0, 00561 0, 00562 Graphics::Window::get_instance().get_width(), 00563 Graphics::Window::get_instance().get_height()); 00564 Graphics::Window::get_instance().set_ortho_projection(); 00565 glLoadIdentity(); 00566 glDisable(GL_DEPTH_TEST); 00567 // darken screen to reduce burn in 00568 glDisable(GL_TEXTURE_2D); 00569 glEnable(GL_BLEND); 00570 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 00571 glColor4f(0.0, 0.0, 0.0, 0.5); 00572 glBegin(GL_QUADS); 00573 float x = Graphics::Window::get_instance().get_width() + 1.0; 00574 float y = Graphics::Window::get_instance().get_height() + 1.0; 00575 glVertex2f(-1, -1); 00576 glVertex2f(x, -1); 00577 glVertex2f(x, y); 00578 glVertex2f(-1, y); 00579 glEnd(); 00580 // show menu in the middle of the screen. 00581 glEnable(GL_TEXTURE_2D); 00582 glColor3f(1, 1, 1); 00583 glTranslatef(int(x/2), int(y/2), 0.0); 00584 glDisable(GL_CULL_FACE); 00585 m_pause_menu.draw(); 00586 glEnable(GL_DEPTH_TEST); 00587 glEnable(GL_CULL_FACE); 00588 glDisable(GL_BLEND); 00589 } 00590 00591 void GameScene::draw_for_player(unsigned int player, float aspect) 00592 { 00593 glMatrixMode(GL_PROJECTION); 00594 glLoadIdentity(); 00595 gluPerspective(65.0, aspect, 0.03, 1000.0); 00596 glMatrixMode(GL_MODELVIEW); 00597 glClear(GL_DEPTH_BUFFER_BIT); 00598 00599 draw_world(player, aspect); 00600 00601 glMatrixMode(GL_PROJECTION); 00602 glLoadIdentity(); 00603 if (aspect > 1.0) 00604 { 00605 // screen wider than it is tall, add extra horizontal space 00606 glOrtho(-aspect, aspect, -1, 1, -1, 1); 00607 } else { 00608 // screen taller than it is wide, add extra vertical space. 00609 GLdouble aspect_inv = 1.0 / aspect; 00610 glOrtho(-1, 1, -aspect_inv, aspect_inv, -1, 1); 00611 } 00612 glMatrixMode(GL_MODELVIEW); 00613 00614 draw_hud(player); 00615 } 00616 00617 void GameScene::draw_world(unsigned int player, float aspect) 00618 { 00619 glLoadIdentity(); 00620 car_cameras[player]->full_transform(); 00621 car_cameras[player]->update_occlusion_tester(occlusion_tester, aspect); 00622 00623 // set up light and fog 00624 track.get_lighting().prepare_render(); 00625 00626 if (!show_debug) 00627 { 00628 const Track::Path & path = track.get_path(); 00629 path.conditional_draw(occlusion_tester); 00630 } 00631 00632 // draw sky box using only the camera rotation 00633 // (no translation to fake infinite distance) 00634 glDisable(GL_FOG); 00635 glDisable(GL_LIGHTING); 00636 glPushMatrix(); 00637 glLoadIdentity(); 00638 car_cameras[player]->rotation_transform(); 00639 sky.draw(); 00640 glPopMatrix(); 00641 if (track.get_lighting().get_fog().enabled) glEnable(GL_FOG); 00642 glEnable(GL_LIGHTING); 00643 00644 // Cars 00645 // Need to be drawn over track & sky because of the blended engine flame. 00646 for (unsigned int i = 0; i < cars.size(); i++) 00647 { 00648 cars[i]->conditional_draw(occlusion_tester); 00649 } 00650 if (track.get_theme().get_use_sky_particles()) 00651 { 00652 m_sky_particles.draw(*(car_cameras[player])); 00653 } 00654 glDisable(GL_LIGHTING); 00655 glDisable(GL_FOG); 00656 // (Debug) draw the ai navigation graph. 00657 #ifndef NDEBUG 00658 if (show_debug) 00659 { 00660 glCallList(m_debug_list_name); 00661 } 00662 #endif 00663 } 00664 00665 void GameScene::draw_hud(unsigned int player) 00666 { 00667 // Heads up display overlayed over the game. 00668 // We leave a border of 0.1 (5%) around the screen edges to keep in the 00669 // safe area incase of playing on a TV. 00670 glLoadIdentity(); 00671 glEnable(GL_BLEND); 00672 glDisable(GL_DEPTH_TEST); 00673 glDisable(GL_CULL_FACE); 00674 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 00675 00676 if (cars[player]->get_finished()) 00677 { 00678 draw_finished(player); 00679 } 00680 else if (cars[player]->get_disqualified()) 00681 { 00682 draw_disqualified(player); 00683 } 00684 else 00685 { 00686 draw_speedometer(player); 00687 draw_lap_info(player); 00688 draw_rank(player); 00689 int backwards = cars[player]->get_facing_backwards(); 00690 if (backwards) 00691 { 00692 glColor4ub(255, 255, 255, backwards); 00693 draw_reverse_sign(player); 00694 glColor3ub(255, 255, 255); 00695 } 00696 if (countdown_timer < 500) 00697 { 00698 draw_countdown(); 00699 } 00700 } 00701 00702 glEnable(GL_DEPTH_TEST); 00703 glEnable(GL_CULL_FACE); 00704 glDisable(GL_BLEND); 00705 } 00706 00707 void GameScene::draw_speedometer(unsigned int player) 00708 { 00709 // size: 0.4, middle: 0. 00710 btScalar speed = cars[player]->get_speed() * 10.0; 00711 m_speedometer_texture->bind(); 00712 glColor3f(1.0, speed / 80.0 + 0.5, speed / 150.0 + 0.5); 00713 glBegin(GL_QUADS); 00714 // background 00715 glTexCoord2f(0.0, 1.0); glVertex2f(0.5, -0.9); 00716 glTexCoord2f(0.0, 0.5); glVertex2f(0.5, -0.5); 00717 glTexCoord2f(0.5, 0.5); glVertex2f(0.9, -0.5); 00718 glTexCoord2f(0.5, 1.0); glVertex2f(0.9, -0.9); 00719 glEnd(); 00720 // needle 00721 glTranslatef(0.7, -0.7, 0.0); 00722 glRotatef(speed * 2.25 + 180, 0.0, 0.0, -1.0); 00723 glBegin(GL_QUADS); 00724 glTexCoord2f(0.0, 0.5); glVertex2f(-0.2, -0.2); 00725 glTexCoord2f(0.0, 0.0); glVertex2f(-0.2, 0.2); 00726 glTexCoord2f(0.5, 0.0); glVertex2f(0.2, 0.2); 00727 glTexCoord2f(0.5, 0.5); glVertex2f(0.2, -0.2); 00728 glEnd(); 00729 glLoadIdentity(); 00730 glColor3f(1.0, speed / 60.0, speed / 100.0); 00731 glBegin(GL_QUADS); 00732 // needle cover 00733 glTexCoord2f(0.5, 1.0); glVertex2f(0.5, -0.9); 00734 glTexCoord2f(0.5, 0.5); glVertex2f(0.5, -0.5); 00735 glTexCoord2f(1.0, 0.5); glVertex2f(0.9, -0.5); 00736 glTexCoord2f(1.0, 1.0); glVertex2f(0.9, -0.9); 00737 glEnd(); 00738 // text stating the speed 00739 std::stringstream speedo_text; 00740 speedo_text.precision(1); 00741 speedo_text << std::fixed << speed << " m/s"; 00742 glTranslatef(0.565, -0.865, 0.0); 00743 glScalef(0.002, 0.002, 1); 00744 FTTextureFont & font = UI::BasicFonts::get_instance().big_font; 00745 glColor4f(1.0, 1.0, 1.0, 0.7); 00746 font.Render(speedo_text.str().c_str()); 00747 glLoadIdentity(); 00748 m_speedometer_texture->bind(); 00749 glColor3f(1.0, 1.0, 1.0); 00750 glBegin(GL_QUADS); 00751 // number cover 00752 glTexCoord2f(0.5, 0.5); glVertex2f(0.5, -0.9); 00753 glTexCoord2f(0.5, 0.0); glVertex2f(0.5, -0.5); 00754 glTexCoord2f(1.0, 0.0); glVertex2f(0.9, -0.5); 00755 glTexCoord2f(1.0, 0.5); glVertex2f(0.9, -0.9); 00756 glEnd(); 00757 } 00758 00759 void GameScene::draw_lap_info(unsigned int player) 00760 { 00761 // display lap counter 00762 // darken corner. 00763 glDisable(GL_TEXTURE_2D); 00764 glColor4f(0.0, 0.0, 0.0, 0.5); 00765 glBegin(GL_QUADS); 00766 glVertex2f(0.5, 0.7); 00767 glVertex2f(0.5, 100.0); 00768 glVertex2f(100.0, 100.0); 00769 glVertex2f(100.0, 0.7); 00770 glEnd(); 00771 glColor3ub(255, 255, 255); 00772 glEnable(GL_TEXTURE_2D); 00773 00774 // display message 00775 std::stringstream lap_text; 00776 lap_text << "Lap " << cars[player]->get_lap() << " of 3"; 00777 glTranslatef(0.5, 0.8, 0.0); 00778 glScalef(0.75/256.0, 0.75/256.0, 1); 00779 FTTextureFont & font = UI::BasicFonts::get_instance().big_font; 00780 font.Render(lap_text.str().c_str()); 00781 glLoadIdentity(); 00782 00783 glDisable(GL_TEXTURE_2D); 00784 glBegin(GL_LINE_LOOP); 00785 glVertex2f(0.55, 0.76); 00786 glVertex2f(0.9, 0.76); 00787 glVertex2f(0.9, 0.78); 00788 glVertex2f(0.55, 0.78); 00789 glEnd(); 00790 float x_end = 0.55 + 0.35 * cars[player]->get_lap_distance() / track.get_lap_length(); 00791 glBegin(GL_QUADS); 00792 glVertex2f(0.55, 0.76); 00793 glVertex2f(x_end, 0.76); 00794 glVertex2f(x_end, 0.78); 00795 glVertex2f(0.55, 0.78); 00796 glEnd(); 00797 glEnable(GL_TEXTURE_2D); 00798 if (cars[player]->get_lap() > 1) 00799 { 00800 std::stringstream time_text; 00801 long unsigned int time = cars[player]->get_last_lap_ticks(); 00802 time_text << "Last " << time / 6000 << ":" << (time % 6000) / 100 00803 << "." << ((time / 10) % 10) << (time % 10); 00804 glTranslatef(0.5, 0.7, 0.0); 00805 glScalef(0.75/256.0, 0.75/256.0, 1); 00806 font.Render(time_text.str().c_str()); 00807 glLoadIdentity(); 00808 // if the lap time is the best of all players, draw an icon next to it. 00809 unsigned long int best_time = std::numeric_limits<unsigned long int>::max(); 00810 for (std::vector<GameObjects::Car *>::const_iterator it = cars.begin(); 00811 it != cars.end(); it++) 00812 { 00813 unsigned long int car_best = (*it)->get_best_lap_ticks(); 00814 if (best_time > car_best) 00815 { 00816 best_time = car_best; 00817 } 00818 } 00819 if (time == best_time) 00820 { 00821 // draw icon. 00822 m_best_lap_texture->bind(); 00823 glBegin(GL_QUADS); 00824 glTexCoord2f(0.0, 1.0); glVertex2f(0.4, 0.675); 00825 glTexCoord2f(0.0, 0.0); glVertex2f(0.4, 0.775); 00826 glTexCoord2f(1.0, 0.0); glVertex2f(0.5, 0.775); 00827 glTexCoord2f(1.0, 1.0); glVertex2f(0.5, 0.675); 00828 glEnd(); 00829 } 00830 } 00831 } 00832 00833 void GameScene::draw_rank(unsigned int player) 00834 { 00835 glDisable(GL_TEXTURE_2D); 00836 glColor4f(0, 0, 0, .75); 00837 glBegin(GL_QUADS); 00838 glVertex2f(-0.1, 0.8); 00839 glVertex2f( 0.1, 0.8); 00840 glVertex2f( 0.1, 100.0); 00841 glVertex2f(-0.1, 100.0); 00842 glEnd(); 00843 glEnable(GL_TEXTURE_2D); 00844 glColor3f(1.0, 1.0, 1.0); 00845 00846 std::stringstream rank_text; 00847 int rank = car_ranks[player] + 1; 00848 rank_text << rank; 00849 // work out what suffix to use (st, nd, rd, or th) 00850 int rank_mod100 = rank % 100; 00851 int rank_mod10 = rank % 10; 00852 // catch 11th, 12th, and 13th, so they don't become 11st, 12nd, and 13rd. 00853 if (rank_mod100 >= 4 && rank_mod100 <= 20) rank_text << "th"; 00854 else if (rank_mod10 == 1) rank_text << "st"; 00855 else if (rank_mod10 == 2) rank_text << "nd"; 00856 else if (rank_mod10 == 3) rank_text << "rd"; 00857 else rank_text << "th"; 00858 glTranslatef(-0.1, 0.8, 0.0); 00859 glScalef(0.004, 0.004, 1); 00860 FTTextureFont & font = UI::BasicFonts::get_instance().big_font; 00861 font.Render(rank_text.str().c_str()); 00862 glLoadIdentity(); 00863 } 00864 00865 void GameScene::draw_disqualified(unsigned int player) 00866 { 00867 glDisable(GL_TEXTURE_2D); 00868 unsigned long int ticks_since = world.get_tick_number() - cars[player]->get_finish_time(); 00869 glColor4f(0, 0, 0, 1.0 - 1.0 / (float(ticks_since) / 100.0)); 00870 glBegin(GL_QUADS); 00871 glVertex2f(-100.0, -100.0); 00872 glVertex2f( 100.0, -100.0); 00873 glVertex2f( 100.0, 100.0); 00874 glVertex2f(-100.0, 100.0); 00875 glEnd(); 00876 glEnable(GL_TEXTURE_2D); 00877 glColor3f(1.0, 1.0, 1.0); 00878 00879 m_disqualified_text_texture->bind(); 00880 glBegin(GL_QUADS); 00881 glTexCoord2f(0, 1); glVertex2f(-1.0, -0.25); 00882 glTexCoord2f(1, 1); glVertex2f( 1.0, -0.25); 00883 glTexCoord2f(1, 0); glVertex2f( 1.0, 0.25); 00884 glTexCoord2f(0, 0); glVertex2f(-1.0, 0.25); 00885 glEnd(); 00886 } 00887 00888 void GameScene::draw_finished(unsigned int player) 00889 { 00890 // black background 00891 glDisable(GL_TEXTURE_2D); 00892 glColor4f(0.0, 0.0, 0.0, 0.75); 00893 glBegin(GL_QUADS); 00894 glVertex2f(-100.0, -0.3); 00895 glVertex2f( 100.0, -0.3); 00896 glVertex2f( 100.0, 0.3); 00897 glVertex2f(-100.0, 0.3); 00898 glEnd(); 00899 00900 // checkered border 00901 glEnable(GL_TEXTURE_2D); 00902 m_finish_border_texture->bind(); 00903 glColor3f(1.0, 1.0, 1.0); 00904 glMatrixMode(GL_TEXTURE); 00905 // make it move 00906 glTranslatef(float(world.get_tick_number()) * 0.003, 0, 0); 00907 glBegin(GL_QUADS); 00908 glTexCoord2f(-1000, 1); glVertex2f(-100.0, 0.3); 00909 glTexCoord2f( 1000, 1); glVertex2f( 100.0, 0.3); 00910 glTexCoord2f( 1000, 0); glVertex2f( 100.0, 0.4); 00911 glTexCoord2f(-1000, 0); glVertex2f(-100.0, 0.4); 00912 00913 glTexCoord2f( 1000, 1); glVertex2f(-100.0, -0.3); 00914 glTexCoord2f(-1000, 1); glVertex2f( 100.0, -0.3); 00915 glTexCoord2f(-1000, 0); glVertex2f( 100.0, -0.4); 00916 glTexCoord2f( 1000, 0); glVertex2f(-100.0, -0.4); 00917 glEnd(); 00918 glLoadIdentity(); 00919 glMatrixMode(GL_MODELVIEW); 00920 00921 // text that says "Finished" in the middle. 00922 m_finish_text_texture->bind(); 00923 glBegin(GL_QUADS); 00924 glTexCoord2f(0, 1); glVertex2f(-1.0, -0.25); 00925 glTexCoord2f(1, 1); glVertex2f( 1.0, -0.25); 00926 glTexCoord2f(1, 0); glVertex2f( 1.0, 0.25); 00927 glTexCoord2f(0, 0); glVertex2f(-1.0, 0.25); 00928 glEnd(); 00929 00930 glDisable(GL_TEXTURE_2D); 00931 00932 // white screen flash 00933 unsigned long int ticks_since = world.get_tick_number() - cars[player]->get_finish_time(); 00934 glColor4f(1.0, 1.0, 1.0, 1.0 / (float(ticks_since) / 10.0)); 00935 glBegin(GL_QUADS); 00936 glVertex2f(-100.0, -100.0); 00937 glVertex2f( 100.0, -100.0); 00938 glVertex2f( 100.0, 100.0); 00939 glVertex2f(-100.0, 100.0); 00940 glEnd(); 00941 glEnable(GL_TEXTURE_2D); 00942 glColor3f(1.0, 1.0, 1.0); 00943 00944 // text displaying the time taken. 00945 std::stringstream time_text; 00946 unsigned long int time = cars[player]->get_finish_time(); 00947 time_text << "Time: " << (time / 6000) 00948 << ((time / 6000 == 1) ? " minute, " : " minutes, ") 00949 << (time / 100) % 60 << "." 00950 << (time / 10) % 10 << (time % 10) << " seconds"; 00951 glTranslatef(-0.9, -0.27, 0.0); 00952 glScalef(0.004, 0.004, 1); 00953 FTTextureFont & font = UI::BasicFonts::get_instance().big_font; 00954 font.Render(time_text.str().c_str()); 00955 glLoadIdentity(); 00956 } 00957 00958 void GameScene::draw_reverse_sign(unsigned int player) 00959 { 00960 m_reverse_texture->bind(); 00961 glBegin(GL_QUADS); 00962 glTexCoord2f(0.0, 1.0); glVertex2f(-0.3, 0.0); 00963 glTexCoord2f(1.0, 1.0); glVertex2f( 0.3, 0.0); 00964 glTexCoord2f(1.0, 0.0); glVertex2f( 0.3, 0.6); 00965 glTexCoord2f(0.0, 0.0); glVertex2f(-0.3, 0.6); 00966 glEnd(); 00967 } 00968 00969 void GameScene::draw_countdown() 00970 { 00971 m_countdown_texture->bind(); 00972 unsigned int tick = 3000+countdown_timer; 00973 // which sign to show (0->3, 1->2, 2->1, 3->Go) 00974 unsigned int n = tick/1000; 00975 float xl = float(n) / 4.0; 00976 float xh = xl + 0.25; 00977 // time taken on this sign in seconds 00978 float s_time = float (tick - n * 1000) / 1000.0; 00979 glPushMatrix(); 00980 if (n == 3) 00981 { 00982 s_time *= 2.0; 00983 float scale = 1.0 + sqrt(s_time) * 2.0; 00984 glScalef(scale, scale, 1.0); 00985 } 00986 if (s_time > 0.875) 00987 { 00988 //glColor4f(1.0, 1.0, 1.0, s_time*16.0-15.0); 00989 float scale_t = 8.0+s_time*-8.0; 00990 float h_scale = 1.0/(scale_t); 00991 float v_scale = scale_t * scale_t; 00992 glScalef(h_scale, v_scale, 1.0); 00993 // white and grey 00994 glBegin(GL_QUADS); 00995 glTexCoord2f(xl, 0.5); glVertex2f(-0.3, -0.3); 00996 glTexCoord2f(xh, 0.5); glVertex2f( 0.3, -0.3); 00997 glTexCoord2f(xh, 0.0); glVertex2f( 0.3, 0.3); 00998 glTexCoord2f(xl, 0.0); glVertex2f(-0.3, 0.3); 00999 glEnd(); 01000 } else { 01001 // coloured outline, dark shadow 01002 glBegin(GL_QUADS); 01003 glTexCoord2f(xl, 1.0); glVertex2f(-0.3, -0.3); 01004 glTexCoord2f(xh, 1.0); glVertex2f( 0.3, -0.3); 01005 glTexCoord2f(xh, 0.5); glVertex2f( 0.3, 0.3); 01006 glTexCoord2f(xl, 0.5); glVertex2f(-0.3, 0.3); 01007 glEnd(); 01008 } 01009 glPopMatrix(); 01010 } 01011 01012 void GameScene::do_sound() 01013 { 01014 // The changes in audio object's state are done in update_logic 01015 // OpenAL needs to be told the result, though: 01016 Audio::get_instance().commit(); 01017 } 01018 01019 Physics::World & GameScene::get_world() 01020 { 01021 return world; 01022 } 01023 01024 void GameScene::set_save_replay(bool value) 01025 { 01026 save_replay = value; 01027 } 01028 01029 void GameScene::rank_cars() 01030 { 01031 car_ranks.resize(cars.size()); 01032 // sort cars by lap position + lap length * number of laps completed. 01033 std::multimap<btScalar, std::size_t> car_positions; 01034 01035 // Add a bit on to the lap length. The lap length is the shortest route 01036 // around, so a car may be further around the lap than the lap is long, 01037 // but we don't want to count this in the position so cars don't get 01038 // ranked behind a car on the previous lap. 01039 btScalar lap_length = track.get_lap_length() + 100.0; 01040 01041 for (std::size_t index = 0; index < cars.size(); index++) 01042 { 01043 GameObjects::Car & car = *cars[index]; 01044 btScalar position; 01045 if (car.get_disqualified()) 01046 { 01047 position = 0; 01048 } else { 01049 position = car.get_lap_distance() + car.get_lap() * lap_length; 01050 } 01051 car_positions.insert(std::pair<btScalar, std::size_t>(position, index)); 01052 } 01053 // rank cars in reverse order of car_positions, as it is sorted from the 01054 // lowest distance around to course to the highest: highest is the leader. 01055 int rank = cars.size() - 1; 01056 for (std::map<btScalar, std::size_t>::iterator it = car_positions.begin(); 01057 it != car_positions.end(); 01058 it++) 01059 { 01060 car_ranks[it->second] = rank; 01061 rank--; 01062 } 01063 01064 } 01065 01066 void GameScene::unpause() 01067 { 01068 // menu is already giving a response. Reset it so we can show it again. 01069 m_pause_menu.reset(); 01070 paused = false; 01071 // start the sound 01072 Audio::get_instance().resume(); 01073 } 01074 01075 }
Generated at Mon Sep 6 00:41:12 2010 by Doxygen version 1.4.7 for Racer version svn335.