Game.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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 Microsoft.Xna.Framework;
  12. using Microsoft.Xna.Framework.Content;
  13. using Microsoft.Xna.Framework.Graphics;
  14. using Microsoft.Xna.Framework.Input;
  15. using Microsoft.Xna.Framework.Input.Touch;
  16. using BoundingVolumeRendering;
  17. #endregion
  18. namespace PickingSample
  19. {
  20. /// <summary>
  21. /// This sample shows how to see if a user's cursor is over an object, and how to
  22. /// find out where on the screen an object is. The sample puts several objects on a
  23. /// table. If the cursor is on the object, the object's name is displayed. See the
  24. /// accompanying doc file for more information.
  25. /// </summary>
  26. public class PickingSampleGame : Microsoft.Xna.Framework.Game
  27. {
  28. #region Constants
  29. // ModelFilenames is the list of models that we will be putting on top of the
  30. // table. These strings will be used as arguments to content.Load<Model> and
  31. // will be drawn when the cursor is over an object.
  32. static readonly string[] ModelFilenames = new string[]{
  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 = .01f;
  41. // the following constants control how the camera's default position
  42. const float CameraDefaultArc = -15.0f;
  43. const float CameraDefaultRotation = 185;
  44. const float CameraDefaultDistance = 4.3f;
  45. #endregion
  46. #region Fields
  47. GraphicsDeviceManager graphics;
  48. // a SpriteBatch and SpriteFont, which we will use to draw the objects' names
  49. // when they are selected.
  50. SpriteBatch spriteBatch;
  51. SpriteFont spriteFont;
  52. // The cursor is used to tell what the user's pointer/mouse is over. The cursor
  53. // is moved with the left thumbstick. On windows, the mouse can be used as well.
  54. Cursor cursor;
  55. // the table that all of the objects are drawn on, and table model's
  56. // absoluteBoneTransforms. Since the table is not animated, these can be
  57. // calculated once and saved.
  58. Model table;
  59. Matrix[] tableAbsoluteBoneTransforms;
  60. // these are the models that we will draw on top of the table. we'll store them
  61. // and their bone transforms in arrays. Again, since these models aren't
  62. // animated, we can calculate their bone transforms once and save the result.
  63. Model[] models = new Model[ModelFilenames.Length];
  64. Matrix[][] modelAbsoluteBoneTransforms = new Matrix[ModelFilenames.Length][];
  65. // each model will need one more matrix: a world transform. This matrix will be
  66. // used to place each model at a different location in the world.
  67. Matrix[] modelWorldTransforms = new Matrix[ModelFilenames.Length];
  68. Matrix viewMatrix;
  69. Matrix projectionMatrix;
  70. // this variable will store the current rotation value as the camera
  71. // rotates around the scene
  72. float cameraRotation = CameraDefaultRotation;
  73. // this variable will tell our game whether or not to draw a mesh's bounding sphere
  74. bool drawBoundingSphere = true;
  75. #endregion
  76. #region Initialization
  77. public PickingSampleGame()
  78. {
  79. graphics = new GraphicsDeviceManager(this);
  80. Content.RootDirectory = "Content";
  81. #if WINDOWS_PHONE
  82. // Frame rate is 30 fps by default for Windows Phone.
  83. TargetElapsedTime = TimeSpan.FromTicks(333333);
  84. graphics.IsFullScreen = true;
  85. #endif
  86. }
  87. protected override void Initialize()
  88. {
  89. // Set up the world transforms that each model will use. They'll be
  90. // positioned in a line along the x axis.
  91. modelWorldTransforms[0] = Matrix.CreateTranslation(new Vector3(-1.5f, 0, 0));
  92. modelWorldTransforms[1] = Matrix.CreateTranslation(new Vector3(-.5f, 0, 0));
  93. modelWorldTransforms[2] = Matrix.CreateTranslation(new Vector3(.5f, 0, 0));
  94. modelWorldTransforms[3] = Matrix.CreateTranslation(new Vector3(1.5f, 0, 0));
  95. cursor = new Cursor(this);
  96. Components.Add(cursor);
  97. base.Initialize();
  98. }
  99. /// <summary>
  100. /// Load your graphics content.
  101. /// </summary>
  102. protected override void LoadContent()
  103. {
  104. // load all of the models that will appear on the table:
  105. for (int i = 0; i < ModelFilenames.Length; i++)
  106. {
  107. // load the actual model, using ModelFilenames to determine what
  108. // file to load.
  109. models[i] = Content.Load<Model>(ModelFilenames[i]);
  110. // create an array of matrices to hold the absolute bone transforms,
  111. // calculate them, and copy them in.
  112. modelAbsoluteBoneTransforms[i] = new Matrix[models[i].Bones.Count];
  113. models[i].CopyAbsoluteBoneTransformsTo(
  114. modelAbsoluteBoneTransforms[i]);
  115. }
  116. // now that we've loaded in the models that will sit on the table, go
  117. // through the same procedure for the table itself.
  118. table = Content.Load<Model>("Table");
  119. tableAbsoluteBoneTransforms = new Matrix[table.Bones.Count];
  120. table.CopyAbsoluteBoneTransformsTo(tableAbsoluteBoneTransforms);
  121. // create a spritebatch and load the font, which we'll use to draw the
  122. // models' names.
  123. spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
  124. spriteFont = Content.Load<SpriteFont>("hudFont");
  125. // calculate the projection matrix now that the graphics device is created.
  126. projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
  127. MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, .01f, 1000);
  128. // Initialize the renderer for our bounding spheres
  129. BoundingSphereRenderer.Initialize(GraphicsDevice, 45);
  130. }
  131. #endregion
  132. #region Update and Draw
  133. /// <summary>
  134. /// Allows the game to run logic.
  135. /// </summary>
  136. protected override void Update(GameTime gameTime)
  137. {
  138. // Check for exit.
  139. if (Keyboard.GetState().IsKeyDown(Keys.Escape) ||
  140. GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
  141. {
  142. Exit();
  143. }
  144. // we rotate our view around the models over time
  145. float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
  146. cameraRotation += time * CameraRotateSpeed;
  147. Matrix unrotatedView = Matrix.CreateLookAt(
  148. new Vector3(0, 0, -CameraDefaultDistance), Vector3.Zero, Vector3.Up);
  149. viewMatrix = Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
  150. Matrix.CreateRotationX(MathHelper.ToRadians(CameraDefaultArc)) *
  151. unrotatedView;
  152. // base.Update will update all of the components in the .Components
  153. // collection, including the cursor.
  154. base.Update(gameTime);
  155. }
  156. /// <summary>
  157. /// This is called when the game should draw itself.
  158. /// </summary>
  159. protected override void Draw(GameTime gameTime)
  160. {
  161. GraphicsDevice.Clear(Color.CornflowerBlue);
  162. // drawing sprites changes some render states around, which don't play
  163. // nicely with 3d models. In particular, we want to enable the depth buffer and turn off alpha blending.
  164. GraphicsDevice.DepthStencilState = DepthStencilState.Default;
  165. GraphicsDevice.BlendState = BlendState.Opaque;
  166. // draw the table. DrawModel is a function defined below draws a model using
  167. // a world matrix and the model's bone transforms.
  168. DrawModel(table, Matrix.Identity, tableAbsoluteBoneTransforms, false);
  169. // use the same DrawModel function to draw all of the models on the table.
  170. for (int i = 0; i < models.Length; i++)
  171. {
  172. DrawModel(models[i], modelWorldTransforms[i],
  173. modelAbsoluteBoneTransforms[i], drawBoundingSphere);
  174. }
  175. // now we'll check to see if the cursor is over any of the models, and draw
  176. // their names if it is.
  177. DrawModelNames();
  178. base.Draw(gameTime);
  179. }
  180. private void DrawModelNames()
  181. {
  182. // begin on the spritebatch, because we're going to be drawing some text.
  183. spriteBatch.Begin();
  184. // If the cursor is over a model, we'll draw its name. To figure out if
  185. // the cursor is over a model, we'll use cursor.CalculateCursorRay. That
  186. // function gives us a world space ray that starts at the "eye" of the
  187. // camera, and shoots out in the direction pointed to by the cursor.
  188. Ray cursorRay = cursor.CalculateCursorRay(projectionMatrix, viewMatrix);
  189. // go through all of the models...
  190. for (int i = 0; i < models.Length; i++)
  191. {
  192. // check to see if the cursorRay intersects the model....
  193. if (RayIntersectsModel(cursorRay, models[i], modelWorldTransforms[i],
  194. modelAbsoluteBoneTransforms[i]))
  195. {
  196. // now we know that we want to draw the model's name. We want to
  197. // draw the name a little bit above the model: but where's that?
  198. // SpriteBatch.DrawString takes screen space coordinates, but the
  199. // model's position is stored in world space.
  200. // we'll use Viewport.Project, which will project a world space
  201. // point into screen space. We'll project the vector (0,0,0) using
  202. // the model's world matrix, and the view and projection matrices.
  203. // that will tell us where the model's origin is on the screen.
  204. Vector3 screenSpace = graphics.GraphicsDevice.Viewport.Project(
  205. Vector3.Zero, projectionMatrix, viewMatrix,
  206. modelWorldTransforms[i]);
  207. // we want to draw the text a little bit above that, so we'll use
  208. // the screen space position - 60 to move up a little bit. A better
  209. // approach would be to calculate where the top of the model is, and
  210. // draw there. It's not that much harder to do, but to keep the
  211. // sample easy, we'll take the easy way out.
  212. Vector2 textPosition =
  213. new Vector2(screenSpace.X, screenSpace.Y - 60);
  214. // we want to draw the text centered around textPosition, so we'll
  215. // calculate the center of the string, and use that as the origin
  216. // argument to spriteBatch.DrawString. DrawString automatically
  217. // centers text around the vector specified by the origin argument.
  218. Vector2 stringCenter =
  219. spriteFont.MeasureString(ModelFilenames[i]) / 2;
  220. // to make the text readable, we'll draw the same thing twice, once
  221. // white and once black, with a little offset to get a drop shadow
  222. // effect.
  223. // first we'll draw the shadow...
  224. Vector2 shadowOffset = new Vector2(1, 1);
  225. spriteBatch.DrawString(spriteFont, ModelFilenames[i],
  226. textPosition + shadowOffset, Color.Black, 0.0f,
  227. stringCenter, 1.0f, SpriteEffects.None, 0.0f);
  228. // ...and then the real text on top.
  229. spriteBatch.DrawString(spriteFont, ModelFilenames[i],
  230. textPosition, Color.White, 0.0f,
  231. stringCenter, 1.0f, SpriteEffects.None, 0.0f);
  232. }
  233. }
  234. spriteBatch.End();
  235. }
  236. /// <summary>
  237. /// DrawModel is a helper function that takes a model, world matrix, and
  238. /// bone transforms. It does just what its name implies, and draws the model.
  239. /// </summary>
  240. /// <param name="model">the model to draw</param>
  241. /// <param name="worldTransform">where to draw the model</param>
  242. /// <param name="absoluteBoneTransforms">the model's bone transforms. this can
  243. /// be calculated using the function Model.CopyAbsoluteBoneTransformsTo</param>
  244. private void DrawModel(Model model, Matrix worldTransform,
  245. Matrix[] absoluteBoneTransforms, bool drawBoundingSphere)
  246. {
  247. // nothing tricky in here; this is the same model drawing code that we see
  248. // everywhere. we'll loop over all of the meshes in the model, set up their
  249. // effects, and then draw them.
  250. foreach (ModelMesh mesh in model.Meshes)
  251. {
  252. foreach (BasicEffect effect in mesh.Effects)
  253. {
  254. effect.EnableDefaultLighting();
  255. effect.View = viewMatrix;
  256. effect.Projection = projectionMatrix;
  257. effect.World = absoluteBoneTransforms[mesh.ParentBone.Index] * worldTransform;
  258. }
  259. mesh.Draw();
  260. if (drawBoundingSphere)
  261. {
  262. // the mesh's BoundingSphere is stored relative to the mesh itself.
  263. // (Mesh space). We want to get this BoundingSphere in terms of world
  264. // coordinates. To do this, we calculate a matrix that will transform
  265. // from coordinates from mesh space into world space....
  266. Matrix world = absoluteBoneTransforms[mesh.ParentBone.Index] * worldTransform;
  267. // ... and then transform the BoundingSphere using that matrix.
  268. BoundingSphere sphere = TransformBoundingSphere(mesh.BoundingSphere, world);
  269. // now draw the sphere with our renderer
  270. BoundingSphereRenderer.Draw(sphere, viewMatrix, projectionMatrix);
  271. }
  272. }
  273. }
  274. /// <summary>
  275. /// This helper function checks to see if a ray will intersect with a model.
  276. /// The model's bounding spheres are used, and the model is transformed using
  277. /// the matrix specified in the worldTransform argument.
  278. /// </summary>
  279. /// <param name="ray">the ray to perform the intersection check with</param>
  280. /// <param name="model">the model to perform the intersection check with.
  281. /// the model's bounding spheres will be used.</param>
  282. /// <param name="worldTransform">a matrix that positions the model
  283. /// in world space</param>
  284. /// <param name="absoluteBoneTransforms">this array of matrices contains the
  285. /// absolute bone transforms for the model. this can be obtained using the
  286. /// Model.CopyAbsoluteBoneTransformsTo function.</param>
  287. /// <returns>true if the ray intersects the model.</returns>
  288. private static bool RayIntersectsModel(Ray ray, Model model,
  289. Matrix worldTransform, Matrix[] absoluteBoneTransforms)
  290. {
  291. // Each ModelMesh in a Model has a bounding sphere, so to check for an
  292. // intersection in the Model, we have to check every mesh.
  293. foreach (ModelMesh mesh in model.Meshes)
  294. {
  295. // the mesh's BoundingSphere is stored relative to the mesh itself.
  296. // (Mesh space). We want to get this BoundingSphere in terms of world
  297. // coordinates. To do this, we calculate a matrix that will transform
  298. // from coordinates from mesh space into world space....
  299. Matrix world = absoluteBoneTransforms[mesh.ParentBone.Index] * worldTransform;
  300. // ... and then transform the BoundingSphere using that matrix.
  301. BoundingSphere sphere = TransformBoundingSphere(mesh.BoundingSphere, world);
  302. // now that the we have a sphere in world coordinates, we can just use
  303. // the BoundingSphere class's Intersects function. Intersects returns a
  304. // nullable float (float?). This value is the distance at which the ray
  305. // intersects the BoundingSphere, or null if there is no intersection.
  306. // so, if the value is not null, we have a collision.
  307. if (sphere.Intersects(ray) != null)
  308. {
  309. return true;
  310. }
  311. }
  312. // if we've gotten this far, we've made it through every BoundingSphere, and
  313. // none of them intersected the ray. This means that there was no collision,
  314. // and we should return false.
  315. return false;
  316. }
  317. /// <summary>
  318. /// This helper function takes a BoundingSphere and a transform matrix, and
  319. /// returns a transformed version of that BoundingSphere.
  320. /// </summary>
  321. /// <param name="sphere">the BoundingSphere to transform</param>
  322. /// <param name="world">how to transform the BoundingSphere.</param>
  323. /// <returns>the transformed BoundingSphere/</returns>
  324. private static BoundingSphere TransformBoundingSphere(BoundingSphere sphere, Matrix transform)
  325. {
  326. BoundingSphere transformedSphere;
  327. // the transform can contain different scales on the x, y, and z components.
  328. // this has the effect of stretching and squishing our bounding sphere along
  329. // different axes. Obviously, this is no good: a bounding sphere has to be a
  330. // SPHERE. so, the transformed sphere's radius must be the maximum of the
  331. // scaled x, y, and z radii.
  332. // to calculate how the transform matrix will affect the x, y, and z
  333. // components of the sphere, we'll create a vector3 with x y and z equal
  334. // to the sphere's radius...
  335. Vector3 scale3 = new Vector3(sphere.Radius, sphere.Radius, sphere.Radius);
  336. // then transform that vector using the transform matrix. we use
  337. // TransformNormal because we don't want to take translation into account.
  338. scale3 = Vector3.TransformNormal(scale3, transform);
  339. // scale3 contains the x, y, and z radii of a squished and stretched sphere.
  340. // we'll set the finished sphere's radius to the maximum of the x y and z
  341. // radii, creating a sphere that is large enough to contain the original
  342. // squished sphere.
  343. transformedSphere.Radius = Math.Max(scale3.X, Math.Max(scale3.Y, scale3.Z));
  344. // transforming the center of the sphere is much easier. we can just use
  345. // Vector3.Transform to transform the center vector. notice that we're using
  346. // Transform instead of TransformNormal because in this case we DO want to
  347. // take translation into account.
  348. transformedSphere.Center = Vector3.Transform(sphere.Center, transform);
  349. return transformedSphere;
  350. }
  351. #endregion
  352. }
  353. }