Landscape.cs 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275
  1. //-----------------------------------------------------------------------------
  2. // Landscape.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 System;
  10. using System.Collections.Generic;
  11. using System.IO;
  12. using System.Text;
  13. using RacingGame.Graphics;
  14. using RacingGame.Helpers;
  15. using RacingGame.Shaders;
  16. using RacingGame.Tracks;
  17. using Model = RacingGame.Graphics.Model;
  18. using RacingGame.GameLogic;
  19. using RacingGame.GameScreens;
  20. using RacingGame.Sounds;
  21. using System.Threading;
  22. namespace RacingGame.Landscapes
  23. {
  24. /// <summary>
  25. /// Landscape
  26. /// </summary>
  27. public class Landscape : IDisposable
  28. {
  29. /// <summary>
  30. /// Grid width and height
  31. /// </summary>
  32. const int GridWidth = 257,
  33. GridHeight = 257;
  34. const float MapWidthFactor = 10,
  35. MapHeightFactor = 10,
  36. MapZScale = 300.0f;
  37. /// <summary>
  38. /// Landscape object
  39. /// </summary>
  40. public class LandscapeObject
  41. {
  42. /// <summary>
  43. /// Model
  44. /// </summary>
  45. Model model;
  46. /// <summary>
  47. /// Matrix
  48. /// </summary>
  49. Matrix matrix;
  50. /// <summary>
  51. /// Is banner, sign or building?
  52. /// Shadows are only generated for these objects, not received.
  53. /// </summary>
  54. bool isBanner = false;
  55. /// <summary>
  56. /// Change model
  57. /// </summary>
  58. /// <param name="setNewModel">Set new model</param>
  59. public void ChangeModel(Model setNewModel)
  60. {
  61. model = setNewModel;
  62. }
  63. /// <summary>
  64. /// Is big building
  65. /// </summary>
  66. /// <returns>Bool</returns>
  67. public bool IsBigBuilding
  68. {
  69. get
  70. {
  71. return model.Name.ToLower().Contains("hotel") ||
  72. model.Name.ToLower().Contains("building");
  73. }
  74. }
  75. /// <summary>
  76. /// Is banner
  77. /// </summary>
  78. public bool IsBanner
  79. {
  80. get
  81. {
  82. return isBanner;
  83. }
  84. }
  85. /// <summary>
  86. /// Position
  87. /// </summary>
  88. /// <returns>Vector 3</returns>
  89. public Vector3 Position
  90. {
  91. get
  92. {
  93. return matrix.Translation;
  94. }
  95. }
  96. /// <summary>
  97. /// Size
  98. /// </summary>
  99. /// <returns>Float</returns>
  100. public float Size
  101. {
  102. get
  103. {
  104. return model.Size;
  105. }
  106. }
  107. /// <summary>
  108. /// Create landscape object
  109. /// </summary>
  110. /// <param name="setModel">Set model</param>
  111. /// <param name="setMatrix">Set matrix</param>
  112. public LandscapeObject(Model setModel, Matrix setMatrix)
  113. {
  114. if (setModel == null)
  115. throw new ArgumentNullException("setModel");
  116. model = setModel;
  117. matrix = setMatrix;
  118. // Also include signs no reason to receive shadows for them!
  119. // Faster and looks better!
  120. isBanner = model.Name.ToLower().Contains("banner")
  121. || model.Name.ToLower().Contains("sign");
  122. }
  123. /// <summary>
  124. /// Render
  125. /// </summary>
  126. public void Render()
  127. {
  128. model.Render(matrix);
  129. }
  130. /// <summary>
  131. /// Generate shadows
  132. /// </summary>
  133. public void GenerateShadows()
  134. {
  135. model.GenerateShadow(matrix);
  136. }
  137. /// <summary>
  138. /// Use shadows
  139. /// </summary>
  140. public void UseShadows()
  141. {
  142. model.UseShadow(matrix);
  143. }
  144. }
  145. /// <summary>
  146. /// List of landscape objects.
  147. /// </summary>
  148. List<LandscapeObject> landscapeObjects = new List<LandscapeObject>();
  149. /// <summary>
  150. /// Extra list for objects that are near the track, all the objects
  151. /// in this list are also in the landscapeObjects list. Usually this
  152. /// list is a lot smaller and it is used for the shadow mapping
  153. /// generation in GenerateShadow and UseShadow methods below.
  154. /// </summary>
  155. List<LandscapeObject> nearTrackObjects = new List<LandscapeObject>();
  156. /// <summary>
  157. /// Remember start light object because we will exchange it
  158. /// as the time goes down.
  159. /// </summary>
  160. LandscapeObject startLightObject = null;
  161. /// <summary>
  162. /// Replace start light object, 0=red, 1=yellow, 2=green.
  163. /// </summary>
  164. /// <param name="number">Number</param>
  165. public void ReplaceStartLightObject(int number)
  166. {
  167. // Make sure we only use 0-2
  168. if (number < 0 || number >= 3)
  169. number = 0;
  170. if (startLightObject != null)
  171. {
  172. if (number == 2)
  173. Sound.Play(Sound.Sounds.Bleep);
  174. else
  175. Sound.Play(Sound.Sounds.Beep);
  176. startLightObject.ChangeModel(landscapeModels[number]);
  177. }
  178. }
  179. /// <summary>
  180. /// Kill all loaded objects
  181. /// </summary>
  182. public void KillAllLoadedObjects()
  183. {
  184. landscapeObjects.Clear();
  185. nearTrackObjects.Clear();
  186. startLightObject = null;
  187. }
  188. /// <summary>
  189. /// All landscape models are preloaded and then used in AddObjectToRender.
  190. /// </summary>
  191. Model[] landscapeModels = new Model[]
  192. {
  193. new Model("StartLight"),
  194. new Model("StartLight2"),
  195. new Model("StartLight3"),
  196. new Model("Blockade"),
  197. new Model("Blockade2"),
  198. new Model("Hydrant"),
  199. new Model("Kaktus"),
  200. new Model("Kaktus2"),
  201. new Model("KaktusBenny"),
  202. new Model("KaktusSeg"),
  203. new Model("AlphaDeadTree"),
  204. new Model("AlphaPalm"),
  205. new Model("AlphaPalm2"),
  206. new Model("AlphaPalm3"),
  207. new Model("AlphaPalmSmall"),
  208. new Model("Laterne"),
  209. new Model("Laterne2Sides"),
  210. new Model("Trashcan"),
  211. new Model("Roadsign"),
  212. new Model("Roadsign2"),
  213. new Model("Goal"),
  214. new Model("Building"),
  215. new Model("Building2"),
  216. new Model("Building3"),
  217. new Model("Building4"),
  218. new Model("Building5"),
  219. new Model("OilPump"),
  220. new Model("OilTanks"),
  221. new Model("RoadColumnSegment"),
  222. new Model("Windmill"),
  223. new Model("Ruin"),
  224. new Model("RuinHouse"),
  225. new Model("SandCastle"),
  226. new Model("Banner"),
  227. new Model("Banner2"),
  228. new Model("Banner3"),
  229. new Model("Banner4"),
  230. new Model("Banner5"),
  231. new Model("Banner6"),
  232. new Model("Sign"),
  233. new Model("Sign2"),
  234. new Model("SignWarning"),
  235. new Model("SignCurveLeft"),
  236. new Model("SignCurveRight"),
  237. new Model("SharpRock"),
  238. new Model("SharpRock2"),
  239. new Model("Stone4"),
  240. new Model("Stone5"),
  241. new Model("AlphaTrain"),
  242. new Model("GuardRailHolder"),
  243. new Model("Hotel01"),
  244. new Model("Hotel02"),
  245. new Model("Casino01"),
  246. };
  247. /// <summary>
  248. /// Combos, which are used in the level file and for the automatic
  249. /// object generation below. Very useful. Each combo contains between
  250. /// 5 and 15 landscape model objects.
  251. /// </summary>
  252. TrackCombiModels[] combos = new TrackCombiModels[]
  253. {
  254. new TrackCombiModels("CombiPalms"),
  255. new TrackCombiModels("CombiPalms2"),
  256. new TrackCombiModels("CombiRuins"),
  257. new TrackCombiModels("CombiRuins2"),
  258. new TrackCombiModels("CombiStones"),
  259. new TrackCombiModels("CombiStones2"),
  260. new TrackCombiModels("CombiOilTanks"),
  261. new TrackCombiModels("CombiSandCastle"),
  262. new TrackCombiModels("CombiBuildings"),
  263. new TrackCombiModels("CombiHotels"),
  264. };
  265. /// <summary>
  266. /// Names for autogenerating stuff near the road to fill the level up.
  267. /// First 6 entries are used with more propability (fit better).
  268. /// </summary>
  269. internal string[] autoGenerationNames = new string[]
  270. {
  271. "CombiPalms",
  272. "CombiPalms2",
  273. "CombiRuins",
  274. "CombiRuins2",
  275. "CombiStones",
  276. "CombiStones2",
  277. //causes to much trouble and overlappings: "CombiOilTanks",
  278. "Kaktus",
  279. "Kaktus2",
  280. "KaktusBenny",
  281. "KaktusSeg",
  282. "AlphaDeadTree",
  283. "AlphaPalm",
  284. "AlphaPalm2",
  285. "AlphaPalm3",
  286. "AlphaPalmSmall",
  287. "Laterne2Sides",
  288. "Trashcan",
  289. "OilPump",
  290. "OilTanks",
  291. "RoadColumnSegment",
  292. "Windmill",
  293. "Ruin",
  294. "RuinHouse",
  295. "Sign",
  296. "Sign2",
  297. "SharpRock",
  298. "SharpRock2",
  299. "Stone4",
  300. "Stone5",
  301. "Casino01",
  302. };
  303. /// <summary>
  304. /// Add object to render
  305. /// </summary>
  306. /// <param name="modelName">Model name</param>
  307. /// <param name="renderMatrix">Render matrix</param>
  308. /// <param name="isNearTrack">Is near track</param>
  309. public void AddObjectToRender(string modelName, Matrix renderMatrix,
  310. bool isNearTrackForShadowGeneration)
  311. {
  312. // Fix wrong model names
  313. if (modelName == "OilWell")
  314. modelName = "OilPump";
  315. else if (modelName == "PalmSmall")
  316. modelName = "AlphaPalmSmall";
  317. else if (modelName == "AlphaPalm4")
  318. modelName = "AlphaPalmSmall";
  319. else if (modelName == "Palm")
  320. modelName = "AlphaPalm";
  321. else if (modelName == "Casino")
  322. modelName = "Casino01";
  323. else if (modelName == "Combi")
  324. modelName = "CombiPalms";
  325. // Always include windmills and buildings for shadow generation
  326. if (modelName.ToLower() == "windmill" ||
  327. modelName.ToLower().Contains("hotel") ||
  328. modelName.ToLower().Contains("building") ||
  329. modelName.ToLower().Contains("casino01"))
  330. isNearTrackForShadowGeneration = true;
  331. // Search for combos
  332. for (int num = 0; num < combos.Length; num++)
  333. {
  334. TrackCombiModels combi = combos[num];
  335. //slower: if (StringHelper.Compare(combi.Name, modelName))
  336. if (combi.Name == modelName)
  337. {
  338. // Add all combi objects (calls this method for each model)
  339. combi.AddAllModels(this, renderMatrix);
  340. // Thats it.
  341. return;
  342. }
  343. }
  344. Model foundModel = null;
  345. // Search model by name!
  346. for (int num = 0; num < landscapeModels.Length; num++)
  347. {
  348. Model model = landscapeModels[num];
  349. //slower: if (StringHelper.Compare(model.Name, modelName))
  350. if (model.Name == modelName)
  351. {
  352. foundModel = model;
  353. break;
  354. }
  355. }
  356. // Only add if we found the model
  357. if (foundModel != null)
  358. {
  359. // Fix z position to be always ABOVE the landscape
  360. Vector3 modelPos = renderMatrix.Translation;
  361. // Get landscape height here
  362. float landscapeHeight = GetMapHeight(modelPos.X, modelPos.Y);
  363. // And make sure we are always above it!
  364. if (modelPos.Z < landscapeHeight)
  365. {
  366. modelPos.Z = landscapeHeight;
  367. // Fix render matrix
  368. renderMatrix.Translation = modelPos;
  369. }
  370. // Check if another object is nearby, then skip this one!
  371. // Don't skip signs or banners!
  372. if (modelName.StartsWith("Banner") == false &&
  373. modelName.StartsWith("Sign") == false &&
  374. modelName.StartsWith("StartLight") == false)
  375. {
  376. for (int num = 0; num < landscapeObjects.Count; num++)
  377. if (Vector3.DistanceSquared(
  378. landscapeObjects[num].Position, modelPos) <
  379. foundModel.Size * foundModel.Size / 4)
  380. {
  381. // Don't add
  382. return;
  383. }
  384. }
  385. LandscapeObject newObject =
  386. new LandscapeObject(foundModel,
  387. // Scale all objects up a little (else world is not filled enough)
  388. Matrix.CreateScale(1.2f) *
  389. renderMatrix);
  390. // Add
  391. landscapeObjects.Add(newObject);
  392. // Add again to the nearTrackObjects list if near the track
  393. if (isNearTrackForShadowGeneration)
  394. nearTrackObjects.Add(newObject);
  395. if (modelName.StartsWith("StartLight"))
  396. startLightObject = newObject;
  397. }
  398. #if DEBUG
  399. else if (modelName.Contains("Track") == false)
  400. // Add warning in log file
  401. Log.Write("Landscape model "+modelName+" is not supported and "+
  402. "can't be added for rendering!");
  403. #endif
  404. }
  405. /// <summary>
  406. /// Add object to render
  407. /// </summary>
  408. /// <param name="modelName">Model name</param>
  409. /// <param name="rotation">Rotation</param>
  410. /// <param name="trackPos">Track position</param>
  411. /// <param name="trackRight">Track right</param>
  412. /// <param name="distance">Distance</param>
  413. public void AddObjectToRender(string modelName,
  414. float rotation, Vector3 trackPos, Vector3 trackRight,
  415. float distance)
  416. {
  417. // Find out size
  418. float objSize = 1;
  419. // Search for combos
  420. for (int num = 0; num < combos.Length; num++)
  421. {
  422. TrackCombiModels combi = combos[num];
  423. //slower: if (StringHelper.Compare(combi.Name, modelName))
  424. if (combi.Name == modelName)
  425. {
  426. objSize = combi.Size;
  427. break;
  428. }
  429. }
  430. // Search model by name!
  431. for (int num = 0; num < landscapeModels.Length; num++)
  432. {
  433. Model model = landscapeModels[num];
  434. //slower: if (StringHelper.Compare(model.Name, modelName))
  435. if (model.Name == modelName)
  436. {
  437. objSize = model.Size;
  438. break;
  439. }
  440. }
  441. // Make sure it is away from the road.
  442. if (distance > 0 &&
  443. distance - 10 < objSize)
  444. distance += objSize;
  445. if (distance < 0 &&
  446. distance + 10 > -objSize)
  447. distance -= objSize;
  448. AddObjectToRender(modelName,
  449. Matrix.CreateRotationZ(rotation) *
  450. Matrix.CreateTranslation(
  451. trackPos + trackRight * distance + new Vector3(0, 0, -100)), false);
  452. }
  453. /// <summary>
  454. /// Add object to render
  455. /// </summary>
  456. /// <param name="modelName">Model name</param>
  457. /// <param name="renderPos">Render position</param>
  458. public void AddObjectToRender(string modelName, Vector3 renderPos)
  459. {
  460. AddObjectToRender(modelName, Matrix.CreateTranslation(renderPos), false);
  461. }
  462. /// <summary>
  463. /// Currently loaded level
  464. /// </summary>
  465. RacingGameManager.Level level = RacingGameManager.Level.Beginner;
  466. /// <summary>
  467. /// Vertices
  468. /// </summary>
  469. TangentVertex[] vertices = new TangentVertex[GridWidth * GridHeight];
  470. /// <summary>
  471. /// Matrix
  472. /// </summary>
  473. Material mat = new Material(
  474. //new Color(62, 62, 62), // ambient
  475. //new Color(240, 240, 240), // diffuse
  476. //new Color(24, 24, 24), // specular
  477. new Color(88, 88, 88), // ambient (bright day)
  478. new Color(234, 234, 234), // diffuse (also bright)
  479. new Color(33, 33, 33), // specular (unused anyway)
  480. "Landscape",
  481. "LandscapeNormal",
  482. "",
  483. "LandscapeDetail");
  484. /// <summary>
  485. /// City material for displaying an extra material whereever the ground
  486. /// is flat. This makes the ground look much better at such locations,
  487. /// especially where the city is at.
  488. /// </summary>
  489. Material cityMat = new Material(
  490. new Color(32, 32, 32),
  491. new Color(200, 200, 200),
  492. new Color(128, 128, 128),
  493. "CityGround",
  494. "CityGroundNormal", "", "");
  495. /// <summary>
  496. /// City material
  497. /// </summary>
  498. /// <returns>Material</returns>
  499. public Material CityMaterial
  500. {
  501. get
  502. {
  503. return cityMat;
  504. }
  505. }
  506. /// <summary>
  507. /// City planes we render additionally to the landscape.
  508. /// Each city plane is just 2 triangles and the cityMat material, very
  509. /// fast and easy stuff.
  510. /// </summary>
  511. PlaneRenderer cityPlane = null;
  512. /// <summary>
  513. /// Vertex buffer for our landscape
  514. /// </summary>
  515. VertexBuffer vertexBuffer;
  516. /// <summary>
  517. /// Index buffer for our landscape
  518. /// </summary>
  519. IndexBuffer indexBuffer;
  520. /// <summary>
  521. /// Map heights
  522. /// </summary>
  523. float[,] mapHeights = null;
  524. /// <summary>
  525. /// Track for our landscape, can be TrackBeginner, TrackAdvanced and
  526. /// TrackExpert, which will be selected in the menu.
  527. /// </summary>
  528. Track track = null;
  529. /// <summary>
  530. /// Best replay for the best lap time showing the player driving.
  531. /// And a new replay which is recorded in case we archive a better
  532. /// time this time when we drive :)
  533. /// </summary>
  534. Replay bestReplay = null,
  535. newReplay = null;
  536. /// <summary>
  537. /// Compare checkpoint time to the bestReplay times.
  538. /// </summary>
  539. /// <param name="checkpointNum">Checkpoint num</param>
  540. /// <returns>Time in milliseconds we improved</returns>
  541. public int CompareCheckpointTime(int checkpointNum)
  542. {
  543. // Invalid data?
  544. if (bestReplay == null ||
  545. checkpointNum >= bestReplay.CheckpointTimes.Count)
  546. // Then we can't return anything
  547. return 0;
  548. // Else just return difference
  549. float differenceMs =
  550. RacingGameManager.Player.GameTimeMilliseconds -
  551. bestReplay.CheckpointTimes[checkpointNum] * 1000.0f;
  552. return (int)differenceMs;
  553. }
  554. /// <summary>
  555. /// Start new lap, checks if the newReplay is good and
  556. /// can be stored as best replay :)
  557. /// </summary>
  558. public void StartNewLap()
  559. {
  560. float thisLapTime =
  561. RacingGameManager.Player.GameTimeMilliseconds / 1000.0f;
  562. // Upload new highscore (as we currently are in game,
  563. // no bonus or anything will be added, this score is low!)
  564. Highscores.SubmitHighscore((int)level,
  565. (int)RacingGameManager.Player.GameTimeMilliseconds);
  566. RacingGameManager.Player.AddLapTime(thisLapTime);
  567. if (thisLapTime < bestReplay.LapTime)
  568. {
  569. // Add final checkpoint
  570. RacingGameManager.Landscape.NewReplay.CheckpointTimes.Add(
  571. thisLapTime);
  572. // Record lap time
  573. newReplay.LapTime = thisLapTime;
  574. // Save this replay to load it everytime in the future
  575. // Do this on a worker thread to prevent the game from skipping frames
  576. ThreadPool.QueueUserWorkItem(new WaitCallback(SaveReplay),
  577. (Replay)newReplay.Clone());
  578. // Set it as the current best replay
  579. bestReplay = newReplay;
  580. }
  581. // And start a new replay for this round
  582. newReplay = new Replay((int)level, true, track);
  583. }
  584. /// <summary>
  585. /// Callback used for saving a replay from a worker thread
  586. /// </summary>
  587. /// <param name="replay">Replay to be saved</param>
  588. private void SaveReplay(object replay)
  589. {
  590. ((Replay)replay).Save();
  591. }
  592. /// <summary>
  593. /// New replay
  594. /// </summary>
  595. public Replay NewReplay
  596. {
  597. get
  598. {
  599. return newReplay;
  600. }
  601. }
  602. /// <summary>
  603. /// Remember a list of brack tracks, which will be generated if we brake.
  604. /// </summary>
  605. List<TangentVertex> brakeTracksVertices = new List<TangentVertex>();
  606. /// <summary>
  607. /// Little helper to avoid creating a new array each frame for rendering
  608. /// </summary>
  609. TangentVertex[] brakeTracksVerticesArray = null;
  610. /// <summary>
  611. /// Current track name
  612. /// </summary>
  613. /// <returns>String</returns>
  614. public string CurrentTrackName
  615. {
  616. get
  617. {
  618. return level.ToString();
  619. }
  620. }
  621. /// <summary>
  622. /// Track length
  623. /// </summary>
  624. /// <returns>Float</returns>
  625. public float TrackLength
  626. {
  627. get
  628. {
  629. return track.Length;
  630. }
  631. }
  632. /// <summary>
  633. /// Remember checkpoint segment positions for easier checkpoint checking.
  634. /// </summary>
  635. public List<int> CheckpointSegmentPositions
  636. {
  637. get
  638. {
  639. return track.CheckpointSegmentPositions;
  640. }
  641. }
  642. /// <summary>
  643. /// Best replay for the best lap time showing the player driving.
  644. /// </summary>
  645. public Replay BestReplay
  646. {
  647. get
  648. {
  649. return bestReplay;
  650. }
  651. }
  652. /// <summary>
  653. /// Get map height at a specific point, int based and not as percise as
  654. /// the float version, which interpolates between our grid points.
  655. /// </summary>
  656. /// <param name="x">X</param>
  657. /// <param name="y">Y</param>
  658. /// <returns>Float</returns>
  659. public float GetMapHeight(int x, int y)
  660. {
  661. if (x < 0)
  662. x = 0;
  663. if (y < 0)
  664. y = 0;
  665. if (x >= GridWidth)
  666. x = GridWidth - 1;
  667. if (y >= GridHeight)
  668. y = GridHeight - 1;
  669. return mapHeights[x, y];
  670. }
  671. /// <summary>
  672. /// This functions keeps sure we keep in 0-max range,
  673. /// simple modulate (%) will do this only correctly for positiv values!
  674. /// </summary>
  675. private static int ModulateValueInRange(float val, int max)
  676. {
  677. if (val < 0.0f)
  678. return (max - 1) - ((int)(-val) % max);
  679. else
  680. return (int)val % max;
  681. }
  682. /// <summary>
  683. /// Get map height at a specific point
  684. /// </summary>
  685. /// <param name="x">X</param>
  686. /// <param name="y">Y</param>
  687. /// <returns>Float</returns>
  688. public float GetMapHeight(float x, float y)
  689. {
  690. // Rescale to our current dimensions
  691. x /= MapWidthFactor;
  692. y /= MapHeightFactor;
  693. // Interpolate the current position
  694. int
  695. // size-1 is because we need +1 for interpolating
  696. ix = ModulateValueInRange(x, GridWidth - 1),
  697. iy = ModulateValueInRange(y, GridHeight - 1);
  698. // Get the position ON the current tile (0.0-1.0)!!!
  699. float
  700. fX = x - ((float)((int)x)),
  701. fY = y - ((float)((int)y));
  702. int ix2 = (ix + 1) % (GridWidth - 1);
  703. int iy2 = (iy + 1) % (GridHeight - 1);
  704. if (fX + fY < 1) // opt. version
  705. {
  706. // we are on triangle 1 !!
  707. // ------- (f_tile_width-mx)/f_tile_width
  708. // 0__v___1
  709. // | /
  710. // | /
  711. // |---/--- (f_tile_height-my)/f_tile_height
  712. // | /
  713. // | /
  714. // 3/
  715. return
  716. mapHeights[ix, iy] + // 0
  717. fX * (mapHeights[ix2, iy] - mapHeights[ix, iy]) + // 1
  718. fY * (mapHeights[ix, iy2] - mapHeights[ix, iy]); // 3
  719. }
  720. // we are on triangle 1 !!
  721. // calc height (as above, but a bit more difficult for triangle 1)
  722. // 1
  723. // /|
  724. // / |
  725. // / | my/f_tile_height (fX)
  726. // / |
  727. // / |
  728. // 3_____2
  729. // ^--- mx/f_tile_width (fY)
  730. return
  731. mapHeights[ix2, iy2] + // 2
  732. (1.0f - fY) * (mapHeights[ix2, iy] - mapHeights[ix2, iy2]) + // 1
  733. (1.0f - fX) * (mapHeights[ix, iy2] - mapHeights[ix2, iy2]); // 3
  734. }
  735. /// <summary>
  736. /// Create landscape.
  737. /// This constructor should only be called
  738. /// from the RacingGame main class!
  739. /// </summary>
  740. /// <param name="setLevel">Level we want to load</param>
  741. internal Landscape(RacingGameManager.Level setLevel)
  742. {
  743. byte[] heights = new byte[GridWidth * GridHeight];
  744. using (Stream file = TitleContainer.OpenStream(
  745. Path.Combine("Content", "LandscapeHeights.data")))
  746. {
  747. file.ReadExactly(heights, 0, GridWidth * GridHeight);
  748. }
  749. mapHeights = new float[GridWidth, GridHeight];
  750. // Build our tangent vertices
  751. for (int x = 0; x < GridWidth; x++)
  752. for (int y = 0; y < GridHeight; y++)
  753. {
  754. // Step 1: Calculate position
  755. int index = x + y * GridWidth;
  756. Vector3 pos = CalcLandscapePos(x, y, heights);//texData);
  757. mapHeights[x, y] = pos.Z;
  758. vertices[index].pos = pos;
  759. //if (x == 0)
  760. // Log.Write("vertices " + y + ": " + pos);
  761. // Step 2: Calculate all edge vectors (for normals and tangents)
  762. // This involves quite complicated optimizations and mathematics,
  763. // hard to explain with just a comment. Read my book :D
  764. Vector3 edge1 = pos - CalcLandscapePos(x, y + 1, heights);
  765. Vector3 edge2 = pos - CalcLandscapePos(x + 1, y, heights);
  766. Vector3 edge3 = pos - CalcLandscapePos(x - 1, y + 1, heights);
  767. Vector3 edge4 = pos - CalcLandscapePos(x + 1, y + 1, heights);
  768. Vector3 edge5 = pos - CalcLandscapePos(x - 1, y - 1, heights);
  769. // Step 3: Calculate normal based on the edges (interpolate
  770. // from 3 cross products we build from our edges).
  771. vertices[index].normal = Vector3.Normalize(
  772. Vector3.Cross(edge2, edge1) +
  773. Vector3.Cross(edge4, edge3) +
  774. Vector3.Cross(edge3, edge5));
  775. // Step 4: Set tangent data, just use edge1
  776. vertices[index].tangent = Vector3.Normalize(edge1);
  777. // Step 5: Set texture coordinates, use full 0.0f to 1.0f range!
  778. vertices[index].uv = new Vector2(
  779. //x / (float)(GridWidth - 1),
  780. //y / (float)(GridHeight - 1));
  781. y / (float)(GridHeight - 1),
  782. x / (float)(GridWidth - 1));
  783. }
  784. // Smooth all normals, first copy them over, then smooth everything
  785. Vector3[,] normalsForSmoothing = new Vector3[GridWidth, GridHeight];
  786. for (int x = 0; x < GridWidth; x++)
  787. for (int y = 0; y < GridHeight; y++)
  788. {
  789. int index = x + y * GridWidth;
  790. normalsForSmoothing[x, y] = vertices[index].normal;
  791. }
  792. // Time to smooth to normals we just saved
  793. for (int x = 1; x < GridWidth - 1; x++)
  794. for (int y = 1; y < GridHeight - 1; y++)
  795. {
  796. int index = x + y * GridWidth;
  797. // Smooth 3x3 normals, but still use old normal to 40% (5 of 13)
  798. Vector3 normal = vertices[index].normal * 4;
  799. for (int xAdd = -1; xAdd <= 1; xAdd++)
  800. for (int yAdd = -1; yAdd <= 1; yAdd++)
  801. normal += normalsForSmoothing[x + xAdd, y + yAdd];
  802. vertices[index].normal = Vector3.Normalize(normal);
  803. // Also recalculate tangent to let it stay 90 degrees on the normal
  804. Vector3 helperVector = Vector3.Cross(
  805. vertices[index].normal,
  806. vertices[index].tangent);
  807. vertices[index].tangent = Vector3.Cross(
  808. helperVector,
  809. vertices[index].normal);
  810. }
  811. // Set vertex buffer
  812. // fix
  813. //vertexBuffer = new VertexBuffer(
  814. // BaseGame.Device,
  815. // typeof(TangentVertex),
  816. // vertices.Length,
  817. // ResourceUsage.WriteOnly,
  818. // ResourceManagementMode.Automatic);
  819. //vertexBuffer.SetData(vertices);
  820. vertexBuffer = new VertexBuffer(
  821. BaseGame.Device,
  822. typeof(TangentVertex),
  823. vertices.Length,
  824. BufferUsage.WriteOnly);
  825. vertexBuffer.SetData(vertices);
  826. // Calc index buffer (Note: have to use uint, ushort is not sufficiant
  827. // in our case because we have MANY vertices ^^)
  828. uint[] indices = new uint[(GridWidth - 1) * (GridHeight - 1) * 6];
  829. int currentIndex = 0;
  830. for (int x = 0; x < GridWidth - 1; x++)
  831. for (int y = 0; y < GridHeight - 1; y++)
  832. {
  833. // Set landscape data (Note: Right handed)
  834. indices[currentIndex + 0] = (uint)(x * GridHeight + y);
  835. indices[currentIndex + 2] =
  836. (uint)((x + 1) * GridHeight + (y + 1));
  837. indices[currentIndex + 1] = (uint)((x + 1) * GridHeight + y);
  838. indices[currentIndex + 3] =
  839. (uint)((x + 1) * GridHeight + (y + 1));
  840. indices[currentIndex + 5] = (uint)(x * GridHeight + y);
  841. indices[currentIndex + 4] = (uint)(x * GridHeight + (y + 1));
  842. // Add indices
  843. currentIndex += 6;
  844. }
  845. // fix
  846. //indexBuffer = new IndexBuffer(
  847. // BaseGame.Device,
  848. // typeof(uint),
  849. // (GridWidth - 1) * (GridHeight - 1) * 6,
  850. // ResourceUsage.WriteOnly,
  851. // ResourceManagementMode.Automatic);
  852. indexBuffer = new IndexBuffer(
  853. BaseGame.Device,
  854. typeof(uint),
  855. (GridWidth - 1) * (GridHeight - 1) * 6,
  856. BufferUsage.WriteOnly);
  857. indexBuffer.SetData(indices);
  858. // Load track based on the level selection and set car pos with
  859. // help of the ReloadLevel method.
  860. ReloadLevel(setLevel);
  861. // Just set one giant plane for the whole city!
  862. foreach (LandscapeObject obj in landscapeObjects)
  863. if (obj.IsBigBuilding)
  864. {
  865. cityPlane = new PlaneRenderer(
  866. obj.Position,
  867. new Plane(new Vector3(0, 0, 1), 0.1f),
  868. cityMat, Math.Min(obj.Position.X, obj.Position.Y));
  869. break;
  870. }
  871. }
  872. /// <summary>
  873. /// Reload level
  874. /// </summary>
  875. /// <param name="setLevel">Level</param>
  876. internal void ReloadLevel(RacingGameManager.Level setLevel)
  877. {
  878. level = setLevel;
  879. // Load track based on the level selection, do this after
  880. // we got all the height data because the track might be adjusted.
  881. if (track == null)
  882. track = new Track("Track" + level.ToString(), this);
  883. else
  884. track.Reload("Track" + level.ToString(), this);
  885. // Load replay for this track to show best player
  886. bestReplay = new Replay((int)level, false, track);
  887. newReplay = new Replay((int)level, true, track);
  888. // Kill brake tracks
  889. brakeTracksVertices.Clear();
  890. brakeTracksVerticesArray = null;
  891. // Set car at start pos
  892. SetCarToStartPosition();
  893. // Begin game with red start light
  894. startLightObject.ChangeModel(landscapeModels[0]);
  895. }
  896. /// <summary>
  897. /// Calc landscape position
  898. /// </summary>
  899. /// <param name="x">X</param>
  900. /// <param name="y">Y</param>
  901. /// <returns>Vector 3</returns>
  902. private static Vector3 CalcLandscapePos(int x, int y, byte[] heights)
  903. {
  904. // Make sure we stay on the valid map data
  905. int mapX = x < 0 ? 0 : x >= GridWidth ? GridWidth - 1 : x;
  906. int mapY = y < 0 ? 0 : y >= GridHeight ? GridHeight - 1 : y;
  907. // Get height
  908. float heightPercent = heights[mapX + mapY * GridWidth] / 255.0f;
  909. // Build landscape position vector
  910. return new Vector3(
  911. x * MapWidthFactor,
  912. y * MapHeightFactor,
  913. heightPercent * MapZScale);
  914. }
  915. /// <summary>
  916. /// Dispose
  917. /// </summary>
  918. public void Dispose()
  919. {
  920. Dispose(true);
  921. GC.SuppressFinalize(this);
  922. }
  923. /// <summary>
  924. /// Dispose
  925. /// </summary>
  926. /// <param name="disposing">Disposing</param>
  927. protected virtual void Dispose(bool disposing)
  928. {
  929. if (disposing)
  930. {
  931. for (int num = 0; num < landscapeModels.Length; num++)
  932. landscapeModels[num].Dispose();
  933. mat.Dispose();
  934. cityMat.Dispose();
  935. vertexBuffer.Dispose();
  936. indexBuffer.Dispose();
  937. track.Dispose();
  938. }
  939. }
  940. /// <summary>
  941. /// Set car to start pos
  942. /// </summary>
  943. public void SetCarToStartPosition()
  944. {
  945. RacingGameManager.Player.SetCarPosition(
  946. track.StartPosition, track.StartDirection, track.StartUpVector);
  947. // Camera is set in zooming in method of the Player class.
  948. }
  949. /// <summary>
  950. /// Render landscape (just at the origin)
  951. /// </summary>
  952. public void Render()
  953. {
  954. // Make sure z buffer is on
  955. BaseGame.Device.DepthStencilState = DepthStencilState.Default;
  956. BaseGame.WorldMatrix = Matrix.Identity;
  957. // Render landscape (pretty easy with all the data we got here)
  958. ShaderEffect.landscapeNormalMapping.Render(
  959. mat, "DiffuseWithDetail20",
  960. new BaseGame.RenderHandler(RenderLandscapeVertices));
  961. cityPlane.Render();
  962. // Render track
  963. track.Render();
  964. // Render all landscape objects
  965. for (int num = 0; num < landscapeObjects.Count; num++)
  966. {
  967. landscapeObjects[num].Render();
  968. }
  969. // Render all brake tracks
  970. RenderBrakeTracks();
  971. }
  972. /// <summary>
  973. /// Render landscape vertices
  974. /// </summary>
  975. private void RenderLandscapeVertices()
  976. {
  977. BaseGame.Device.SetVertexBuffer(vertexBuffer);
  978. BaseGame.Device.Indices = indexBuffer;
  979. BaseGame.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
  980. 0, 0, (GridWidth - 1) * (GridHeight - 1) * 2);
  981. }
  982. /// <summary>
  983. /// Generate shadow
  984. /// </summary>
  985. public void GenerateShadow()
  986. {
  987. // Don't generate shadow for the landscape, it only receives shadow!
  988. // Just generate shadows for the road.
  989. track.GenerateShadow();
  990. // Render shadow all landscape objects that near our road
  991. for (int num = 0; num < nearTrackObjects.Count; num++)
  992. {
  993. nearTrackObjects[num].GenerateShadows();
  994. }
  995. }
  996. /// <summary>
  997. /// Use shadow
  998. /// </summary>
  999. public void UseShadow()
  1000. {
  1001. // Receive shadow on the landscape, just render it out.
  1002. ShaderEffect.shadowMapping.UpdateCalcShadowWorldMatrix(Matrix.Identity);
  1003. // Render shadows for palms and other objects near the road.
  1004. RenderLandscapeVertices();
  1005. // Also receive shadows for all landscape objects that near our road.
  1006. // This is not really required (still looks good without it), but
  1007. // sometimes objects may have lookthrough-shadows or windmills
  1008. // are usually a problem. This fixes this or makes it at least less
  1009. // noticable.
  1010. if (BaseGame.HighDetail)
  1011. {
  1012. for (int num = 0; num < nearTrackObjects.Count; num++)
  1013. // Don't receive shadows on signs, looks weird.
  1014. if (nearTrackObjects[num].IsBanner == false)
  1015. {
  1016. nearTrackObjects[num].UseShadows();
  1017. }
  1018. }
  1019. // And the track receives shadow too
  1020. track.UseShadow();
  1021. }
  1022. /// <summary>
  1023. /// Get track position matrix, used for the game background and unit tests.
  1024. /// </summary>
  1025. /// <param name="carTrackPos">Car track position</param>
  1026. /// <param name="roadWidth">Road width</param>
  1027. /// <param name="nextRoadWidth">Next road width</param>
  1028. /// <returns>Matrix</returns>
  1029. public Matrix GetTrackPositionMatrix(float carTrackPos,
  1030. out float roadWidth, out float nextRoadWidth)
  1031. {
  1032. return track.GetTrackPositionMatrix(carTrackPos,
  1033. out roadWidth, out nextRoadWidth);
  1034. }
  1035. /// <summary>
  1036. /// Get track position matrix
  1037. /// </summary>
  1038. /// <param name="trackSegmentNum">Track segment number</param>
  1039. /// <param name="trackSegmentPercent">Track segment percent</param>
  1040. /// <param name="roadWidth">Road width</param>
  1041. /// <param name="nextRoadWidth">Next road width</param>
  1042. /// <returns>Matrix</returns>
  1043. public Matrix GetTrackPositionMatrix(
  1044. int trackSegmentNum, float trackSegmentPercent,
  1045. out float roadWidth, out float nextRoadWidth)
  1046. {
  1047. return track.GetTrackPositionMatrix(
  1048. trackSegmentNum, trackSegmentPercent,
  1049. out roadWidth, out nextRoadWidth);
  1050. }
  1051. /// <summary>
  1052. /// Update car track position
  1053. /// </summary>
  1054. /// <param name="carPos">Car position</param>
  1055. /// <param name="trackSegmentNumber">Track segment number</param>
  1056. /// <param name="trackPositionPercent">Track position percent</param>
  1057. public void UpdateCarTrackPosition(
  1058. Vector3 carPos,
  1059. ref int trackSegmentNumber, ref float trackPositionPercent)
  1060. {
  1061. track.UpdateCarTrackPosition(carPos,
  1062. ref trackSegmentNumber, ref trackPositionPercent);
  1063. }
  1064. /// <summary>
  1065. /// Helper to skip track generation if it is near the last generated pos.
  1066. /// </summary>
  1067. Vector3 lastAddedTrackPos = new Vector3(-1000, -1000, -1000);
  1068. /// <summary>
  1069. /// Render a maximum of 140 brake tracks.
  1070. /// </summary>
  1071. const int MaxBrakeTrackVertices = 6 * 140;
  1072. /// <summary>
  1073. /// The amount to raise the break tracks decal off the road surface
  1074. /// </summary>
  1075. const float RaiseBreakTracksAmount = 0.2f;
  1076. /// <summary>
  1077. /// Add brake track
  1078. /// </summary>
  1079. /// <param name="position">Position</param>
  1080. /// <param name="dir">Dir vector</param>
  1081. /// <param name="right">Right vector</param>
  1082. public void AddBrakeTrack(CarPhysics car)
  1083. {
  1084. Vector3 position = car.CarPosition + car.CarDirection * 1.25f;
  1085. // Just skip if we setting to a similar location again.
  1086. // This check is much faster and accurate for tracks on top of each
  1087. // other than the foreach loop below, which is only useful to
  1088. // put multiple tracks correctly behind each other!
  1089. if (Vector3.DistanceSquared(position, lastAddedTrackPos) < 0.024f ||
  1090. // Limit number of tracks to keep rendering fast.
  1091. brakeTracksVertices.Count > MaxBrakeTrackVertices)
  1092. return;
  1093. lastAddedTrackPos = position;
  1094. const float width = 2.4f; // car is 2.6m width, we use 2.4m for tires
  1095. const float length = 4.5f; // Length of break tracks
  1096. float maxDist =
  1097. (float)Math.Sqrt(width * width + length * length) / 2 - 0.35f;
  1098. // Check if there is any track already set here or nearby?
  1099. for (int num = 0; num < brakeTracksVertices.Count; num++)
  1100. if (Vector3.DistanceSquared(brakeTracksVertices[num].pos, position) <
  1101. maxDist * maxDist)
  1102. // Then skip this brake track, don't put that much stuff on
  1103. // top of each other.
  1104. return;
  1105. // Move position a little bit up (above the road)
  1106. position += Vector3.Normalize(car.CarUpVector) * RaiseBreakTracksAmount;
  1107. // Just add 6 new vertices to render (2 triangles)
  1108. TangentVertex[] newVertices = new TangentVertex[]
  1109. {
  1110. // First triangle
  1111. new TangentVertex(
  1112. position -car.CarRight*width/2 -car.CarDirection*length/2, 0, 0,
  1113. car.CarUpVector, car.CarRight),
  1114. new TangentVertex(
  1115. position -car.CarRight*width/2 +car.CarDirection*length/2, 0, 5,
  1116. car.CarUpVector, car.CarRight),
  1117. new TangentVertex(
  1118. position +car.CarRight*width/2 +car.CarDirection*length/2, 1, 5,
  1119. car.CarUpVector, car.CarRight),
  1120. // Second triangle
  1121. new TangentVertex(
  1122. position -car.CarRight*width/2 -car.CarDirection*length/2, 0, 0,
  1123. car.CarUpVector, car.CarRight),
  1124. new TangentVertex(
  1125. position +car.CarRight*width/2 +car.CarDirection*length/2, 1, 5,
  1126. car.CarUpVector, car.CarRight),
  1127. new TangentVertex(
  1128. position +car.CarRight*width/2 -car.CarDirection*length/2, 1, 0,
  1129. car.CarUpVector, car.CarRight),
  1130. };
  1131. brakeTracksVertices.AddRange(newVertices);
  1132. brakeTracksVerticesArray = brakeTracksVertices.ToArray();
  1133. }
  1134. /// <summary>
  1135. /// Render brake tracks
  1136. /// </summary>
  1137. public void RenderBrakeTracks()
  1138. {
  1139. // Nothing to render?
  1140. if (brakeTracksVerticesArray == null)
  1141. return;
  1142. BaseGame.SetAlphaBlendingEnabled(true);
  1143. BaseGame.WorldMatrix = Matrix.Identity;
  1144. ShaderEffect.lighting.Render(
  1145. RacingGameManager.BrakeTrackMaterial,
  1146. "Diffuse20",
  1147. delegate
  1148. {
  1149. // Draw the vertices
  1150. BaseGame.Device.DrawUserPrimitives(
  1151. PrimitiveType.TriangleList,
  1152. brakeTracksVerticesArray, 0, brakeTracksVerticesArray.Length / 3);
  1153. });
  1154. }
  1155. }
  1156. }