1 /*
2 Copyright (C) 2005 by Ruben Henner Zilibowitz <rzilibowitz@users.sourceforge.net>
3 Part of the Toy Cars Project http://toycars.sourceforge.net
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the license.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12
13 /*
14 * Created by Ruben Henner Zilibowitz on 1/01/05.
15 */
16
17 #include "RobotPlayer.h"
18 #include "CarGame.h"
19 #include "GameConstants.h"
20 #include "Rectangle.h"
21 #include "ScException.h"
22 #include "Kaboom.h"
23 #include "Startline.h"
24 #include "glf.h"
25 #include "TcPreferences.h"
26 #include <cstdlib>
27 #include "PathLine.h"
28 #include <cstdio>
29 #ifndef WIN32
30 #include <ctime>
31 #endif
32 #include <unistd.h>
33 #include "SDL.h"
34 #include "SDL_image.h"
35 //------------
36
37 /* When using MINGW (-mno-cygwin), srandom/random isn't defined */
38 #ifndef srandom
39 #define srandom srand
40 #endif
41 #ifndef random
42 #define random rand
43 #endif
44 #ifndef seed48
seed48(unsigned short xseed[3])45 unsigned short * seed48(unsigned short xseed[3])
46 {
47 srandom(*((unsigned long*)(xseed+1)));
48 return NULL;
49 }
50 #endif
51
52 #ifndef INFINITY
53 #define INFINITY 1e20
54 #endif
55
56 const double kWrongWayCheckInterval = 2.0; // in seconds
57 const double kWrongWayPosOffset = 1e-3;
58 const double kSignFadeSpeed = 4.0;
59 const double kSignScreenPosition = 0.7; // 1 is top 0 is bottom
60 const double kWrongWayScreenPosition = 0.6; // 1 is top 0 is bottom
61
62 // defined in main.cpp
63 void PixelDataForSurface(const SDL_Surface* src, ScPixelData& dst);
64
testRand()65 void testRand()
66 {
67 int i;
68 printf("RAND_MAX: %d\n", RAND_MAX);
69 for (i = 0; i < 200; i++)
70 printf("%lf ", drand48());
71 printf("\n");
72 }
73
74 extern TcPreferences *gPreferences; // the maximum number of particles in the animation at any one time
75 const int kNumLaps = 4; // the number of laps to the race
76
77 // external functions
78 void ERRCHECK(FMOD_RESULT result);
79
80 // -----------------------------------------------------------------------------
81 // CarGame::CarGame
82 //
83 // Constructor. Sets up animation. Inits random number generator.
84 // -----------------------------------------------------------------------------
85
CarGame(Tileset * inTilemap,Player ** inPlayers,int inNumPlayers,FMOD::System * inFMOD_system,Tuple inStartLineA,Tuple inStartLineB,bool inClockwise,const list<Tuple> * inPathPolygon,const list<RoadSignInterval> * inSignIntervals)86 CarGame::CarGame(Tileset* inTilemap, Player** inPlayers, int inNumPlayers, FMOD::System* inFMOD_system, Tuple inStartLineA, Tuple inStartLineB, bool inClockwise, const list<Tuple> *inPathPolygon, const list<RoadSignInterval> *inSignIntervals)
87 : tilemap(*inTilemap), players(inPlayers), numPlayers(inNumPlayers),
88 particles(new ScParticles(gPreferences->getMaxParticles())),
89 runningTime(0.0), startLineA(inStartLineA), startLineB(inStartLineB), lastLight(-1), pathPolygon(inPathPolygon),
90 FMOD_System(inFMOD_system), clockwise(inClockwise), signIntervals(inSignIntervals), cameraMode(kRotoZoomingCamera)
91 {
92 #if HAVE_FMOD_LIB
93 FMOD_RESULT result;
94 #endif
95 int i;
96
97 // set the game rect
98 gameRect.left = 0;
99 gameRect.bottom = 0;
100 gameRect.right = tilemap.getMapWidth() * tilemap.getTileWidth();
101 gameRect.top = tilemap.getMapHeight() * tilemap.getTileHeight();
102
103 // Set up Scoobie animation
104
105 // put the tile map inside the animation
106 animation.push_back(inTilemap);
107 // put the particles inside the animation
108 animation.push_back(particles);
109 // create and add start line
110 animation.push_back(new Startline(startLineA, startLineB));
111
112 // put the cars inside the top layer
113 // and put players into playersInGame list
114 // and tell the players about this
115 for (i = 0; i < numPlayers; i++)
116 {
117 animation.push_back(&players[i]->car);
118 playersInGame.push_back(players[i]);
119 players[i]->setGame(this);
120 }
121
122 // load needle image
123 needle = new NeedleImage();
124
125 // load label images
126 labelKPH = new StaticImage("images/KPH.png");
127 labelRPM = new StaticImage("images/RPM.png");
128
129 // load signs sprite
130 SDL_Surface *signsImg = IMG_Load("images/signs.png");
131 ScPixelData pixData;
132 PixelDataForSurface(signsImg, pixData);
133 signsSprite = new ScSprite(64,64,0,pixData);
134 SDL_FreeSurface(signsImg);
135
136 // compute total path length
137 totalPathLength = computeTotalPathLength();
138
139 // load start lights
140 for (int k = 0; k < kNumStartLights; k++) {
141 lights[k] = new StartLight(k);
142 }
143
144 fprintf(stderr, "done all lights\n");
145
146 // the glf library should have already been initialised
147
148 // create and load the minimap
149 minimap = new Minimap(tilemap, playersInGame);
150 fprintf(stderr, "done minimap\n");
151 /*
152 Load some sounds
153 */
154
155 #if HAVE_FMOD_LIB
156 if (gPreferences->getSounds()) {
157 result = FMOD_System->createSound("sounds/engine.wav", FMOD_SOFTWARE | FMOD_3D, 0, &engineSound);
158 ERRCHECK(result);
159 result = engineSound->set3DMinMaxDistance(4.0f, 10000.0f);
160 ERRCHECK(result);
161 result = engineSound->setMode(FMOD_LOOP_NORMAL);
162 ERRCHECK(result);
163
164 result = FMOD_System->createSound("sounds/mud.wav", FMOD_SOFTWARE | FMOD_3D, 0, &dustSlideSound);
165 ERRCHECK(result);
166 result = dustSlideSound->set3DMinMaxDistance(4.0f, 10000.0f);
167 ERRCHECK(result);
168 result = dustSlideSound->setMode(FMOD_LOOP_NORMAL);
169 ERRCHECK(result);
170
171 result = FMOD_System->createSound("sounds/bang_6.wav", FMOD_SOFTWARE | FMOD_3D, 0, &crashSound);
172 ERRCHECK(result);
173 result = crashSound->set3DMinMaxDistance(4.0f, 10000.0f);
174 ERRCHECK(result);
175 result = crashSound->setVariations(44.1e2,0.01,0);
176 ERRCHECK(result);
177
178 result = FMOD_System->createSound("sounds/organ-c6.aif", FMOD_SOFTWARE, 0, &organC6Sound);
179 ERRCHECK(result);
180
181 result = FMOD_System->createSound("sounds/organ-c7.aif", FMOD_SOFTWARE, 0, &organC7Sound);
182 ERRCHECK(result);
183 }
184 #endif
185
186 // path line
187 //animation.push_back(new PathLine(tilemap.getBounds(), pathPolygon));
188
189 // init seed for random number generator
190 unsigned short s[4];
191 #ifdef WIN32
192 s[0] = 1; s[1] = 2; s[2] = 3; s[3] = 4;
193 #else
194 time((time_t*)s);
195 *(clock_t*)(s+2) = clock();
196 #endif
197 seed48(s + 1);
198
199 setupStart();
200
201 fprintf(stderr, "done setup\n");
202 /*
203 Play sounds at certain positions
204 */
205 #if HAVE_FMOD_LIB
206 if (gPreferences->getSounds()) {
207 for (list<Player*>::iterator i = playersInGame.begin(); i != playersInGame.end(); i++)
208 {
209 (*i)->setEngineSound(engineSound);
210 (*i)->setDustSlideSound(dustSlideSound);
211 }
212 }
213 #endif
214 }
215
216 // -----------------------------------------------------------------------------
217 // CarGame::setupStart
218 //
219 // Sets up start locations of cars
220 // -----------------------------------------------------------------------------
221
setupStart()222 void CarGame::setupStart()
223 {
224 // compute intersection of the path with the start line
225 Tuple A = startLineA / kScale;
226 Tuple B = startLineB / kScale;
227 Tuple E, F;
228 Tuple result(0,0);
229 double s, t, divisor;
230 list<Tuple>::const_iterator i, j;
231 for (i = pathPolygon->begin(), (j = pathPolygon->end())--; i != pathPolygon->end(); j = i++) {
232 E = *i;
233 F = *j;
234 divisor = A.x*(F.y-E.y)+B.x*(E.y-F.y)+(B.y-A.y)*F.x+(A.y-B.y)*E.x;
235 s = -(B.x*(F.y-E.y)+E.x*(B.y-F.y)+(E.y-B.y)*F.x) / divisor; // along path AB
236 t = (A.x*(F.y-B.y)+B.x*(A.y-F.y)+(B.y-A.y)*F.x) / divisor; // along path EF
237 if (0 <= s && s < 1 && 0 <= t && t < 1) {
238 result = s*A + (1-s)*B;
239 break;
240 }
241 }
242
243 // set start locations of all cars
244 // we setup a two by two line behind the start line perpendicular to it
245 Tuple vOffset = (A - B).Direction().perp();
246 Tuple hOffset = vOffset.perp();
247 double theta = atan2(-vOffset.y, -vOffset.x);
248 Tuple placement;
249 for (int k = 0; k < numPlayers; k++)
250 {
251 if (k % 2 == 0) {
252 result += vOffset * players[k]->car.getWidth() / kScale;
253 placement = result - hOffset * players[k]->car.getHeight() / kScale * 0.55;
254 }
255 else {
256 placement = result + hOffset * players[k]->car.getHeight() / kScale * 0.55;
257 }
258 players[k]->car.setLocation(placement);
259 players[k]->car.setOrientation(theta);
260 players[k]->computePathLocation();
261 players[k]->setNearestPathPos(computeNearestPathPosition(players[k]->car.getLocation_wc()));
262 }
263
264 /* This is a bt of a hack but it fixes the bug with the wheels being in the wrong spots
265 at the start of the game. There is probably a better way to fix this bug but this should be fine for now. */
266 list<Player*>::iterator playerI;
267 for (playerI = playersInGame.begin(); playerI != playersInGame.end(); playerI++)
268 (*playerI)->car.rk2_step(0.001);
269 }
270
271 // -----------------------------------------------------------------------------
272 // CarGame::~CarGame
273 //
274 // Destructor. Cleans up.
275 // -----------------------------------------------------------------------------
276
~CarGame()277 CarGame::~CarGame()
278 {
279 int i;
280 #if HAVE_FMOD_LIB
281 if (gPreferences->getSounds()) {
282 FMOD_RESULT result;
283 /*
284 Release sounds
285 */
286 result = engineSound->release();
287 ERRCHECK(result);
288 result = crashSound->release();
289 ERRCHECK(result);
290 result = dustSlideSound->release();
291 ERRCHECK(result);
292 result = organC6Sound->release();
293 ERRCHECK(result);
294 result = organC7Sound->release();
295 ERRCHECK(result);
296 }
297 #endif
298
299 // delete players
300 for (i = 0; i < numPlayers; i++)
301 delete players[i];
302 delete [] players;
303
304 // delete the views
305 for (list<ViewFollow>::iterator i = views.begin(); i != views.end(); i++)
306 delete i->view;
307
308 // delete the minimap
309 delete minimap;
310
311 // delete the needle image
312 delete needle;
313
314 // delete the label images
315 delete labelKPH;
316 delete labelRPM;
317
318 // detele the start lights
319 for (i = 0; i < kNumStartLights; i++)
320 delete lights[i];
321
322 // delete the path polygon
323 delete pathPolygon;
324
325 // delete the sign intervals
326 delete signIntervals;
327 }
328
329 // -----------------------------------------------------------------------------
330 // CarGame::addView
331 //
332 // Adds a view to the game with a given player to follow.
333 // -----------------------------------------------------------------------------
334
addView(ScPortRect & portRect,int playerFollow)335 void CarGame::addView(ScPortRect& portRect, int playerFollow)
336 {
337 ScRect viewRect = {0, 0, portRect.width, portRect.height};
338 ScView* viewP = new ScView(portRect, viewRect, animation);
339 views.push_back(ViewFollow(viewP, playerFollow));
340 }
341
342 // -----------------------------------------------------------------------------
343 // CarGame::doEvent
344 // Receive an event for a particular player.
345 // -----------------------------------------------------------------------------
346
doEvent(PlayerEvent * event,unsigned short playerNum)347 void CarGame::doEvent(PlayerEvent* event, unsigned short playerNum)
348 {
349 if (playerNum < numPlayers)
350 players[playerNum]->doEvent(event);
351 }
352
353 // -----------------------------------------------------------------------------
354 // CarGame::step
355 //
356 // Calculate and draw a single frame of the game being played.
357 // -----------------------------------------------------------------------------
358
step(double dt)359 void CarGame::step(double dt)
360 {
361 if (runningTime >= kNumStartLights-1)
362 computePhysics(dt);
363 animation.process(dt);
364
365 for (list<Player*>::iterator i = playersInGame.begin(); i != playersInGame.end(); i++)
366 (*i)->updateLaps(startLineA, startLineB, runningTime, clockwise);
367
368 drawViews(dt);
369 drawTime(runningTime);
370 minimap->draw(150, 20, 1.0);
371
372 runningTime += dt;
373 }
374
375 // -----------------------------------------------------------------------------
376 // CarGame::isPlayerHuman
377 //
378 // Tells if the given player number is a human controlled player or not.
379 // -----------------------------------------------------------------------------
380
isPlayerHuman(int i)381 bool CarGame::isPlayerHuman(int i)
382 {
383 if (i < numPlayers)
384 {
385
386 //RobotPlayer *rb = dynamic_cast<RobotPlayer*>(players[i]);
387 //return rb == NULL;
388
389 return players[i]->getPlayerType() == kPlayerType;
390 }
391 return false;
392 }
393
394 // -----------------------------------------------------------------------------
395 // CarGame::renderEveryTexture
396 //
397 // This should be called once before the beginning of the game. This function
398 // renders all textures in the game once, which is necessary to load them into
399 // VRAM. Without calling this, the game may run slowly at the beginning until
400 // it has finally drawn every texture.
401 // -----------------------------------------------------------------------------
402
renderEveryTexture()403 void CarGame::renderEveryTexture()
404 {
405 int i;
406 const ScRect& viewRect = views.front().view->getViewRect();
407 WheelSprite* wheelSprite = players[0]->car.getLeftWheelSprite();
408 ToyCar* carSprite;
409
410 glLoadIdentity();
411 glOrtho(0, viewRect.right - viewRect.left, 0, viewRect.top - viewRect.bottom, -1.0, 1.0);
412
413 // render all the car sprite frames
414 for (i = 0; i < numPlayers; i++)
415 {
416 carSprite = &players[i]->car;
417 carSprite->moveTo(0,0);
418 for (i = 0; i < carSprite->getNumFrames(); i++)
419 {
420 carSprite->setFrame(i);
421 carSprite->doDraw();
422 }
423 }
424
425 // render all the wheel sprite frames
426 wheelSprite->moveTo(0,0);
427 for (i = 0; i < wheelSprite->getNumFrames(); i++)
428 {
429 wheelSprite->setFrame(i);
430 wheelSprite->doDraw();
431 }
432
433 // render the needle image
434 needle->setDegrees(0);
435 needle->draw();
436
437 // render the label images
438 labelKPH->draw();
439 labelRPM->draw();
440
441 // render the minimap image
442 minimap->draw(0, 0, 1.0);
443 }
444
445 // -----------------------------------------------------------------------------
446 // CarGame::computePhysics
447 //
448 // Performs integration on forces for each car. Then checks for collisions
449 // between cars and wall tiles. Then checks for collisions between all cars
450 // in game. Finally calls resolveCollisions.
451 // -----------------------------------------------------------------------------
452
453 const double kStepFrequency = 500.0; // number of integrations per second
454
computePhysics(double dt)455 void CarGame::computePhysics(double dt)
456 {
457 list<Player*>::iterator playerI, playerJ;
458
459 // perform numerical integration on vehicles
460 // generate dust particles and integrate user inputs
461 int j, steps = int(ceil(dt * kStepFrequency));
462 double t, small_dt = dt / steps;
463 for (playerI = playersInGame.begin(); playerI != playersInGame.end(); playerI++)
464 {
465 // integrate car dynamics and keyboard input and generate dust particles
466 t = 0.0;
467 for (j = 0; j < steps; j++)
468 {
469 (*playerI)->updatePathLocation();
470 /*
471 #if (MOUSE_CONTROL_PLAYER_1 || GAMEPAD_CONTROL_PLAYER_1)
472 if ((*playerI)->getPlayerNumber() != 1)
473 #endif
474 */
475 // nb: if the player is an ai then it is controlled through the integrateKeys function so we should call it here
476 if (gPreferences->getControllerType((*playerI)->getPlayerNumber()) == kKeyboardController || (*playerI)->isAI())
477 {
478 (*playerI)->integrateKeys(small_dt);
479 }
480
481 //#if GAMEPAD_CONTROL_PLAYER_1
482 else
483 (*playerI)->dampSteering(small_dt);
484 //(*playerI)->integrateSteerKeys(small_dt);
485 //#endif
486
487 (*playerI)->car.computeWheelSurfaceFactors(&tilemap);
488 (*playerI)->car.rk2_step(small_dt);
489
490 t += small_dt;
491 generateDustFromWheel((*playerI)->car.getWheel_FL(), small_dt, t);
492 generateDustFromWheel((*playerI)->car.getWheel_FR(), small_dt, t);
493 generateDustFromWheel((*playerI)->car.getWheel_BL(), small_dt, t);
494 generateDustFromWheel((*playerI)->car.getWheel_BR(), small_dt, t);
495 }
496
497 // update geometry
498 (*playerI)->car.compute_geom_world_coords();
499 }
500
501 // check for collisions
502 list<Contact> contacts;
503 for (playerI = playersInGame.begin(); playerI != playersInGame.end(); playerI++)
504 {
505 findTileContacts((*playerI)->car, contacts);
506 for ((playerJ = playerI)++; playerJ != playersInGame.end(); playerJ++)
507 {
508 if (Geom::collide((*playerI)->car.getGeom_wc(), (*playerJ)->car.getGeom_wc(), contacts))
509 {
510 collisionAction(contacts, *playerI, *playerJ);
511 }
512 }
513 }
514
515 // ==========================================================================================
516 // Play the collision sound if necessary
517 // ==========================================================================================
518 #if HAVE_FMOD_LIB
519 if (gPreferences->getSounds()) {
520 double vel, minVel=0; // note these will be negative values, so minimum is really maximum absolute value.
521 Tuple collisionSoundLoc;
522 for (list<Contact>::iterator i = contacts.begin(); i != contacts.end(); i++) {
523 vel = i->compute_velocity();
524 if (vel < minVel) {
525 minVel = vel;
526 collisionSoundLoc = i->getLoc();
527 }
528 }
529 if (minVel < kCollisionThreshold) {
530 FMOD_VECTOR pos = { collisionSoundLoc.x, collisionSoundLoc.y, 0.0f };
531 FMOD_VECTOR vel = { 0.0f, 0.0f, 0.0f };
532 FMOD_RESULT result;
533 FMOD::Channel *channel3;
534
535 result = FMOD_System->playSound(FMOD_CHANNEL_FREE, crashSound, false, &channel3);
536 ERRCHECK(result);
537 result = channel3->set3DAttributes(&pos, &vel);
538 ERRCHECK(result);
539 result = channel3->setVolume(0.1 + (-minVel) * 0.09);
540 ERRCHECK(result);
541 }
542 }
543 #endif
544
545 // ==========================================================================================
546 // UPDATE THE LISTENER
547 // n.b. We need to check that the player we are getting values from is still in the game.
548 // Thus the use of the find function. The player may be out of the game for some reason.
549 // ==========================================================================================
550 #if HAVE_FMOD_LIB
551 if (gPreferences->getSounds()) {
552 if (find(playersInGame.begin(), playersInGame.end(), players[0]) != playersInGame.end())
553 {
554 const FMOD_VECTOR up = { 0.0f, 0.0f, 1.0f };
555 FMOD_VECTOR listenerpos;
556 FMOD_VECTOR forward;
557 FMOD_VECTOR vel;
558 FMOD_RESULT result;
559
560 listenerpos.x = players[0]->car.getLocation_wc().x;
561 listenerpos.y = players[0]->car.getLocation_wc().y;
562 listenerpos.z = 0;
563
564 vel.x = players[0]->car.getVelocity_wc().x;
565 vel.y = players[0]->car.getVelocity_wc().y;
566 vel.z = 0;
567
568 forward.x = -players[0]->car.getNorm().y;
569 forward.y = players[0]->car.getNorm().x;
570 forward.z = 0;
571
572 result = FMOD_System->set3DListenerAttributes(0, &listenerpos, &vel, &forward, &up);
573 ERRCHECK(result);
574 }
575
576 // ==========================================================================================
577 // UPDATE THE SOURCES (Engine sounds and dust sounds)
578 // ==========================================================================================
579 for (list<Player*>::iterator i = playersInGame.begin(); i != playersInGame.end(); i++)
580 {
581 (*i)->updateSounds();
582 }
583
584 //if (fabs(players[0]->car.getVelocity_wc().x*players[0]->car.getVelocity_wc().x + players[0]->car.getVelocity_wc().y*players[0]->car.getVelocity_wc().y) > 100*100)
585 // printf("Yikes\n");
586
587 // Tell FMOD to update internals
588 FMOD_System->update();
589 }
590 #endif
591
592 resolveCollisions(contacts);
593 }
594
computeCameraRotation(const ScView * view,const Player * player,double dt)595 double CarGame::computeCameraRotation(const ScView *view, const Player *player, double dt)
596 {
597 double rot = view->getRotation();
598 double dr = (cameraMode == kRotoZoomingCamera ? -(player->car.getOrientation_wc() * 180 / kPi - 90) : 0) - rot;
599 while (dr > 180)
600 dr -= 360;
601 while (dr < -180)
602 dr += 360;
603 if (fabs(dr) > 1e4*dt)
604 dr = (dr > 0 ? 1 : -1)*1e4*dt;
605 rot += dt*dr*4;
606 return rot;
607 }
608
computeCameraZoom(const ScView * view,const Player * player,double dt)609 double CarGame::computeCameraZoom(const ScView *view, const Player *player, double dt)
610 {
611 double zoom = view->getZoom();
612 double dz = (cameraMode != kFixedCamera ? 1 / (0.001*sqr(player->car.getVelocity().x) + 1) : 1) - zoom;
613 zoom += dt*dz*4;
614 return zoom;
615 }
616
617 // -----------------------------------------------------------------------------
618 // CarGame::drawViews
619 //
620 // Scrolls views. Bounds the scrolling to the game rect. Invokes the draw
621 // function for each view. Draws additional things for each view such as
622 // speedometre and engine rpm of the corresponding player.
623 // -----------------------------------------------------------------------------
624
drawViews(double dt)625 void CarGame::drawViews(double dt)
626 {
627 glClear(GL_COLOR_BUFFER_BIT);
628 for (list<ViewFollow>::iterator i = views.begin(); i != views.end(); i++)
629 {
630 // scroll the view to the car location
631
632 i->view->scrollTo( floor(players[i->follow]->car.getLocation_wc().x * kScale),
633 floor(players[i->follow]->car.getLocation_wc().y * kScale));
634
635 // bound the view to the animation bounds
636
637 if (i->view->getViewRect().left < gameRect.left)
638 i->view->scrollBy(gameRect.left - i->view->getViewRect().left, 0);
639
640 if (i->view->getViewRect().bottom < gameRect.bottom)
641 i->view->scrollBy(0, gameRect.bottom - i->view->getViewRect().bottom);
642
643 if (i->view->getViewRect().right > gameRect.right)
644 i->view->scrollBy(gameRect.right - i->view->getViewRect().right, 0);
645
646 if (i->view->getViewRect().top > gameRect.top)
647 i->view->scrollBy(0, gameRect.top - i->view->getViewRect().top);
648
649 // compute rotation and zoom of camera
650
651 i->view->setRotation(computeCameraRotation(i->view, players[i->follow], dt));
652 i->view->setZoom(computeCameraZoom(i->view, players[i->follow], dt));
653
654 // draw scene
655
656 i->view->draw();
657
658 //
659 // draw display
660 //
661 glLoadIdentity();
662 glOrtho(0, i->view->getViewRect().right - i->view->getViewRect().left, 0, i->view->getViewRect().top - i->view->getViewRect().bottom, -1, 1);
663 drawDisplay(players[i->follow], i->view->getViewRect(), dt);
664 }
665 }
666
drawDisplay(Player * player,const ScRect & viewRect,double dt)667 void CarGame::drawDisplay(Player *player, const ScRect& viewRect, double dt)
668 {
669 drawSpeedometer(player);
670 drawRPMMeter(player);
671 drawLapCount(player);
672 if (player->getLaps() >= kNumLaps)
673 drawFinishTime(player);
674 drawLights();
675 drawSign(player, viewRect, dt);
676 }
677
computeNearestPathPosition(Tuple pos)678 double CarGame::computeNearestPathPosition(Tuple pos) {
679 double bestPathDist = 0;
680 double curPathDist = 0;
681 double bestNormDist = INFINITY;
682 Tuple p, norm;
683 double len, x, dist;
684 for (list<Tuple>::const_iterator it = pathPolygon->begin(); it != pathPolygon->end(); it++) {
685 list<Tuple>::const_iterator next = it;
686 next++;
687 if (next == pathPolygon->end())
688 next = pathPolygon->begin();
689 // project pos onto the line it..next
690 p = *next - *it;
691 len = p.Normalise();
692 x = p * (pos - *it);
693 // check if this edge is the closest to pos so far
694 if (0 <= x && x <= len) {
695 norm = (x * p) + *it;
696 dist = (norm - pos).Norm();
697 if (dist < bestNormDist) {
698 bestNormDist = dist;
699 bestPathDist = curPathDist + x;
700 }
701 }
702 // check if this vertrex is the closest to pos so far
703 dist = (*it - pos).Norm();
704 if (dist < bestNormDist) {
705 bestNormDist = dist;
706 bestPathDist = curPathDist;
707 }
708 curPathDist += len;
709 }
710 return bestPathDist;
711 }
712
computeTotalPathLength()713 double CarGame::computeTotalPathLength() {
714 double dist = 0;
715 list<Tuple>::const_iterator tmp;
716 for (list<Tuple>::const_iterator it = pathPolygon->begin(); it != pathPolygon->end(); it++) {
717 tmp = it;
718 tmp++;
719 if (tmp == pathPolygon->end())
720 tmp = pathPolygon->begin();
721 dist += (*tmp - *it).Length();
722 }
723 return dist;
724 }
725
findRoadSignInterval(Player * player)726 const RoadSignInterval *CarGame::findRoadSignInterval(Player *player) {
727 double pos = computeNearestPathPosition(player->car.getLocation_wc());
728 double bigpos = pos + totalPathLength;
729 for (list<RoadSignInterval>::const_iterator it = signIntervals->begin(); it != signIntervals->end(); it++) {
730 if (it->start <= pos && pos <= it->end) {
731 return &(*it);
732 }
733 if (it->start <= bigpos && bigpos <= it->end) {
734 return &(*it);
735 }
736 }
737 return NULL;
738 }
739
drawSign(Player * player,const ScRect & viewRect,double dt)740 void CarGame::drawSign(Player *player, const ScRect& viewRect, double dt)
741 {
742 double modTime;
743 modTime = fmod(runningTime, kWrongWayCheckInterval);
744 if (modTime < 1 && modTime+dt > 1) {
745 double pos = computeNearestPathPosition(player->car.getLocation_wc());
746 player->setRightWay((pos+kWrongWayPosOffset > player->getNearestPathPos() || player->getNearestPathPos() - pos > totalPathLength*0.5) &&
747 pos - player->getNearestPathPos() < totalPathLength*0.5);
748 player->setNearestPathPos(pos);
749 }
750
751 const RoadSignInterval *s = findRoadSignInterval(player);
752 if (s && player->getRightWay()) {
753 if (player->getSignFade() < 1) {
754 player->setSignFade(player->getSignFade() + dt * kSignFadeSpeed);
755 }
756 player->setLastRoadSignInterval(s);
757 signsSprite->setFrame(s->type);
758 signsSprite->moveTo((viewRect.right - viewRect.left)*0.5, (viewRect.top - viewRect.bottom)*kSignScreenPosition);
759 signsSprite->setTransparency(player->getSignFade());
760 signsSprite->doDraw();
761 }
762 else if (player->getSignFade() > 0) {
763 player->setSignFade(player->getSignFade() - dt * kSignFadeSpeed);
764 if (player->getLastRoadSignInterval() != NULL) {
765 signsSprite->setFrame(player->getLastRoadSignInterval()->type);
766 signsSprite->moveTo((viewRect.right - viewRect.left)*0.5, (viewRect.top - viewRect.bottom)*kSignScreenPosition);
767 signsSprite->setTransparency(player->getSignFade());
768 signsSprite->doDraw();
769 }
770 }
771
772 if (!player->getRightWay()) {
773 glDisable(GL_TEXTURE_2D);
774 glEnable(GL_BLEND);
775 glColor4f(1,0,0,1);
776 glTranslatef((viewRect.right - viewRect.left)*0.5, (viewRect.top - viewRect.bottom)*kWrongWayScreenPosition, 0);
777 glScalef(12, 12, 1);
778 glfDrawSolidString("Wrong way");
779 }
780 }
781
drawLights()782 void CarGame::drawLights()
783 {
784 if (runningTime < kNumStartLights+1) {
785 glPushMatrix();
786 glTranslatef(100, 300, 0);
787 int k = int(runningTime);
788 #if HAVE_FMOD_LIB
789 if (gPreferences->getSounds()) {
790 if (k != lastLight) {
791 // play sound
792 FMOD::Channel *channel;
793 FMOD_RESULT result;
794 if (k <= kNumStartLights-2) {
795 result = FMOD_System->playSound(FMOD_CHANNEL_FREE, organC6Sound, false, &channel);
796 ERRCHECK(result);
797 }
798 else if (k == kNumStartLights-1) {
799 result = FMOD_System->playSound(FMOD_CHANNEL_FREE, organC7Sound, false, &channel);
800 ERRCHECK(result);
801 }
802 }
803 }
804 #endif
805 if (k == kNumStartLights)
806 lights[k-1]->draw((unsigned char)(255*(4 - runningTime)));
807 else
808 lights[k]->draw();
809 glPopMatrix();
810 lastLight = k;
811 }
812 }
813
drawSpeedometer(Player * player)814 void CarGame::drawSpeedometer(Player *player)
815 {
816 glPushMatrix();
817 needle->setDegrees(player->car.getVelocity().x*5 - 90.0);
818 glTranslatef(100, 64, 0);
819 glPushMatrix();
820 glTranslatef(0, -16, 0);
821 glScalef(0.5, 0.5, 1);
822 labelKPH->draw();
823 glPopMatrix();
824 needle->draw();
825 glPopMatrix();
826 }
827
drawRPMMeter(Player * player)828 void CarGame::drawRPMMeter(Player *player)
829 {
830 glPushMatrix();
831 needle->setDegrees(player->car.getRPM()*0.1 - 90.0);
832 glTranslatef(200, 64, 0);
833 glPushMatrix();
834 glTranslatef(0, -16, 0);
835 glScalef(0.5, 0.5, 1);
836 labelRPM->draw();
837 glPopMatrix();
838 needle->draw();
839 glPopMatrix();
840 }
841
drawLapCount(Player * player)842 void CarGame::drawLapCount(Player *player)
843 {
844 static char str15[16];
845 glDisable(GL_TEXTURE_2D);
846 glEnable(GL_BLEND);
847 glColor4f(1.0f,1.0f,1.0f,0.5f);
848 glPushMatrix();
849 glTranslatef(24, gPreferences->getScreenHeight()-48, 0);
850 glScalef(10, 10, 1);
851 //sprintf(str15, "Laps: %d\nDist: %lf\npathIndex: %d", player->getLaps(), player->getActualPathDistance(), player->getPathIndex());
852 sprintf(str15, "Laps: %d/%d", player->getLaps(), kNumLaps);
853 glfDrawSolidString(str15);
854 glPopMatrix();
855 }
856
drawFinishTime(Player * player)857 void CarGame::drawFinishTime(Player *player)
858 {
859 static char str15[16];
860 glPushMatrix();
861 glScalef(12, 12, 1);
862 glTranslatef(2, gPreferences->getScreenHeight()/24.0 + 1, 0);
863 sprintf(str15, "Finish time: %.2lf s", player->getCourseTime());
864 glfDrawSolidString(str15);
865 glTranslatef(0, -2, 0);
866 sprintf(str15, "Best lap time: %.2lf s", player->getBestLapTime());
867 glfDrawSolidString(str15);
868 glPopMatrix();
869 }
870
871 // -----------------------------------------------------------------------------
872 // CarGame::drawTime
873 //
874 // Takes time in seconds and renders clock digits
875 // -----------------------------------------------------------------------------
876
drawTime(double theTime)877 void CarGame::drawTime(double theTime)
878 {
879 static char str31[32];
880
881 glDisable(GL_TEXTURE_2D);
882 glEnable(GL_BLEND);
883 glColor4f(1.0f,1.0f,1.0f,0.5f);
884
885 glLoadIdentity();
886 glOrtho(0, gPreferences->getScreenWidth(), 0, gPreferences->getScreenHeight(), -1, 1);
887 glViewport(0, 0, gPreferences->getScreenWidth(), gPreferences->getScreenHeight());
888 glTranslatef(24, gPreferences->getScreenHeight()-24, 0);
889
890 int minutes = int(theTime) / 60;
891 sprintf(str31, "%d:%.2lf", minutes, theTime - 60*minutes);
892 glScalef(12, 12, 1);
893 glfDrawSolidString(str31);
894 }
895
896 // -----------------------------------------------------------------------------
897 // CarGame::findTileContacts
898 //
899 // Compute contacts for any collisions between car and tiles.
900 // -----------------------------------------------------------------------------
901
findTileContacts(ToyCar & car,list<Contact> & contacts)902 void CarGame::findTileContacts(ToyCar& car, list<Contact>& contacts)
903 {
904 /*
905 const double radius = car.getGeomRadius();
906 const Tuple pos = car.getLocation_wc();
907 int firstrow = int((pos.y - radius) * kScale) / tilemap.getTileHeight();
908 int lastrow = int((pos.y + radius) * kScale) / tilemap.getTileHeight();
909 int firstcol = int((pos.x - radius) * kScale) / tilemap.getTileWidth();
910 int lastcol = int((pos.x + radius) * kScale) / tilemap.getTileWidth();
911 */
912 int firstrow = int(car.getBounds().bottom) / tilemap.getTileHeight();
913 int lastrow = int(car.getBounds().top) / tilemap.getTileHeight();
914 int firstcol = int(car.getBounds().left) / tilemap.getTileWidth();
915 int lastcol = int(car.getBounds().right) / tilemap.getTileWidth();
916 int row, col;
917
918 firstrow = std::max(firstrow, 0);
919 lastrow = std::min(lastrow, (tilemap.getMapHeight() - 1));
920 firstcol = std::max(firstcol, 0);
921 lastcol = std::min(lastcol, (tilemap.getMapWidth() - 1));
922
923 list<block> blocks;
924
925 for (row = firstrow; row <= lastrow; row++)
926 {
927 for (col = firstcol; col <= lastcol; col++)
928 {
929 if (tilemap(col,row) == kWallTile)
930 {
931 blocks.push_back(block(col+1,col,row+1,row));
932 }
933 }
934 }
935
936 sewSeams(blocks);
937
938 for (list<block>::const_iterator i = blocks.begin(); i != blocks.end(); i++)
939 {
940 ToyCars::Rectangle block( i->right * tilemap.getTileWidth() / kScale,
941 i->left * tilemap.getTileWidth() / kScale,
942 i->top * tilemap.getTileHeight() / kScale,
943 i->bottom * tilemap.getTileHeight() / kScale);
944
945 Geom::collide(car.getGeom_wc(), block, contacts);
946 }
947 }
948
949 // -----------------------------------------------------------------------------
950 // CarGame::sewSeams
951 //
952 // This algorithm reduces a list of blocks to one where there are no 'seams'.
953 // A seam is two blocks sharing a common edge. Applying this to the list
954 // significantly improves collision detection with the blocks.
955 //
956 // If we do not apply this algorithm to the list of blocks, then crashing
957 // into a straight wall sometimes behaves as if the car is crashing into the
958 // corner of one of the tiles in the wall, producing a very undesirable effect.
959 // -----------------------------------------------------------------------------
960
sewSeams(list<block> & blocks)961 void CarGame::sewSeams(list<block>& blocks)
962 {
963 bool seam_found;
964 list<block>::iterator tileA, tileB;
965
966 do
967 {
968 seam_found = false;
969
970 for (tileA = blocks.begin(); tileA != blocks.end() && !seam_found; tileA++)
971 {
972 for ((tileB = tileA)++; tileB != blocks.end() && !seam_found; tileB++)
973 {
974 // check for seams between tileA and tileB
975
976 if (tileA->top == tileB->top && tileA->bottom == tileB->bottom)
977 {
978 if (tileA->right == tileB->left)
979 {
980 tileB->left = tileA->left;
981 blocks.erase(tileA);
982 seam_found = true;
983 }
984 else if (tileB->right == tileA->left)
985 {
986 tileB->right = tileA->right;
987 blocks.erase(tileA);
988 seam_found = true;
989 }
990 }
991 else if (tileA->left == tileB->left && tileA->right == tileB->right)
992 {
993 if (tileA->top == tileB->bottom)
994 {
995 tileB->bottom = tileA->bottom;
996 blocks.erase(tileA);
997 seam_found = true;
998 }
999 else if (tileB->top == tileA->bottom)
1000 {
1001 tileB->top = tileA->top;
1002 blocks.erase(tileA);
1003 seam_found = true;
1004 }
1005 }
1006 }
1007 if (seam_found)
1008 break;
1009 }
1010 }
1011 while (seam_found);
1012 }
1013
1014 // -----------------------------------------------------------------------------
1015 // CarGame::resolveCollisions
1016 //
1017 // Attempts to resolve all the collisions in the given contact list.
1018 // An iterative method is used. Overlap error is compensated for by
1019 // translating bodies away from each other in resolve_overlap().
1020 // -----------------------------------------------------------------------------
1021
resolveCollisions(list<Contact> & contacts)1022 void CarGame::resolveCollisions(list<Contact>& contacts)
1023 {
1024 list<Contact>::iterator c;
1025 bool collisions;
1026 unsigned char maxIterations = 20;
1027
1028 // resolve collisions
1029
1030 do
1031 {
1032 collisions = false;
1033 for (c = contacts.begin(); c != contacts.end(); c++)
1034 {
1035 if (c->resolve_collision()) {
1036 collisions = true;
1037 }/*
1038 if (c->compute_velocity() > 4.0) {
1039 printf("bang\n");
1040 }*/
1041 }
1042 }
1043 while (collisions && --maxIterations);
1044
1045 // seperate overlaps
1046
1047 for (c = contacts.begin(); c != contacts.end(); c++)
1048 {
1049 c->resolve_overlap();
1050 }
1051 }
1052
1053 // -----------------------------------------------------------------------------
1054 // CarGame::generateDustFromWheel
1055 //
1056 // Computes the dust particles sprayed out by the given wheel over a given
1057 // time period dt (usually very short).
1058 //
1059 // Time of creation of the particles can be moved backwards by specifying a
1060 // positive value for rewind. The reason for this is because this function may
1061 // be called at many sub-frame times for each frame (ie during integration of
1062 // forces and velocities). One wants to synchronise the particles however, as
1063 // if they were created on the actual start of the frame. Otherwise, they will
1064 // look not right.
1065 // -----------------------------------------------------------------------------
1066
1067 const double kDustProliferation = 25.0;
1068 const double kDustLife = 1.2;
1069
generateDustFromWheel(const Wheel & wheel,double dt,double rewind)1070 void CarGame::generateDustFromWheel(const Wheel& wheel, double dt, double rewind)
1071 {
1072 int numParticles, i;
1073 Tuple lat, longt, loc;
1074 const Vehicle& vehicle = wheel.getVehicle();
1075 double dustVel;
1076 const TileGroup *group;
1077
1078 // local class rround
1079 // rounds off x based on probability from fractional part
1080 class _rround
1081 {
1082 public:
1083 int operator() (double x)
1084 {
1085 if (x - floor(x) > drand48())
1086 return (int)(x) + 1;
1087 else
1088 return (int)(x);
1089 }
1090 }rround;
1091
1092 // tyre location in world coords
1093 loc = (vehicle.getLocation_wc() + vehicle.getNorm().fromLocal(wheel.getLocation_vc())) * kScale;
1094
1095 // get tile group
1096 group = tilemap.tileGroup(tilemap.getTileAtPoint(loc.x, loc.y));
1097
1098 // dust direction in world coords
1099 lat = vehicle.getNorm().fromLocal(wheel.getNorm());
1100 longt = -lat.perp();
1101
1102 // dust from longtitudinal slip
1103 dustVel = wheel.getLongtitudinalSlipVelocity();
1104 if (fabs(dustVel) > 5.0)
1105 {
1106 numParticles = rround(dt * kLongtitudinalDustFactor*kDustProliferation * fabs(dustVel));
1107 //if (numParticles) particles->newParticle(Particle(loc.x, loc.y, 10.0, 0, 0, 0));
1108 for (i = 0; i < numParticles; i++)
1109 particles->newParticle(Particle(loc.x, loc.y, kDustLife, longt.x + (drand48()-0.5)/dustVel*4.0, longt.y + (drand48()-0.5)/dustVel*4.0, dustVel * kScale, group->r, group->g, group->b).step(-rewind));
1110 }
1111
1112 // dust from lateral slip
1113 dustVel = wheel.getVelocity().y;
1114 if (fabs(dustVel) > 5.0)
1115 {
1116 numParticles = rround(dt * kLateralDustFactor*kDustProliferation * fabs(dustVel));
1117 //if (numParticles) particles->newParticle(Particle(loc.x, loc.y, 10.0, 0, 0, 0));
1118 for (i = 0; i < numParticles; i++)
1119 particles->newParticle(Particle(loc.x, loc.y, kDustLife, lat.x + (drand48()-0.5)/dustVel*4.0, lat.y + (drand48()-0.5)/dustVel*4.0, dustVel * kScale, group->r, group->g, group->b).step(-rewind));
1120 }
1121 }
1122