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