CarPhysics.cs 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204
  1. //-----------------------------------------------------------------------------
  2. // CarPhysics.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using Microsoft.Xna.Framework;
  8. using Microsoft.Xna.Framework.Graphics;
  9. using Microsoft.Xna.Framework.Input;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Text;
  13. using System.Threading;
  14. using RacingGame.GameLogic.Physics;
  15. using RacingGame.Graphics;
  16. using RacingGame.Helpers;
  17. using RacingGame.Shaders;
  18. using RacingGame.Sounds;
  19. using Model = RacingGame.Graphics.Model;
  20. using RacingGame.Tracks;
  21. using RacingGame.Properties;
  22. namespace RacingGame.GameLogic
  23. {
  24. /// <summary>
  25. /// Car controller class for controlling the car we drive.
  26. /// This class is derived from the BasePlayer class, which stores all
  27. /// important game values for us (game time, game over, etc.).
  28. /// The ChaseCamera is then derived from this class and finally we got the
  29. /// Player class itself at the top, which is deriven from all these classes.
  30. /// This way we can easily access everything from the Player class.
  31. /// </summary>
  32. /// <returns>Base player</returns>
  33. public class CarPhysics : BasePlayer
  34. {
  35. /// <summary>
  36. /// Car is 1000 kg
  37. /// </summary>
  38. public const float DefaultCarMass = 1000;
  39. /// <summary>
  40. /// Gravity on earth is 9.81 m/s^2
  41. /// You could change this for different track behaviors
  42. /// </summary>
  43. const float Gravity = 9.81f;
  44. /// <summary>
  45. /// Max speed of our car is 275 mph.
  46. /// While we use mph for the display, we calculate internally with
  47. /// meters per sec since meter is the unit we use for everthing in the
  48. /// game. And it is a much nicer unit than miles or feet.
  49. /// </summary>
  50. public const float DefaultMaxSpeed =
  51. 275.0f * MphToMeterPerSec,
  52. MaxPossibleSpeed =
  53. 290.0f * MphToMeterPerSec;
  54. /// <summary>
  55. /// Max. acceleration in m/s^2 we can do per second.
  56. /// We have also to define the max and min overall accelleration we can
  57. /// do with our car (very car specfic, but for this game always the same
  58. /// to make it fair). Driving backwards is slower than driving forward.
  59. /// </summary>
  60. public const float DefaultMaxAccelerationPerSec = 2.5f,
  61. MaxAcceleration = 5.75f,
  62. MinAcceleration = -3.25f;
  63. /// <summary>
  64. /// Friction we have on the road each second. If we are driving slow,
  65. /// this slows us down quickly. If we drive really fast, this does not
  66. /// matter so much anymore. The main slowdown will be the air friction.
  67. /// </summary>
  68. const float CarFrictionOnRoad = 17.523456789f;
  69. /// <summary>
  70. /// Air friction we get if we drive fast, this slows us down very fast
  71. /// if we drive fast. It makes it also much harder to drive faster if
  72. /// we drive already at a very fast speed. For slow speeds the air
  73. /// friction does not matter so much. This could be extended to include
  74. /// wind and then even at low speeds the air friction would slow us
  75. /// down or even influcene our movement. Maybe in a Game Mod sometime ...
  76. /// </summary>
  77. const float AirFrictionPerSpeed = 0.66f;
  78. /// <summary>
  79. /// Max air friction, this way we can have a good air friction for low
  80. /// speeds but we are not limited to 190-210mph, but can drive even faster.
  81. /// </summary>
  82. const float MaxAirFriction = AirFrictionPerSpeed * 200.0f;
  83. /// <summary>
  84. /// Break slowdown per second, 1.0 means we need 1 second to do a full
  85. /// break. Slowdown is also limited by max. 100 per sec!
  86. /// Note: This would not make sense in a real world simulation because
  87. /// stopping the car needs usually more time and is highly dependant
  88. /// on the speed resultin in bigger stopping distances.
  89. /// For this game it is easier and more fun to just brake always the same.
  90. /// </summary>
  91. const float BrakeSlowdown = 1.0f;
  92. /// <summary>
  93. /// Convert our meter per sec to mph for display.
  94. /// 1 mile = 1.609344 kilometers
  95. /// Each hour has 3600 seconds (60 min * 60 sec).
  96. /// 1 kilometer = 1000 meter.
  97. /// </summary>
  98. public const float MeterPerSecToMph =
  99. 1.609344f * ((60.0f * 60.0f) / 1000.0f),
  100. MphToMeterPerSec = 1.0f / MeterPerSecToMph;
  101. /// <summary>
  102. /// Max rotation per second we use for our car.
  103. /// </summary>
  104. public const float MaxRotationPerSec = 1.3f;
  105. /// <summary>
  106. /// Minimum controller sensitivity
  107. /// </summary>
  108. public const float MinSensitivity = 0.5f;
  109. /// <summary>
  110. /// This will be elevated above the car position to let our camera
  111. /// look at the roof of our car and not at the street.
  112. /// </summary>
  113. protected const float CarHeight = 2.0f;
  114. /// <summary>
  115. /// The closest the camera should ever come to the car
  116. /// </summary>
  117. private const float MinViewDistance = 0.4f;
  118. /// <summary>
  119. /// The furthest the camera should ever be from the car
  120. /// This is ignored during at the start of the race for zooming in
  121. /// </summary>
  122. private const float MaxViewDistance = 1.8f;
  123. /// <summary>
  124. /// Max speed of the car, set from the car type (see CarSelection screen).
  125. /// We start with the speed 0, then it is increased based on the
  126. /// current acceleration value to this maxSpeed value.
  127. /// </summary>
  128. protected static float maxSpeed = DefaultMaxSpeed * 1.05f;
  129. /// <summary>
  130. /// Mass of the car. Used for physics calculations.
  131. /// Set from the car type (see CarSelection screen).
  132. /// </summary>
  133. protected static float carMass = DefaultCarMass * 1.015f;
  134. /// <summary>
  135. /// Current acceleration of the car. Drive faster or break with up/down,
  136. /// left/right mouse or the gamepad (left/right triggers or right thumb).
  137. /// The acceleration influcences the current speed of the car.
  138. /// </summary>
  139. protected static float maxAccelerationPerSec =
  140. DefaultMaxAccelerationPerSec * 0.85f;
  141. /// <summary>
  142. /// Set car variables from car model. Called from CarSelection screen.
  143. /// See CarSelection class for all the car variables.
  144. /// </summary>
  145. /// <param name="setMaxCarSpeed">Set max car speed</param>
  146. /// <param name="setCarMass">Set car mass</param>
  147. /// <param name="setMaxAccelerationPerSec">Set max acceleration per second
  148. /// </param>
  149. public static void SetCarVariablesForCarType(float setMaxCarSpeed,
  150. float setCarMass, float setMaxAccelerationPerSec)
  151. {
  152. maxSpeed = setMaxCarSpeed;
  153. carMass = setCarMass;
  154. maxAccelerationPerSec = setMaxAccelerationPerSec;
  155. carPitchPhysics = new SpringPhysicsObject(
  156. carMass, 1.5f, 120, 0);
  157. }
  158. /// <summary>
  159. /// Car position, updated each frame by our current carSpeed vector.
  160. /// </summary>
  161. Vector3 carPos;
  162. /// <summary>
  163. /// Direction the car is currently heading.
  164. /// </summary>
  165. Vector3 carDir;
  166. /// <summary>
  167. /// Speed of our car, just in the direction of our car.
  168. /// Sliding is a nice feature, but it overcomplicates too much and
  169. /// for this game sliding would be really bad and make it much harder
  170. /// to drive!
  171. /// </summary>
  172. float speed;
  173. /// <summary>
  174. /// Car up vector for orientation.
  175. /// </summary>
  176. Vector3 carUp;
  177. /// <summary>
  178. /// Car up vector
  179. /// </summary>
  180. /// <returns>Vector 3</returns>
  181. public Vector3 CarUpVector
  182. {
  183. get
  184. {
  185. return carUp;
  186. }
  187. }
  188. /// <summary>
  189. /// Force we currently apply on our car. Usually we determinate how
  190. /// much acceleration we get through the controls and multiplying that
  191. /// with the current carDir.
  192. /// But for gravity, staying on the ground, crashes, collisions impulses
  193. /// and all other forces this vector might be somewhat different.
  194. /// Used each frame to change carSpeed.
  195. /// </summary>
  196. Vector3 carForce;
  197. /// <summary>
  198. /// Car pitch physics helper for a simple spring effect for
  199. /// accelerating, decelerating and crashing.
  200. /// </summary>
  201. static SpringPhysicsObject carPitchPhysics = new SpringPhysicsObject(
  202. DefaultCarMass, 1.5f, 120, 0);
  203. /// <summary>
  204. /// View distance, which we can change with page up/down and the mouse
  205. /// wheel, but it always moves back to 1. The real view distance is
  206. /// also changed depending on how fast we drive (see UpdateCar stuff below)
  207. /// </summary>
  208. float viewDistance = 1.0f;
  209. /// <summary>
  210. /// Wheel position, used for animating the wheels
  211. /// </summary>
  212. private float wheelPos = 0.0f;
  213. /// <summary>
  214. /// Wheel movement speed for the animation used in the model class.
  215. /// </summary>
  216. const float WheelMovementSpeed = 1.0f;
  217. /// <summary>
  218. /// Rotate car after collision.
  219. /// </summary>
  220. float rotateCarAfterCollision = 0;
  221. /// <summary>
  222. /// Is car on ground? Only allow rotation, apply ground friction,
  223. /// speed changing if we are on ground and adding brake tracks.
  224. /// </summary>
  225. protected bool isCarOnGround = false;
  226. /// <summary>
  227. /// Helper variables to keep track of our car position on the current
  228. /// track. Always start with 0 (start pos) and update each frame!
  229. /// We could also check for the track position each frame by going
  230. /// through all the track segments, but that would be very slow because
  231. /// we got a few thousand track segments. Instead we only have to check
  232. /// the previous and next track segments until we find the right location.
  233. /// Usually this means we don't have to change or just change the
  234. /// trackSegmentNumber by 1.
  235. /// </summary>
  236. int trackSegmentNumber = 0;
  237. /// <summary>
  238. /// Track segment percent, tells us where we are on the current segment.
  239. /// Always between 0 and 1, for more information
  240. /// <see>trackSegmentNumber</see>
  241. /// </summary>
  242. float trackSegmentPercent = 0;
  243. /// <summary>
  244. /// Car render matrix we calculate each frame.
  245. /// </summary>
  246. Matrix carRenderMatrix = Matrix.Identity;
  247. /// <summary>
  248. /// Car position
  249. /// </summary>
  250. /// <returns>Vector 3</returns>
  251. public Vector3 CarPosition
  252. {
  253. get
  254. {
  255. return carPos;
  256. }
  257. }
  258. /// <summary>
  259. /// Speed
  260. /// </summary>
  261. /// <returns>Float</returns>
  262. public float Speed
  263. {
  264. get
  265. {
  266. return speed;
  267. }
  268. }
  269. float lastAccelerationResult = 0.0f;
  270. int lastGear = 0;
  271. /// <summary>
  272. /// Acceleration
  273. /// </summary>
  274. /// <returns>Float</returns>
  275. public float Acceleration
  276. {
  277. get
  278. {
  279. // Find out how much force we apply to the current car direction.
  280. // Always interpolate our result.
  281. lastAccelerationResult +=
  282. Vector3.Dot(carForce, carDir) * 0.01f * BaseGame.MoveFactorPerSecond;
  283. if (lastAccelerationResult < -0.25f)
  284. lastAccelerationResult = -0.25f;
  285. if (lastAccelerationResult > 1)
  286. lastAccelerationResult = 1;
  287. // Drop to 0 for a short time if gear change happend
  288. int thisGear = 1 + (int)(5 * Speed / MaxPossibleSpeed);
  289. if (thisGear != lastGear)
  290. {
  291. lastAccelerationResult = 0;
  292. lastGear = thisGear;
  293. }
  294. return lastAccelerationResult;
  295. }
  296. }
  297. /// <summary>
  298. /// Look at position
  299. /// </summary>
  300. /// <returns>Vector 3</returns>
  301. public Vector3 LookAtPos
  302. {
  303. get
  304. {
  305. return carPos + carUp * CarHeight;
  306. }
  307. }
  308. /// <summary>
  309. /// Car direction
  310. /// </summary>
  311. /// <returns>Vector 3</returns>
  312. public Vector3 CarDirection
  313. {
  314. get
  315. {
  316. return carDir;
  317. }
  318. }
  319. /// <summary>
  320. /// Car wheel position
  321. /// </summary>
  322. /// <returns>Float</returns>
  323. public float CarWheelPos
  324. {
  325. get
  326. {
  327. return wheelPos;
  328. }
  329. }
  330. /// <summary>
  331. /// Car right
  332. /// </summary>
  333. /// <returns>Vector 3</returns>
  334. public Vector3 CarRight
  335. {
  336. get
  337. {
  338. return Vector3.Cross(carDir, carUp);
  339. }
  340. }
  341. /// <summary>
  342. /// Car render matrix, this is the final matrix for rendering our car,
  343. /// which is calculated in UpdateCarMatrixAndCamera, which is called
  344. /// by Update each frame.
  345. /// </summary>
  346. /// <returns>Matrix</returns>
  347. public Matrix CarRenderMatrix
  348. {
  349. get
  350. {
  351. return carRenderMatrix;
  352. }
  353. }
  354. /// <summary>
  355. /// Create car physics controller
  356. /// </summary>
  357. /// <param name="setCarPosition">Set car position</param>
  358. public CarPhysics(Vector3 setCarPosition)
  359. {
  360. SetCarPosition(setCarPosition,
  361. new Vector3(0, 1, 0), new Vector3(0, 0, 1));
  362. }
  363. /// <summary>
  364. /// Create car physics controller
  365. /// </summary>
  366. /// <param name="setCarPosition">Set car position</param>
  367. /// <param name="setDirection">Set direction</param>
  368. /// <param name="setUp">Set up</param>
  369. public CarPhysics(Vector3 setCarPosition,
  370. Vector3 setDirection,
  371. Vector3 setUp)
  372. {
  373. SetCarPosition(setCarPosition, setDirection, setUp);
  374. }
  375. /// <summary>
  376. /// Set car position
  377. /// </summary>
  378. /// <param name="setCarPosition">Set car position</param>
  379. /// <param name="setDirection">Set direction</param>
  380. /// <param name="setUp">Set up</param>
  381. public void SetCarPosition(
  382. Vector3 setNewCarPosition,
  383. Vector3 setDirection,
  384. Vector3 setUp)
  385. {
  386. // Add car height to make camera look at the roof and not at the street.
  387. carPos = setNewCarPosition;
  388. carDir = setDirection;
  389. carUp = setUp;
  390. }
  391. /// <summary>
  392. /// Reset all player entries for restarting a game, just resets the
  393. /// car speed here.
  394. /// </summary>
  395. public override void Reset()
  396. {
  397. base.Reset();
  398. speed = 0;
  399. carForce = Vector3.Zero;
  400. trackSegmentNumber = 0;
  401. trackSegmentPercent = 0;
  402. }
  403. /// <summary>
  404. /// Clear variables for game over
  405. /// </summary>
  406. public override void ClearVariablesForGameOver()
  407. {
  408. base.ClearVariablesForGameOver();
  409. speed = 0;
  410. carForce = Vector3.Zero;
  411. trackSegmentNumber = 0;
  412. trackSegmentPercent = 0;
  413. }
  414. float virtualRotationAmount = 0.0f;
  415. float rotationChange = 0.0f;
  416. /// <summary>
  417. /// Update game logic for our car.
  418. /// </summary>
  419. public override void Update()
  420. {
  421. base.Update();
  422. // Don't use the car position and car handling if in free camera mode.
  423. if (RacingGameManager.Player.FreeCamera)
  424. return;
  425. // Only allow control if zommed in, use carOnGround as helper
  426. if (ZoomInTime > 0)
  427. isCarOnGround = false;
  428. wheelPos += BaseGame.MoveFactorPerSecond * speed / WheelMovementSpeed;
  429. float moveFactor = BaseGame.MoveFactorPerSecond;
  430. // Make sure this is never below 0.001f and never above 0.5f
  431. // Else our formulars below might mess up or carSpeed and carForce!
  432. if (moveFactor < 0.001f)
  433. moveFactor = 0.001f;
  434. if (moveFactor > 0.5f)
  435. moveFactor = 0.5f;
  436. float effectiveSensitivity = MinSensitivity +
  437. GameSettings.Default.ControllerSensitivity;
  438. // First handle rotations (reduce last value)
  439. rotationChange *= 0.95f;
  440. // Left/right changes rotation
  441. if (Input.KeyboardLeftPressed ||
  442. Input.Keyboard.IsKeyDown(Keys.A))
  443. rotationChange += effectiveSensitivity *
  444. MaxRotationPerSec * moveFactor / 2.5f;
  445. else if (Input.KeyboardRightPressed ||
  446. Input.Keyboard.IsKeyDown(Keys.D) ||
  447. Input.Keyboard.IsKeyDown(Keys.E))
  448. rotationChange -= effectiveSensitivity *
  449. MaxRotationPerSec * moveFactor / 2.5f;
  450. else
  451. rotationChange = 0;
  452. if (Input.MouseXMovement != 0)
  453. rotationChange -= effectiveSensitivity *
  454. (Input.MouseXMovement / 15.0f) *
  455. MaxRotationPerSec * moveFactor;
  456. if (Input.IsGamePadConnected)
  457. {
  458. // More dynamic force changing with gamepad (slow, faster, etc.)
  459. rotationChange -= effectiveSensitivity *
  460. Input.GamePad.ThumbSticks.Left.X *
  461. MaxRotationPerSec * moveFactor / 1.12345f;
  462. // Also allow pad to simulate same behaviour as on keyboard
  463. if (Input.GamePad.DPad.Left == ButtonState.Pressed)
  464. rotationChange += effectiveSensitivity *
  465. MaxRotationPerSec * moveFactor / 1.5f;
  466. else if (Input.GamePad.DPad.Right == ButtonState.Pressed)
  467. rotationChange -= effectiveSensitivity *
  468. MaxRotationPerSec * moveFactor / 1.5f;
  469. }
  470. float maxRot = MaxRotationPerSec * moveFactor * 1.25f;
  471. // Handle car rotation after collision
  472. if (rotateCarAfterCollision != 0)
  473. {
  474. if (rotateCarAfterCollision > maxRot)
  475. {
  476. rotationChange += maxRot;
  477. rotateCarAfterCollision -= maxRot;
  478. }
  479. else if (rotateCarAfterCollision < -maxRot)
  480. {
  481. rotationChange -= maxRot;
  482. rotateCarAfterCollision += maxRot;
  483. }
  484. else
  485. {
  486. rotationChange += rotateCarAfterCollision;
  487. rotateCarAfterCollision = 0;
  488. }
  489. }
  490. else
  491. {
  492. // If we are staying or moving very slowly, limit rotation!
  493. if (speed < 10.0f)
  494. rotationChange *= 0.67f + 0.33f * speed / 10.0f;
  495. else
  496. rotationChange *= 1.0f + (speed - 10) / 100.0f;
  497. }
  498. // Limit rotation change to MaxRotationPerSec * 1.5 (usually for mouse)
  499. if (rotationChange > maxRot)
  500. rotationChange = maxRot;
  501. if (rotationChange < -maxRot)
  502. rotationChange = -maxRot;
  503. // Rotate dir around up vector
  504. // Interpolate rotatation amount.
  505. virtualRotationAmount += rotationChange;
  506. // Smooth over 200ms
  507. float interpolatedRotationChange =
  508. (rotationChange + virtualRotationAmount) *
  509. moveFactor / 0.225f;
  510. virtualRotationAmount -= interpolatedRotationChange;
  511. if (isCarOnGround)
  512. carDir = Vector3.TransformNormal(carDir,
  513. Matrix.CreateFromAxisAngle(carUp, interpolatedRotationChange));
  514. if (Input.Keyboard.IsKeyDown(Keys.PageUp) ||
  515. Input.GamePadXPressed)
  516. viewDistance -= moveFactor * 2.0f;
  517. if (Input.Keyboard.IsKeyDown(Keys.PageDown) ||
  518. Input.GamePadYPressed)
  519. viewDistance += moveFactor * 2.0f;
  520. if (Input.MouseWheelDelta != 0)
  521. viewDistance -= Input.MouseWheelDelta / 500.0f;
  522. // Restrict the camera's distance to a range, but allow the camera
  523. // to be as far as it likes during the start of race zoom in
  524. if (ZoomInTime <= 0)
  525. viewDistance =
  526. MathHelper.Clamp(viewDistance, MinViewDistance, MaxViewDistance);
  527. else
  528. viewDistance = Math.Max(viewDistance, MinViewDistance);
  529. // With keyboard, do heavy changes, but still smooth over 200ms
  530. // Up or left mouse button accelerates
  531. // Also support ASDW (querty) and AOEW (dvorak) shooter like controlling!
  532. float newAccelerationForce = 0.0f;
  533. if (Input.KeyboardUpPressed ||
  534. Input.Keyboard.IsKeyDown(Keys.W) ||
  535. Input.MouseLeftButtonPressed ||
  536. Input.GamePadAPressed)
  537. newAccelerationForce +=
  538. maxAccelerationPerSec;// * moveFactor;
  539. // Down or right mouse button decelerates
  540. else if (Input.KeyboardDownPressed ||
  541. Input.Keyboard.IsKeyDown(Keys.S) ||
  542. Input.Keyboard.IsKeyDown(Keys.O) ||
  543. Input.MouseRightButtonPressed)
  544. newAccelerationForce -=
  545. maxAccelerationPerSec;// * moveFactor;
  546. else if (Input.IsGamePadConnected)
  547. {
  548. // More dynamic force changing with gamepad (slow, faster, etc.)
  549. newAccelerationForce +=
  550. (Input.GamePad.Triggers.Right) *
  551. maxAccelerationPerSec;// *moveFactor;
  552. // Also allow pad to simulate same behaviour as on keyboard
  553. if (Input.GamePad.DPad.Up == ButtonState.Pressed)
  554. newAccelerationForce +=
  555. maxAccelerationPerSec;
  556. else if (Input.GamePad.DPad.Down == ButtonState.Pressed)
  557. newAccelerationForce -=
  558. maxAccelerationPerSec;
  559. }
  560. // Limit acceleration (but drive as fast forwards as possible if we
  561. // are moving backwards)
  562. if (speed > 0 &&
  563. newAccelerationForce > MaxAcceleration)
  564. newAccelerationForce = MaxAcceleration;
  565. if (newAccelerationForce < MinAcceleration)
  566. newAccelerationForce = MinAcceleration;
  567. // Add acceleration force to total car force, but use the current carDir!
  568. if (isCarOnGround)
  569. carForce +=
  570. carDir * newAccelerationForce * (moveFactor * 85);
  571. // Change speed with standard formula, use acceleration as our force
  572. float oldSpeed = speed;
  573. Vector3 speedChangeVector = carForce / carMass;
  574. // Only use the amount important for our current direction (slower rot)
  575. if (isCarOnGround &&
  576. speedChangeVector.Length() > 0)
  577. {
  578. float speedApplyFactor =
  579. Vector3.Dot(Vector3.Normalize(speedChangeVector), carDir);
  580. if (speedApplyFactor > 1)
  581. speedApplyFactor = 1;
  582. speed += speedChangeVector.Length() * speedApplyFactor;
  583. }
  584. // Apply friction. Basically we have 2 frictions that slow us down:
  585. // The friction from the contact of the wheels with the road (rolling
  586. // friction) and the air friction, which becomes bigger as we drive
  587. // faster. We need more force to overcome the resistances if we drive
  588. // faster. Our engine is strong enough to overcome the initial
  589. // car friction and air friction, but we want simulate that we need
  590. // more force to overcome the resistances at high speeds.
  591. // Usually this would require a more complex formula and the car
  592. // should need more fuel and force at high speeds, we just simulate that
  593. // by reducing the force depending on the frictions to get the same
  594. // effect while having our constant forces that are calculated above.
  595. // Max. air friction to MaxAirFiction, else driving very fast becomes
  596. // too hard.
  597. float airFriction = AirFrictionPerSpeed * Math.Abs(speed);
  598. if (airFriction > MaxAirFriction)
  599. airFriction = MaxAirFriction;
  600. // Don't use ground friction if we are not on the ground.
  601. float groundFriction = CarFrictionOnRoad;
  602. if (isCarOnGround == false)
  603. groundFriction = 0;
  604. carForce *= 1.0f - (0.275f * 0.02125f *
  605. 0.2f * // 20% for force slowdown
  606. (groundFriction + airFriction));
  607. // Reduce the speed, but use very low values to make the game more fun!
  608. float noFrictionSpeed = speed;
  609. speed *= 1.0f - (0.01f *
  610. 0.1f * 0.02125f *
  611. (groundFriction + airFriction));
  612. // Never change more than by 1
  613. if (speed < noFrictionSpeed - 1)
  614. speed = noFrictionSpeed - 1;
  615. if (isCarOnGround)
  616. {
  617. bool downPressed =
  618. Input.MouseRightButtonPressed ||
  619. Input.KeyboardDownPressed ||
  620. Input.GamePad.DPad.Down == ButtonState.Pressed;
  621. if (Input.Keyboard.IsKeyDown(Keys.Space) ||
  622. Input.MouseMiddleButtonPressed ||
  623. Input.GamePad.Triggers.Left > 0.5f ||
  624. Input.GamePadBPressed ||
  625. // Also use back for this
  626. downPressed)
  627. {
  628. float slowdown =
  629. 1.0f - moveFactor *
  630. // Use only half if we just decelerate
  631. (downPressed ? BrakeSlowdown / 2 : BrakeSlowdown) *
  632. // Don't brake so much if we are already driving backwards
  633. (speed < 0 ? 0.33f : 1.0f);
  634. speed *= Math.Max(0, slowdown);
  635. // Limit to max. 100 mph slowdown per sec
  636. if (speed > oldSpeed + 100 * moveFactor)
  637. speed = (oldSpeed + 100 * moveFactor);
  638. if (speed < oldSpeed - 100 * moveFactor)
  639. speed = (oldSpeed - 100 * moveFactor);
  640. // Remember that we slowed down for generating tracks.
  641. downPressed = true;
  642. }
  643. // Calculate pitch depending on the force
  644. float speedChange = speed - oldSpeed;
  645. // Add brake tracks.
  646. if (speed > 0.5f && speed < 7.5f && speedChange > 5.5f * moveFactor ||
  647. speed > 0.75f && speedChange < 10 * moveFactor && downPressed)
  648. {
  649. Sound.Sounds brakeType =
  650. Sound.GetBreakSoundType(speed, speedChange, rotationChange);
  651. // Add brake tracks for major breaks
  652. if (brakeType == Sound.Sounds.BrakeCurveMajor ||
  653. brakeType == Sound.Sounds.BrakeMajor)
  654. {
  655. RacingGameManager.Landscape.AddBrakeTrack(this);
  656. }
  657. // And play sound for braking
  658. Sound.PlayBrakeSound(brakeType);
  659. }
  660. // Limit speed change, never apply more than 5 per sec.
  661. if (speedChange < -8 * moveFactor)
  662. speedChange = -8 * moveFactor;
  663. if (speedChange > 8 * moveFactor)
  664. speedChange = 8 * moveFactor;
  665. carPitchPhysics.ChangePos(speedChange);
  666. }
  667. // Limit speed
  668. if (speed > maxSpeed)
  669. speed = maxSpeed;
  670. if (speed < -maxSpeed)
  671. speed = -maxSpeed;
  672. // Apply speed and calculate new car position.
  673. carPos += speed * carDir * moveFactor * 1.75f;
  674. // Handle pitch spring
  675. carPitchPhysics.Simulate(moveFactor);
  676. int oldTrackSegmentNumber = trackSegmentNumber;
  677. // Find out where we currently are on the track.
  678. RacingGameManager.Landscape.UpdateCarTrackPosition(
  679. carPos, ref trackSegmentNumber, ref trackSegmentPercent);
  680. // Was the track segment changed?
  681. if (trackSegmentNumber != oldTrackSegmentNumber &&
  682. // And we in game?
  683. RacingGameManager.InGame && !GameOver)
  684. {
  685. // Was this the start? Did we finish a lap?
  686. if (trackSegmentNumber == 0 &&
  687. // Ignore if we missed one checkpoint.
  688. RacingGameManager.Landscape.NewReplay.CheckpointTimes.Count >=
  689. RacingGameManager.Landscape.CheckpointSegmentPositions.Count - 1)
  690. {
  691. // Show time we made for this lap
  692. BaseGame.UI.AddTimeFadeupEffect((int)GameTimeMilliseconds,
  693. UIRenderer.TimeFadeupMode.Normal);
  694. // We finished this lap, start next
  695. StartNewLap();
  696. }
  697. else
  698. {
  699. // Always only check for the next checkpoint
  700. int num =
  701. RacingGameManager.Landscape.NewReplay.CheckpointTimes.Count;
  702. if (ZoomInTime <= 0 && // Do not check before race starts
  703. num <
  704. RacingGameManager.Landscape.CheckpointSegmentPositions.Count &&
  705. RacingGameManager.Landscape.CheckpointSegmentPositions[num] >
  706. oldTrackSegmentNumber &&
  707. RacingGameManager.Landscape.CheckpointSegmentPositions[num] <=
  708. trackSegmentNumber)
  709. {
  710. // We passed that checkpoint, show time
  711. // Show improvements of time stored in best replay.
  712. int differenceMs =
  713. RacingGameManager.Landscape.CompareCheckpointTime(num);
  714. if (differenceMs < 0)
  715. Sound.Play(Sound.Sounds.CheckpointBetter);
  716. else
  717. Sound.Play(Sound.Sounds.CheckpointWorse);
  718. BaseGame.UI.AddTimeFadeupEffect(
  719. //normal: (int)GameTimeMilliseconds,
  720. Math.Abs(differenceMs),
  721. differenceMs < 0 ? UIRenderer.TimeFadeupMode.Minus :
  722. UIRenderer.TimeFadeupMode.Plus);
  723. // Add this checkpoint time to the current replay
  724. RacingGameManager.Landscape.NewReplay.CheckpointTimes.Add(
  725. RacingGameManager.Player.GameTimeMilliseconds / 1000.0f);
  726. }
  727. }
  728. }
  729. // And get the TrackMatrix and track values at this location.
  730. float roadWidth, nextRoadWidth;
  731. Matrix trackMatrix =
  732. RacingGameManager.Landscape.GetTrackPositionMatrix(
  733. trackSegmentNumber, trackSegmentPercent,
  734. out roadWidth, out nextRoadWidth);
  735. // Just set car up from trackMatrix, this should be done
  736. // better with a more accurate gravity model (see gravity calculation!)
  737. Vector3 remOldRightVec = CarRight;
  738. carUp = trackMatrix.Up;
  739. carDir = Vector3.Cross(carUp, remOldRightVec);
  740. // Set up the ground and guardrail boundings for the physics calculation.
  741. Vector3 trackPos = trackMatrix.Translation;
  742. RacingGameManager.Player.SetGroundPlaneAndGuardRails(
  743. trackPos, trackMatrix.Up,
  744. // Construct our guardrail positions for the collision testing
  745. trackPos - trackMatrix.Right *
  746. (roadWidth / 2 - GuardRail.InsideRoadDistance / 2),
  747. trackPos - trackMatrix.Right *
  748. (roadWidth / 2 - GuardRail.InsideRoadDistance / 2) +
  749. trackMatrix.Forward,
  750. trackPos + trackMatrix.Right *
  751. (nextRoadWidth / 2 - GuardRail.InsideRoadDistance / 2),
  752. trackPos + trackMatrix.Right *
  753. (nextRoadWidth / 2 - GuardRail.InsideRoadDistance / 2) +
  754. trackMatrix.Forward);
  755. carRenderMatrix = RacingGameManager.Player.UpdateCarMatrixAndCamera();
  756. // Finally check for collisions with the guard rails.
  757. // Also handle gravity.
  758. ApplyGravityAndCheckForCollisions();
  759. }
  760. /// <summary>
  761. /// Current gravity speed, increases as we fly around ^^
  762. /// </summary>
  763. float gravitySpeed = 0.0f;
  764. /// <summary>
  765. /// Apply gravity to our car in case any of our wheels is in the air.
  766. /// Check for collisions, we only have the road and the guard rails
  767. /// as colision objects for this game. This way we can simplify
  768. /// the physics quite a lot. Usually it would be much better to have
  769. /// a fullblown physics engine, but thats a lot of work and goes beyond
  770. /// this starter kit game :)
  771. /// </summary>
  772. public void ApplyGravityAndCheckForCollisions()
  773. {
  774. // Don't do it in the menu
  775. if (RacingGameManager.InMenu)
  776. return;
  777. // Calc normals for the guard rail with help of the next guard rail
  778. // position and the ground normal.
  779. Vector3 guardrailLeftVec =
  780. Vector3.Normalize(nextGuardrailLeft - guardrailLeft);
  781. Vector3 guardrailRightVec =
  782. Vector3.Normalize(nextGuardrailRight - guardrailRight);
  783. Vector3 guardrailLeftNormal =
  784. Vector3.Cross(guardrailLeftVec, groundPlaneNormal);
  785. Vector3 guardrailRightNormal =
  786. Vector3.Cross(groundPlaneNormal, guardrailRightVec);
  787. float roadWidth = (guardrailLeft - guardrailRight).Length();
  788. // Calculate position we will have NEXT frame!
  789. float moveFactor = BaseGame.MoveFactorPerSecond;
  790. Vector3 pos = carPos;
  791. // Check all 4 corner points of our car.
  792. Vector3 carRight = Vector3.Cross(carDir, carUp);
  793. Vector3 carLeft = -carRight;
  794. // Car dimensions are 2.6m (width) x 5.6m (length) x 1.8m (height)
  795. // Note: This could be improved by using more points or using
  796. // the actual car geometry.
  797. // Note: We ignore the height, this way the collision is simpler.
  798. // We then check the height above the road to see if we are flying
  799. // above the guard rails out into the landscape.
  800. Vector3[] carCorners = new Vector3[]
  801. {
  802. // Top left
  803. pos + carDir * 5.6f/2.0f - carRight * 2.6f/2.0f,
  804. // Top right
  805. pos + carDir * 5.6f/2.0f + carRight * 2.6f/2.0f,
  806. // Bottom right
  807. pos - carDir * 5.6f/2.0f + carRight * 2.6f/2.0f,
  808. // Bottom left
  809. pos - carDir * 5.6f/2.0f - carRight * 2.6f/2.0f,
  810. };
  811. float applyGravity = 0;
  812. // Check for each corner if we collide with the guard rail
  813. for (int num = 0; num < carCorners.Length; num++)
  814. {
  815. // Apply gravity if we are flying, do this for each wheel.
  816. if (carCorners[num].Z > groundPlanePos.Z)
  817. applyGravity += Gravity / 4;
  818. // Hit any guardrail?
  819. float leftDist = Vector3Helper.DistanceToLine(
  820. carCorners[num], guardrailLeft, nextGuardrailLeft);
  821. float rightDist = Vector3Helper.DistanceToLine(
  822. carCorners[num], guardrailRight, nextGuardrailRight);
  823. // If we are closer than 0.1f, thats a collision!
  824. //TODO: ignore if we are too high (higher than guardrail).
  825. if (leftDist < 0.1f ||
  826. // Also include the case where we are father away from rightDist
  827. // than the road is width.
  828. rightDist > roadWidth)
  829. {
  830. // Force car back on the road, for that calculate impulse and
  831. float collisionAngle =
  832. Vector3Helper.GetAngleBetweenVectors(
  833. carRight, guardrailLeftNormal);
  834. // Flip at 180 degrees (if driving in wrong direction)
  835. if (collisionAngle > MathHelper.Pi / 2)
  836. collisionAngle -= MathHelper.Pi;
  837. // Just correct rotation if 0-45 degrees (slowly)
  838. if (Math.Abs(collisionAngle) < MathHelper.Pi / 4.0f)
  839. {
  840. // Play crash sound
  841. Sound.PlayCrashSound(false);
  842. // For front wheels to full collision rotation, for back half!
  843. if (num < 2)
  844. {
  845. rotateCarAfterCollision = -collisionAngle / 1.5f;
  846. speed *= 0.93f;
  847. if (viewDistance > 0.75f)
  848. viewDistance -= 0.1f;
  849. }
  850. else
  851. {
  852. rotateCarAfterCollision = -collisionAngle / 2.5f;
  853. speed *= 0.96f;
  854. if (viewDistance > 0.75f)
  855. viewDistance -= 0.05f;
  856. }
  857. ChaseCamera.WobbelCamera(0.00075f * speed);
  858. }
  859. // If 90-45 degrees (in either direction), make frontal crash
  860. // + stop car + wobble camera
  861. else if (Math.Abs(collisionAngle) < MathHelper.Pi * 3.0f / 4.0f)
  862. {
  863. // Also rotate car if less than 60 degrees
  864. if (Math.Abs(collisionAngle) < MathHelper.Pi / 3.0f)
  865. rotateCarAfterCollision = +collisionAngle / 3.0f;
  866. // Play crash sound
  867. Sound.PlayCrashSound(true);
  868. // Shake camera
  869. ChaseCamera.WobbelCamera(0.005f * speed);
  870. // Just stop car!
  871. speed = 0;
  872. }
  873. // For all collisions, kill the current car force
  874. carForce = Vector3.Zero;
  875. // Always make sure we are OUTSIDE of the collision range for
  876. // the next frame. But first find out how much we have to move.
  877. float speedDistanceToGuardrails =
  878. speed * Math.Abs(Vector3.Dot(carDir, guardrailLeftNormal));
  879. if (leftDist > 0)
  880. {
  881. float correctCarPosValue = (leftDist + 0.01f +
  882. 0.1f * speedDistanceToGuardrails * moveFactor);
  883. carPos += correctCarPosValue * guardrailLeftNormal;
  884. }
  885. }
  886. if (rightDist < 0.1f ||
  887. // Also include the case where we are father away from rightDist
  888. // than the road is width.
  889. leftDist > roadWidth)
  890. {
  891. // Force car back on the road
  892. float collisionAngle =
  893. Vector3Helper.GetAngleBetweenVectors(
  894. carLeft, guardrailRightNormal);
  895. // Flip at 180 degrees (if driving in wrong direction)
  896. if (collisionAngle > MathHelper.Pi / 2)
  897. collisionAngle -= MathHelper.Pi;
  898. // Just correct rotation if 0-45 degrees (slowly)
  899. if (Math.Abs(collisionAngle) < MathHelper.Pi / 4.0f)
  900. {
  901. // Play crash sound
  902. Sound.PlayCrashSound(false);
  903. // For front wheels to full collision rotation, for back half!
  904. if (num < 2)
  905. {
  906. rotateCarAfterCollision = +collisionAngle / 1.5f;
  907. speed *= 0.935f;
  908. if (viewDistance > 0.75f)
  909. viewDistance -= 0.1f;
  910. }
  911. else
  912. {
  913. rotateCarAfterCollision = +collisionAngle / 2.5f;
  914. speed *= 0.96f;
  915. if (viewDistance > 0.75f)
  916. viewDistance -= 0.05f;
  917. }
  918. ChaseCamera.WobbelCamera(0.00075f * speed);
  919. }
  920. // If 90-45 degrees (in either direction), make frontal crash
  921. // + stop car + wobble camera
  922. else if (Math.Abs(collisionAngle) < MathHelper.Pi * 3.0f / 4.0f)
  923. {
  924. // Also rotate car if less than 60 degrees
  925. if (Math.Abs(collisionAngle) < MathHelper.Pi / 3.0f)
  926. rotateCarAfterCollision = +collisionAngle / 3.0f;
  927. // Play crash sound
  928. Sound.PlayCrashSound(true);
  929. // Shake camera
  930. ChaseCamera.WobbelCamera(0.005f * speed);
  931. // Just stop car!
  932. speed = 0;
  933. }
  934. // For all collisions, kill the current car force
  935. carForce = Vector3.Zero;
  936. // Always make sure we are OUTSIDE of the collision range for
  937. // the next frame. But first find out how much we have to move.
  938. float speedDistanceToGuardrails =
  939. speed * Math.Abs(Vector3.Dot(carDir, guardrailLeftNormal));
  940. if (rightDist > 0)
  941. {
  942. float correctCarPosValue = (rightDist + 0.01f +
  943. 0.1f * speedDistanceToGuardrails * moveFactor);
  944. carPos += correctCarPosValue * guardrailRightNormal;
  945. }
  946. }
  947. }
  948. ApplyGravity();
  949. }
  950. /// <summary>
  951. /// Apply gravity
  952. /// </summary>
  953. private void ApplyGravity()
  954. {
  955. float moveFactor = BaseGame.MoveFactorPerSecond;
  956. // Fix car on ground
  957. float distFromGround = Vector3Helper.SignedDistanceToPlane(
  958. carPos,
  959. // Substract a little to let car be more on ground and not fly around.
  960. groundPlanePos - new Vector3(0, 0, 0.15f),
  961. groundPlaneNormal);
  962. isCarOnGround = distFromGround > -0.5f;
  963. // Use very hard and instant gravity to fix if car is below ground!
  964. float maxGravity = Gravity * moveFactor;
  965. // Use more smooth gravity for jumping
  966. // (Needs tweaking! see formula above)
  967. float minGravity = -Gravity * moveFactor;
  968. if (distFromGround > maxGravity)
  969. {
  970. distFromGround = maxGravity;
  971. gravitySpeed = 0;
  972. }
  973. if (distFromGround < minGravity)
  974. {
  975. distFromGround = minGravity;
  976. gravitySpeed -= distFromGround;
  977. }
  978. carPos.Z += distFromGround;
  979. // Loopings are currently buggy, fix by putting car directly road!
  980. // Find out if this is a looping
  981. bool upsideDown = carUp.Z < +0.05f;
  982. bool movingUp = carDir.Z > 0.65f;
  983. bool movingDown = carDir.Z < -0.65f;
  984. if (upsideDown || movingUp || movingDown)
  985. {
  986. carPos.Z = groundPlanePos.Z;
  987. }
  988. }
  989. /// <summary>
  990. /// Ground plane and guardrail positions.
  991. /// We update this every frame!
  992. /// </summary>
  993. protected Vector3 groundPlanePos, groundPlaneNormal,
  994. guardrailLeft, nextGuardrailLeft,
  995. guardrailRight, nextGuardrailRight;
  996. /// <summary>
  997. /// Set guard rails. We only calculate collisions to the current left
  998. /// and right guard rails, not with the complete level!
  999. /// </summary>
  1000. /// <param name="setGroundPlanePos">Set ground plane position</param>
  1001. /// <param name="setGroundPlaneNormal">Set ground plane normal</param>
  1002. /// <param name="setGuardrailLeft">Set guardrail left</param>
  1003. /// <param name="setNextGuardrailLeft">Set next guardrail left</param>
  1004. /// <param name="setGuardrailRight">Set guardrail right</param>
  1005. /// <param name="setNextGuardrailRight">Set next guardrail right</param>
  1006. public void SetGroundPlaneAndGuardRails(
  1007. Vector3 setGroundPlanePos, Vector3 setGroundPlaneNormal,
  1008. Vector3 setGuardrailLeft, Vector3 setNextGuardrailLeft,
  1009. Vector3 setGuardrailRight, Vector3 setNextGuardrailRight)
  1010. {
  1011. groundPlanePos = setGroundPlanePos;
  1012. groundPlaneNormal = setGroundPlaneNormal;
  1013. guardrailLeft = setGuardrailLeft;
  1014. nextGuardrailLeft = setNextGuardrailLeft;
  1015. guardrailRight = setGuardrailRight;
  1016. nextGuardrailRight = setNextGuardrailRight;
  1017. }
  1018. /// <summary>
  1019. /// Update car matrix and camera
  1020. /// </summary>
  1021. public Matrix UpdateCarMatrixAndCamera()
  1022. {
  1023. // Get car matrix with help of the current car position, dir and up
  1024. Matrix carMatrix = Matrix.Identity;
  1025. carMatrix.Right = CarRight;
  1026. carMatrix.Up = carUp;
  1027. carMatrix.Forward = carDir;
  1028. carMatrix.Translation = carPos;
  1029. // Change distance based on our speed
  1030. float chaseCamDistance =
  1031. (4.25f + 9.75f * speed / maxSpeed) * viewDistance;
  1032. if (RacingGameManager.InMenu == false &&
  1033. ZoomInTime > 1500)
  1034. {
  1035. // Calculate zooming in camera position
  1036. Vector3 camPos =
  1037. carPos + carUp * CarHeight +
  1038. carMatrix.Forward * (chaseCamDistance +
  1039. (MathHelper.Max(ZoomInTime - StartGameZoomedInTime, 0.0f)
  1040. / ((float)StartGameZoomTimeMilliseconds)) * 250.0f)
  1041. - carMatrix.Up * (0.6f +
  1042. (MathHelper.Max(ZoomInTime - StartGameZoomedInTime, 0.0f)
  1043. / ((float)StartGameZoomTimeMilliseconds)) * 200.0f);
  1044. // Make sure we don't interpolate at the first time
  1045. if (ZoomInTime - BaseGame.ElapsedTimeThisFrameInMilliseconds >= 3000)
  1046. RacingGameManager.Player.SetCameraPosition(camPos);
  1047. else
  1048. RacingGameManager.Player.InterpolateCameraPosition(camPos);
  1049. }
  1050. else if (RacingGameManager.Player.FreeCamera)
  1051. RacingGameManager.Player.InterpolateCameraPosition(
  1052. carPos + carUp * CarHeight +
  1053. carMatrix.Forward * chaseCamDistance -
  1054. carMatrix.Up * chaseCamDistance / (viewDistance + 6.0f) -
  1055. carMatrix.Up * 1.0f);
  1056. else if (RacingGameManager.InMenu &&
  1057. BaseGame.TotalTimeMilliseconds < 100)
  1058. // No interpolation in menu, just set it (at least for the first ms)
  1059. RacingGameManager.Player.SetCameraPosition(
  1060. carPos + carUp * CarHeight +
  1061. carMatrix.Forward * chaseCamDistance -
  1062. carMatrix.Up * 0.6f);
  1063. else
  1064. RacingGameManager.Player.InterpolateCameraPosition(
  1065. carPos + carMatrix.Up * CarHeight +
  1066. carMatrix.Forward * chaseCamDistance / 1.125f -
  1067. carMatrix.Up * 0.8f);
  1068. // Save this carMatrix into the current replay every time the
  1069. // replay interval passes.
  1070. if (RacingGameManager.Player.GameTimeMilliseconds >
  1071. RacingGameManager.Landscape.NewReplay.NumberOfTrackMatrices *
  1072. Replay.TrackMatrixIntervals * 1000.0f)
  1073. RacingGameManager.Landscape.NewReplay.AddCarMatrix(carMatrix);
  1074. // For rendering rotate car to stay correctly on the road
  1075. carMatrix =
  1076. Matrix.CreateRotationX(MathHelper.Pi / 2.0f -
  1077. carPitchPhysics.pos / 60) *
  1078. Matrix.CreateRotationZ(MathHelper.Pi) *
  1079. carMatrix;
  1080. return carMatrix;
  1081. }
  1082. }
  1083. }