Game.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // Game.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. using System.Collections.Generic;
  12. using Microsoft.Xna.Framework;
  13. using Microsoft.Xna.Framework.Content;
  14. using Microsoft.Xna.Framework.Graphics;
  15. using Microsoft.Xna.Framework.Input;
  16. #endregion
  17. namespace TrianglePicking
  18. {
  19. /// <summary>
  20. /// Sample showing how to implement per-triangle picking. This uses a custom
  21. /// content pipeline processor to attach a list of vertex position data to each
  22. /// model as part of the build process, and then implements a ray-to-triangle
  23. /// intersection method to collide against this vertex data.
  24. /// </summary>
  25. public class TrianglePickingGame : Microsoft.Xna.Framework.Game
  26. {
  27. #region Constants
  28. // ModelFilenames is the list of models that we will be putting on top of the
  29. // table. These strings will be used as arguments to content.Load<Model> and
  30. // will be drawn when the cursor is over an object.
  31. static readonly string[] ModelFilenames =
  32. {
  33. "Sphere",
  34. "Cats",
  35. "P2Wedge",
  36. "Cylinder",
  37. };
  38. // the following constants control the speed at which the camera moves
  39. // how fast does the camera move up, down, left, and right?
  40. const float CameraRotateSpeed = .1f;
  41. // how fast does the camera zoom in and out?
  42. const float CameraZoomSpeed = .01f;
  43. // the camera can't be further away than this distance
  44. const float CameraMaxDistance = 10.0f;
  45. // and it can't be closer than this
  46. const float CameraMinDistance = 1.2f;
  47. // the following constants control how the camera's default position
  48. const float CameraDefaultArc = -30.0f;
  49. const float CameraDefaultRotation = 225;
  50. const float CameraDefaultDistance = 3.5f;
  51. #endregion
  52. #region Fields
  53. GraphicsDeviceManager graphics;
  54. // the current input states. These are updated in the HandleInput function,
  55. // and used primarily in the UpdateCamera function.
  56. KeyboardState currentKeyboardState;
  57. GamePadState currentGamePadState;
  58. // a SpriteBatch and SpriteFont, which we will use to draw the objects' names
  59. // when they are selected.
  60. SpriteBatch spriteBatch;
  61. SpriteFont spriteFont;
  62. // The cursor is used to tell what the user's pointer/mouse is over. The cursor
  63. // is moved with the left thumbstick. On windows, the mouse can be used as well.
  64. Cursor cursor;
  65. // the table that all of the objects are drawn on, and table model's
  66. // absoluteBoneTransforms. Since the table is not animated, these can be
  67. // calculated once and saved.
  68. Model table;
  69. Matrix[] tableAbsoluteBoneTransforms;
  70. // these are the models that we will draw on top of the table. we'll store them
  71. // and their bone transforms in arrays. Again, since these models aren't
  72. // animated, we can calculate their bone transforms once and save the result.
  73. Model[] models = new Model[ModelFilenames.Length];
  74. Matrix[][] modelAbsoluteBoneTransforms = new Matrix[ModelFilenames.Length][];
  75. // each model will need one more matrix: a world transform. This matrix will be
  76. // used to place each model at a different location in the world.
  77. Matrix[] modelWorldTransforms = new Matrix[ModelFilenames.Length];
  78. // The next set of variables are used to control the camera used in the sample.
  79. // It is an arc ball camera, so it can rotate in a sphere around the target, and
  80. // zoom in and out.
  81. float cameraArc = CameraDefaultArc;
  82. float cameraRotation = CameraDefaultRotation;
  83. float cameraDistance = CameraDefaultDistance;
  84. Matrix viewMatrix;
  85. Matrix projectionMatrix;
  86. // To keep things efficient, the picking works by first applying a bounding
  87. // sphere test, and then only bothering to test each individual triangle
  88. // if the ray intersects the bounding sphere. This allows us to trivially
  89. // reject many models without even needing to bother looking at their triangle
  90. // data. This field keeps track of which models passed the bounding sphere
  91. // test, so you can see the difference between this approximation and the more
  92. // accurate triangle picking.
  93. List<string> insideBoundingSpheres = new List<string>();
  94. // Store the name of the model underneath the cursor (or null if there is none).
  95. string pickedModelName;
  96. // Vertex array that stores exactly which triangle was picked.
  97. VertexPositionColor[] pickedTriangle =
  98. {
  99. new VertexPositionColor(Vector3.Zero, Color.Magenta),
  100. new VertexPositionColor(Vector3.Zero, Color.Magenta),
  101. new VertexPositionColor(Vector3.Zero, Color.Magenta),
  102. };
  103. // Effect and vertex declaration for drawing the picked triangle.
  104. BasicEffect lineEffect;
  105. // Custom rasterizer state for drawing in wireframe.
  106. static RasterizerState WireFrame = new RasterizerState
  107. {
  108. FillMode = FillMode.WireFrame,
  109. CullMode = CullMode.None
  110. };
  111. #endregion
  112. #region Initialization
  113. public TrianglePickingGame()
  114. {
  115. graphics = new GraphicsDeviceManager(this);
  116. Content.RootDirectory = "Content";
  117. // Set up the world transforms that each model will use. They'll be
  118. // positioned in a line along the x axis.
  119. for (int i = 0; i < modelWorldTransforms.Length; i++)
  120. {
  121. float x = i - modelWorldTransforms.Length / 2;
  122. modelWorldTransforms[i] =
  123. Matrix.CreateTranslation(new Vector3(x, 0, 0));
  124. }
  125. cursor = new Cursor(this, Content);
  126. Components.Add(cursor);
  127. }
  128. protected override void Initialize()
  129. {
  130. // now that the GraphicsDevice has been created, we can create the projection matrix.
  131. projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
  132. MathHelper.ToRadians(45.0f), GraphicsDevice.Viewport.AspectRatio, .01f, 1000);
  133. base.Initialize();
  134. }
  135. /// <summary>
  136. /// Load your graphics content.
  137. /// </summary>
  138. protected override void LoadContent()
  139. {
  140. // load all of the models that will appear on the table:
  141. for (int i = 0; i < ModelFilenames.Length; i++)
  142. {
  143. // load the actual model, using ModelFilenames to determine what
  144. // file to load.
  145. models[i] = Content.Load<Model>(ModelFilenames[i]);
  146. // create an array of matrices to hold the absolute bone transforms,
  147. // calculate them, and copy them in.
  148. modelAbsoluteBoneTransforms[i] = new Matrix[models[i].Bones.Count];
  149. models[i].CopyAbsoluteBoneTransformsTo(
  150. modelAbsoluteBoneTransforms[i]);
  151. }
  152. // now that we've loaded in the models that will sit on the table, go
  153. // through the same procedure for the table itself.
  154. table = Content.Load<Model>("Table");
  155. tableAbsoluteBoneTransforms = new Matrix[table.Bones.Count];
  156. table.CopyAbsoluteBoneTransformsTo(tableAbsoluteBoneTransforms);
  157. // create a spritebatch and load the font, which we'll use to draw the
  158. // models' names.
  159. spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
  160. spriteFont = Content.Load<SpriteFont>("hudFont");
  161. // create the effect and vertex declaration for drawing the
  162. // picked triangle.
  163. lineEffect = new BasicEffect(graphics.GraphicsDevice);
  164. lineEffect.VertexColorEnabled = true;
  165. }
  166. #endregion
  167. #region Update and Draw
  168. /// <summary>
  169. /// Allows the game to run logic.
  170. /// </summary>
  171. protected override void Update(GameTime gameTime)
  172. {
  173. HandleInput();
  174. UpdateCamera(gameTime);
  175. UpdatePicking();
  176. base.Update(gameTime);
  177. }
  178. /// <summary>
  179. /// Runs a per-triangle picking algorithm over all the models in the scene,
  180. /// storing which triangle is currently under the cursor.
  181. /// </summary>
  182. void UpdatePicking()
  183. {
  184. // Look up a collision ray based on the current cursor position. See the
  185. // Picking Sample documentation for a detailed explanation of this.
  186. Ray cursorRay = cursor.CalculateCursorRay(projectionMatrix, viewMatrix);
  187. // Clear the previous picking results.
  188. insideBoundingSpheres.Clear();
  189. pickedModelName = null;
  190. // Keep track of the closest object we have seen so far, so we can
  191. // choose the closest one if there are several models under the cursor.
  192. float closestIntersection = float.MaxValue;
  193. // Loop over all our models.
  194. for (int i = 0; i < models.Length; i++)
  195. {
  196. bool insideBoundingSphere;
  197. Vector3 vertex1, vertex2, vertex3;
  198. // Perform the ray to model intersection test.
  199. float? intersection = RayIntersectsModel(cursorRay, models[i],
  200. modelWorldTransforms[i],
  201. out insideBoundingSphere,
  202. out vertex1, out vertex2,
  203. out vertex3);
  204. // If this model passed the initial bounding sphere test, remember
  205. // that so we can display it at the top of the screen.
  206. if (insideBoundingSphere)
  207. insideBoundingSpheres.Add(ModelFilenames[i]);
  208. // Do we have a per-triangle intersection with this model?
  209. if (intersection != null)
  210. {
  211. // If so, is it closer than any other model we might have
  212. // previously intersected?
  213. if (intersection < closestIntersection)
  214. {
  215. // Store information about this model.
  216. closestIntersection = intersection.Value;
  217. pickedModelName = ModelFilenames[i];
  218. // Store vertex positions so we can display the picked triangle.
  219. pickedTriangle[0].Position = vertex1;
  220. pickedTriangle[1].Position = vertex2;
  221. pickedTriangle[2].Position = vertex3;
  222. }
  223. }
  224. }
  225. }
  226. /// <summary>
  227. /// Checks whether a ray intersects a model. This method needs to access
  228. /// the model vertex data, so the model must have been built using the
  229. /// custom TrianglePickingProcessor provided as part of this sample.
  230. /// Returns the distance along the ray to the point of intersection, or null
  231. /// if there is no intersection.
  232. /// </summary>
  233. static float? RayIntersectsModel(Ray ray, Model model, Matrix modelTransform,
  234. out bool insideBoundingSphere,
  235. out Vector3 vertex1, out Vector3 vertex2,
  236. out Vector3 vertex3)
  237. {
  238. vertex1 = vertex2 = vertex3 = Vector3.Zero;
  239. // The input ray is in world space, but our model data is stored in object
  240. // space. We would normally have to transform all the model data by the
  241. // modelTransform matrix, moving it into world space before we test it
  242. // against the ray. That transform can be slow if there are a lot of
  243. // triangles in the model, however, so instead we do the opposite.
  244. // Transforming our ray by the inverse modelTransform moves it into object
  245. // space, where we can test it directly against our model data. Since there
  246. // is only one ray but typically many triangles, doing things this way
  247. // around can be much faster.
  248. Matrix inverseTransform = Matrix.Invert(modelTransform);
  249. ray.Position = Vector3.Transform(ray.Position, inverseTransform);
  250. ray.Direction = Vector3.TransformNormal(ray.Direction, inverseTransform);
  251. // Look up our custom collision data from the Tag property of the model.
  252. Dictionary<string, object> tagData = (Dictionary<string, object>)model.Tag;
  253. if (tagData == null)
  254. {
  255. throw new InvalidOperationException(
  256. "Model.Tag is not set correctly. Make sure your model " +
  257. "was built using the custom TrianglePickingProcessor.");
  258. }
  259. // Start off with a fast bounding sphere test.
  260. BoundingSphere boundingSphere = (BoundingSphere)tagData["BoundingSphere"];
  261. if (boundingSphere.Intersects(ray) == null)
  262. {
  263. // If the ray does not intersect the bounding sphere, we cannot
  264. // possibly have picked this model, so there is no need to even
  265. // bother looking at the individual triangle data.
  266. insideBoundingSphere = false;
  267. return null;
  268. }
  269. else
  270. {
  271. // The bounding sphere test passed, so we need to do a full
  272. // triangle picking test.
  273. insideBoundingSphere = true;
  274. // Keep track of the closest triangle we found so far,
  275. // so we can always return the closest one.
  276. float? closestIntersection = null;
  277. // Loop over the vertex data, 3 at a time (3 vertices = 1 triangle).
  278. Vector3[] vertices = (Vector3[])tagData["Vertices"];
  279. for (int i = 0; i < vertices.Length; i += 3)
  280. {
  281. // Perform a ray to triangle intersection test.
  282. float? intersection;
  283. RayIntersectsTriangle(ref ray,
  284. ref vertices[i],
  285. ref vertices[i + 1],
  286. ref vertices[i + 2],
  287. out intersection);
  288. // Does the ray intersect this triangle?
  289. if (intersection != null)
  290. {
  291. // If so, is it closer than any other previous triangle?
  292. if ((closestIntersection == null) ||
  293. (intersection < closestIntersection))
  294. {
  295. // Store the distance to this triangle.
  296. closestIntersection = intersection;
  297. // Transform the three vertex positions into world space,
  298. // and store them into the output vertex parameters.
  299. Vector3.Transform(ref vertices[i],
  300. ref modelTransform, out vertex1);
  301. Vector3.Transform(ref vertices[i + 1],
  302. ref modelTransform, out vertex2);
  303. Vector3.Transform(ref vertices[i + 2],
  304. ref modelTransform, out vertex3);
  305. }
  306. }
  307. }
  308. return closestIntersection;
  309. }
  310. }
  311. /// <summary>
  312. /// Checks whether a ray intersects a triangle. This uses the algorithm
  313. /// developed by Tomas Moller and Ben Trumbore, which was published in the
  314. /// Journal of Graphics Tools, volume 2, "Fast, Minimum Storage Ray-Triangle
  315. /// Intersection".
  316. ///
  317. /// This method is implemented using the pass-by-reference versions of the
  318. /// XNA math functions. Using these overloads is generally not recommended,
  319. /// because they make the code less readable than the normal pass-by-value
  320. /// versions. This method can be called very frequently in a tight inner loop,
  321. /// however, so in this particular case the performance benefits from passing
  322. /// everything by reference outweigh the loss of readability.
  323. /// </summary>
  324. static void RayIntersectsTriangle(ref Ray ray,
  325. ref Vector3 vertex1,
  326. ref Vector3 vertex2,
  327. ref Vector3 vertex3, out float? result)
  328. {
  329. // Compute vectors along two edges of the triangle.
  330. Vector3 edge1, edge2;
  331. Vector3.Subtract(ref vertex2, ref vertex1, out edge1);
  332. Vector3.Subtract(ref vertex3, ref vertex1, out edge2);
  333. // Compute the determinant.
  334. Vector3 directionCrossEdge2;
  335. Vector3.Cross(ref ray.Direction, ref edge2, out directionCrossEdge2);
  336. float determinant;
  337. Vector3.Dot(ref edge1, ref directionCrossEdge2, out determinant);
  338. // If the ray is parallel to the triangle plane, there is no collision.
  339. if (determinant > -float.Epsilon && determinant < float.Epsilon)
  340. {
  341. result = null;
  342. return;
  343. }
  344. float inverseDeterminant = 1.0f / determinant;
  345. // Calculate the U parameter of the intersection point.
  346. Vector3 distanceVector;
  347. Vector3.Subtract(ref ray.Position, ref vertex1, out distanceVector);
  348. float triangleU;
  349. Vector3.Dot(ref distanceVector, ref directionCrossEdge2, out triangleU);
  350. triangleU *= inverseDeterminant;
  351. // Make sure it is inside the triangle.
  352. if (triangleU < 0 || triangleU > 1)
  353. {
  354. result = null;
  355. return;
  356. }
  357. // Calculate the V parameter of the intersection point.
  358. Vector3 distanceCrossEdge1;
  359. Vector3.Cross(ref distanceVector, ref edge1, out distanceCrossEdge1);
  360. float triangleV;
  361. Vector3.Dot(ref ray.Direction, ref distanceCrossEdge1, out triangleV);
  362. triangleV *= inverseDeterminant;
  363. // Make sure it is inside the triangle.
  364. if (triangleV < 0 || triangleU + triangleV > 1)
  365. {
  366. result = null;
  367. return;
  368. }
  369. // Compute the distance along the ray to the triangle.
  370. float rayDistance;
  371. Vector3.Dot(ref edge2, ref distanceCrossEdge1, out rayDistance);
  372. rayDistance *= inverseDeterminant;
  373. // Is the triangle behind the ray origin?
  374. if (rayDistance < 0)
  375. {
  376. result = null;
  377. return;
  378. }
  379. result = rayDistance;
  380. }
  381. /// <summary>
  382. /// This is called when the game should draw itself.
  383. /// </summary>
  384. protected override void Draw(GameTime gameTime)
  385. {
  386. GraphicsDevice device = graphics.GraphicsDevice;
  387. device.Clear(Color.CornflowerBlue);
  388. device.BlendState = BlendState.Opaque;
  389. device.DepthStencilState = DepthStencilState.Default;
  390. // Draw the table.
  391. DrawModel(table, Matrix.Identity, tableAbsoluteBoneTransforms);
  392. // Use the same DrawModel function to draw all of the models on the table.
  393. for (int i = 0; i < models.Length; i++)
  394. {
  395. DrawModel(models[i], modelWorldTransforms[i],
  396. modelAbsoluteBoneTransforms[i]);
  397. }
  398. // Draw the outline of the triangle under the cursor.
  399. DrawPickedTriangle();
  400. // Draw text describing the picking results.
  401. DrawText();
  402. base.Draw(gameTime);
  403. }
  404. /// <summary>
  405. /// Helper for drawing the outline of the triangle currently under the cursor.
  406. /// </summary>
  407. void DrawPickedTriangle()
  408. {
  409. if (pickedModelName != null)
  410. {
  411. GraphicsDevice device = graphics.GraphicsDevice;
  412. // Set line drawing renderstates. We disable backface culling
  413. // and turn off the depth buffer because we want to be able to
  414. // see the picked triangle outline regardless of which way it is
  415. // facing, and even if there is other geometry in front of it.
  416. device.RasterizerState = WireFrame;
  417. device.DepthStencilState = DepthStencilState.None;
  418. // Activate the line drawing BasicEffect.
  419. lineEffect.Projection = projectionMatrix;
  420. lineEffect.View = viewMatrix;
  421. lineEffect.CurrentTechnique.Passes[0].Apply();
  422. // Draw the triangle.
  423. device.DrawUserPrimitives(PrimitiveType.TriangleList,
  424. pickedTriangle, 0, 1);
  425. // Reset renderstates to their default values.
  426. device.RasterizerState = RasterizerState.CullCounterClockwise;
  427. device.DepthStencilState = DepthStencilState.Default;
  428. }
  429. }
  430. /// <summary>
  431. /// Helper for drawing text showing the current picking results.
  432. /// </summary>
  433. void DrawText()
  434. {
  435. // Draw the text twice to create a drop-shadow effect, first in black one
  436. // pixel down and to the right, then again in white at the real position.
  437. Vector2 shadowOffset = new Vector2(1, 1);
  438. spriteBatch.Begin();
  439. // Draw a list of which models passed the initial bounding sphere test.
  440. if (insideBoundingSpheres.Count > 0)
  441. {
  442. string text = "Inside bounding sphere: " +
  443. string.Join(", ", insideBoundingSpheres.ToArray());
  444. Vector2 position = new Vector2(50, 50);
  445. spriteBatch.DrawString(spriteFont, text,
  446. position + shadowOffset, Color.Black);
  447. spriteBatch.DrawString(spriteFont, text,
  448. position, Color.White);
  449. }
  450. // Draw the name of the model that passed the per-triangle picking test.
  451. if (pickedModelName != null)
  452. {
  453. Vector2 position = cursor.Position;
  454. // Draw the text below the cursor position.
  455. position.Y += 32;
  456. // Center the string.
  457. position -= spriteFont.MeasureString(pickedModelName) / 2;
  458. spriteBatch.DrawString(spriteFont, pickedModelName,
  459. position + shadowOffset, Color.Black);
  460. spriteBatch.DrawString(spriteFont, pickedModelName,
  461. position, Color.White);
  462. }
  463. spriteBatch.End();
  464. }
  465. /// <summary>
  466. /// DrawModel is a helper function that takes a model, world matrix, and
  467. /// bone transforms. It does just what its name implies, and draws the model.
  468. /// </summary>
  469. private void DrawModel(Model model, Matrix worldTransform,
  470. Matrix[] absoluteBoneTransforms)
  471. {
  472. foreach (ModelMesh mesh in model.Meshes)
  473. {
  474. foreach (BasicEffect effect in mesh.Effects)
  475. {
  476. effect.EnableDefaultLighting();
  477. effect.PreferPerPixelLighting = true;
  478. effect.View = viewMatrix;
  479. effect.Projection = projectionMatrix;
  480. effect.World = absoluteBoneTransforms[mesh.ParentBone.Index] *
  481. worldTransform;
  482. }
  483. mesh.Draw();
  484. }
  485. }
  486. #endregion
  487. #region Handle Input
  488. /// <summary>
  489. /// Handles input for quitting the game.
  490. /// </summary>
  491. void HandleInput()
  492. {
  493. currentKeyboardState = Keyboard.GetState();
  494. currentGamePadState = GamePad.GetState(PlayerIndex.One);
  495. // Check for exit.
  496. if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
  497. currentGamePadState.Buttons.Back == ButtonState.Pressed)
  498. {
  499. Exit();
  500. }
  501. }
  502. /// <summary>
  503. /// Handles input for moving the camera.
  504. /// </summary>
  505. void UpdateCamera(GameTime gameTime)
  506. {
  507. float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
  508. // should we reset the camera?
  509. if (currentKeyboardState.IsKeyDown(Keys.R) ||
  510. currentGamePadState.Buttons.RightStick == ButtonState.Pressed)
  511. {
  512. cameraArc = CameraDefaultArc;
  513. cameraDistance = CameraDefaultDistance;
  514. cameraRotation = CameraDefaultRotation;
  515. }
  516. // Check for input to rotate the camera up and down around the model.
  517. if (currentKeyboardState.IsKeyDown(Keys.Up) ||
  518. currentKeyboardState.IsKeyDown(Keys.W))
  519. {
  520. cameraArc += time * CameraRotateSpeed;
  521. }
  522. if (currentKeyboardState.IsKeyDown(Keys.Down) ||
  523. currentKeyboardState.IsKeyDown(Keys.S))
  524. {
  525. cameraArc -= time * CameraRotateSpeed;
  526. }
  527. cameraArc += currentGamePadState.ThumbSticks.Right.Y * time *
  528. CameraRotateSpeed;
  529. // Limit the arc movement.
  530. cameraArc = MathHelper.Clamp(cameraArc, -90.0f, 90.0f);
  531. // Check for input to rotate the camera around the model.
  532. if (currentKeyboardState.IsKeyDown(Keys.Right) ||
  533. currentKeyboardState.IsKeyDown(Keys.D))
  534. {
  535. cameraRotation += time * CameraRotateSpeed;
  536. }
  537. if (currentKeyboardState.IsKeyDown(Keys.Left) ||
  538. currentKeyboardState.IsKeyDown(Keys.A))
  539. {
  540. cameraRotation -= time * CameraRotateSpeed;
  541. }
  542. cameraRotation += currentGamePadState.ThumbSticks.Right.X * time *
  543. CameraRotateSpeed;
  544. // Check for input to zoom camera in and out.
  545. if (currentKeyboardState.IsKeyDown(Keys.Z))
  546. cameraDistance += time * CameraZoomSpeed;
  547. if (currentKeyboardState.IsKeyDown(Keys.X))
  548. cameraDistance -= time * CameraZoomSpeed;
  549. cameraDistance += currentGamePadState.Triggers.Left * time
  550. * CameraZoomSpeed;
  551. cameraDistance -= currentGamePadState.Triggers.Right * time
  552. * CameraZoomSpeed;
  553. // clamp the camera distance so it doesn't get too close or too far away.
  554. cameraDistance = MathHelper.Clamp(cameraDistance,
  555. CameraMinDistance, CameraMaxDistance);
  556. Matrix unrotatedView = Matrix.CreateLookAt(
  557. new Vector3(0, 0, -cameraDistance), Vector3.Zero, Vector3.Up);
  558. viewMatrix = Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
  559. Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
  560. unrotatedView;
  561. }
  562. #endregion
  563. }
  564. #region Entry Point
  565. /// <summary>
  566. /// The main entry point for the application.
  567. /// </summary>
  568. static class Program
  569. {
  570. static void Main()
  571. {
  572. using (TrianglePickingGame game = new TrianglePickingGame())
  573. {
  574. game.Run();
  575. }
  576. }
  577. }
  578. #endregion
  579. }